Cloud9の環境を作成する権限を持ったIAMユーザーを作ってみた

Cloud9の環境を作成する権限を持ったIAMユーザーを作ってみた

1つのAWSアカウント上で複数のハンズオン参加者にCloud9環境を作成してもらう際に
Clock Icon2024.07.14

Cloud9の環境を作成する権限をハンズオン参加者に付与したい

こんにちは、のんピ(@non____97)です。

皆さんはCloud9の環境を作成する権限をハンズオン参加者に付与したいと思ったことはありますか? 私はあります。

2時間程度の少人数のハンズオンだと1つのAWSアカウントに相乗りする形でCloud9の環境を用意したいことがあります。

この時、AWSのAPIを叩くことはなく、Cloud9のターミナル上で操作が完結するのであれば、基本的にCloud9だけを触れる権限のみを付与したいです。余計な権限は付与させたくはありません。

ということで、なるべく付与する権限が少なくなるように設定してみました。

やってみた

AWS CDKのコード

以下記事をベースにAWS CDKでIAMユーザーおよびIAMグループ、IAMポリシーを作成します。

https://dev.classmethod.jp/articles/aws-cdk-mfa-iam-group-and-ssm-session-manager-role/

付与している権限は以下のとおりです。

  • 自IAMユーザーのタグの変更
  • MFA設定や自IAMユーザーのパスワード変更
  • Cloud9の環境の作成
    • 特定のインスタンスタイプのみ許可
    • 環境名は<自IAMユーザー名>*のみ許可
  • Cloud9の環境作成にあたって必要なDescribe、List、UpdateUserSettings
  • サービスリンクドロールAWSCloud9SSMAccessRoleの作成、関連付け
  • インスタンスプロファイルAWSCloud9SSMInstanceProfileの作成、関連付け
  • SSMセッションマネージャーの接続

Cloud9環境の削除や更新の権限は付与していません。以下ドキュメントに記載のとおりDeleteEnvironmentUpdateEnvironmentなどはRBACに対応しておらず、リソースもarn:${Partition}:cloud9:${Region}:${Account}:environment:${ResourceId}と環境名が使用できないためです。

https://docs.aws.amazon.com/ja_jp/service-authorization/latest/reference/list_awscloud9.html

今回のシチュエーションである2時間程度の少人数のハンズオンであれば、こういった操作は主催者側で対応できるかなと感じ、参加者には付与しませんでした。

IAMポリシーの設計をするにあたって以下AWS公式ドキュメントが非常に参考になりました。

https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/security-iam.html#auth-and-access-control-customer-policies

IAMグループ、IAMポリシーを作成している箇所のコードは以下のとおりです。

lib/construct/group-construct.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

export interface GroupConstructProps {}

export class GroupConstruct extends Construct {
  readonly group: cdk.aws_iam.IGroup;

  constructor(scope: Construct, id: string, props?: GroupConstructProps) {
    super(scope, id);

    const tagPolicy = new cdk.aws_iam.ManagedPolicy(this, "TagPolicy", {
      statements: [
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: [
            "arn:aws:iam::" +
              cdk.Stack.of(this).account +
              ":user/${aws:username}",
          ],
          actions: ["iam:ListUserTags", "iam:UntagUser", "iam:TagUser"],
        }),
      ],
    });

    const selfManagedMfaPolicy = new cdk.aws_iam.ManagedPolicy(
      this,
      "SelfManagedMfaPolicy",
      {
        statements: [
          new cdk.aws_iam.PolicyStatement({
            sid: "SelfManagedMfa",
            effect: cdk.aws_iam.Effect.ALLOW,
            resources: [
              "arn:aws:iam::" +
                cdk.Stack.of(this).account +
                ":mfa/${aws:username}*",
              "arn:aws:iam::" +
                cdk.Stack.of(this).account +
                ":user/${aws:username}*",
            ],
            actions: [
              "iam:ChangePassword",
              "iam:CreateVirtualMFADevice",
              "iam:DeleteVirtualMFADevice",
              "iam:DeactivateMFADevice",
              "iam:EnableMFADevice",
              "iam:GetLoginProfile",
              "iam:GetUser",
              "iam:GetUserPolicy",
              "iam:ListGroupsForUser",
              "iam:ListAttachedUserPolicies",
              "iam:ListUserPolicies",
              "iam:ResyncMFADevice",
              "iam:ListMFADevices",
            ],
          }),
        ],
      }
    );

    const cloud9Policy = new cdk.aws_iam.ManagedPolicy(this, "Cloud9Policy", {
      statements: [
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: ["*"],
          actions: ["cloud9:CreateEnvironmentEC2"],
          conditions: {
            StringLike: {
              "cloud9:EnvironmentName": "${aws:username}*",
              "cloud9:InstanceType": [
                "t3.nano",
                "t3.micro",
                "t3.small",
                "t3.medium",
                "t3.large",
              ],
            },
            Null: {
              "cloud9:OwnerArn": "true",
            },
          },
        }),
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: ["*"],
          actions: [
            "cloud9:DescribeEnvironments",
            "cloud9:DescribeEnvironmentStatus",
            "cloud9:DescribeEnvironmentMemberships",
            "cloud9:ListEnvironments",
            "cloud9:ListTagsForResource",
            "cloud9:UpdateUserSettings",
            "ec2:DescribeVpcs",
            "ec2:DescribeSubnets",
            "ec2:DescribeInstanceTypeOfferings",
            "ec2:DescribeRouteTables",
          ],
        }),
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: ["arn:aws:iam::*:role/service-role/*"],
          actions: ["iam:ListRoles", "iam:ListInstanceProfilesForRole"],
        }),
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: [
            "arn:aws:iam::*:role/service-role/AWSCloud9SSMAccessRole",
          ],
          actions: ["iam:ListInstanceProfilesForRole", "iam:CreateRole"],
        }),
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: [
            "arn:aws:iam::*:role/service-role/AWSCloud9SSMAccessRole",
          ],
          actions: ["iam:AttachRolePolicy"],
          conditions: {
            StringEquals: {
              "iam:PolicyARN":
                "arn:aws:iam::aws:policy/AWSCloud9SSMInstanceProfile",
            },
          },
        }),
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: [
            "arn:aws:iam::*:role/service-role/AWSCloud9SSMAccessRole",
          ],
          actions: ["iam:PassRole"],
          conditions: {
            StringEquals: {
              "iam:PassedToService": "ec2.amazonaws.com",
            },
          },
        }),
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: [
            "arn:aws:iam::*:instance-profile/cloud9/AWSCloud9SSMInstanceProfile",
          ],
          actions: [
            "iam:CreateInstanceProfile",
            "iam:AddRoleToInstanceProfile",
          ],
        }),
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: ["*"],
          actions: ["iam:CreateServiceLinkedRole"],
          conditions: {
            StringEquals: {
              "iam:AWSServiceName": "cloud9.amazonaws.com",
            },
          },
        }),
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: ["arn:aws:ec2:*:*:instance/*"],
          actions: ["ssm:StartSession", "ssm:GetConnectionStatus"],
          conditions: {
            StringLike: {
              "ssm:resourceTag/aws:cloud9:environment": "*",
            },
            StringEquals: {
              "aws:CalledViaFirst": "cloud9.amazonaws.com",
            },
          },
        }),
        new cdk.aws_iam.PolicyStatement({
          effect: cdk.aws_iam.Effect.ALLOW,
          resources: ["arn:aws:ssm:*:*:document/*"],
          actions: ["ssm:StartSession"],
        }),
      ],
    });

    const group = new cdk.aws_iam.Group(this, "Default", {
      managedPolicies: [tagPolicy, selfManagedMfaPolicy, cloud9Policy],
    });
    this.group = group;
  }
}

全体は以下GitHubリポジトリに保存しています。

https://github.com/non-97/aws-cdk-operation-cloud9-iam-group

Cloud9の環境の作成

実際にCloud9の環境を作成してみます。

IAMユーザー名はnon-97-test-user1です。

名前がnon-97-test-user1でインスタンスタイプがt3.microの環境を作成します。その他はデフォルト値です。

1.環境を作成_non-97-test-user1

1分ほどで作成が完了しました。

2.1 個の環境が正常に作成されました。

Cloud9で開くで接続します。問題なく接続できていますね。

3.Cloud 9に接続できたことを確認

ちなみに環境名の先頭が自IAMユーザー名でなかったり、インスタンスタイプがt2.microなど許可しているものでない場合はUser: arn:aws:iam::<AWSアカウント>:user/non-97-test-user1 is not authorized to perform: cloud9:CreateEnvironmentEC2 on resource: * because no identity-based policy allows the cloud9:CreateEnvironmentEC2 actionというようにエラーになります。

4.1 is not authorized to perform- cloud9-CreateEnvironmentEC2

ハンズオンに参加している他のユーザーが作ったCloud9には接続できません。test2という環境を他プリンシパルで作成しましたが、アクセスなしとなっています。

5.アクセス許可がない他の環境には接続できない

また、Cloud9の環境を10個作成してみました。問題なく動作しています。

7.Cloud9環境一覧

クォータを確認すると、ユーザーごとに100環境作成できるようです。

https://docs.aws.amazon.com/ja_jp/cloud9/latest/user-guide/limits.html

Cloud9の裏側のEC2インスタンスも元気です。

6.EC2インスタンス一覧

ある程度の規模のハンズオンでも捌けそうですね。

1つのAWSアカウント上で複数のハンズオン参加者にCloud9環境を作成してもらう際に

Cloud9の環境を作成する権限を持ったIAMユーザーを作ってみました。

1つのAWSアカウント上で複数のハンズオン参加者にCloud9環境を作成してもらう際に参考にしてみてください。

Cloud9のターミナル上からAWSのAPIを叩く際は、IAMユーザーに権限を付与して、AWS Managed Temporary Credentialsを活用する形でも良いと思います。AWS Managed Temporary Credentialsの詳細は以下記事をご覧ください。

https://dev.classmethod.jp/articles/aws-cloud9-aws-managed-temporary-credentials/

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.