[Kuberntes] 汎用OAuth2 Proxyをサービスの手前に置く:認証認可編
前回の記事を読んでない人は
先にこちらをご覧下さい
本日のゴール
- oauth2-proxyがOpenID Connectプロバイダー兼認可サーバー(Keycloak)からアクセストークンを受け取れるようにする
- Upstreamのサービスが、X-Auth-Request-XXXを用いて認証を行う
システムアーキテクチャ図
OIDCのフローはだいぶ端折っているので、詳しくは下記の記事が分かりやすいと思います。
今回は、既存のKeycloadサーバーと連携してますが、時間とPCリソースに余裕のある方はKeycloakもKubernetes上にデプロイすると、より理解が深まるかと。
oauth2-proxy用のNamespaceを作成
テナントのNamespaceには、OIDC Client情報を公開しないコンセプトですので、oauth2-proxyは専用のNamespaceで運用します。
必要なリソースを作成
Namespaceは cicd-system
としていますが、こちらも好みで変更してください。
CLIENT_ID, CLIENT_SECRETおよびOIDC_ISSUERは、Keycloakの設定によって読み替えてください。
COOKIE_SECRETは、oauth2-proxyが発行するCookieのシードに使われる値で、ランダムな文字列の生成方法が紹介されています。
sessionの値をCookieに持たせるとヘッダーが大きすぎてエラーになったりするので、Redisに保存するためにsidecar方式でRedis containerも追加しています。本番運用する場合は、スケーラビリティや可用性の観点から別途Redis Clusterを作成した方が良いです。
apiVersion: v1
kind: Namespace
metadata:
labels:
istio-injection: enabled
name: cicd-system
---
apiVersion: v1
kind: Service
metadata:
labels:
app: oauth2-proxy
name: oauth2-proxy
namespace: cicd-system
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 4180
selector:
app: oauth2-proxy
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: oauth2-proxy
name: oauth2-proxy
namespace: cicd-system
spec:
replicas: 1
selector:
matchLabels:
app: oauth2-proxy
template:
metadata:
labels:
app: oauth2-proxy
spec:
containers:
- args:
- --client-id={{ CLIENT_ID }}
- --client-secret={{ CLIENT_SECRET }}
- --whitelist-domain=.svc.macbook.local
- --cookie-domain=.svc.macbook.local
- --cookie-secret={{ COOKIE_SECRET }}
- --cookie-secure=false
- --email-domain=*
- --http-address=0.0.0.0:4180
- --oidc-issuer-url={{ OIDC_ISSUER }}
- --pass-access-token=false
- --pass-authorization-header=true
- --pass-host-header=true
- --pass-user-headers=true
- --provider=oidc
- --redirect-url=http://oauth2-proxy.cicd-system.svc.macbook.local/oauth2/callback
- --reverse-proxy=true
- --set-xauthrequest=true
- --upstream=static://200
- --skip-provider-button=true
- --session-store-type=redis
- --redis-connection-url=redis://localhost:6379
image: quay.io/oauth2-proxy/oauth2-proxy:v7.2.1
name: oauth2-proxy
ports:
- containerPort: 4180
protocol: TCP
- image: redis:6.2.6-alpine
name: redis
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: cicd-system-gateway
namespace: cicd-system
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- '*.cicd-system.svc.macbook.local'
port:
name: http
number: 80
protocol: HTTP
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: oauth2-proxy
namespace: cicd-system
spec:
gateways:
- cicd-system-gateway
hosts:
- oauth2-proxy.cicd-system.svc.macbook.local
http:
- route:
- destination:
host: oauth2-proxy.cicd-system.svc.cluster.local
port:
number: 80
$ kubectl apply -f cicd-system.yaml
KeycloadにリダイレクトURLを登録
http://oauth2-proxy.cicd-system.svc.macbook.local/oauth2/callback
/etc/hostsを更新
oauth2-proxy用のドメインも追加してください。
127.0.0.1 oauth2-proxy.cicd-system.svc.macbook.local
Istioに外部認可サーバーを登録
要はoauth2-proxyサービスのエンドポイントと、Upstreamのサービスに送るヘッダー等を設定します。
既存のConfigMapを取得して
$ kubectl get cm istio -n istio-system -o yaml > istio.cm.yaml
extensionProvidersとして、oauth2-proxyを登録します。
extensionProviders:
- name: oauth2-proxy
envoyExtAuthzHttp:
service: oauth2-proxy.cicd-system.svc.cluster.local
port: 80
timeout: 10s
includeHeadersInCheck:
- cookie
- authorization
includeAdditionalHeadersInCheck:
X-Auth-Request-Redirect: 'http://%REQ(Host)%'
headersToUpstreamOnAllow:
- authorization
- path
- x-auth-request-user
- x-auth-request-email
- x-auth-request-groups
- x-auth-request-preferred-username
headersToDownstreamOnDeny:
- content-type
- set-cookie
$ kubectl apply -f istio.cm.yaml
Upstreamサービスとして、Jenkinsをデプロイする
上記のステップまで設定が完了していれば、あとはどんなサービスでもUpstreamサービスとして置くことが可能です。今回は、みんな大好きJenkinsと連携させてみます。
あくまでテスト用なので、Podリスタートしたら設定は消し飛びますのであしからず。永続化したい場合は、PersistentVolumeを作成してJENKINS_HOMEにマウントしてください。
必要なリソースを作成
default Namespaceは既に存在しますが、Istio sidecarのインジェクションを有効にしたいのでNamespaceの再定義しています。
肝はAuthorizationPolicyリソースで、CUSTOM actionとしてoauth2-proxy(IstioにextensionProvidersとして登録した名前)を指定しています。そして、ラベルセレクター(任意のセレクターを設定可能)にマッチしたPodへのアクセスは、自動的にoauth2-proxyにプロキシされ200 OKが返ってくればそのままUpstreamへリクエストが送られますが、それ以外の場合はKeycloakのログインフォームに遷移するので、ユーザー名とパスワードを入力してログインすると再びアプリのトップページに戻ってこれます。
この時、アプリはx-auth-request-xxxヘッダーを受け取ることができるので、ユーザー名やグループ名での認証認可が可能になります。言い換えると、それらの値を利用しないとアプリにとってはanonymousユーザーになってしまうので、注意が必要です。
apiVersion: v1
kind: Namespace
metadata:
labels:
istio-injection: enabled
name: default
---
apiVersion: v1
kind: Service
metadata:
labels:
app: jenkins
name: jenkins
namespace: default
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: jenkins
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: jenkins
name: jenkins
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: jenkins
template:
metadata:
labels:
app: jenkins
oauth: enabled
spec:
containers:
- image: jenkins/jenkins:2.333-alpine-jdk8
name: jenkins
---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: default-gateway
namespace: default
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- '*.default.svc.macbook.local'
port:
name: http
number: 80
protocol: HTTP
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: jenkins
namespace: default
spec:
gateways:
- default-gateway
hosts:
- jenkins.default.svc.macbook.local
http:
- route:
- destination:
host: jenkins
port:
number: 8080
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ext-authz-oauth2-proxy
namespace: default
spec:
action: CUSTOM
provider:
name: oauth2-proxy
rules:
- to:
- operation:
hosts:
- jenkins.default.svc.macbook.local
selector:
matchLabels:
oauth: enabled
$ kubectl apply -f default.yaml
Jenkinsの初期設定
Jenkins PodのログからinitialAdminPasswordを取得
$ kubectl logs $(kubectl get po -l app=jenkins --no-headers -o name | cut -f2 -d/)
*************************************************************
*************************************************************
*************************************************************
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
{{ initialAdminPassword }}
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
*************************************************************
*************************************************************
*************************************************************
認証回避のためport-forwardでJenkins Podへ直接アクセス
これができちゃうので、テナントにはNamespaceへアクセスする権限を与えない方が良いです。
$ kubectl port-forward svc/jenkins 8080:8080
http://localhost:8080
初期設定
先程の initialAdminPassword を入力して、Continueをクリック。
Install Suggested Pluginsも入れておきましょう。
Admin Userは適当に設定しておいてください。x-auth-requestでのログインを有効にした後は、adminユーザとしてログインはできなくなります。
Jenkins URLは、localhostではなく jenkins.default.svc.macbook.local
を設定してください。hostsファイルの更新もお忘れなく。
127.0.0.1 jenkins.default.svc.macbook.local
x-auth-request-xxxで認証できるようにする
http://localhost:8080/pluginManager/
Manage Jenkins => Manage Plugins => Availableで
"reverse proxy auth"と検索すると、同名のプラグインがヒットするはずです。
プラグイン名にチェックをつけてインストールしましょう。
http://localhost:8080/configureSecurity/
Manage Jenkins => Configure Global Security
Security Realmとして、"HTTP Header by reverse proxy"を選択してデフォルト値を下記の値で上書きした後に保存してください。
Header User Name: X-Auth-Request-User もしくは X-Auth-Request-Preferred-Username
Header Groups Name: X-Auth-Request-Groups
Header Groups Delimiter Name: ,
動作確認
全部の設定がうまくいっていれば、下記のページにアクセスすると
http://jenkins.default.svc.macbook.local/
- Keycloakのログインページにリダイレクトされる
- ユーザー名、パスワードを入力してログインボタンをクリックすると、Jenkinsのトップページに戻ってくる
右上のユーザー名にはHeader User Nameに指定したヘッダーの値が表示されているはずです。
Groupsは http://jenkins.default.svc.macbook.local/whoAmI/
から確認可能です。区切り文字が間違っていると、Groupがリスト表示されないのでその場合は http://localhost:8080/configureSecurity/
からDelimiter Nameを変更してください。
応用
ユーザー名やグループ名を利用して、Jenkins UIレベルのRoll Base Access Control(RBAC)を設定したい場合は、追加でRole-based Authorization Strategyプラグインをインストールしてください。
http://jenkins.default.svc.macbook.local/configureSecurity/
Manage Jenkins => Configure Global Security
AuthorizationをRole-Based Strategyに変更
http://jenkins.default.svc.macbook.local/manage
Manage Jenkinsに戻ると、Manage and Assign Rolesという項目が増えているので、こちらからRoleとAssignmentを設定可能です。
フォルダーのパターンは再起的にマッチするようにしないと、親フォルダへのアクセス権限が無いために子フォルダにアクセスできないことになります。
Foo/Bar/Baz
フォルダ以下全部のジョブ or フォルダに権限を与えたい場合は
Job Readの権限を以下のフォルダに対して与えるRoleを定義して
- Foo
- Foo/Bar
- Foo/Bar/Baz
- Foo/Bar/Baz/.*
^Foo(|/Bar(|/Baz(|/.*)))
全権限を以下のフォルダに与えるRoleを別途定義する必要があります。
- Foo/Bar/Baz
- Foo/Bar/Baz/.*
^Foo/Bar/Baz(|/.*)
そして、対象のUser/Groupに上記2つのRoleをアサインすれば、Foo/Bar
フォルダへはアクセス権限のみで、Foo/Bar/Baz
以下のフォルダでは自由にジョブの作成が可能になる権限を付与することができます。
Discussion