第六回は、第五回に引き続き、内部向けのAPIであるマイクロサービスの認証を強化する最先端の機能を紹介します。
前回と同様、すべてのサービスをKubernetes(Minikube)上にデプロイする構成とし、堅牢化の対象としてIstioが提供しているBookinfoアプリケーションを取り上げます(図1)。
図1:システム構成
使用する各製品のバージョンを表 1に示します。
表1:使用する製品とバージョン
製品 | バージョン |
Minikube | v1.13.1 |
(Kubernetes) | v1.19.2 |
(Docker) | 19.03.8 |
Keycloak | 11.0.3 |
Istio | 1.7.4 |
Quarkus | 1.9.2.Final |
なお、Kubernetes環境(Minikube)はドキュメント等を参考にすでにインストールされているものとします。また、前回の記事でご紹介したKeycloakとIstioの導入/各種設定はなされているものとします。
QuarkusとKeycloak
Quarkusとは、KubernetesネイティブのJavaフレームワークです。Quarkusを使用することで、コンテナに最適化されたJavaアプリケーションを開発できます。ここではQuarkusとKeycloakを組み合わせた堅牢化方法をご紹介します(図2)。
図2:QuarkusとKeycloakを用いたBookinfoアプリケーションの堅牢化
前回は各サービスにそれぞれIstioのAuthorizationPolicyを作成し導入することで、各サービスにJWTによるアクセス制御を追加しました。Quarkusのkeycloak-authorizationエクステンションを使うと、各サービスのアクセス制御ロジックをKeycloakの認可サービスに集約することができます。
各サービスのアクセス制御ロジックを、各サービスで個別に持つべきか、認可サーバ側で集約して持つべきかについては一長一短があり、またシステムの要件に大きく依存する部分があるため、一概にどちらが良いとは言えません。アクセス制御ロジックを認可サーバに集約した場合、Keycloakのスコープやロール、グループといったリソースを、各サービス側で再定義することなくアクセス制御に使用することができ、またKeycloakのリソースの変更を即座にアクセス制御に反映させることができるという利点があります。
Quarkusアプリケーションの実装
まずは、Quarkusアプリケーションを実装していきます。Quarkusアプリケーションの実装には、Mavenが必要となるので、先にインストールしておきます。
次にプロジェクトを作成します。今回は、Keycloakの認可サービスを利用したり、アプリケーションをKubernetesにデプロイしたりするために、oidc、keycloak-authorization、resteasy-jsonb、kubernetes、container-image-dockerの5つのエクステンションを使います。
リスト2:プロジェクトの作成
1 | $ mvn io.quarkus:quarkus-maven-plugin:1.9.2.Final:create -DprojectGroupId=org.acme -DprojectArtifactId=security-keycloak-authorization-quickstart -Dextensions="oidc, keycloak-authorization, resteasy-jsonb, kubernetes, container-image-docker" |
既存のDetailsサービスを参考に、実装していきます。security-keycloak-authorization-quickstart/src/main/java/org/acme/security/keycloak/authorization/Book.javaを実装します。
リスト3:Book.javaの実装
01 | package org.acme.security.keycloak.authorization; |
03 | import com.fasterxml.jackson.annotation.JsonProperty; |
05 | import io.quarkus.security.identity.SecurityIdentity; |
09 | private String type = "article"; |
11 | private int pages = 3; |
13 | private String publisher; |
15 | private String language = "Japanese"; |
16 | @JsonProperty("ISBN-10") |
17 | private String isbn10 = "1234567890"; |
18 | @JsonProperty("ISBN-13") |
19 | private String isbn13 = "123-1234567890"; |
21 | public Book(SecurityIdentity identity) { |
22 | this.publisher = identity.getPrincipal().getName(); |
security-keycloak-authorization-quickstart/src/main/java/org/acme/security/keycloak/authorization/BooksResource.javaを実装します。
リスト4:BooksResource.javaの実装
01 | package org.acme.security.keycloak.authorization; |
03 | import javax.inject.Inject; |
04 | import javax.ws.rs.GET; |
05 | import javax.ws.rs.Path; |
06 | import javax.ws.rs.core.MediaType; |
07 | import javax.ws.rs.core.Response; |
09 | import org.jboss.resteasy.annotations.cache.NoCache; |
11 | import com.fasterxml.jackson.core.JsonProcessingException; |
12 | import com.fasterxml.jackson.databind.ObjectMapper; |
14 | import io.quarkus.security.identity.SecurityIdentity; |
17 | public class BooksResource { |
20 | SecurityIdentity identity; |
24 | public Response health() { |
25 | return Response.ok().type(MediaType.APPLICATION_JSON).entity("{\"status\": \"Details is healthy\"}").build(); |
31 | public Response details() { |
32 | ObjectMapper mapper = new ObjectMapper(); |
35 | json = mapper.writeValueAsString(new Book(identity)); |
36 | } catch (JsonProcessingException e) { |
39 | return Response.ok().type(MediaType.APPLICATION_JSON).entity(json).build(); |
application.properties(security-keycloak-authorization-quickstart/src/main/ resources/application.properties)に設定を加えます。
リスト5:application.propertiesに設定を追加
03 | quarkus.oidc.client-id=details |
04 | quarkus.oidc.credentials.secret=<client secret> |
06 | # Enable Policy Enforcement |
07 | quarkus.keycloak.policy-enforcer.enable=true |
09 | quarkus.kubernetes.part-of=details |
10 | quarkus.container-image.registry= |
11 | quarkus.container-image.group= |
12 | quarkus.container-image.name=details |
13 | quarkus.container-image.tag=1.0 |
14 | quarkus.kubernetes.image-pull-policy=never |
15 | quarkus.kubernetes.labels.app=details |
16 | quarkus.kubernetes.labels.version=v2 |
以上で、Quarkusアプリケーションの実装は完了です。
Quarkusアプリケーションのデプロイ
作成したQuarkusアプリケーションをビルドし、Kubernetesにデプロイしましょう。
リスト6:Quarkusアプリケーションのデプロイ
1 | $ cd security-keycloak-authorization-quickstart/ |
2 | $ ./mvnw clean package -Dquarkus.kubernetes.deploy=true |
以上で、Detailsサービスのv2がデプロイされました。
リスト7:デプロイされていることを確認
2 | NAME READY STATUS RESTARTS AGE |
3 | details-766f98fcf8-pc8fw 2/2 Running 1 10s |
4 | $ kubectl get deployments |
5 | NAME READY UP-TO-DATE AVAILABLE AGE |
DestinationRule(destination-rule-all-mtls-v2.yaml)を書き換えて、Detailsサービスのv2にルーティングするようにします。
リスト8:DestinationRuleの書き換え
03 | apiVersion: networking.istio.io/v1alpha3 |
変更したDestinationRuleを適用します。
リスト9:変更したDestinationRuleの適用
1 | $ kubectl apply -f destination-rule-all-mtls-v2.yaml |
以上で、Quarkusアプリケーションのデプロイは完了です。
Keycloakの認可サービスの設定
Keycloakの認可サービスでは、Resource(またはAuthorization Scope)、Policy、Permissionという3つのリソースを用いて、アクセス制御を設定することができます(図3)。
図3:Keycloakの認可サービス
Resourceには、その名の通り、「/protected/*」や「/secret」といったリソースを設定し、Policyには、「adminロールを持つ」や「ユーザAである」といった条件を設定します。そして設定したResourceとPolicyをPermissionで結びつけることで、アクセス制御を実現します。もちろん、複数のResourceに対して同一の条件を設定したり、同一のResourceに対して複数の条件を設定したりすることもできます。
実際に設定していきます。Keycloakの認可サービスは、管理コンソールの各クライアント設定画面のAuthorizationタブで設定できます(図4)。
図4:Authorizationタブ
今回は、/details/*にadminロールチェックを導入してみましょう。つまり、Resourceとして「/details/*」、Policyとして「adminロールを持つ」を作成し、それらを結びつけるPermissionを作成します。
まずは事前準備として、adminロールとadminロールを持つユーザを作成し、Detailsサービス用クライアントの認可サービスを有効化します(表2)。
表2:作成するKeycloakのリソース
リソース | 設定項目名 | 設定値 | 備考 |
クライアント | Client ID | details | Detailsサービス用クライアント。 |
| Access Type | confidential | ― |
| Standard Flow Enabled | OFF | ― |
| Direct Access Grants Enabled | OFF | ― |
| Service Accounts Enabled | ON | ― |
| Authorization Enabled | ON | ― |
ロール | Role Name | admin | ― |
ユーザ | Username | admin_user | ― |
ロール | Client | details | ― |
| Assigned Roles | admin | ― |
まずは、Resourceを作成します。Resourcesタブの[Create]ボタンをクリックすると、Add Resource画面が表示されますので(図5)、Resource名(ここでは「Details Resource」とします)とURI(ここでは「/details/*」とします)を入力し、[Save]ボタンをクリックします。
図5:Add Resource画面
次に、Policyを作成します。Policiesタブの[Create Policy…]をクリックし、[Role]をクリックすると、Add Role Policy画面が表示されますので(図 6)、Policy名(ここでは「Admin Role Policy」とします)とクライアント名(ここでは「details」とします)とクライアントロール名(ここでは「admin」とします)を入力し、[Save]ボタンをクリックします。
図6:Add Role Policy画面
次に、Permissionを作成します。Permissionsタブの[Create Permission…]をクリックし、[Resource-based]をクリックすると、Add Resource Permission画面が表示されますので(図7)、Permission名(ここでは「Details Resource Permission」とします)とResource(ここでは「Details Resource」とします)とPolicy(ここでは「Admin Role Policy」とします)を入力し、[Save]ボタンをクリックします。
図7:Add Resource Permission画面
以上で、Keycloakの認可サービスの設定は完了です。試しに、sample_userを用いて発行したアクセストークンを付与してBookinfoアプリケーションのProductページにアクセスすると、Productページは表示されますが、Details部分(左半分)が表示されないこと(「Sorry, product details are currently unavailable for this book.」が表示されること)を確認できます(図8)。
図8:BookinfoアプリケーションのProductページ(DetailsサービスAccess Denied)
次に、admin_userを用いて発行したアクセストークンを付与してBookinfoアプリケーションのProductページにアクセスすると、Details部分も表示されることを確認できます(図9)。またDetails部分には、今回実装した情報が表示されていることにもご注目ください。
図9:BookinfoアプリケーションのProductページ
次回は、コンテナ上のマイクロサービスの認証強化について、StrimziとKeycloakを組み合わせて、システムを堅牢化する方法を説明します。