Tanzu Application Platform Tanzu アプリケーションのモダナイゼーション

Tanzu Application Platform 1.2 新機能!AppSSO で SSOaaS の実現!

VMware のモダンアプリケーションの真髄である Tanzu Application Platform (以降 TAP ) 1.2 がリリースされました。このバージョン 1.2 は ReleaseNote に記載されているとおり非常に多くの機能がリリースされています。その中でも、特に筆者が気になっていた機能が Application Single-SignOn(AppSSO) でした。本記事では、開発者が喜ぶ機能であるアプリケーション認証機能、これを素早く TAP で as a Service 型で提供する方法を紹介します。

アプリケーション開発を悩ませる”ユーザー認証”

セキュアな API やアプリケーションエンドポイント開発をしていく上でユーザー認証・認可の設定は避けて通れない作業です。さて、アプリケーションの認証認可の開発方法は多くの外部情報が公開されており、例えば Spring の開発では以下のようなブログが参考になるかと思います。

https://spring.io/guides/tutorials/spring-boot-oauth2/

このチュートリアルにしたがっていく中で注目すべきは以下の箇所です。最終的にアプリケーション にSingle Sign-On 機能をいれようとすると以下のような設定が必要です。これは、作成したアプリケーションが企業のユーザー情報を格納した IdP (認証プロバイダ)への認証情報です。

spring:
  security:
    oauth2:
      client:
        registration:
          okta:
            client-id: xxxxx
            client-secret: yyyyy
        provider:
          okta:
            issuer-uri: zzzz

この段階で二つの問題が浮かび上がってきます。最初に問題になるのが、client-id や client-secret の値の取得方法です。いわゆる本番環境において開発者が IdP に自由にクライアントID を発行する権限をもっているケースは稀です。そうなると社内のチケットサービスなどから依頼することとなり、効率的な開発を妨げることになります。

次の問題が IdP の違いの吸収です。この例では Okta を IdP として使っていますが、例えば本番環境が Workspace One などのSAML サービスだとまた設定が変わってきてしまいます。バックエンドのサービスがなんであるかを強く意識する必要が開発者にでてきてしまい、アプリケーションと認証プロバイダの間に強い依存関係を生んでしまいます。そして、その強い依存関係はアプリケーションの別環境への展開に大きな障壁となりえます。

より Developer を意識してこの SSO の問題を As a Service モデルで解決するための方法が今回紹介する AppSSO 機能です。

Kubernetes Nativeかつペルソナベースで高い抽象度を実現する:AppSSO

AppSSO の正式名称は Application Single Sign-On for VMware Tanzu と呼ばれ、VMware によって新しく開発されたプロジェクトです。(今後オープンソース化予定)。専用のマニュアルページもすでに用意されています。

https://docs.vmware.com/en/Application-Single-Sign-On-for-VMware-Tanzu/1.0/appsso/GUID-getting-started-application.html

そのマニュアルからの AppSSO のアーキテクチャが以下となります。

Diagram of AppSSO's components and how they interact with End-Users and Client applications

AppSSO については大きくわけると以下の二つの機能があります。

  • AuthServer
    • プラットフォーム担当者が設定する想定
    • バックエンドの IdP との設定を行う箇所
    • 様々な IdP の Federation (統合)機能を提供し、執筆時点以下をサポート
      • OpenID
      • LDAP(Experimental)
      • SAML (Experimental)
      • Internal User
    • Kubernetes のカスタムリソースとして管理されており、各ネームスペースごとにデプロイできるほか、別ネームスペースからのアクセスも制御できる
  • ClientRegistration
    • 開発者が設定する想定
    • AuthServer へアプリケーションの登録を依頼する
    • AuthServer はリクエスト毎に個別の Client ID と Client Secret を発行する
    • Kubernetes のカスタムリソースとして管理されており、開発者は Kubernetes に完結して as a Service 型でリクエストができる

これらの機能を実機で見ながら、深堀りしていきましょう。

IdP の準備を待たずに早く開発したい!そんなシナリオに最適

例えば、このようなシナリオを考えましょう。

  • IdP の設計やクライアントの ID の発行が完了していない
  • 開発者はクライアントの ID の発行を待たずして、開発を開始したい

この要望を TAP だと、どう答えられるか、みていきましょう。

プラットフォーム管理者「とりあえず、Internal User で AuthServer を作りますね」

AppSSO の面白い機能が AuthServer を Internal User とよばれる、IdP 上にはいないテンポラリユーザを定義することができます。この機能を利用することで「とりあえず」のSSO 機能を開発者に提供できます。TAP 1.2 以上がインストールされた環境で以下のような YAML ファイルを作成します。
*例では “example.com” のドメインを使っていますが、ドメイン名はそれぞれの TAP 環境に合わせてください。

apiVersion: sso.apps.tanzu.vmware.com/v1alpha1
kind: AuthServer
metadata:
  name: my-authserver-example
  namespace: default
  labels:
    name: my-first-auth-server
    env: tutorial
  annotations:
    sso.apps.tanzu.vmware.com/allow-client-namespaces: "default"
    sso.apps.tanzu.vmware.com/allow-unsafe-issuer-uri: ""
    sso.apps.tanzu.vmware.com/allow-unsafe-identity-provider: ""
spec:
  replicas: 1
  issuerURI: "http://authserver.example.com"
  tokenSignature:
    signAndVerifyKeyRef:
      name: "authserver-signing-key"
  identityProviders:
    - name: "internal"
      internalUnsafe:
        users:
          - username: "user"
            password: "$2a$10$201z9o/tHlocFsHFTo0plukh03ApBYe4dRiXcqeyRQH6CNNtS8jWK"
---
apiVersion: secretgen.k14s.io/v1alpha1
kind: RSAKey
metadata:
  name: authserver-signing-key
  namespace: default
spec:
  secretTemplate:
    type: Opaque
    stringData:
      key.pem: $(privateKey)
      pub.pem: $(publicKey)
---
apiVersion: v1
kind: Service
metadata:
  name: my-authserver-example
  namespace: default
spec:
  selector:
    app.kubernetes.io/part-of: my-authserver-example
    app.kubernetes.io/component: authorization-server
  ports:
    - port: 80
      targetPort: 8080
---
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: my-authserver-example
  namespace: default
spec:
  virtualhost:
    fqdn: authserver.example.com
  routes:
    - conditions:
        - prefix: /
      services:
        - name: my-authserver-example
          port: 80
---

この長い YAML ファイルで重要なのが以下の箇所であり、Internal User として、ユーザー名: User とパスワードを bcrypt フォーマットで変換した password を登録しています。

  identityProviders:
    - name: "internal"
      internalUnsafe:
        users:
          - username: "user"
            password: "$2a$10$201z9o/tHlocFsHFTo0plukh03ApBYe4dRiXcqeyRQH6CNNtS8jWK"

上の YAML ファイルを実行するとしばらくすると指定した namespace に2つのPodが起動します。

% kubectl get po
NAME                                                 READY   STATUS    RESTARTS   AGE
my-authserver-example-auth-server-74544d459b-wmqmb   1/1     Running   0          28s
my-authserver-example-redis-65c89b79f8-wz8ln         1/1     Running   0          39m

この AuthServer はアクセスできるものになっています。以下のコマンドを実行してポートフォーワードしてログインできるようにします。

% kubectl port-forward -n default deploy/my-authserver-example-auth-server 7777:8080

ポートフォワード完了後、http://localhost:7777 にログインすると以下のような画面が出てきます。

“user” および “password” でログインができます。ログインすると以下のような画面がでます。まだアプリケーションも何も登録していないのでこれは想定の動作です。

開発者「いいですね、その AuthServer を使わせてください」

AuthServer が用意できているので、アプリケーションを定義することができます。開発者は以下のような YAML を用意します。
※ ここでも”example.com”のドメインは環境ごとにアップデートしてください。
(注:なお見ての通り、この段階では開発者の操作にもかかわらず、YAML つまり Kubernetes の知識が要求されるものが登場します。今後、これらの抽象化をさらにすすめる予定はありますが、現在は AppOperator というペルソナ、つまりある程度 Kubernetes の素養がある方が操作する想定をしています。)

apiVersion: sso.apps.tanzu.vmware.com/v1alpha1
kind: ClientRegistration
metadata:
   name: my-client-registration
   namespace: default
spec:
   authServerSelector:
      matchLabels:
         name: my-first-auth-server
         env: tutorial
   redirectURIs:
      - "http://appsso-starter-java.default.example.com/login/oauth2/code/appsso-starter-java"
   requireUserConsent: false
   clientAuthenticationMethod: basic
   authorizationGrantTypes:
      - "authorization_code"
   scopes:
      - name: "openid"

この YAML ファイルを適用後、さていよいよ、アプリをデプロイしてみたいと思います。サンプルの Spring アプリケーションがすでに存在するので、これを利用してみます。

https://github.com/sample-accelerators/appsso-starter-java

なお、特徴的なこととして、このアプリケーションにどこにも client_id や client_secret の情報をもっていません。これらの情報は、この回でとりあげている Service Bindings を使いながら、アプリケーションとは独立して、値をいれることができます。

以下の手順でアプリを起動します。”–service-ref” に前手順のリソースを指定します。


tanzu apps workload apply appsso-starter-java \
    --type web \
    --label app.kubernetes.io/part-of=appsso-starter-java \
    --service-ref "appsso-starter-java=sso.apps.tanzu.vmware.com/v1alpha1:ClientRegistration:my-client-registration" \
    --git-repo https://github.com/sample-accelerators/appsso-starter-java \
    --git-branch main

しばらくすると、http://appsso-starter-java.<namespace>.<domain> にアプリが起動してきますので、URL にアクセスしてみます。
すると以下のようなシンプルな UI にアクセスができます。

 

ここで、ログインボタンをさきほどの AuthServer と同じ認証画面が登場します。

おなじく “user” および “password” でログインすると認証済みページにログインができるようになります。

さて、これで SSO 向けのアプリケーションが公開できました。開発者はこれをベースに開発をすすめることができます。

プラットフォーム管理者「IdP 準備できました!」

さて、しばらくして、プラットフォーム管理者が IdP を用意できたとします。AppSSO の大きな特徴が、この IdP の追加を開発者からは透過的に、さらに一切コードを編集せずに提供することができます。

前手順の AuthServer の設定をアップデートします。


apiVersion: sso.apps.tanzu.vmware.com/v1alpha1
kind: AuthServer
metadata:
  name: my-authserver-example
  namespace: default
  labels:
    name: my-first-auth-server
    env: tutorial
  annotations:
    sso.apps.tanzu.vmware.com/allow-client-namespaces: "default"
    sso.apps.tanzu.vmware.com/allow-unsafe-issuer-uri: ""
    sso.apps.tanzu.vmware.com/allow-unsafe-identity-provider: ""
spec:
  replicas: 1
  issuerURI: "http://authserver.example.com"
  tokenSignature:
    signAndVerifyKeyRef:
      name: "authserver-signing-key"
  identityProviders:
    - name: "internal"
      internalUnsafe:
        users:
          - username: "user"
            password: "$2a$10$201z9o/tHlocFsHFTo0plukh03ApBYe4dRiXcqeyRQH6CNNtS8jWK"
    - name: okta
      openID:
        issuerURI: https://dev-75067013-admin.okta.com/
        clientID: 0oa5smy3nmJuNUP2H5d7
        clientSecretRef:
          name: my-openid-client-secret
        authorizationUri: https://dev-75067013.okta.com/oauth2/default/v1/authorize
        tokenUri: https://dev-75067013.okta.com/oauth2/default/v1/token
        jwksUri: https://dev-75067013.okta.com/oauth2/default/v1/keys
        scopes:
          - "openid"

この YAML の更新箇所が以下の箇所です。ここでは新しく Okta Developer の無料アカウントの情報をいれています。なお、ユーザーごとに違う値ですので、生成された情報をもとにアップデートしてください。


    - name: okta
      openID:
        issuerURI: https://dev-75067013-admin.okta.com/
        clientID: 0oa5smy3nmJuNUP2H5d7
        clientSecretRef:
          name: my-openid-client-secret
        authorizationUri: https://dev-75067013.okta.com/oauth2/default/v1/authorize
        tokenUri: https://dev-75067013.okta.com/oauth2/default/v1/token
        jwksUri: https://dev-75067013.okta.com/oauth2/default/v1/keys
        scopes:
          - "openid"

上の YAML ファイルの更新とさらに以下のコマンドを実行して clientSecret を登録します。

kubectl create secret generic my-openid-client-secret --from-literal="clientSecret=<ClientSecret>"

開発者「お、新しいIdPが増えている!」

さて現在アプリケーションには何も変更していませんが、アプリケーションはどうなっているのでしょうか?
アプリケーションに再度アクセスすると、ログインボタンで新しく OKTA のプロバイダが表示されます。

これをクリックして okta の情報をもとにログインすると以下のようになります。ポイントが Hello に続く文字列で、Okta のユーザー情報が表示されています。

 

このようにアプリケーションから透過的にIdPを新しくつけることができました。AppSSO の目的はこのデモでお見せしたように開発者から可能な限り SSO のわずわらしさから解放するだけでなく、依存度を下げて、開発の流れを止めないことです。

まとめ

今回はTAP 1.2 の AppSSO 機能を紹介しました。今回のバージョンから初登場の機能ですので、今後さらなる使いやすさが期待されることです。VMware ブログでは引き続き Tanzu Application Platform の新情報をご提供します。