Daprのミドルウェアを使ってアプリをOAuth2で保護してみる
CX事業本部@大阪の岩田です。先日のブログに引き続きDaprのクイックスタートを試してみました。今回試したのはミドウェアを使ってアプリをOAuth2で保護するという内容です。そもそもDaprって何?という方は前回のブログを参照して下さい。
Daprのミドルウェアについて
Daprにはミドルウェアというコンポーネントが存在します。アプリケーションにミドルウェアを追加することでリクエスト/レスポンスを処理する独自のパイプラインが自由に構築できます。以下は公式ドキュメント記載の画像です。
Daprのミドルウェアがサイドカーコンテナとして動作し、クライアントとアプリケーション間のリクエスト/レスポンスに独自処理を実行できることが分かります。
パイプラインにはデフォルトで
- tracing middleware
- CORS middleware
が組み込まれていますが、必要に応じてさらに追加のミドルウェアを構成可能です。
2021/7時点では以下のようなミドルウェアが提供されています。Alpha版がほとんどなので、本番環境での利用はもう少し様子を見たほうが良さそうです。
Name | Description | Status | Component version |
---|---|---|---|
Rate limit | Restricts the maximum number of allowed HTTP requests per second | Alpha | v1 |
OAuth2 | Enables the OAuth2 Authorization Grant flow on a Web API | Alpha | v1 |
OAuth2 client credentials | Enables the OAuth2 Client Credentials Grant flow on a Web API | Alpha | v1 |
Bearer | Verifies a Bearer Token using OpenID Connect on a Web API | Alpha | v1 |
Open Policy Agent | Applies Rego/OPA Policies to incoming Dapr HTTP requests | Alpha | v1 |
Uppercase | Converts the body of the request to uppercase letters | GA (For local development) | v1 |
公式から提供されているミドルウェアを利用する以外にも、ミドルウェアインターフェースを実装した独自ミドルウェアを自作して利用することも可能です。ミドルウェアのインターフェースは以下の定義になります。
type Middleware interface { GetHandler(metadata Metadata) (func(h fasthttp.RequestHandler) fasthttp.RequestHandler, error) }
やってみる
ミドルウェアの概要が分かったので、サンプルアプリをEKS上にデプロイして動かしてみます。今回はGitHubのリポジトリで提供されている以下のサンプルアプリをデプロイしていきます。
https://github.com/dapr/quickstarts/tree/master/middleware
環境
今回利用した環境です
- Kubernetes: 1.19
- eksctl: 0.56.0
- kubectl: v1.19.6-eks-49a6c0
- Dapr CLI: 1.2.0
- Helm: v3.6.2+gee407bd
EKSクラスタの構築とDaprの初期化は完了している前提で進めていきます。手順が分からない場合は前回のブログを参照して下さい。
Helmチャートのインストール
このクイックスターとではIngressコントローラーとしてNginxを利用します。以下の手順でHelmチャートをインストールし、クラスタにNginxを追加しましょう。
まずはリポジトリの追加
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx "ingress-nginx" has been added to your repositories
Helmチャートのインストール
$ helm install my-release ingress-nginx/ingress-nginx NAME: my-release LAST DEPLOYED: Fri Jul 23 06:56:29 2021 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: The ingress-nginx controller has been installed. It may take a few minutes for the LoadBalancer IP to be available. You can watch the status by running 'kubectl --namespace default get services -o wide -w my-release-ingress-nginx-controller' An example Ingress that makes use of the controller: apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx name: example namespace: foo spec: rules: - host: www.example.com http: paths: - backend: serviceName: exampleService servicePort: 80 path: / # This section is only required if TLS is to be enabled for the Ingress tls: - hosts: - www.example.com secretName: example-tls If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided: apiVersion: v1 kind: Secret metadata: name: example-tls namespace: foo data: tls.crt: <base64 encoded cert> tls.key: <base64 encoded key> type: kubernetes.io/tls
NginxのPodが稼働していることを確認しておきましょう
$ kubectl get pods NAME READY STATUS RESTARTS AGE my-release-ingress-nginx-controller-74586884b9-5vg9v 1/1 Running 0 86s
Google APIs ConsoleからOAuth 2.0 クライアント IDを作成
続いてアプリをOAuth2で保護するために、Google APIs ConsoleからOAuth 2.0 クライアント IDを発行します。
「認証情報を作成」からOAuthクライアントIDを選択します
「アプリケーションの種類」にウェブアプリケーションを選択し、「承認済みのリダイレクト URI」には自分がRoute53で管理しているドメインから適当な名前を払い出して設定します。
※クイックスタートではdummy.comという名前とhostsファイルを利用しているのすが、せっかくなので後ほどデプロイされるALBにエイリアスレコードを設定して独自ドメインで動かしてみます
パイプラインのデプロイ
$ cd quickstarts/middleware/
deploy/oauth2.yaml
を編集し
- clientId
- clientSecret
- redirectURL
に先程作成したOAuth2クライアントの情報を設定します
apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: oauth2 spec: type: middleware.http.oauth2 version: v1 metadata: - name: clientId value: "<ここにクライアントIDを入力>" - name: clientSecret value: "<ここにクライアントシークレットを入力>" - name: scopes value: "https://www.googleapis.com/auth/userinfo.email" - name: authURL value: "https://accounts.google.com/o/oauth2/v2/auth" - name: tokenURL value: "https://accounts.google.com/o/oauth2/token" - name: redirectURL value: "<ここに承認済みのリダイレクト URIを入力>" - name: authHeaderName value: "authorization"
設定できたらデプロイメントを作成します
$ kubectl apply -f deploy/oauth2.yaml component.dapr.io/oauth2 created
続いてパイプラインのデプロイメントを作成
$ kubectl apply -f deploy/pipeline.yaml
参考までにdeploy/pipeline.yaml
の定義は以下の通りです
apiVersion: dapr.io/v1alpha1 kind: Configuration metadata: name: pipeline spec: tracing: samplingRate: "1" zipkin: endpointAddress: "http://zipkin.default.svc.cluster.local:9411/api/v2/spans" httpPipeline: handlers: - type: middleware.http.oauth2 name: oauth2
サンプルアプリのデプロイ
パイプラインの準備ができたのでアプリ本体をデプロイします。アプリのコードは以下の通りで、/echo
というルートでリクエストを待ち受け、リクエストヘッダのAuthorizationをそのまま返却するだけのアプリです
// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // ------------------------------------------------------------ const express = require('express'); const bodyParser = require('body-parser'); const app = express(); app.use(bodyParser.json()); const daprPort = process.env.DAPR_HTTP_PORT || 3500; const port = 3000; app.get('/echo', (req, res) => { var text = req.query.text; console.log("Echoing: " + text); res.send("Access token: " + req.headers["authorization"] + " Text: " + text); }); app.listen(port, () => console.log(`Node App listening on port ${port}!`));
アプリをデプロイします
$ kubectl apply -f deploy/echoapp.yaml deployment.apps/echoapp created
デプロイしたアプリのPodが動作していることを確認します
$ kubectl get pods NAME READY STATUS RESTARTS AGEechoapp-c56bfd446-vnp2s 2/2 Running 0 20s my-release-ingress-nginx-controller-74586884b9-5vg9v 1/1 Running 0 25m
最後にアプリをIngressコントローラーの配下に紐付けます
$ kubectl apply -f deploy/ingress.yaml Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingressingress.extensions/echo-ingress created
最後にIngressコントローラーのFQDNを確認し、サンプルアプリ用に払い出した名前にエイリアスレコードを作成します。まずはALBのFQDNを確認
$ kubectl get svc my-release-ingress-nginx-controller --output 'jsonpath={.status.loadBalancer.ingress[0].hostname}'a04104cf898dc446ab5e74a185037ff4-956392576.ap-northeast-1.elb.amazonaws.com
確認したFQDNをもとにエイリアスレコードを作成します
サンプルアプリにアクセスしてみる
一通り準備ができたのでサンプルアプリにアクセスしてみます。ブラウザのアドレスバーに http://<サンプルアプリ用に払い出した名前>/v1.0/invoke/echoapp/method/echo?text=hello
と入力すると...
Googleのログイン画面にリダイレクトされました。Googleアカウントにログインすると...
アプリの画面にリダイレクトされ、Authorizationヘッダの中身が表示されました。動作確認成功です
ちなみに、この時のアクセスをChromeの開発者ツールからCopy as cURL
すると以下のようなテキストがコピーされました。
curl 'http://<サンプルアプリ用のFQDN>/v1.0/invoke/echoapp/method/echo?text=hello' \ -H 'Connection: keep-alive' \ -H 'Pragma: no-cache' \ -H 'Cache-Control: no-cache' \ -H 'Upgrade-Insecure-Requests: 1' \ -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36' \ -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' \ -H 'Accept-Language: ja' \ -H 'Cookie: gosessionsid=xxxxxx' \ --compressed \ --insecure
サンプルアプリはAuthorizationヘッダの中身をそのまま返却する仕様ですが、ブラウザはAuthorizaionヘッダはセットしておらず、Cookieを送信していることが分かります。この挙動から考えると、Daprのミドルウェア側でCookieとトークンの紐付けを管理し、サンプルアプリへのリクエスト時にAuthorizaionヘッダをセットしてくれているようですね。
まとめ
Daprのミドルウェアを使うとコード修正無しで簡単にアプリケーションをOAuth2で保護できることが確認できました。認証/認可のような定型的な処理についてはミドルウェアをうまく活用して開発者の負担を軽減していきたいですね。