CloudflareをALB(AWS)の前に配置して、CloudflareのAccessでAzureAD認証してみました。
どのクラウドでも実装できるはずなので、ちょっと認証かけたい時などなかなか便利です。
構成
- Cloudflare以外からのアクセスは、ALBのセキュリティーグループでブロックします。
- 自分のCloudflare以外のCloudflareからのアクセスは、Lambdaでjwtを検証してブロックします。
必要なもの
- Cloudflareアカウント
- AWSアカウント
- ドメイン2個
- ドメインA
- ドメインB
- ドメインBに対しAWS ACMで取得したワイルドカード証明書1個
設定
- Azure AD認証の対象リソースを作成する
- Cloudflare DNSを設定する
- Cloudflare Accessを設定する
- CloudfraleのIPアドレスをALBのセキュリティグループに設定する
- テストする
1. Azure AD認証の対象リソースを作成する
- AWS側でALBのターゲット用のLambdaを作成する
- ALBを作成する
- HTTPSのリスナーを作成しドメインBの証明書を紐づける
- ターゲットグループを作成して、Lambdaをターゲットに設定する
- Route53に、ALBのエイリアスレコードをドメインBのサブドメインとして設定する
- jwtの検証は以下を参考にしました。
- Lambda作成時のハマりポイント
- Amazon Linux上でpip installしてLambdaのパッケージをzipで固めます。
- Amazon Linuxの現在のyumリポジトリのPythonのバージョンは3.6なので、Lambda側も3.6に合わせます。
- Lambdaのパッケージをzipで固める時、".libs_cffi_backend"配下も忘れずに含めます。
- Cloudflareからのjwtを検証するためALBからのヘルスチェックは失敗するので、ヘルスチェックを外します。
以下Lambda内で指定している値は、Cloudflareの設定後に確認します。
Lambda内で指定している定数 | 値 |
---|---|
CERTS_URL | CloudflareのAccessのLogin Page Domain+"/cdn-cgi/access/certs" |
AUDIENCE_TAG | CloudflareのAccessのAccess PoliciesのAudience Tag |
pyjwt
cryptography
cffi
import jwt
import json
import urllib.request
CERTS_URL = 'https://*******************/cdn-cgi/access/certs'
AUDIENCE_TAG = '**************************************'
def get_public_keys():
headers = {
'User-Agent': 'curl/7.61.1'
}
req = urllib.request.Request(CERTS_URL, headers=headers)
with urllib.request.urlopen(req) as response:
res = response.read()
public_keys = []
jwk_set = json.loads(res)
for key_dict in jwk_set['keys']:
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key_dict))
public_keys.append(public_key)
return public_keys
def validate(header):
is_valid = False
token = ''
if 'cf-access-jwt-assertion' in header:
token = header['cf-access-jwt-assertion']
else:
return is_valid
keys = get_public_keys()
for key in keys:
try:
decoded_token = jwt.decode(token, key=key, audience=AUDIENCE_TAG)
# print(decoded_token)
is_valid = True
break
except Exception as e:
print(e)
pass
return is_valid
def lambda_handler(event, context):
is_valid = validate(event['headers'])
if is_valid:
res = {
"isBase64Encoded": False,
"statusCode": 200,
"statusDescription": "200 OK",
"headers": {
"Set-cookie": "cookies",
"Content-Type": "text/html"
}
}
res['body'] = """<html>
<head>
<title>hello world</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>"""
else:
res = {
"isBase64Encoded": False,
"statusCode": 403,
"statusDescription": "403"
}
return res
2. Cloudflare DNSを設定する
Cloudflare側でドメインAを登録します。
プランを選択します。
ドメインAのレジストラで、Cloudfraleのネームサーバを設定します。
設定後、Cloudflareの画面に戻って、下記をクリックします。
Cloudflare側がドメインを認識するまで、数時間かかりました。
CNAMEレコードを登録します。
項目 | 値 |
---|---|
Type | CNAME |
Name | ドメインA |
Content | ドメインBのサブドメイン(Route53に設定したALBのCNAME) |
Proxy status | Proxied |
SSL/TLSの画面で、Fullを選択します。
ここまででの設定で、ドメインAにアクセスするとドメインBのサブドメインにproxyされてALBのターゲットのLambdaが呼ばれるようになります。
3. Cloudflare Accessを設定する
Cloudflare Accessを有効化します。
下記以外はデフォルトのまま設定します。
- プランはAzure ADを利用するため、Access Premiumを選択
Login methodでAzure ADを選択します
Azure ADでアプリケーションを登録します。
クライアントシークレットを作成します。
パーミッションを設定します。
Application IDとDirectory IDを確認します。
Azure ADで設定した値をCloud flare側で設定します。
4. CloudfraleのIPアドレスをALBのセキュリティグループに設定する
下記IPアドレスをALBのセキュリティグループに設定します。
https://www.cloudflare.com/ips/
5. テストする
ドメインAに対しアクセスするとCloudflareの認証画面が表示されます。
Azure ADで認証して…
Lambdaで生成したページが表示されれば成功です!