Envoy OAuth2 Filter を使ったログイン
庄司です。
いくつか Envoy Proxy の JWT Authentication や External Authorization を使った認証や認可の記事を書いてきました。
今回は OAuth2 Filter を使った例を説明します。
あらためて説明すると、Envoy Proxy は C++ で書かれた軽量かつ高速なプロキシです。
Envoy Proxy は構成を動的に変更でき、ゼロダウンタイムで反映できる特徴を持っています。
Kubernetes 環境のような頻繁にスケールアウト/インするサービスへのルーティングを設定して、個々のサービスの実装に接続先サービスの位置やポートのハードコーディングを不要とする、サービスメッシュパターンなどで多く利用されるコンポーネントです。
Amazon Cognito user pools の UI を使用して認証する
#次の構成ファイルは Amazon Cognito user pools でホストされた Web UI を使用したログインの設定例です。
front-envoy.yaml:
admin:
address:
socket_address:
address: 0.0.0.0
port_value: 9901
static_resources:
secrets:
- name: token
generic_secret:
secret:
inline_string: "<Your client secret here>"
- name: hmac
generic_secret:
secret:
inline_string: "<Your hmac secret here>"
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 8081
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
http_filters:
- name: envoy.filters.http.oauth2
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.oauth2.v3.OAuth2
config:
token_endpoint:
cluster: oauth
uri: "<Your amazon cognito userpools domain here>/oauth2/token"
timeout: 10s
authorization_endpoint: "<Your amazon cognito userpools domain here>/login"
credentials:
client_id: "<Your client id here>"
token_secret:
name: token
hmac_secret:
name: hmac
redirect_uri: "%REQ(x-forwarded-proto)%://%REQ(:authority)%/callback"
redirect_path_matcher:
path:
exact: /callback
signout_path:
path:
exact: /signout
forward_bearer_token: true
auth_scopes:
- openid
- email
- name: envoy.filters.http.lua
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
default_source_code:
inline_string: |
function envoy_on_request(request_handle)
headers = request_handle:headers()
headers:remove("authorization")
path = headers:get(":path"):gsub("/$", "/index.html")
headers:replace(":path", path)
end
- name: envoy.filters.http.aws_request_signing
typed_config:
'@type': type.googleapis.com/envoy.extensions.filters.http.aws_request_signing.v3.AwsRequestSigning
service_name: s3
region: "<Your s3 bucket region here>"
use_unsigned_payload: true
host_rewrite: "<Your s3 bucket name here>.s3.<Your s3 bucket region here>.amazonaws.com"
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
codec_type: AUTO
stat_prefix: ingress_http
route_config:
name: s3_route
virtual_hosts:
- name: static_web
domains:
- '*'
routes:
- match:
prefix: /
route:
cluster: s3
clusters:
- name: oauth
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: oauth
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: "<Your amazon cognito userpools domain here>"
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: "<Your amazon cognito userpools domain here>"
- name: s3
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: s3
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: "<Your s3 bucket name here>.s3.<Your s3 bucket region here>.amazonaws.com"
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: "<Your s3 bucket name here>.s3.<Your s3 bucket region here>.amazonaws.com"
不等号 (<>
) で囲まれた部分には、次の内容を設定します。
キー | 説明 |
---|---|
Your client secret here | Cognito user pools の「全般設定」-「アプリクライアント」の詳細で表示される「アプリクライアントのシークレット」 |
Your hmac secret here | hmac 署名のためのシークレット (任意の値のシークレット) |
Your amazon cognito userpools domain here | 「アプリの統合」-「ドメイン名」に設定した Cognito でホストする Web UI のドメイン名 |
Your client id here | 「全般設定」-「アプリクライアント」の「アプリクライアント ID」 |
ここでの例は認証をパスして場合に S3 の静的コンテンツを返すアプリケーションです。
したがって、次のように追加の設定が必要です。
キー | 説明 |
---|---|
Your s3 bucket region here | 静的コンテンツを配置した S3 バケットのリージョン |
Your s3 bucket name here | 静的コンテンツを配置した S3 バケット名 |
この構成ファイルを使用してコンテナイメージをビルドします。
Dockerfile:
FROM envoyproxy/envoy:v1.23-latest
COPY front-envoy.yaml /etc/envoy/front-envoy.yaml
EXPOSE 8081
EXPOSE 9901
CMD ["envoy", "-c", "/etc/envoy/front-envoy.yaml", "--service-cluster", "front-proxy", "--service-node", "node"]
コンテナイメージのビルド手順は次のようになります。
docker build -t envoy-proxy .
実行するためには、AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY が必要です。これらの値を環境変数に設定して実行します。
docker run -p 8081:8081 -e AWS_ACCESS_KEY_ID={Your access key here} -e AWS_SECRET_ACCESS_KEY={Your secret access key here} envoy-proxy
上述の構成ファイルは、Okta の利用例を説明する「Protecting web applications via Envoy OAuth2 filter」の場合とほとんど同じです。
Okta、Auth0 などさまざまな IDaaS、Keycloak など OpenID Connect をサポートしている IdP (Identity Provider) を使用する場合は、ここで説明した構成ファイルをテンプレートとしてわずかな変更で使用可能です。
OAuth2 Filter を使用した構成
#上記のコンテナを実行して、コンテンツにアクセスする場合のシーケンスは図のようになります。
- ユーザが静的コンテンツにアクセスすると、未ログイン時には、Cognito user pools の Web UI にリダイレクト
- ログインが成功すると、Code Flow にしたがって
/callback
にリダイレクト - Bearer Token (持参人トークン) により認証を確認
- AWS_ACCESS_TOKEN、AWS_SECRET_ACCESS_KEY を使ったリクエスト署名で S3 にアクセス
この記事の例は、バックエンドに S3 を使用するため、URL の最後の文字が '/' の場合にそれを '/index.html' に変更し、また後段で SigV4 署名するため、Bearer Token が設定された Authorization ヘッダを削除するスクリプトを Lua で記述しています。
AWS API リクエストを使用する場合、SigV4 署名が必要です。SigV4 署名は、AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY が必要です、AWS API リクエストの Authorization ヘッダに設定します。このため、元のリクエストに Authorization ヘッダがあると AWS API リクエストに失敗するため、前述の Lua スクリプトで Authorization ヘッダを削除します。
OAuth2 Filter により設定される Cookie
#認証に成功すると、この例の場合次の Cookie が設定されます。
- OauthHMAC: 署名
- OauthExpires: 有効期限
- BearerToken: AccessToken
- IdToken: ID Token
- RefreshToken: RefreshToken
OAuth2 Filter と JWT Authentication / External Authorization の使い分け
#JWT Authentication は認証フローによって取得された BearerToken や IDToken を検証する場合に使用します。
JSON 等のリクエストを受け取り、JSON 等でレスポンスする API の場合、認証されていない場合に 401 Unauthorized
をクライアントに返す以上のことができません。
このような API で構成されたマイクロサービスのサイドカーでは、JWT Authentication と認可のための OPA などを利用する External Authorization が適しています。
一方、OpenID Connect の認証フローの実行はリダイレクト応答等の対応やユーザとの対話のために OAuth2 Filter が必要です。
図で示すと、下のようになります。
JWT Authentication / External Authorization については「第5回 Open Policy Agent とサイドカーパターンによる認可の実装」等の過去の記事を参照してください。
おわりに
#この記事のサンプルコードは、GitHub Repository にあります。
GitHub Repository に Kubernetes 環境にインストールするための Helm チャートを追加しました。