他のアプリをjettyで動かした実績があったのでwarからjenkinsを動かしてみたが、ログインだの設定変更だのする度にhttpにリダイレクトされてイライラしたって話。
http://qiita.com/spamoc/items/fb005dc6e544249035c9 と大まかな内容は同じ。こっちの方が備忘録寄り。
いきさつ
- jenkinsをnginxの下に立てようとした
- httpsで受けてhttpでjenkins.warを動かしたjettyに流すようにした
- ログイン/アウト時にhttpにリダイレクトされる
- httpsしかファイアウォールで許可してなかったので怒られる
そんな感じ。ファイアウォールを開ける対応は今回入れないようにしている。
とりあえず試す
nginx
インストールやSSLの鍵云々については省略。とにかくプロキシ周りの設定をぶち込む。
server {
listen 443;
server_name _;
ssl on;
ssl_certificate ssl/cert.crt;
ssl_certificate_key ssl/cert.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
location /jenkins/ {
proxy_pass http://localhost:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
jetty
- jetty公式ページより9.3.11.v20160721をダウンロード(※これを書いている現在では9.3.12まで出ている)。解凍して/etc/jettyに配置する。
- jenkins公式ページよりjenkins.warをダウンロードして/etc/jetty/webappsの中にぶち込む。
- webappsの下にjenkins.xmlを設置する。
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/jenkins</Set>
<Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/jenkins.war</Set>
<Get name="securityHandler">
<Set name="loginService">
<New class="org.eclipse.jetty.security.HashLoginService">
<Set name="name">Jenkins Realm</Set>
<Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/realm.properties</Set>
</New>
</Set>
</Get>
</Configure>
これでjava -jar start.jar
すればひとまず動くようになった。
動作結果
最初に書いた通り。目をつぶればなんとかなるんだが、操作するたびにhttpにリダイレクトされてタイムアウトになるので流石に使えない。
jenkinsのリダイレクトについて
githubで↓のリポジトリに対して"sendRedirect"などと検索してみると結構引っかかる。とかいろいろ掘っていくに当たってこれが臭いと睨む。
色々見てみるとresponse.sendRedirect(request.getContextPath()+"/hoge");
というような書き方をしているところがほとんどで、アプリ上では"/jenkins/hoge"というような形でしかパスを渡していないことが分かった。
simplehttpserverで渡してるパラメータの確認してみる
ひとまずjenkinsを止めて↓のスクリプトを動かしてアクセスしてみる。
#!/usr/bin/env python
import SimpleHTTPServer
import SocketServer
class ServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_GET(self):
print(self.headers)
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
Handler = ServerHandler
SocketServer.TCPServer(("", 8080), Handler).serve_forever()
結果:
127.0.0.1 - - [21/Sep/2016 00:00:00] "GET /jenkins/ HTTP/1.0" 404 -
Host: hoge.example.jp
X-Forwarded-Proto: https
X-Forwarded-For: 192.128.0.2
X-Forwarded-Host: hoge.example.jp:443
X-Forwarded-Port: 443
X-Forwarded-Server: hoge.example.jp
X-Real-IP: 192.128.0.2
思った通りの結果が返ってきてる。これでもダメなのだろうか?
tomcatで試してみる
下記のファイルを作ってjavac -classpath "./lib/servlet-api.jar" Risa.java
とやってコンパイルした後よしなにやってjenkinsアプリを作る。
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class Risa extends HttpServlet{
public void doGet(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException {
System.out.println(req.getContextPath());
res.sendRedirect(req.getContextPath());
}
}
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<servlet>
<servlet-name>Risa</servlet-name>
<servlet-class>Risa</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Risa</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
そして先ほどのsimplehttpserverのアプリを落とした後./bin/startup.sh
を実行してアクセスかけてみるとhttpにリダイレクトされた。
tomcatで何とかしてみる
見たところ正しくproxyとして渡したパラメーターが使われていないらしい。というところで調査。
conf/server.xml以下の要素を他のValveの近くに置き足すことで無事解決。効果は書いてる内容通りでX-Forward-Protoの内容を読むというもの。上記のパラメータが返ってきてるのでこの場合httpsでリダイレクトが通るようになる。
<Valve className="org.apache.catalina.valves.RemoteIpValve"
protocolHeader="x-forwarded-proto"/>
これでサーバー側の設定で何とかなる、という説得力を得る。
jettyで試してみる
もともとjenkinsはjettyで動かしてるんだからこっちで確かめないと意味がない、ということでいろいろ試してみる。要はtomcatと同じだろ?と思ってたら結構痛い目を見た。
動作の検証を行う際にはtomcatの検証時に作ったjenkinsディレクトリをそのままjettyに持ってきて動かした。jarとかwarとかで動かす方法しか知らなかったからそれで動くとは思わなかった(クソ)。
web.xmlも書き換えた。宣言だけ書き換えてるけど正直意味があるかはわからない。ないと思う。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>Risa</servlet-name>
<servlet-class>Risa</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Risa</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
これでhttps://hoge.example.jp/jenkins/test とアクセスをすると無事http://hoge.example.jp にリダイレクトされた。
jettyを何とかする
jettyの9.3系だと/etc以下のファイルにいろいろxmlファイルが入ってる。そのうちのjetty-http-forwarded.xmlファイルの中にX-Forwarded系を利用する設定を含む記述があったためそれをそのまま使うようにした。
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
<Call name="addCustomizer">
<Arg>
<New class="org.eclipse.jetty.server.ForwardedRequestCustomizer">
<Set name="forwardedHostHeader"><Property name="jetty.httpConfig.forwardedHostHeader" default="X-Forwarded-Host"/></Set>
<Set name="forwardedServerHeader"><Property name="jetty.httpConfig.forwardedServerHeader" default="X-Forwarded-Server"/></Set>
<Set name="forwardedProtoHeader"><Property name="jetty.httpConfig.forwardedProtoHeader" default="X-Forwarded-Proto"/></Set>
<Set name="forwardedForHeader"><Property name="jetty.httpConfig.forwardedForHeader" default="X-Forwarded-For"/></Set>
<Set name="forwardedSslSessionIdHeader"><Property name="jetty.httpConfig.forwardedSslSessionIdHeader" /></Set>
<Set name="forwardedCipherSuiteHeader"><Property name="jetty.httpConfig.forwardedCipherSuiteHeader" /></Set>
</New>
</Arg>
</Call>
</Configure>
これを起動スクリプトの実行時に引数として加えるようにして無事解決。これでjenkinsをオールhttpsで利用できるようになった。やったね!
#!/bin/sh
cd `dirname $0`
/usr/bin/java -jar start.jar ./etc/jetty-http-forwarded.xml
※jettyは$JETTY_HOME/bin/jetty.shで実行することができるが、supervisorで管理するために別途スクリプトを書いてる。jetty.shで実行するための方法については未確認。