Jenkinsのパラメータ付きビルドをAPI経由で実行しようとした時にCORSでハマった話

## これ何？

- Jenkinsのパラメータ付きビルドをAPI経由で実行しようとした時にCORSでハマった話です
- ググったのですが、まとまった記事がなかなか見つからなかったので記事にまとめてみました
- まぁ、CORSは常識ですよ！お前、勉強不足だよね！って言われてもしょうがない記事です

## 前提

### curlでの実行は確認済み

以下、curlのコマンドです。
下記、コマンドにてJenkinsのパラメータビルドができることは確認済みでした。
`http://hoge:8080/job/hoge_job`が`Jenkinsのパラメータ付きビルドのURL`です

```bash
$ curl -X POST \
-u JENKINS_USER:JENKINS_TOKEN \
http://hoge:8080/job/hoge_job/buildWithParameters?\
github_url=*
```

参考
[JenkinsジョブをAPI経由でパラメータ付きビルドする](https://qiita.com/kentana20/items/5dd1111f9f31fefbccf4)

### Node.jsのaxiosを採用

クライアントはNode.jsのaxiosを採用しました。
[axios](https://github.com/axios/axios)はこちら

下記、postStartJobというメソッドを使って、jenkinsのjobを起動します。

```javascript
// JenkinsのJobを開始する
postStartJob(data) {
  return axios.post('http://hoge:8080/job/hoge_job/buildWithParameters', {}, {
    params: {
      github_url: data.GithubUrl,
    },
    auth: {
      username: JENKINS_USER,
      password: JENKINS_TOKEN,
    },
  });
}
```

## 現象😢

postStartJobメソッドを実行したところ下記エラーが発生しました。
うーん困った😓

```
Access to XMLHttpRequest at 
'http://hoge:8080/job/technical_analysis_staging/buildWithParameters?
github_url=*' from origin 'http://fuga:8082' has been blocked by CORS policy:
Request header field authorization is not allowed 
by Access-Control-Allow-Headers in preflight response.
```

## 原因

原因は2つあります。

### 原因1. Same-Origin Policy

**Jenkinsのデフォルト設定だと、Same-Origin Policy(日本語では同一生成元ポリシー)が適用されて、違うドメインからのアクセスを許可していないから。**
[CORSまとめ](https://qiita.com/tomoyukilabs/items/81698edd5812ff6acb34)を参考にしました。@tomoyukilabsさん、ありがとうございます！

Same-Origin Policyとは、1995年Netscape社によってJavaScriptと同時にウェブブラウザに導入されたセキュリティ上の考え方で、コンテンツがブラウザに来る源泉（origin）に基づいて整理して、外部からのアクセスを防ぐというポリシーです。
[WIKI](https://ja.wikipedia.org/wiki/%E5%90%8C%E4%B8%80%E7%94%9F%E6%88%90%E5%85%83%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC) より参照

つまり、今回、Jenkinsは`http://hoge` でクライアントは `http://fuga` で違うドメインであることから、Jeninsはアクセスを拒否したっていうわけです。

### 原因2. pre-flight

ライブラリのaxiosは異なるドメインに対してリクエストする時に、OPTIONSメソッドでpre-flightのリクエストを送信します。

axiosはデフォルトで`application/json`フォーマットを使用しており、`application/json`の場合、常にpre-flightされる仕様になっているとのことです。
https://github.com/axios/axios/issues/475#issuecomment-253968037 を参考

pre-flightとは、 CORS プロトコルが理解されているかどうかを確認する CORS リクエストとのことです。つまり、自分の理解だと、`CORSがちゃんと設定できているドメインか事前にチェックするよ！` って確認する作業と理解しました。
https://developer.mozilla.org/ja/docs/Glossary/Preflight_request を参考

## 解決方法

**[Cors Filter Plugin](https://wiki.jenkins.io/display/JENKINS/Cors+Filter+Plugin)をJenkinsにインストールしてCORSの設定をする！**

この解決方法で無事にJenkinsのパラメータビルドがAPI経由で実行できるようになりました:smile: 

### Cors Filter Pluginのインストール

プラグイン管理画面で`cors`と検索してインストール

<img width="830" alt="スクリーンショット 2019-04-12 9.04.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112294/62c29eb6-f1a7-3510-fb3f-b3d92996a1fd.png">

### CORSの設定

システム設定より以下、設定する。

- Access-Control-Allow-Origins
    - *
- Access-Control-Allow-Methods
    - GET, OPTIONS, POST
- Access-Control-Allow-Headers
    - authorization, content-type

<img width="901" alt="スクリーンショット 2019-04-12 9.08.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112294/4a9e3dd0-425d-a625-665b-952af712e94b.png">

#### Access-Control-Allow-Origins

- 許可するドメインを指定します。`*` の場合は全てのドメインからアクセス可能ということです。
- Jenkins側でアクセストークンが必要な設定になっているので特に問題ないかと思い、`*` を設定しました。本番環境では場合は、ちゃんと設定しようと思います。

#### Access-Control-Allow-Methods

- 異なるドメインから許可するメソッドを指定します。
- APIでメソッドは、GET, OPTIONS, POSTの3つなので、これらを許可します。
**OPTIONSは、pre-flightで使うメソッドなので必須です**。

#### Access-Control-Allow-Headers

- 異なるドメインから許可するヘッダーを指定します。
- `authorization`は、認証情報のヘッダーです。これがあることで、`JENKINS_USER:JENKINS_TOKEN`の認証を通すことができます。

## まとめ

- Same-Origin Policy があるので、同一ドメイン以外からAPIを実行する時にはCORSの設定が必要になる
- Node.jsのaxiosは、[pre-flight](https://developer.mozilla.org/ja/docs/Glossary/Preflight_request)を実行するので、OPTIONSメソッドを許可する必要がある
- Jenkinsの認証情報を送るには、`authorization`ヘッダーを許可する必要がある

あー、半日くらいハマったので疲れました :persevere: 
でも解決できてよかったです。まだまだ未熟だ。。。