AWS KMS로 CMEK 구성하기

AWS KMS로 CMEK 구성하기

이 가이드는 AWS KMS 키를 Anthropic 조직의 고객 관리 암호화 키(CMEK)로 구성하는 방법을 안내해 드릴게요.

CMEK 활성화는 영구적이에요. KMS 키가 삭제되거나 비활성화되면 Anthropic은 그 아래에서 암호화된 데이터를 복구할 수 없어요. 시작하기 전에 경고와 제한 사항을 검토하세요.

AWS의 Claude Platform: Claude Platform on AWS에서는 키 정책이 Anthropic의 IAM 역할이 아니라 AWS 서비스 principal에 접근 권한을 부여하고, 별도의 검증 단계가 없으며, 키를 Claude Console에서 등록·연결해요. 이 페이지의 AWS의 Claude Platform에서 CMEK 설정을 다음 섹션들의 단계 대신 따르세요.

출처: 문서

본문

전제 조건

  • KMS 키 생성과 키 정책 설정 권한(kms:CreateKey, kms:PutKeyPolicy)이 있는 AWS 계정.
  • 조직의 Anthropic Admin API 키.
  • 설치·인증된 AWS CLI.

Anthropic용 Amazon 리소스 이름(ARN)

Anthropic이 암호화 키를 사용하게 하려면 Anthropic의 IAM 역할에 데이터 암호화에 사용할 KMS 키를 부여해야 해요. Anthropic CMEK의 ARN은:

arn:aws:iam::915198916910:role/anthropic-cmek-client-us

게시된 이 ARN만 사용하세요. 이메일, 채팅, 온보딩 채널로 제공된 식별자는 절대 신뢰하지 마세요.

암호화 키 설정

크로스 계정 키 정책으로 KMS 키 만들기

AWS의 Claude Platform: 이 단계는 건너뛰세요. 키 정책이 AWS 서비스 principal에 접근 권한을 부여하고 조직 조건이 없어요. AWS의 Claude Platform에서 CMEK 설정이 그 정책을 제공해요.

키 정책은 Anthropic의 IAM 역할에 크로스 계정 접근을 부여해요. 세 개의 문(statement)이 필요해요:

  1. 계정 루트 admin: 표준 KMS 패턴. 계정이 전체 관리 제어권을 유지해요.
  2. Anthropic 암호화·복호화: kms:Encryptkms:Decrypt 작업. Anthropic이 워크스페이스 데이터를 보호하는 데이터 키를 암호화·복호화(봉투 암호화, envelope encryption)하는 데 사용해요.
  3. Anthropic describe: 시작 시 Anthropic이 수행하는 메타데이터 읽기. DescribeKey에는 EncryptionContext 매개변수가 없어서 이 작업에 EncryptionContext 조건을 걸면 항상 거부되므로 별도로 부여돼요.

내 AWS 계정 ID를 찾으려면 aws sts get-caller-identity --query Account --output text를 실행하세요.

정책에서 <AWS_ACCOUNT_ID>를 AWS 계정 ID로, <ORGANIZATION_UUID>를 조직 ID로 바꾸세요. kms:EncryptionContext:anthropic:org_uuidStringEquals 조건은 키를 Anthropic 조직에 바인딩하며, 검증은 조건이 없는 키를 거부해요. 여러 Anthropic 조직 간에 하나의 키를 공유하려면 조건 값에 각 조직 ID를 나열하세요.

조직 ID 찾기: Claude Console의 Settings > Organization 아래 Organization ID 필드를 복사하거나, claude.ai의 Organization settings > Organization 아래에서 복사하거나, Organization Info 엔드포인트에서 id 필드를 읽으세요. org_ 접두사가 없는 순수 UUID를 사용하세요.

정책을 key-policy.json으로 저장하세요. 대신 AWS Console에서 키를 만들려면 이 단계 뒷부분에 설명한 대로 거기에 정책을 붙여넣으세요.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AccountRootAdmin",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<AWS_ACCOUNT_ID>:root"
      },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "AllowAnthropicCMEKCrypto",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::915198916910:role/anthropic-cmek-client-us"
      },
      "Action": ["kms:Encrypt", "kms:Decrypt"],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:EncryptionContext:anthropic:org_uuid": ["<ORGANIZATION_UUID>"]
        }
      }
    },
    {
      "Sid": "AllowAnthropicCMEKDescribe",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::915198916910:role/anthropic-cmek-client-us"
      },
      "Action": "kms:DescribeKey",
      "Resource": "*"
    }
  ]
}

선택 사항: 키를 일부 워크스페이스로 제한하려면 앞선 정책 JSON 대신 이 AllowAnthropicCMEKCrypto 문을 워크스페이스마다 하나의 컴파트먼트(compartment) ID로 사용하세요. 키를 연결하기 전에 워크스페이스의 컴파트먼트 ID를 추가하세요. 새 워크스페이스는 키 없이 만든 뒤 컴파트먼트 ID를 추가하고 키를 연결하세요.

{
  "Sid": "AllowAnthropicCMEKCrypto",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::915198916910:role/anthropic-cmek-client-us"
  },
  "Action": ["kms:Encrypt", "kms:Decrypt"],
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:anthropic:org_uuid": ["<ORGANIZATION_UUID>"]
    },
    "StringEqualsIfExists": {
      "kms:EncryptionContext:anthropic:compartment_uuid": ["<COMPARTMENT_UUID>"]
    }
  }
}
aws kms create-key \
  --region <REGION> \
  --description "Anthropic CMEK" \
  --key-usage ENCRYPT_DECRYPT \
  --policy file://key-policy.json

출력에서 KeyMetadata.Arn을 기록하세요. 다음 단계에서 키를 등록할 때 필요해요.

키가 이미 CMEK용으로 구성되어 기존 데이터를 보호한다면, 앞선 정책의 세 문 외에 Anthropic이 그 데이터를 복호화할 수 있게 하는 문을 추가해야 해요. 그 조건에는 키가 현재 또는 과거에 연결된 모든 워크스페이스의 컴파트먼트 ID를 나열하세요.

{
  "Sid": "AllowAnthropicCMEKDecryptExistingData",
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::915198916910:role/anthropic-cmek-client-us"
  },
  "Action": "kms:Decrypt",
  "Resource": "*",
  "Condition": {
    "StringEquals": {
      "kms:EncryptionContext:anthropic:compartment_uuid": ["<COMPARTMENT_UUID>"]
    }
  }
}

Anthropic은 키를 검증하거나 워크스페이스에 연결할 때 키를 검증해요. 각 검증은 CloudTrail에 네 개의 접근 거부 오류를 추가해요. 이는 예상된 동작이에요. 걸러내야 한다면 다음 세 값 모두로 필터링하세요. 누구나 설정할 수 있으므로 첫 번째 값만으로는 충분하지 않아요:

  • requestParameters.encryptionContext.associatedData: Y21lay12YWxpZGF0aW9u
  • userIdentity.accountId: 915198916910
  • resources.ARN: arn:aws:kms:<REGION>:<AWS_ACCOUNT_ID>:key/<KEY_ID>

컴파트먼트 ID 찾기: Anthropic에 키 등록Claude Platform 탭을 참고하세요.

AWS Console에서도 키를 만들 수 있어요. 암호화·복호화 키 사용, 단일 리전 키, KMS 키 자료 원본을 가진 대칭 키를 선택하세요. Create-key 마법사는 Review 단계에서 키 정책을 확정해요. 거기서 키 사용 권한에 Anthropic의 계정 ID 915198916910을 추가하면 생성된 정책이 EncryptionContext 조건 없이 Anthropic 전체 계정에 더 넓은 작업(예: kms:ReEncrypt*, kms:GenerateDataKey*)을 부여하고 검증이 이를 거부해요. 과도하게 허용된 키를 남기지 않으려면 관리 권한만으로 마법사를 끝낸 뒤 키의 Key policy 탭을 열고 JSON을 이 단계 앞부분에 보여준 key-policy.json 정책으로 교체하세요.

Anthropic에 키 등록

키 등록 방법은 사용하는 제품에 따라 달라져요.

Claude Platform

AWS의 Claude Platform: principal, 키 정책, 등록 흐름이 다르고 별도 검증 단계가 없어요. 이 탭 대신 AWS의 Claude Platform에서 CMEK 설정을 따르세요.

컴파트먼트 ID 찾기: 각 워크스페이스에는 CMEK 데이터의 범위를 지정하는 컴파트먼트 ID가 있어요. Claude Console에서 찾으려면 Manage > Security로 이동해 사이드바 상단의 워크스페이스 선택기에서 워크스페이스를 선택하세요. ID는 Encryption key 아래 Compartment ID 필드에 있어요. Get Workspace 엔드포인트가 반환하는 compartment_id 필드에서도 읽을 수 있어요.

키는 Claude Console이나 Admin API로 설정할 수 있으며 결과는 같아요.

Claude Console:

  1. Anthropic에 키 등록 — Claude Console에서 Settings > Encryption keys를 열고 Add key를 클릭하세요. 표시 이름을 입력하고 AWS KMS를 선택한 뒤 Continue를 클릭하세요. KMS key ARN에 키 ARN을 붙여넣고 Add를 클릭하세요. 키 세부 사항 단계에 조직 ID가 표시돼요. Add를 클릭하기 전에 이를 키 정책에 추가하세요.
  2. 키 검증Encryption keys 페이지에서 키 옆 Verify를 클릭하세요. 확인이 통과하면 Connected가 표시돼요. 실패하면 메시지가 이유를 알려줘요.
  3. 키를 워크스페이스에 연결 — Claude Console에서 Manage > Security로 이동해 사이드바 상단의 워크스페이스 선택기에서 워크스페이스를 선택하세요. Encryption key 아래에서 키를 선택하고 Save를 클릭한 뒤 확인하세요. 키 연결은 되돌릴 수 없어요. 이미 요청을 받는 워크스페이스의 경우 키가 적용되는 데 최대 하루 걸릴 수 있어요.

API:

  1. Anthropic에 키 등록 — Admin API를 통해 외부 키 구성을 만드세요.

    ```bash cURL curl -sS "https://api.anthropic.com/v1/organizations/external_keys" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "display_name": "", "geo": "us", "provider_config": { "type": "aws", "kms_arn": "" } }' ```
    ant beta:organization:external-keys create <<'YAML'
    display_name: "<friendly-name>"
    geo: us
    provider_config:
      type: aws
      kms_arn: "<key-arn-from-create-key-step>"
    YAML
    
    client = anthropic.Anthropic()
    
    external_key = client.beta.organization.external_keys.create(
        display_name="<friendly-name>",
        geo="us",
        provider_config={"type": "aws", "kms_arn": "<key-arn-from-create-key-step>"},
    )
    
    print(f"id: {external_key.id}")
    print(f"display_name: {external_key.display_name}")
    
    const client = new Anthropic();
    
    const externalKey = await client.beta.organization.externalKeys.create({
      display_name: "<friendly-name>",
      geo: "us",
      provider_config: {
        type: "aws",
        kms_arn: "<key-arn-from-create-key-step>"
      }
    });
    
    console.log(`id: ${externalKey.id}`);
    console.log(`display_name: ${externalKey.display_name}`);
    
    using Anthropic.Models.Beta.Organization.ExternalKeys;
    
    AnthropicClient client = new();
    
    var externalKey = await client.Beta.Organization.ExternalKeys.Create(new()
    {
        DisplayName = "<friendly-name>",
        Geo = Geo.Us,
        ProviderConfig = new BetaAwsExternalKeyConfig
        {
            KmsArn = "<key-arn-from-create-key-step>"
        }
    });
    
    Console.WriteLine($"id: {externalKey.ID}");
    Console.WriteLine($"display_name: {externalKey.DisplayName}");
    
    client := anthropic.NewClient()
    
    externalKey, err := client.Beta.Organization.ExternalKeys.New(context.Background(), anthropic.BetaOrganizationExternalKeyNewParams{
    	DisplayName: anthropic.String("<friendly-name>"),
    	Geo:         anthropic.BetaOrganizationExternalKeyNewParamsGeoUs,
    	ProviderConfig: anthropic.BetaOrganizationExternalKeyNewParamsProviderConfigUnion{
    		OfAWS: &anthropic.BetaAWSExternalKeyConfigParam{
    			KMSARN: "<key-arn-from-create-key-step>",
    		},
    	},
    })
    if err != nil {
    	log.Fatal(err)
    }
    
    fmt.Printf("id: %s\n", externalKey.ID)
    fmt.Printf("display_name: %s\n", externalKey.DisplayName)
    
    import com.anthropic.models.beta.organization.externalkeys.BetaAwsExternalKeyConfig;
    import com.anthropic.models.beta.organization.externalkeys.ExternalKeyCreateParams;
    
    void main() {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();
    
        var params = ExternalKeyCreateParams.builder()
            .displayName("<friendly-name>")
            .geo(ExternalKeyCreateParams.Geo.US)
            .providerConfig(BetaAwsExternalKeyConfig.builder()
                .kmsArn("<key-arn-from-create-key-step>")
                .build())
            .build();
        var externalKey = client.beta().organization().externalKeys().create(params);
    
        IO.println("id: " + externalKey.id());
        IO.println("display_name: " + externalKey.displayName().orElseThrow());
    }
    
    use Anthropic\Beta\Organization\ExternalKeys\ExternalKeyCreateParams\Geo;
    // ...
    
    $client = new Client();
    
    $externalKey = $client->beta->organization->externalKeys->create(
        displayName: '<friendly-name>',
        geo: Geo::US,
        providerConfig: [
            'type' => 'aws',
            'kmsARN' => '<key-arn-from-create-key-step>',
        ],
    );
    
    echo "id: {$externalKey->id}\n";
    echo "display_name: {$externalKey->displayName}\n";
    
    client = Anthropic::Client.new
    
    external_key = client.beta.organization.external_keys.create(
      display_name: "<friendly-name>",
      geo: :us,
      provider_config: {
        type: :aws,
        kms_arn: "<key-arn-from-create-key-step>"
      }
    )
    
    puts "id: #{external_key.id}"
    puts "display_name: #{external_key.display_name}"
    

    응답에 외부 키 ID가 포함돼요:

    {
      "type": "external_key",
      "id": "ekey_<id>",
      "display_name": "<friendly-name>"
    }
    
  2. 키 검증 — 키에 대해 암호화·복호화 왕복을 트리거하세요.

    ```bash cURL curl -sS -X POST "https://api.anthropic.com/v1/organizations/external_keys/ekey_/validate" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" ```
    ant beta:organization:external-keys validate --external-key-id "ekey_<id>"
    
    client = anthropic.Anthropic()
    
    validation = client.beta.organization.external_keys.validate("ekey_<id>")
    
    print(f"status: {validation.status}")
    print(f"error: {validation.error}")
    
    const client = new Anthropic();
    
    const validation = await client.beta.organization.externalKeys.validate("ekey_<id>");
    
    console.log(`status: ${validation.status}`);
    console.log(`error: ${validation.error}`);
    
    AnthropicClient client = new();
    
    var validation = await client.Beta.Organization.ExternalKeys.Validate("ekey_<id>");
    
    Console.WriteLine($"status: {validation.Status.Raw()}");
    Console.WriteLine($"error: {validation.Error}");
    
    client := anthropic.NewClient()
    
    validation, err := client.Beta.Organization.ExternalKeys.Validate(context.Background(), "ekey_<id>")
    if err != nil {
    	log.Fatal(err)
    }
    
    fmt.Printf("status: %s\n", validation.Status)
    fmt.Printf("error: %s\n", validation.Error)
    
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();
    
    var validation = client.beta().organization().externalKeys().validate("ekey_<id>");
    
    IO.println("status: " + validation.status().asString());
    IO.println("error: " + validation.error().orElse(""));
    
    $client = new Client();
    
    $validation = $client->beta->organization->externalKeys->validate(
        externalKeyID: 'ekey_<id>',
    );
    
    echo "status: {$validation->status}\n";
    echo "error: {$validation->error}\n";
    
    client = Anthropic::Client.new
    
    external_key_id = "ekey_<id>"
    validation = client.beta.organization.external_keys.validate(external_key_id)
    
    puts "status: #{validation.status}"
    puts "error: #{validation.error}"
    

    성공 응답은 이렇게 생겼어요:

    { "type": "external_key_validation", "status": "success", "error": null }
    

    검증이 실패하면 일반적인 원인은:

    • 암호화 컨텍스트 불일치: 정책에 kms:EncryptionContext:anthropic:compartment_uuid 조건이 있다면 키가 연결된 각 워크스페이스의 컴파트먼트 ID를 나열했는지 확인하세요. 검증은 검사하는 워크스페이스의 컴파트먼트 ID를 보내요. 컴파트먼트 문이 여전히 kms:Encrypt를 허용하는 이전 키는 연결되지 않은 동안 모두 0 값(00000000-0000-0000-0000-000000000000)으로 검증되므로 그 값을 목록에 유지하세요.
    • 리소스 제어 정책(RCP): AWS 조직에 aws:PrincipalOrgID가 조직과 일치하지 않을 때 KMS 작업을 거부하는 RCP가 있다면 Anthropic의 크로스 계정 역할을 차단해요. RCP에는 이 키 또는 Anthropic 역할 ARN에 대한 제외가 필요해요. 서비스 제어 정책은 리소스 기반 정책을 통해 호출하는 외부 principal을 평가하지 않으므로 여기에는 적용되지 않아요.
    • 키 정책이 아닌 IAM을 통해 부여된 접근: 크로스 계정 KMS 접근은 계정의 IAM 정책이 아니라 키 정책 자체에서 부여해야 해요. aws kms get-key-policy --key-id <id> --policy-name default로 확인하세요.
    • 리전 불일치: 키의 리전이 구성한 geo 티어에 대해 Anthropic이 운영하는 리전 중 하나인지 확인하세요.
  3. 키를 워크스페이스에 연결 — 키가 검증되면 그 워크스페이스로 요청을 보내기 전에 새 워크스페이스에 연결하세요. 이미 요청을 받는 워크스페이스의 경우 키가 적용되는 데 최대 하루 걸릴 수 있어요.

    ```bash cURL curl -sS -X POST "https://api.anthropic.com/v1/organizations/workspaces/" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "external_key_id": "ekey_" }' ```
    ant beta:organization:workspaces update \
      --workspace-id "<workspace-id>" \
      --external-key-id "ekey_<id>"
    
    client = anthropic.Anthropic()
    
    workspace = client.beta.organization.workspaces.update(
        "<workspace-id>", external_key_id="ekey_<id>"
    )
    
    print(f"id: {workspace.id}")
    print(f"external_key_id: {workspace.external_key_id}")
    
    const client = new Anthropic();
    
    const workspace = await client.beta.organization.workspaces.update("<workspace-id>", {
      external_key_id: "ekey_<id>"
    });
    
    console.log(`id: ${workspace.id}`);
    console.log(`external_key_id: ${workspace.external_key_id}`);
    
    AnthropicClient client = new();
    
    var workspace = await client.Beta.Organization.Workspaces.Update("<workspace-id>", new()
    {
        ExternalKeyID = "ekey_<id>"
    });
    
    Console.WriteLine($"id: {workspace.ID}");
    Console.WriteLine($"external_key_id: {workspace.ExternalKeyID}");
    
    client := anthropic.NewClient()
    
    workspace, err := client.Beta.Organization.Workspaces.Update(
    	context.Background(),
    	"<workspace-id>",
    	anthropic.BetaOrganizationWorkspaceUpdateParams{
    		ExternalKeyID: anthropic.String("ekey_<id>"),
    	},
    )
    if err != nil {
    	log.Fatal(err)
    }
    
    fmt.Printf("id: %s\n", workspace.ID)
    fmt.Printf("external_key_id: %s\n", workspace.ExternalKeyID)
    
    import com.anthropic.models.beta.organization.workspaces.WorkspaceUpdateParams;
    
    void main() {
        AnthropicClient client = AnthropicOkHttpClient.fromEnv();
    
        var params = WorkspaceUpdateParams.builder()
            .externalKeyId("ekey_<id>")
            .build();
        var workspace = client.beta().organization().workspaces().update("<workspace-id>", params);
    
        IO.println("id: " + workspace.id());
        IO.println("external_key_id: " + workspace.externalKeyId().orElseThrow());
    }
    
    $client = new Client();
    
    $workspace = $client->beta->organization->workspaces->update(
        workspaceID: '<workspace-id>',
        externalKeyID: 'ekey_<id>',
    );
    
    echo "id: {$workspace->id}\n";
    echo "external_key_id: {$workspace->externalKeyID}\n";
    
    client = Anthropic::Client.new
    
    workspace_id = "<workspace-id>"
    workspace = client.beta.organization.workspaces.update(
      workspace_id,
      external_key_id: "ekey_<id>"
    )
    
    puts "id: #{workspace.id}"
    puts "external_key_id: #{workspace.external_key_id}"
    
Claude Enterprise

claude.ai > Organization settings > Data and privacy에서 Encryption keys를 연 뒤 Add key를 클릭하세요. AWS를 선택하고 Continue를 클릭한 다음 이전 단계의 Key ARN을 붙여넣고 Add를 클릭하세요. Anthropic이 암호화·복호화 왕복으로 키를 검증해요. 검증된 것으로 표시되면 그 시점부터 조직이 CMEK로 보호돼요.

이 흐름의 키 세부 사항 단계에는 복사 버튼이 있는 Organization ID for the key policy가 표시돼요. 키 정책에서 그 값을 <ORGANIZATION_UUID> 대신 사용하세요. 키를 만들기 전에 흐름을 열어 ID를 복사할 수 있어요.

Claude Enterprise에서 CMEK는 전체 조직에 적용되므로 별도의 워크스페이스 연결 단계가 없고, 조직은 키를 하나만 가질 수 있어요.

AWS의 Claude Platform에서 CMEK 설정

Claude Platform on AWS에서 CMEK는 AWS KMS 키만 사용하며, 설정은 다음 방식으로 앞선 섹션들과 달라요:

  • Principal: 키 정책이 AWS 서비스 principal aws-external-anthropic.amazonaws.com에 접근 권한을 부여해요. Anthropic의 IAM 역할과 계정 ID는 사용되지 않으므로 Anthropic용 ARN은 적용되지 않아요.
  • 키 요구 사항: 키는 암호화·복호화 사용, 단일 리전, 그리고 연결하는 워크스페이스와 동일한 AWS 계정·리전의 대칭 KMS 키여야 해요. 크로스 계정 키는 지원되지 않아요. 키는 조직을 호스팅하는 AWS 계정에 있어야 해요. 멀티 리전 키(mrk-로 시작하는 키 ID)와 별칭 ARN은 등록 시 거부되므로 키 ARN을 사용하세요.
  • 별도 검증 단계 없음: 등록 시 키 ARN에 대한 이러한 검사 외에 키는 워크스페이스에 연결할 때 검증돼요. 연결 호출이 워크스페이스의 컴파트먼트 ID를 암호화 컨텍스트로 키에 대해 암호화·복호화 왕복을 수행하므로, 키 정책 문제는 등록이 아니라 연결 시점에 드러나요. 따라서 EncryptionContext 조건에는 모두 0 항목이 필요 없어요.
  • 키 관리 위치: Admin 역할로 AWS를 통해 로그인한 Claude Console에서 키를 등록·연결하세요. 외부 키 엔드포인트도 AWS의 Claude Platform에서 IAM 작업으로 인가되어 사용할 수 있어요. 그곳에서 키는 ekey_ ID가 아니라 KMS 키 ARN으로 식별돼요.

게시된 이 서비스 principal 이름만 사용하세요. 이메일, 채팅, 온보딩 채널로 제공된 식별자는 절대 신뢰하지 마세요.

전제 조건

  • Claude Platform on AWS 조직을 호스팅하는 AWS 계정으로, KMS 키 생성과 키 정책 설정 권한(kms:CreateKey, kms:PutKeyPolicy)이 있어야 해요.
  • Claude Platform on AWS용 Claude Console의 Admin 역할. Claude Console 사용하기 참고.
  • Claude Console에 로그인하는 IAM principal은 aws-external-anthropic:AssumeConsole 외에도 그곳에서 수행하는 작업에 대한 IAM 작업이 필요해요. Encryption keys 페이지와 키 연결이 AWS 게이트웨이를 거치기 때문이에요. 키 등록은 RegisterKey(등록 보기에는 ListKeysGetKey), 연결은 UpdateWorkspace 또는 CreateWorkspace예요. 외부 키 작업(및 CreateWorkspace)은 계정 범위이므로 Resource: "*"로 부여하세요. 워크스페이스 ARN으로 제한된 정책은 이를 포함하지 않아요.
  • 키를 워크스페이스에 연결하는 IAM principal(Claude Console에 로그인한 신원): 키에 대한 kms:DescribeKey, kms:Encrypt, kms:Decrypt. 연결 시 서비스 principal 외에도 principal의 키 접근이 확인돼요.
  • 선택 사항, Claude Console의 키 선택기용: 로그인하는 principal의 kms:ListKeyskms:DescribeKey. 없으면 대신 키 ARN을 붙여넣으세요.

KMS 키 만들기

키 정책은 세 문이에요: 계정 루트 admin 문; Claude Platform on AWS 서비스 principal이 데이터 키를 암호화·복호화·생성할 수 있게 하는 문; kms:DescribeKey용 별도 문. 두 서비스 principal 문 모두 권장 aws:SourceArn 조건을 담아요. 서비스가 특정 워크스페이스를 대신해 키를 호출하고 그 워크스페이스의 ARN을 출처 ARN으로 전달하므로, 보여진 패턴은 부여를 계정의 워크스페이스로 제한해요. DescribeKeyEncryptionContext 매개변수가 없어서 그 작업에 EncryptionContext 조건을 걸면 항상 거부되므로 별도로 부여돼요.

여기에 보여진 선택적 EncryptionContext 조건을 사용하려면 먼저 워크스페이스를(키 없이) 만들고 그 컴파트먼트 ID를 복사해 <compartment-uuid>에 넣으세요. ID를 Claude Console에서 찾으려면 Manage > Security로 이동해 사이드바 상단의 워크스페이스 선택기에서 워크스페이스를 선택하세요. ID는 Encryption key 아래 Compartment ID 필드에 있어요. Get Workspace 엔드포인트가 반환하는 compartment_id 필드에서도 읽을 수 있어요. 조건을 사용하지 않으려면 그 문의 Condition 블록에서 StringEquals 항목을 삭제하고 ArnLike 항목은 유지하세요.

export YOUR_ACCOUNT=$(aws sts get-caller-identity --query Account --output text)

aws kms create-key \
  --region <workspace-region> \
  --description "Anthropic CMEK (Claude Platform on AWS)" \
  --key-usage ENCRYPT_DECRYPT \
  --policy "{
    \"Version\": \"2012-10-17\",
    \"Statement\": [
      {
        \"Sid\": \"AccountRootAdmin\",
        \"Effect\": \"Allow\",
        \"Principal\": {\"AWS\": \"arn:aws:iam::${YOUR_ACCOUNT}:root\"},
        \"Action\": \"kms:*\",
        \"Resource\": \"*\"
      },
      {
        \"Sid\": \"AllowClaudePlatformOnAWSCrypto\",
        \"Effect\": \"Allow\",
        \"Principal\": {\"Service\": \"aws-external-anthropic.amazonaws.com\"},
        \"Action\": [\"kms:Encrypt\", \"kms:Decrypt\", \"kms:GenerateDataKey\"],
        \"Resource\": \"*\",
        \"Condition\": {
          \"ArnLike\": {
            \"aws:SourceArn\": \"arn:aws:aws-external-anthropic:*:${YOUR_ACCOUNT}:workspace/*\"
          },
          \"StringEquals\": {
            \"kms:EncryptionContext:anthropic:compartment_uuid\": [
              \"<compartment-uuid>\"
            ]
          }
        }
      },
      {
        \"Sid\": \"AllowClaudePlatformOnAWSDescribe\",
        \"Effect\": \"Allow\",
        \"Principal\": {\"Service\": \"aws-external-anthropic.amazonaws.com\"},
        \"Action\": \"kms:DescribeKey\",
        \"Resource\": \"*\",
        \"Condition\": {
          \"ArnLike\": {
            \"aws:SourceArn\": \"arn:aws:aws-external-anthropic:*:${YOUR_ACCOUNT}:workspace/*\"
          }
        }
      }
    ]
  }"

출력에서 KeyMetadata.Arn을 기록하세요. 키를 등록할 때 필요해요.

두 조건 모두 선택적 강화이며 서로 결합해요. aws:SourceArn 조건은 워크스페이스가 존재하기 전에 작성할 수 있어요. 전체 계정 대신 특정 워크스페이스에 키를 고정하려면 와일드카드 패턴 대신 전체 워크스페이스 ARN을 나열하고, 없이 시작하려면 두 서비스 principal 문에서 ArnLike 항목을 삭제하세요(그러면 비게 되는 Condition 블록도 제거). EncryptionContext 조건도 선택적이에요. 워크스페이스에 대해 이루어지는 모든 암호화·복호화·데이터 키 호출(연결 시점 검사 포함)은 해당 워크스페이스의 컴파트먼트 ID를 anthropic:compartment_uuid로 담아요. 따라서 조건은 키를 연결하는 각 워크스페이스의 컴파트먼트 ID를 나열하며 모두 0 항목이 필요 없어요. 추가하면 IAM 계층에서도 키를 나열한 워크스페이스에 바인딩해요. 컴파트먼트 ID는 워크스페이스가 존재해야만 존재하므로 순서는: 워크스페이스를 만들고, 조건에 컴파트먼트 ID를 넣고(키 생성 시 또는 나중에 kms:PutKeyPolicy로), 키를 연결하는 거예요. 각 추가 워크스페이스에 키를 연결하기 전에 같은 방식으로 그 워크스페이스의 컴파트먼트 ID를 추가하세요. 없이 시작하려면 AllowClaudePlatformOnAWSCrypto 문의 Condition 블록에서 StringEquals 항목을 삭제하세요. 나중에 추가한다면 키가 이미 연결된 모든 워크스페이스의 컴파트먼트 ID를 포함하세요.

AWS Console에서도 키를 만들 수 있어요: 워크스페이스의 리전에서 대칭 키, 암호화·복호화 키 사용, 단일 리전 키, KMS 키 자료 원본을 선택하세요. Create-key 마법사에서 키 사용 권한은 비워 두고, 키의 Key policy 탭을 열어 JSON을 여기 보여준 정책으로 교체하세요.

키 등록 및 연결

  1. 키 등록 — Claude Console에서 Settings > Encryption keys를 열고 Add key를 클릭하세요. 표시 이름을 입력한 뒤 키 선택기에서 키를 선택하거나 Enter ARN manually를 선택하고 키 ARN을 붙여넣고 Add를 클릭하세요. 키는 조직을 호스팅하는 AWS 계정에 있어야 해요. 크로스 계정 키는 지원되지 않아요. 선택기는 조직의 리전 중 하나에서 계정의 활성화된, 고객 관리, 대칭, 단일 리전 키를 나열해요. 선택기에 없는 키는 ARN을 입력하세요. 로그인한 principal이 kms:ListKeyskms:DescribeKey를 호출할 수 있는 경우에만 키를 나열해요.

  2. 키를 워크스페이스에 연결 — 그 워크스페이스로 요청을 보내기 전에 키를 새 워크스페이스에 연결하세요. 이미 요청을 받는 워크스페이스의 경우 키가 적용되는 데 최대 하루 걸릴 수 있어요. Claude Console에서 Manage > Security로 이동해 사이드바 상단의 워크스페이스 선택기에서 워크스페이스를 선택하세요. Encryption key 아래에서 키를 선택하고 Save를 클릭한 뒤 확인하세요. 키 정책이 아직 특정 워크스페이스를 지명하지 않은 경우에만(즉 EncryptionContext 조건이 없고 개별 워크스페이스 ARN이 아닌 계정 전체 aws:SourceArn 패턴) Claude Console에서 워크스페이스를 만들 때 키를 선택할 수도 있어요. 워크스페이스의 ID와 컴파트먼트 ID가 생성 시 할당되기 때문이에요. 일단 연결되면 워크스페이스의 키는 변경할 수 없어요.

    이때 키가 검증돼요. 연결 호출이 principal의 키 접근을 확인하고 워크스페이스의 컴파트먼트 ID를 암호화 컨텍스트로 키에 대해 암호화·복호화 왕복을 수행하므로, 키 정책이나 principal 권한의 문제는 해당 호출에서 오류로 드러나요. KMS 접근 오류로 연결이 실패하면 다음을 확인하세요:

    • 키 정책이 aws-external-anthropic.amazonaws.com 서비스 principal을 지명하고 kms:Encrypt, kms:Decrypt, kms:GenerateDataKey를 부여하며, EncryptionContext 조건이 없는 별도 문에 kms:DescribeKey도 부여해요.
    • aws:SourceArn 조건이 이 워크스페이스의 ARN(계정 ID, 특정 ARN을 나열했다면 워크스페이스)과 일치하고, 어떤 EncryptionContext 조건이든 이 워크스페이스의 컴파트먼트 ID를 포함해요.
    • 키가 활성화·단일 리전이며 워크스페이스와 동일한 AWS 계정·리전에 있어요.
    • 로그인한 principal이 키에 대한 kms:DescribeKey, kms:Encrypt, kms:Decrypt 권한을 가져요.
    • AWS 조직의 어떤 서비스 제어 정책이나 리소스 제어 정책도 서비스 principal 또는 principal이 키를 사용하는 것을 막지 않아요.
    • 정책이 맞아 보여도 연결이 여전히 실패하면 키의 계정에서 CloudTrail에서 거부된 kms: 이벤트를 찾아보고(호출 principal과, 암호화 호출의 경우 암호화 컨텍스트를 보여줌) aws:SourceArn 조건을 임시로 제거한 채 다시 시도해 출처 ARN 불일치와 암호화 컨텍스트 불일치를 구분하세요. 키가 연결되면(그 재시도에서든 암호화 컨텍스트를 고친 후든) kms:PutKeyPolicy로 두 서비스 principal 문에 ArnLike 항목을 복원하되, 계정 전체 aws:SourceArn 패턴이나 키가 연결된 모든 워크스페이스의 ARN을 사용하세요.

Terraform

IaC(infrastructure-as-code) 배포의 경우 동일한 단계가 aws 제공자의 aws_kms_keyaws_kms_alias 리소스로 매핑돼요.

더 알아보기 (Learn more)