本記事は以下2部構成で作成しています。
[Ansible]Cloudflare Zero Trust配下のAAP設定をGitLabで管理①~Playbook作成~
[Ansible]Cloudflare Zero Trust配下のAAP設定をGitLabで管理②~Cloudflare・GitLab設定~(本記事)
はじめに
全体の構成は以下の通りです。
詳細は前記事を参照ください。
前記事では、AAPでWebhookを受け取れるようにしました。
本記事では、Cloudflare Zero Trust(以降Cloudflareという)経由でGitLab RunnerからWebhookを送れるようにします。(図の②、③)
構築
②CloudflareでGitLab通信のみ通信許可
まずは、外部からの通信をCloudflareで制御してGitLab Runnerからの通信のみAAPへ通すようにします。
Service Token作成
Cloudflareでは、原則としてWebアクセスはSAMLなどのユーザ認証に成功したユーザアクセスのみがポリシー評価対象となり、それ以外はポリシー評価すらされません。
そのため、今までのような特定IPアドレスレンジは問答無用で許可
というような制御は出来ません。
しかし、それではユーザ認証ができないサービスアプリケーションから利用できないため、Service Token
という仕組みをが用意されています。
これは、Cloudflareがサービス用に発行したID(CF-Access-Client-Id
)とシークレット(CF-Access-Client-Secret
)をヘッダーに加えることでどのアプリケーションからのアクセスかを識別できるようにするものです。
具体的には上記ヘッダーによってJWT生成してCookieに保存してそちらで識別します。
Cloudflare Zero Trustダッシュボードより、Access
> Service Auth
> Service Tokens
にアクセスしてトークンを作成します。
作成すると以下のようにIDとシークレットが表示されます。
シークレットはこのタイミングでしか表示されないのでコピー必須です。
アプリケーションポリシー設定
先程作成したService Token
を使った通信を許可するようにアプリケーションポリシーを設定します。
事前に以下の公式ドキュメントを参考にAAP用のアプリケーションを作成しておきます。
Cloudflare Zero TrustダッシュボードよりAccess
> Applications
> <AAP用のアプリケーション>
> Policies
にアクセスして新規ポリシーを作成します。
設定項目は以下内容のとおりです。
-
Action
をService Auth
にする -
Create additional rules
に先程作成したService Tokenを指定する
これで、Service Tokenを使った通信は許可されWebhookを送信することが出来ます。
実際に手元のPCから以下curlを送ることでWebhookを実行できました。
curl <Webhook URL> -X POST \
-v \
-H "Content-Type: application/json" \
-H "X-Gitlab-Token: <Webhookキー>" \
-H "CF-Access-Client-Id: <CFトークンID>" \
-H "CF-Access-Client-Secret: <CFトークンシークレット>" \
-d "{\"test\": \"test_cf\"}"
通信内容を見てみると以下の通りでサービストークンの通信をユーザ認証せずにそのまま許可してAAPのコンテンツを返しています。
また、レスポンスの中でCookieにCF_Authorization
が追加されJWTが入っています。
これで以降Cookieの有効期限まではサービストークンを使わなくてもCookieを使えば通信が許可されます。
* Trying 172.67.194.15:443...
* Connected to <AAP FQDN> (172.67.194.15) port 443 (#0)
# 省略
> POST <Webhook URLパス> HTTP/2
> Host: <AAP FQDN>
> user-agent: curl/7.76.1
> accept: */*
> content-type: application/json
> x-gitlab-token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
> cf-access-client-id: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.access
> cf-access-client-secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
> content-length: 21
>
# 省略
< set-cookie: CF_Authorization=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; Expires=Fri, 22 Sep 2023 23:23:36 GMT; Path=/; Secure; HttpOnly; SameSite=none
< access-control-expose-headers: X-API-Request-Id
< allow: POST, OPTIONS
< content-language: en
< vary: Accept, Accept-Language, Origin, Cookie
< x-api-node: xxxxxxxx
< x-api-product-name: Red Hat Ansible Automation Platform
< x-api-product-version: 4.2.0
< x-api-request-id: xxxxxxxxxxxxxxxxxxxxx
< x-api-time: 0.082s
< x-api-total-time: 0.193s
< cf-cache-status: DYNAMIC
< report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=xxxxxxxx"}],"group":"cf-nel","max_age":604800}
< nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
< server: cloudflare
< cf-ray: xxxxx-NRT
< alt-svc: h3=":443"; ma=86400
<
# 省略
③GitLabからmain pushでWebhookをAAPに送信
最後に、GitLabからのWebhookを送信できるように設定します。
GitLabの変数定義
GitLabから今まで作成したWebhook URLやトークン情報をCI/CDで使えるように変数定義します。
全記事の①で作成したAAP構築プロジェクトページより、設定
> CI/CDの設定
> 変数
にアクセスして以下のような変数を作成します。
トークン情報は、マスク
属性をつけることで誤ってログなどに表示することを防ぐようにしておきます。
キー | 値 | 属性 |
---|---|---|
AAP_WEBHOOK_URL | AAPのWebhook URL |
保護 展開
|
GITLAB_TOKEN | AAPのWebhookトークン |
保護 マスク 展開
|
CF_ACCESS_CLIENT_ID | CloudflareのトークンID |
保護 マスク 展開
|
CF_ACCESS_CLIENT_SECRET | Cloudflareのトークンシークレット |
保護 マスク 展開
|
これで、GitLabから変数が使えるようになりました。
GitLab Runner設定
AAPのGitLab Webhookサービスは、もともとはGitLabのWebhook機能
を使うことを想定しています。
しかし、②で定義したCloudflareトークンはヘッダー形式で設定する必要があり、GitLabではそれを設定することが出来ません。
(一応、以前から議論はされているようですが全く実装されていないようです。)
そのため、今回は.gitlab-ci.yml
を作成して、パイプラインからcurlコマンドで実行するようにしました。
先程作成した環境変数を使って以下のような.gitlab-ci.yml
を作成します。
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "main"
stages:
- deploy
deploy-aap:
stage: deploy
script:
- >-
curl ${AAP_WEBHOOK_URL}
-X POST
-L
-v
-H "Content-Type: application/json"
-H "CF-Access-Client-Id: ${CF_ACCESS_CLIENT_ID}"
-H "CF-Access-Client-Secret: ${CF_ACCESS_CLIENT_SECRET}"
-H "X-Gitlab-Token: ${GITLAB_TOKEN}"
-d "{\"before\": \"${CI_COMMIT_BEFORE_SHA}\", \"after\": \"${CI_COMMIT_SHA}\"}"
これにより、mainにコミットプッシュをトリガーとしてパイプラインが起動してAAP側にジョブが実行されます。
AAP側にどんなheaderが来ているか見たかったので以下のとおり、Nginxのログ出力を変えてヘッダー内容を確認しました。
log_format main '"$request" $status $bytes_sent '
'content-type: "$http_content_type" \n'
'cookie: "$http_cookie" \n'
'x-gitlab-token: "$http_x_gitlab_token" \n'
'cf-access-client-id: "$http_cf_access_client_id" \n'
'cf-access-client-secret: "$http_cf_access_client_secret"';
"POST /api/v2/job_templates/69/gitlab/ HTTP/1.1" 202 541 content-type: "application/json"
cookie: "CF_Authorization=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;"
x-gitlab-token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
cf-access-client-id: "-"
cf-access-client-secret: "-"
Cloudflareのトークン情報はAAP側まで届いておらず、Cloudflare側で取り除かれているようです。
一方、JWT情報(CF_Authorization
)はサーバ側がCloudflare Zero Trustからの通信であることを証明するためそのまま残っています。
ちなみに、このCF_Authorization
を使って同じ組織の別アプリケーションにもアクセスしてMITM攻撃出来るか試してみましたが駄目でした。
どうやらアプリケーション or セッションごとにJWT発行しているようです
さいごに
本記事では、Cloudflare Zero Trust環境下のサーバにGitLabから外部アプリからアクセスする方法を紹介しました。
Cloudflare Zero Trustは機能が多すぎてなかなか検証しづらいですがいざ実際に使ってみるとドキュメントも揃っていて、すんなりと使いこなすことが出来ました。
また、細かいところでもセキュリティを担保するための技術が使われておりとても勉強になりました。
今回はGitLabからの利用でしたが同じようなクラウドとオンプレの通信をゼロトラスト技術で制御することが増えてくると思います。
製品によってこのあたりは思想が違ってくる部分でもあると思うので今後ゼロトラストを導入する上で検証が必要な観点だと感じました。
参考リンク
-
https://developers.cloudflare.com/cloudflare-one/identity/service-tokens/
- Cloudflareの
Service Tokens
についての説明
- Cloudflareの
-
https://developers.cloudflare.com/cloudflare-one/identity/authorization-cookie/validating-json/
- CloudflareのJWTをサーバから検証する説明
-
https://gitlab-docs.creationline.com/ee/ci/yaml/
- GitLabのCI/CDパイプラインの説明
-
https://gitlab-docs.creationline.com/ee/ci/variables/
- GitLabのCI/CD変数の説明