Amazon Web Services ブログ

Java ベースの Kubernetes オペレーターを使用した Amazon EKS での Kubernetes RBAC と IAM の統合

 

はじめに

Kubernetes ネイティブアプリケーションは、Kubernetes クラスターにデプロイされ、Kubernetes API と kubectl などのクライアント側ツールの両方を使用して管理されるアプリケーションです。Kubernetes オペレーターは、etcd データベースクラスターや Prometheus モニタリング/アラートシステムなど、重要な Kubernetes アプリケーションをデプロイするための抽象概念です。このようなアプリケーションに必要なドメイン固有の知識を持つカスタムリソースとコントローラを使用して Kubernetes 機能を拡張するメカニズムを提供します。

カスタムコントローラを開発するには、Kubernetes コントローラランタイムと対話するために低レベルの API が必要です。GoLang 開発者コミュニティは、豊富なフレームワークと一連の安定した SDK から得た利点を長い間使用してきました。Java は世界で最も人気のあるプログラミング言語の 1 つですが、そのようなライブラリリソースは Java 開発者コミュニティでは利用できませんでした。最近、公式の Kubernetes Java SDK プロジェクトは、カスタムコントローラの開発に役立つコントローラビルダー Java SDK を作成するために、GoLang SDK の主要機能の多くが Java に移植されたと発表しました。

このブログ記事では、Kubernetes Java SDK の新機能を活用して Kubernetes のロールベースのアクセスコントロール (RBAC) と、Amazon Elastic Kubernetes Service (Amazon EKS) を使用してプロビジョニングされた Kubernetes クラスター内の AWS Identity and Access Management (IAM) との統合を管理するのに役立つ Kubernetes オペレーターの実装について詳しく説明します。

Amazon EKS での認証と承認

Amazon EKS は IAM を使用して、Kubernetes クラスターに認証を提供します。kubectl を使用して内部で Amazon EKS とやり取りする場合、AWS CLI のバージョン 1.18.49 以降で利用可能な aws eks get-token コマンド、または Kubernetes 向け AWS IAM Authenticator を使用して認証トークンをフェッチします。これは、Kubernetes API サーバーに送信される HTTP リクエストの認証ヘッダーで渡されます。デフォルトでは、aws sts get-caller-identity コマンドで返される IAM 認証情報を使用してこのトークンを生成します。Amazon EKS は、token authentication webhook を使用してリクエストを認証しますが、認証はネイティブの Kubernetes RBAC に依存しています。認証された IAM プリンシパルに関連付けられた IAM ポリシーによって付与される一連のアクセス許可は、Amazon EKS クラスターでクライアントが実行できる操作や実行できない操作には何の影響も及ぼしません。IAM と RBAC の間の統合で核心となるのは、IAM プリンシパル (ロール/ユーザー) と Kubernetes サブジェクト (ユーザー/グループ) 間のマッピングを提供する Amazon EKS クラスターに適用される aws-auth ConfigMap です。後者は、クライアントに付与されるアクセスをコントロールする Kubernetes Roles/ClusterRoles および RoleBindings/ClusterRoleBindings に関連付けられます。

AWS アカウント管理者が 1 つ以上の IAM グループにユーザーを追加または削除して IAM ユーザーの属性を変更するとき、それらのアクションが Amazon EKS クラスター内の aws-auth ConfigMap に対応する更新を自動的にトリガーし、Amazon EKS クラスターでそのユーザーに付与されるアクセスレベルをコントロールできる状態が望ましいでしょう。Kubernetes オペレーターは、このような自動化を設計するための理想的なパターンです。

Amazon EKS クラスター内のクライアントの認証と承認

Amazon EKS クラスター内のクライアントの認証と承認

 

アーキテクチャ

Kubernetes RBAC と IAM 間の自動化された統合を実装するために使用されるアーキテクチャは、次の主な要素で構成されています。

  1. Kubernetes Java SDK を使用して実装されたオペレーター。このオペレーターは、デプロイメントとして実装されたカスタムコントローラである CustomResourceDefinition が定義した IamUserGroup という名前のカスタムリソースをパッケージ化します。aws-auth ConfigMap を変更できるようにする IamUserGroup カスタムリソース、Role/RoleBinding リソースに対する追加/更新/削除アクションに関連する Kubernetes クラスターのイベントに対して、カスタムコントローラが応答します。
  2. IAM ユーザーが IAM グループに追加、または IAM グループから削除されるたびに実行がトリガーされる AWS Lambda 関数として実装された Kubernetes Java クライアント。これは、IAM サービスからリアルタイムデータのストリームを配信し、そのデータを AWS Lambda などのターゲットにルーティングするのを容易にするサーバーレスイベントバスサービスである Amazon EventBridge を使用して実現されます。
  3. IAM グループがマッピングされている Kubernetes サブジェクトに付与されるアクセスをコントロールする Role/RoleBinding リソース。

Kubernetes RBAC および IAM とカスタムコントローラー統合

Kubernetes RBAC および IAM とカスタムコントローラー統合

 

カスタムコントローラの実装

Java SDK を使用してカスタムコントローラを作成するには、io.kubernetes.client.informer.SharedInformer インターフェイスの実装を提供する必要があります。SharedInformer は、コントローラなどのクライアントに、Kubernetes API サーバーによって実行されたアクション (クラスターリソースの作成/更新/削除などのアクション) を通知する責任を負います。SharedInformer の個別インスタンスは、カスタムコントローラに監視させたいライフサイクルイベントを持つポッド、ConfigMap などのクラスターリソースごとに必要になります。各カスタムコントローラは、io.kubernetes.client.extended.controller.Controller インターフェイスの実装によって表されます。SDK は、デフォルトの実装クラスである DefaultController を提供します。これは、ほとんどのユースケースに対して十分であり、クラスターイベントに応答するために必要な配管を含んでいます。次に、io.kubernetes.client.extended.controller.reconciler.Reconciler インターフェイスの実装が各カスタムコントローラに関連付けられます。リコンサイラサーバーは、ユーザーがオブジェクトで指定した状態を実際のクラスター状態と比較してから、実際のクラスター状態にユーザーが指定した状態を反映させる操作を実行します。最後に、io.kubernetes.client.extended.controller.builder.ControllerWatch インターフェイスの実装が各カスタムコントローラに必要です。これは、Informer フレームワークから送信された Kubernetes リソースに関連するイベント通知のフィルタリングと、カスタムコントローラ応答の定義を担当します。これらのクラスは、Java SDK のカスタムコントローラビルダーフレームワークのコアを構成します。

Spring Framework を使用して Kubernetes アプリケーションを開発する場合、SDK の Spring 統合コンポーネントを利用し、宣言型セマンティクスと Spring アノテーションを使用して前述のクラスを結び付けることができます。以下のコードは、@KubernetesInformer アノテーションを使用して Spring Bean として宣言されている SharedInformerFactory クラスを示しています。SDK の Spring BeanDefinitionRegistryPostProcessor は、そのような Bean を処理し、@KubernetesInformer アノテーションごとに SharedInformer をインスタンス化して、Spring Bean として登録します。

@KubernetesInformers({
    @KubernetesInformer(
            apiTypeClass = IamUserGroupCustomObject.class,
            apiListTypeClass = IamUserGroupCustomObjectList.class,
            groupVersionResource = @GroupVersionResource(
                    apiGroup = "octank.com",
                    apiVersion = "v1",
                    resourcePlural = "iamusergroups"),
            resyncPeriodMillis = 60    * 1000L)
    })
public class SpringSharedInformerFactory extends SharedInformerFactory {
    public SpringSharedInformerFactory (ApiClient apiClient) {
        super (apiClient);
    }
}

次に、リコンサイラサーバーの実装を提供し、@KubernetesReconciler アノテーションを使用してそれを Spring Bean として宣言します。SDK の Spring BeanFactoryPostProcessor はこれらの Bean を処理し、各リコンサイラサーバーのコントローラを作成します。各リコンサイラは、@AddWatchEventFilter、@UpdateWatchEventFilter、および @DeleteWatchEventFilter で注釈が付けられた一連のメソッドを提供する必要があります。これらは、コントローラがカスタムリソースの各ライフサイクルイベントを調整する必要があるかどうかに応じて true/false を返します。@KubernetesReconcilerWatch アノテーションごとに、ControllerWatch インスタンスが作成され、Kubernetes リソースに関連する追加/更新/削除イベント通知のフィルタリングに役立つハンドラーとして、前述のメソッドが割り当てられます。以下のコードは、Reconciler Bean のスケルトンを示しています (詳細については、Git リポジトリから完全なソースコードをダウンロードしてください) 。

@KubernetesReconciler(
        value = "iamUserGroupController",
        workerCount = 2,
        watches = @KubernetesReconcilerWatches({
            @KubernetesReconcilerWatch(
                    apiTypeClass = IamUserGroupCustomObject.class,
                    resyncPeriodMillis = 60*1000L)
            }))
public class IamUserGroupReconciler implements Reconciler {
    private GenericKubernetesApi<V1ConfigMap, V1ConfigMapList> apiConfigMap;
    private SharedInformer<IamUserGroupCustomObject> iamUserGroupInformer;
    public IamUserGroupReconciler(ApiClient apiClient, SharedInformer<IamUserGroupCustomObject> iamGroupInformer) {
        this.iamUserGroupInformer = iamGroupInformer;
        
        this.apiConfigMap = new GenericKubernetesApi<V1ConfigMap, V1ConfigMapList>(
                V1ConfigMap.class,
                V1ConfigMapList.class,
                "",
                "v1",
                "configmaps",
                apiClient);
    }

    @AddWatchEventFilter(apiTypeClass = IamUserGroupCustomObject.class)
    public boolean onAddFilter(IamUserGroupCustomObject iamUserGroup) {
    }

    @UpdateWatchEventFilter(apiTypeClass = IamUserGroupCustomObject.class)
    public boolean onUpdateFilter(IamUserGroupCustomObject oldIamUserGroup, IamUserGroupCustomObject newIamUserGroup) {
    }

    @DeleteWatchEventFilter(apiTypeClass = IamUserGroupCustomObject.class)
    public boolean onDeleteFilter(IamUserGroupCustomObject iamUserGroup, boolean deletedFinalStateUnknown) {
    }

    @Override
    public Result reconcile(Request request) {
    }
}

次に、カスタムリソースの実装が必要です。以下に示す YAML マニフェストを使用して CustomResourceDefinition を定義することから始めます。CRD は IamUserGroup という名前のカスタムリソースのスキーマを定義します。

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: iamusergroups.octank.com
spec:
  group: octank.com
  version: v1
  versions:
    - name: v1
      served: true
      storage: true
  scope: Namespaced    
  names:
    kind: IamUserGroup
    plural: iamusergroups
    singular: iamusergroup
    shortNames:
    - ig
  preserveUnknownFields: false
  validation:
    openAPIV3Schema:
      type: object
      properties:
        spec:
          type: object
          properties:
            iamUser:
              type: string
            iamGroups:
              type: array
              items:
                type: string
            username:
              type: string

IamUserGroup カスタムリソースのインスタンスをデプロイするには、以下に示すような YAML マニフェストを使用します。マニフェストは、IAM プリンシパルのユーザー名、Amazon リソースネーム (ARN) 間の関連付け、および IAM グループのリストとしての関連付けを提供します。

---
apiVersion: octank.com/v1
kind: IamUserGroup
metadata:
  namespace: kube-system
  name: viji-developers
spec:
  iamUser: 'arn:aws:iam::937351234567:user/viji'
  iamGroups: 
    - developers
  username: viji

カスタムコントローラは、IamUserGroupCustomObjectIamUserGroupCustomObjectList、および IamUserGroupCustomObjectSpec の一連の Java クラスを使用して IamUserGroup カスタムリソースを管理します。IamUserGroupCustomObject は、マニフェスト全体のオブジェクト表現です。IamUserGroupCustomObjectSpec はマニフェストの仕様セクションに対応し、IamUserGroupCustomObjectList は IamUserGroup リソースのコレクションを表します。Reconcilerの reconcile メソッドは、これらのクラスを使用して IamUserGroup リソースの iamUseriamGroups、および username 属性を取得し、当該 IamUserGroup リソースが追加、更新、または削除されたかどうかに応じて、aws-auth ConfigMap に関連する変更を加えます。

オペレーターパッケージにあるアーティファクトの最後のセットは、以下に示す Kubernetes ServiceAccount、ClusterRole、および ClusterRoleBinding の定義になります。カスタムコントローラは、iamusergroup という名前のサービスアカウント ID で実行されます。これには、IamUserGroup および ConfigMap リソースに対するすべてのアクションを実行する権限が付与されます。

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: iamusergroup
  namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: iamusergroup-role
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: iamusergroup-operator
rules:
- apiGroups:
  - octank.com
  resources:
  - iamusergroups
  verbs:
  - '*'
- apiGroups:
  - ""
  resources:
  - configmaps
  verbs:
  - '*'

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: iamusergroup-rolebinding
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: iamusergroup-operator
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: iamusergroup-role
subjects:
- kind: ServiceAccount
  name: iamusergroup
  namespace: kube-system

認証済みユーザーに付与されるアクセスをコントロールするには、別の一連の Role/RoleBinding 定義が必要です。これらの RBAC リソース自体はオペレーターパッケージ per se の一部ではありません。これらの定義は、Kubernetes サブジェクトを管理する方法と、ターゲット環境でのアクセスに依存します。実演を目的とするこの実装で、IamUserGroup カスタムリソースに挙げられている IAM グループは、Kubernetes グループと 1 対 1 で関連付けられていると見なされます。したがって、以下に示す YAML ファイルでは、IAM グループ開発者は Kubernetes グループ開発者に関連付けられ、後者は developer-role という名前のロールにバインドされ、dev 名前空間のすべての Kubernetes リソースへの完全なアクセス権を付与します。

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: developers-role
  namespace: dev
rules:
  - apiGroups:
      - ""
      - apps
      - batch
      - extensions
      - rbac.authorization.k8s.io
    resources: ["*"]
    verbs:
      - get
      - list
      - watch
      - create
      - update
      - patch
      - delete
---  
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: developers-role-binding
  namespace: dev
roleRef:
  kind: Role
  name: developers-role
  apiGroup: rbac.authorization.k8s.io
subjects:
  - kind: Group
    name: developers
    apiGroup: rbac.authorization.k8s.io

 

AWS Lambda を使用する Kubernetes クライアント

Lambda 関数は、IAM ユーザーが IAM グループに追加または削除されたときに、IAM サービスから送信されたイベント通知を処理するように設計されています。これらの通知は、以下に示す JSON データの形式で送信されます。

{
   "version":"0",
   "id":"5628e467-803a-8ffb-7194-e2b6f9831549",
   "detail-type":"AWS API Call via CloudTrail",
   "source":"aws.iam",
   "account":"937351234567",
   "time":"2020-05-15T15:47:12Z",
   "region":"us-east-1",
   "resources":[
   ],
   "detail":{
      "eventVersion":"1.05",
      "userIdentity":{
      },
      "eventTime":"2020-05-15T15:47:12Z",
      "eventSource":"iam.amazonaws.com",
      "eventName":"AddUserToGroup",
      "awsRegion":"us-east-1",
      "sourceIPAddress":"72.21.196.64",
      "userAgent":"console.amazonaws.com",
      "requestParameters":{
         "groupName":"developers",
         "userName":"viji"
      },
      "responseElements":null,
      "requestID":"02814799-fb64-4f01-96f4-a674ebdc9119",
      "eventID":"22648995-6e51-4edc-acc7-2d719df264d8",
      "eventType":"AwsApiCall"
   }
}

データの eventSource フィールドは iam.amazonaws.com に設定されます。eventName フィールドは、実行されたアクションを示します。AddUserToGroupRemoveUserFromGroup は、当社が関心を寄せている 2 つのイベントです。requestParameters フィールドには、アクションに関連付けられた IAM ユーザーとグループの名前が含まれています。このデータが与えられると、Lambda 関数は IamGroupCustomObject クラスのインスタンスを作成し、Kubernetes API を呼び出して IamUserGroup カスタムリソースのインスタンスを作成または削除します。

Lambda 関数ランタイム環境は、aws eks get-token または aws-iam-authenticator トークンコマンドを実行して、Kubernetes API サーバーとの通信に必要な認証トークンを取得できません。したがって、このトークンはプログラムで構築する必要があります。Kubernetes AWS IAM Authenticator のドキュメントでは、このトークンがどのように構築されるかについて、クラスターの外部からの API 承認というセクションで詳しく説明しています。トークンは、Java を使用した署名計算の例で提供されているヘルパークラスを使用して、AWS 署名バージョン 4 アルゴリズムで生成されます。認証トークンは、次に示すように構築された HTTP URL の base64 エンコードバージョンです。

https://sts.us-east-1.amazonaws.com/? 
Action=GetCallerIdentity& 
Version=2011-06-15& 
X-Amz-Algorithm=AWS4-HMAC-SHA256& 
X-Amz-Credential=ASIA12345NGDGUPABCDEF/20200517/us-east-1/sts/aws4_request& 
X-Amz-Date=20200517T131023Z& 
X-Amz-Expires=60& 
X-Amz-Security-Token=IQoJb3JpZ2luX2VjEHYaCXVzLWVhc3QtMSJHMEUCIQCSdHOdiAh4c1+FiOM/diA8NNkFSMnwORgyfO68rdxdgAIgAXEVzjsu9H5WDvbqkihQ94Ugw5MzczNTE5MzA5NzUiDGon8DFp20Zw0pgiayqzASBP4b/bAa0gZtoP8U4bM+gFiH5xpVtj18HyVEy5uGm1xgCpb3P/z0/lFmWm3bQbqwOeA809NyDWfaYD79FgqydT4lD3H0AQg4IsvLx/qQSSK0pXwoMYe82xhbG8yQQzW7x5flYqiP80xe1R1HPrlmAVfhCnCyKifghmQujucJoRfW4wcC0wGwetSXDpu1fCsCfZkMO2CD2dgDdnliz07kFig0A9EVMawCzoOnQP9jYRhfxWML7vhPYFOuMBlpzWN219LlZo982Q/4o/lSE5USnCCbAsKd59JkzjrH2G5HEp1EGs4rlRrZNYHwFMVeW3FbED8+crQcOz2F95AN9D8gmaFHn4my68Vz/b8gW7F2C11q7wNk18at0/EGrKl4ty18vMjmB/qxUk2xvO5YDLxcnwThXS3AL5HL1eT15xg0wLIz1OzZ0n4Cd94iLR/U29IgY5VlV3G03RjdP8+uvv+OrHheL96CsCDY4MQ+55J7wbNWIPLWjqFtGFZgNUIslifZv4bAX5F8dj2cQAidMxyK2EXi+aAgmP/B4WtSM38OQ=& 
X-Amz-SignedHeaders=host;x-k8s-aws-id& 
X-Amz-Signature=fd5b5f2f8a3d0c7b128ca785d9fb71f3b583741c8648f036e20d80d2c0450503

Lambda 関数で IamUserGroup カスタムリソースを作成または削除できるようにするには、次の構成設定が必要です。

  1. K8s-Lambda-Client-Role などの IAM ロールの Amazon リソースネーム (ARN) は、Amazon EKS クラスターの aws-auth ConfigMap の mapRoles セクションにある、lambda-clients などの Kubernetes グループにマッピングされています。この IAM ロールの一時的な認証情報は、認証トークンを生成するために使用されます。
  2. IAM ユーザーの認証情報 (アクセスキー、シークレットアクセスキー) は、最小権限のセキュリティガイドラインに準拠しており、IAM ロールを引き受ける権限のみが付与されています。
  3. IamUserGroup カスタムリソースを追加/更新/削除するための lambda-clients Kubernetes グループアクセス許可を付与する Amazon EKS クラスターの Role および RoleBinding 定義。
  4. Amazon EKS クラスターの API サーバーエンドポイント URL と認証局データは、どちらも aws eks update-kubeconfig コマンドを使用して取得されます。

以下のコードは、Lambda 関数の Java ハンドラーのスケルトンを示しています (詳細については、Git リポジトリから完全なソースコードをダウンロードしてください)。

public class IAMEventHandler implements RequestStreamHandler {
    private GenericKubernetesApi<IamUserGroupCustomObject, IamUserGroupCustomObjectList> apiIamGroupClient = // Initialize Kubernetes API client
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
        String inputString = // Retrieve from input stream
        JsonObject inputObject = new JsonObject (inputString);

        String account = inputObject.getString("account");
        String eventName = inputObject.getJsonObject("detail").getString("eventName");
        String eventSource = inputObject.getJsonObject("detail").getString("eventSource");
        String groupName = inputObject.getJsonObject("detail").getJsonObject("requestParameters").getString("groupName");
        String userName = inputObject.getJsonObject("detail").getJsonObject("requestParameters").getString("userName");
        String userArn = String.format("arn:aws:iam::%s:user/%s", account, userName);
        
        String objName = userName.concat("-").concat(groupName).toLowerCase();
        String objNamespace = "kube-system";
        
        IamUserGroupCustomObject iamUserGroup =
                new IamUserGroupCustomObject()
                .apiVersion("octank.com/v1")
                .kind("IamUserGroup")
                .metadata(new V1ObjectMeta()
                        .name(objName)
                        .namespace(objNamespace))
                .spec(new IamUserGroupCustomObjectSpec()
                        .iamUser(userArn)
                        .username(userName)
                        .group(groupName));
                        
        if (eventName.equals(ADD_USER_TO_GROUP)) {
            KubernetesApiResponse<IamUserGroupCustomObject> createResponse = apiIamGroupClient.create(iamUserGroup);
        }
        else if (eventName.equals(REMOVE_USER_FROM_GROUP)) {
            KubernetesApiResponse<IamUserGroupCustomObject> createResponse = apiIamGroupClient.delete(objNamespace, objName);
        }
    }
}

Amazon EKS クラスターでのオペレーターのテスト

次に、この Kubernetes オペレーターを Amazon EKS クラスターで試してみましょう。operator.yaml という名前の次の YAML マニフェストは、このオペレーターを Kubernetes クラスターにデプロイするために必要なすべてのアーティファクトを定義します。

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: iamusergroups.octank.com
spec:
  group: octank.com
  version: v1
  versions:
    - name: v1
      served: true
      storage: true
  scope: Namespaced    
  names:
    kind: IamUserGroup
    plural: iamusergroups
    singular: iamusergroup
    shortNames:
    - ig
  preserveUnknownFields: false
  validation:
    openAPIV3Schema:
      type: object
      properties:
        spec:
          type: object
          properties:
            iamUser:
              type: string
            iamGroups:
              type: array
              items:
                type: string
            username:
              type: string

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: iamusergroup
  namespace: kube-system

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: iamusergroup-role
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: iamusergroup-operator
rules:
- apiGroups:
  - apiextensions.k8s.io
  resources:
  - customresourcedefinitions
  verbs:
  - '*'  
- apiGroups:
  - octank.com
  resources:
  - iamusergroups
  verbs:
  - '*'
- apiGroups:
  - ""
  resources:
  - configmaps
  verbs:
  - '*'
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - '*'

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: iamusergroup-rolebinding
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: iamusergroup-operator
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: iamusergroup-role
subjects:
- kind: ServiceAccount
  name: iamusergroup
  namespace: kube-system
  
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: iamusergroup
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: iamusergroup
      role: operator
  template:
    metadata:
      labels:
        app: iamusergroup
        role: operator
      annotations:
        prometheus.io/scrape: 'false'     
    spec: 
      serviceAccountName: iamusergroup
      containers:          
        - name: java  
          image: eksworkshop/k8s-iam-operator:latest
          imagePullPolicy: Always   
          ports:
            - containerPort: 8080
              name: http 
              protocol: TCP
          resources:
            requests:
              cpu: "100m"
              memory: "256Mi"
            limits:
              cpu: "500m" 
              memory: "1000Mi"
          livenessProbe:
            httpGet: 
              path: /live
              port: 8080
            initialDelaySeconds: 15
            timeoutSeconds: 1
            periodSeconds: 10
            failureThreshold: 3            
          readinessProbe:
            httpGet: 
              path: /ready
              port: 8080
            initialDelaySeconds: 15
            timeoutSeconds: 1
            periodSeconds: 10
            failureThreshold: 3        
---
apiVersion: v1
kind: Service
metadata:
  name: iamusergroup-svc
  namespace: kube-system
spec:
  sessionAffinity: None
  type: ClusterIP  
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: iamusergroup
    role: operator             

kubectl apply -f operator.yaml を使用して、Amazon EKS クラスターにデプロイします。以下は、このコマンドの出力です。

customresourcedefinition.apiextensions.k8s.io/iamusergroups.octank.com created 
serviceaccount/iamusergroup created 
clusterrole.rbac.authorization.k8s.io/iamusergroup-role created 
clusterrolebinding.rbac.authorization.k8s.io/iamusergroup-rolebinding created 
deployment.apps/iamusergroup created service/iamusergroup-svc created

クラスター内の aws-auth ConfigMap の初期状態には、ワーカーノードが Amazon EKS クラスターに参加できるようにするマッピングが含まれています。以下に示す YAML マニフェストの変更を適用して、この ConfigMap を変更します。これにより、ロール K8s-Lambda-Client-Role に属する一時的な認証情報を使用してクライアントが認証された場合、クライアントを lambda-clients Kubernetes グループに関連付けるマッピングが作成されます。

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: arn:aws:iam::937351234567:role/eks-worker-stack-WorkerNodeInstanceRole-1FET9MJ4MU56
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
    - rolearn: arn:aws:iam::937351234567:role/K8s-Lambda-Client-Role 
      username: lambda-client 
      groups: 
        - lambda-clients

Kubernetes グループ lambda-clients には、以下の YAML マニフェストに示されている Role/RoleBinding 定義を作成することにより、kube-system 名前空間内の IamUserGroup カスタムリソースへのフルアクセスのみが付与されます。したがって、Amazon KKS クラスターに対して Lambda クライアントを認証するためにロール K8s-Lambda-Client-Role を使用することで、クラスター内のアクションのスコープを制限します。

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: lambda-clients-role
  namespace: kube-system
rules:
  - apiGroups:
    - octank.com
    resources:
    - iamusergroups
    verbs:
    - '*'
    
---  
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: lambda-clients-rolebinding
  namespace: kube-system
roleRef:
  kind: Role
  name: lambda-clients-role
  apiGroup: rbac.authorization.k8s.io
subjects:
  - kind: Group
    name: lambda-clients
    apiGroup: rbac.authorization.k8s.io

Amazon EKS の Lambda クライアントは、コマンド aws lambda create-function を使用して、次の JSON ファイルを入力としてデプロイされます。

{
    "FunctionName": "K8sClientForIAMEvents",
    "Runtime": "java8",
    "Role": "arn:aws:iam::937351234567:role/Lambda-Execution-Role",
    "Handler": "com.octank.IAMEventHandler::handleRequest",
    "Description": "K8s Client to Process IAM Notifications",
    "Timeout": 30,
    "MemorySize": 512,
    "Code": {
        "S3Bucket": "sarathy-lambda-handlers",
        "S3Key": "eksLambda.jar"
    },   
    "Environment": {
        "Variables": {
            "REGION": "us-east-1",
            "STS_ENDPOINT": "sts.us-east-1.amazonaws.com",
            "ACCESS_KEY_ID": "AKIATRU5TN0127H5VTXM",
            "SECRET_ACCESS_KEY": "sIbm2UbhXhusmk8sDjy-+Gens85n6ym///rQySTMb7Aw",
            "ASSUMED_ROLE": "arn:aws:iam::937351234567:role/K8s-Lambda-Client-Role",
            "CLUSTER_NAME": "k8s-sarathy-cluster",
            "API_SERVER": "https://9E700EEF26B4378A9109685E2C99D393.gr7.us-east-1.eks.amazonaws.com",
            "CERT_AUTHORITY": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJd01EVXhPREF4TURNd05sb1hEVE13TURVeE5qQXhNRE13Tmxvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBT2IwCkQrT3ZESDRRYy9wcXRVR0FjRlVnMWUrNFhta0lBY0VUNzZXaEVWYVFRWDNLMzkwNUZ1Y1g4U2ppY3hQd3hUSEYKczBhR0puWEl0WDh3V2hrQU53Q0VkUnM5bGdaNTQxcEdKQ1JTdFVYbTR5UWNoVUg1Uk0xR2Fpelk0OVAyQ0RlVApuMzh2TXhDb2JSSWdacW5IaFFCeWNQY21hT3p6dnNjQXFlRCtveGYzLzFSVWhRdEdvZE5iS284KzdnbHhiVVhsCmFTS2VWamhBcWZMMzJTK3plUFlncFUzN0pjSlo5cEY0VTNoYUlDN0ZPWUhvNlVWU1VUN1o2SjlvcFVnT1AyemgKRzBhVUVCaUpaYkdIRlluOVJ5aUJxc1lHRk9VZ1J2OEYwdWRsUnNXUWFkVVJrOVRmQitBMW15YnZVUzRTK09xbAo4cjlBcHhQZSthVUpTZSszL0RrQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFBVVhLeDFENkdHcnc4SGhrZ3VzNmFTTmlzcDgKQSt1TkhSS2J3aTRxTjR1SVBHR05rOTdXNjJwZkJvc09lMElYdjN1dlBXZDl4M2kwVG9qWVphZ2xTOVRUOWNycwpod0xhNlpNWmRpMkZFeThYRC8vdVpoRUszditMNlY0cXY1b2E1SWw3a3IrVDZpYUVOMzhZMllSaGJ0MDNVUUJVClV2ZXlUVDVKdENuZkFMaUJWRzBJQk0xUUkxRkpiRVJqOWpTVXFiWHpWUnNBUHJYTktSSkJxdktjRG5DK09OWkoKVkpCWTN2RDJVVGI2N1VtVXhaS2tKRHpiK1N5SEFkV3lLTzYvWCtyNEZWRHl2NjRyME1Kd0RQLzc3OGd1OGlrbgo0MzhDUVFSQmIyMUN6ZFhkOEw0MDBpbzNYVUNJQTNvajZ2aWViRXR0VmVYZUFSTTV5aG1yQTBOejl3dz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
        }
    }
}

次に、EventBridge ルールが作成され、次の一連のコマンドを使用して Lambda 関数がターゲットとして割り当てられます。

EVENT_RULE_ARN=$(aws events put-rule --name IAMUserGroupRule --event-pattern "{\"source\":[\"aws.iam\"]}" --query RuleArn --output text)

aws lambda add-permission \
--function-name K8sClientForIAMEvents \
--statement-id 'd6f44629-efc0-4f38-96db-d75ba7d06579' \
--action 'lambda:InvokeFunction' \
--principal events.amazonaws.com \
--source-arn $EVENT_RULE_ARN

aws events put-targets --rule IAMUserGroupRule --targets file://lambdaTarget.json

次に、IAM ダッシュボードに移動して、viji という名前のユーザーを編集し、そのユーザーを developer および testers という名前のグループに追加します。IAM AddUserToGroup API は一度に 1 つのグループのみをユーザーに追加でき、UI では複数を選択できるため、CloudWatch Logs からわかるように、これにより Lambda 関数への 2 つの個別のイベント通知がトリガーされます。

AWS IAM ダッシュボードでのユーザーとグループの関連付けの管理

AWS IAM ダッシュボードでのユーザーとグループの関連付けの管理

 

AWS IAM からのイベント通知を処理する AWS Lambda の CloudWatch Logs

AWS IAM からのイベント通知を処理する AWS Lambda の CloudWatch Logs

kubectl get configmap aws-auth -n kube-system -o yaml コマンドを実行すると、IAM ダッシュボードで実行されたアクションと一致する aws-auth ConfigMap に加えられた変更が確認されます。

apiVersion: v1
data:
  mapRoles: |
    - rolearn: arn:aws:iam::937351234567:role/eks-worker-stack-WorkerNodeInstanceRole-134DR0KSSN2K
      username: system:node:{{EC2PrivateDNSName}}
      groups: ['system:bootstrappers', 'system:nodes']
    - rolearn: arn:aws:iam::937351234567:role/K8s-Lambda-Client-Role
      username: lambda-client
      groups: [lambda-clients]
  mapUsers: |
    - userarn: arn:aws:iam::937351234567:user/viji
      groups: [testers, developers]
      username: viji
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system

次に、aws iam remove-user-from-group –group-name developers –user-name viji コマンドを使用して、ユーザー viji を開発者グループから削除します。 kubectl get configmap aws-auth -n kube-system -o yaml コマンドからの出力は次のようになります。

apiVersion: v1
data:
  mapRoles: |
    - rolearn: arn:aws:iam::937351234567:role/eks-worker-stack-WorkerNodeInstanceRole-134DR0KSSN2K
      username: system:node:{{EC2PrivateDNSName}}
      groups: ['system:bootstrappers', 'system:nodes']
    - rolearn: arn:aws:iam::937351234567:role/K8s-Lambda-Client-Role
      username: lambda-client
      groups: [lambda-clients]
  mapUsers: |
    - userarn: arn:aws:iam::937351234567:user/viji
      groups: [testers]
      username: viji
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system

IAM ユーザーを IAM グループに追加、または IAM グループから削除するには、kubectl コマンドを使用して、IAmUserGroup カスタムリソースを定義する適切な YAML マニフェストを作成することもできます。これらのイベントは、カスタムコントローラによってまったく同じ方法で処理されます。

ソースコード

カスタムコントローラと Lambda 関数の完全なソースコードは、次のリンクからダウンロードできます。

https://github.com/aws-samples/k8s-rbac-iam-java-operator/tree/master/java-operator

https://github.com/aws-samples/k8s-rbac-iam-java-operator/tree/master/lambda-client

最後に

オペレーターは、ドメイン固有のロジックをカプセル化する Kubernetes 上に重要なアプリケーションを構築する優れた方法です。Kubernetes コントローラビルダー Java SDK を使用すれば、開発者はカスタムコントローラを作成して、関連するコンポーネントを Kubernetes コントローラランタイムに簡単に接続できます。コンテナワークロードのオーケストレーションに Kubernetes を採用していて、Java に関する社内専門知識が豊富な組織は、Kubernetes Java SDK を利用して、Kubernetes API 経由で Java でカスタムツールを構築できます。