httpsからリバースプロキシでjenkinsにアクセスする

他のアプリをjettyで動かした実績があったのでwarからjenkinsを動かしてみたが、ログインだの設定変更だのする度にhttpにリダイレクトされてイライラしたって話。

http://qiita.com/spamoc/items/fb005dc6e544249035c9 と大まかな内容は同じ。こっちの方が備忘録寄り。


## いきさつ

* jenkinsをnginxの下に立てようとした
* httpsで受けてhttpでjenkins.warを動かしたjettyに流すようにした
* ログイン/アウト時にhttpにリダイレクトされる
* httpsしかファイアウォールで許可してなかったので怒られる

そんな感じ。ファイアウォールを開ける対応は今回入れないようにしている。

## とりあえず試す

### nginx

インストールやSSLの鍵云々については省略。とにかくプロキシ周りの設定をぶち込む。

```ssl.conf
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

1. [jetty公式ページ](http://www.eclipse.org/jetty/)より9.3.11.v20160721をダウンロード(※これを書いている現在では9.3.12まで出ている)。解凍して/etc/jettyに配置する。
2. [jenkins公式ページ](https://jenkins.io/)よりjenkins.warをダウンロードして/etc/jetty/webappsの中にぶち込む。
3. webappsの下にjenkins.xmlを設置する。

```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"などと検索してみると結構引っかかる。とかいろいろ掘っていくに当たってこれが臭いと睨む。

https://github.com/jenkinsci/jenkins

色々見てみると`response.sendRedirect(request.getContextPath()+"/hoge");`というような書き方をしているところがほとんどで、アプリ上では"/jenkins/hoge"というような形でしかパスを渡していないことが分かった。

## simplehttpserverで渡してるパラメータの確認してみる

ひとまずjenkinsを止めて↓のスクリプトを動かしてアクセスしてみる。

```simple.py
#!/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アプリを作る。

```Risa.java
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());
  }
}
```

```web.xml
<?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も書き換えた。宣言だけ書き換えてるけど正直意味があるかはわからない。ないと思う。

```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系を利用する設定を含む記述があったためそれをそのまま使うようにした。

```jetty-http-forwarded.xml
<?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で利用できるようになった。やったね！

```run.sh
#!/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で実行するための方法については未確認。