CloudFormation 템플릿으로 SAML 자격 증명 공급자(IdP) 생성하기
CloudFormation 템플릿으로 SAML 자격 증명 공급자(IdP) 생성하기 (튜토리얼)
AWS 계정에 SAML 페더레이션을 설정하려면 SAML 자격 증명 공급자(IdP)를 만들어야 해요. 이 튜토리얼은 CloudFormation 템플릿을 사용해 AWS와 외부 IdP 간의 신뢰를 설정하는 SAML IdP를 만드는 방법을 보여줘요.
출처: 문서
본문
템플릿은 IdP의 메타데이터 문서로 구성된 SAML IdP를 만들어요. 그러면 페더레이션 IAM 역할이 이 IdP를 참조해 외부 IdP에서 인증된 사용자가 AWS 리소스에 접근할 수 있게 해줘요.
배포된 리소스는 IdP의 메타데이터 문서와 선택적 암호화 설정으로 구성된 SAML IdP로 이루어져요.
사전 요구 사항 (Prerequisites)
이 튜토리얼은 다음이 이미 준비되어 있다고 가정해요.
- 이 튜토리얼에서 IdP의 SAML 메타데이터 XML 파일을 포맷하는 데 사용하는 Python 명령을 실행할 로컬 머신에 설치된 Python 3.6 이상
- 외부 IdP의 SAML 메타데이터 문서를 XML 파일로 저장한 것
CloudFormation으로 SAML IdP 생성 (Create a SAML IdP using CloudFormation)
SAML IdP를 만들려면 CloudFormation 템플릿을 만들고 이를 사용해 IdP 리소스를 포함하는 스택을 만들어요.
템플릿 생성 (Create the template)
먼저 CloudFormation 템플릿을 만들어요.
- Template 섹션에서 JSON 또는 YAML 탭의 복사 아이콘을 선택해 템플릿 내용을 복사해요.
- 템플릿 내용을 새 파일에 붙여넣어요.
- 파일을 로컬에 저장해요.
스택 생성 (Create the stack)
다음으로 저장한 템플릿을 사용해 CloudFormation 스택을 프로비저닝해요.
- https://console.aws.amazon.com/cloudformation 에서 CloudFormation 콘솔을 열어요.
- Stacks 페이지에서 Create stack 메뉴에서 **with new resources (standard)**를 선택해요.
- 템플릿을 지정해요:
- Prerequisite 아래에서 Choose an existing template을 선택해요.
- Specify template 아래에서 Upload a template file을 선택해요.
- Choose file을 선택하고 템플릿 파일로 이동한 뒤 선택해요.
- Next를 선택해요.
- 다음 스택 세부 정보를 지정해요:
- 스택 이름을 입력해요.
- IdentityProviderName은 비워 두면 스택 이름을 기반으로 이름이 자동 생성되거나, SAML IdP에 사용자 지정 이름을 입력할 수 있어요. 사용자 지정 이름은 영숫자 문자, 마침표, 밑줄, 하이픈만 포함해야 해요.
- IdentityProviderSAMLMetadataDocument는 SAML 메타데이터 XML 파일을 이 필드에 붙여넣기 전에 단일 줄로 포맷해야 해요. CloudFormation 콘솔이 콘솔 매개변수로 전달할 때 XML 내용을 단일 줄로 포맷해야 하기 때문이에요. 다음 Python 명령을 사용해 XML 파일을 다시 포맷해요.
python3 -c "import sys, re; content=open(sys.argv[1]).read(); print(re.sub(r'>\s+<', '><', content.replace('\n', '').replace('\r', '').strip()))" saml-metadata.xml참고 – IdP의 SAML 메타데이터 문서는 콘솔 매개변수 입력을 위해 단일 줄로 포맷되어야 해요. Python 명령은 모든 원본 내용과 구조를 유지하면서 줄 바꿈과 추가 공백을 제거해 필요한 형식을 만들어요.
- Python 명령의 출력을 복사해 IdentityProviderSAMLMetadataDocument 필드에 붙여넣어요.
단일 줄로 포맷된 SAML 메타데이터 문서의 예시(축약):
<?xml version="1.0" encoding="UTF-8"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://portal.sso.example.com/saml/assertion/CompanyIdP"><md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>MIIDXTCCAkWgAwIBAgIJAJC1HiIAZAiIMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV...</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://portal.sso.example.com/saml/logout/CompanyIdP"/><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://portal.sso.example.com/saml/assertion/CompanyIdP"/></md:IDPSSODescriptor></md:EntityDescriptor> - 다른 매개변수는 요구 사항에 따라 기본값을 수락하거나 직접 입력해요:
- IdentityProviderAddPrivateKey – SAML 어서션을 복호화하기 위한 선택적 개인 키
- IdentityProviderAssertionEncryptionMode – 선택적, SAML 어서션의 암호화 모드를 설정해요 (Allowed, Required 또는 비어 있음)
- Next를 선택해요.
- 스택 옵션을 구성해요:
- Stack failure options 아래에서 Delete all newly created resources를 선택해요.
참고 – 이 옵션을 선택하면 스택 생성이 실패해도 삭제 정책이 리소스를 보관하도록 지정된 리소스에 대해 요금이 부과되는 것을 방지할 수 있어요.
- 다른 모든 기본값을 수락해요.
- Capabilities 아래에서 CloudFormation이 계정에 IAM 리소스를 만들 수 있음을 인정하는 확인란을 선택해요.
- Next를 선택해요.
- Stack failure options 아래에서 Delete all newly created resources를 선택해요.
- 스택 세부 정보를 검토하고 Submit을 선택해요.
- CloudFormation이 스택을 만들어요. 스택 생성이 완료되면 스택 리소스를 사용할 준비가 돼요. 스택 상세 페이지의 Resources 탭을 사용해 계정에 프로비저닝된 리소스를 볼 수 있어요.
스택은 다음 값을 출력하며 Outputs 탭에서 볼 수 있어요.
- ProviderARN: 생성된 SAML IdP의 ARN(예:
arn:aws:iam::123456789012:saml-provider/CompanyIdP). 이 공급자를 신뢰하는 역할을 만들 때 이 ARN이 필요해요. - ProviderName: 생성된 SAML IdP의 이름(예: 사용자 지정 이름을 지정했다면
CompanyIdP, 기본 이름을 사용했다면my-saml-stack-saml-provider).
이 출력도 내보내지므로 Fn::ImportValue 함수로 다른 CloudFormation 스택에 가져올 수 있어요.
SAML IdP 확인 (Verify the SAML IdP)
SAML IdP를 만든 후에는 구성을 확인하고 페더레이션 역할에 사용할 ARN을 기록할 수 있어요.
- https://console.aws.amazon.com/iam/ 에서 IAM 콘솔을 열어요.
- 탐색 창에서 Identity providers를 선택해요.
- 목록에 새로 만든 SAML IdP가 보여야 해요.
- IdP 이름을 선택해 세부 정보를 확인해요.
- IdP 상세 페이지에서 SAML 메타데이터 문서와 기타 구성 세부 정보를 볼 수 있어요.
- 세부 정보 페이지에 표시된 Provider ARN을 기록해요. 이 IdP를 신뢰하는 페더레이션 IAM 역할을 만들 때 이 ARN이 필요해요.
- 메타데이터 문서를 검토해 외부 IdP에서 제공한 것과 일치하는지 확인해요.
이제 SAML IdP를 페더레이션 IAM 역할에서 사용할 준비가 됐어요. 이 IdP를 신뢰하는 역할을 만들어 외부 IdP에서 인증된 사용자가 해당 역할을 맡아 AWS 리소스에 접근할 수 있게 할 수 있어요.
정리: 리소스 삭제 (Clean up: delete resources)
마지막 단계로 스택과 그 안에 포함된 리소스를 삭제해요.
- CloudFormation 콘솔을 열어요.
- Stacks 페이지에서 템플릿으로 만든 스택을 선택하고 Delete를 선택한 뒤 Delete를 확인해요.
- CloudFormation이 스택과 여기에 포함된 모든 리소스의 삭제를 시작해요.
CloudFormation 템플릿 세부 정보 (CloudFormation template details)
리소스 (Resources)
이 튜토리얼의 CloudFormation 템플릿은 계정에 다음 리소스를 만들어요.
AWS::IAM::SAMLProvider: AWS와 외부 IdP 간의 신뢰를 설정하는 SAML IdP
구성 (Configuration)
템플릿은 다음 구성 가능한 매개변수를 포함해요.
- IdentityProviderName – SAML IdP의 이름 (자동 생성 이름을 위해 비워 두기) 예:
CompanyIdP또는EnterpriseSSO - IdentityProviderSAMLMetadataDocument – 외부 IdP의 SAML 메타데이터 문서 (단일 줄로 포맷)
- IdentityProviderAddPrivateKey – SAML 어서션을 복호화하기 위한 선택적 개인 키
- IdentityProviderAssertionEncryptionMode – 선택적, SAML 어서션의 암호화 모드를 설정
CloudFormation 템플릿 (CloudFormation template)
이 튜토리얼의 CloudFormation 템플릿으로 사용할 다음 JSON 또는 YAML 코드를 별도 파일로 저장해요.
JSON
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "[AWSDocs] IAM: tutorial_saml-idp",
"Parameters": {
"IdentityProviderName": {
"Type": "String",
"Description": "Name of the SAML Identity Provider (leave empty for auto-generated name like '{StackName}-{UniqueId}')",
"Default": "",
"AllowedPattern": "^$|^[a-zA-Z0-9._-]+$",
"ConstraintDescription": "Must be empty or contain only alphanumeric characters, periods, underscores, and hyphens"
},
"IdentityProviderSAMLMetadataDocument": {
"Type": "String",
"Description": "SAML metadata document from identity provider"
},
"IdentityProviderAddPrivateKey": {
"Type": "String",
"Description": "Optional private key for decrypting SAML assertions. The private key must be a .pem file that uses AES-GCM or AES-CBC encryption algorithm to decrypt SAML assertions.",
"Default": ""
},
"IdentityProviderAssertionEncryptionMode": {
"Type": "String",
"Description": "Optional, sets encryption mode for SAML assertions",
"Default": "",
"AllowedValues": ["", "Allowed", "Required"]
}
},
"Conditions": {
"HasPrivateKey": {"Fn::Not": [{"Fn::Equals": [{"Ref": "IdentityProviderAddPrivateKey"}, ""]}]},
"HasEncryptionMode": {"Fn::Not": [{"Fn::Equals": [{"Ref": "IdentityProviderAssertionEncryptionMode"}, ""]}]},
"HasCustomName": {"Fn::Not": [{"Fn::Equals": [{"Ref": "IdentityProviderName"}, ""]}]}
},
"Resources": {
"SAMLProvider": {
"Type": "AWS::IAM::SAMLProvider",
"Properties": {
"Name": {"Fn::If": ["HasCustomName", {"Ref": "IdentityProviderName"}, {"Ref": "AWS::NoValue"}]},
"SamlMetadataDocument": {"Ref": "IdentityProviderSAMLMetadataDocument"},
"Tags": [
{
"Key": "Name",
"Value": {"Fn::If": ["HasCustomName", {"Ref": "IdentityProviderName"}, {"Fn::Sub": "${AWS::StackName}-saml-provider"}]}
}
],
"AddPrivateKey": {"Fn::If": ["HasPrivateKey", {"Ref": "IdentityProviderAddPrivateKey"}, {"Ref": "AWS::NoValue"}]},
"AssertionEncryptionMode": {"Fn::If": ["HasEncryptionMode", {"Ref": "IdentityProviderAssertionEncryptionMode"}, {"Ref": "AWS::NoValue"}]}
}
}
},
"Outputs": {
"ProviderARN": {
"Description": "ARN of the created SAML Identity Provider",
"Value": {"Ref": "SAMLProvider"},
"Export": {
"Name": {"Fn::Sub": "${AWS::StackName}-ProviderARN"}
}
},
"ProviderName": {
"Description": "Name of the SAML Identity Provider",
"Value": {"Fn::If": ["HasCustomName", {"Ref": "IdentityProviderName"}, {"Fn::Sub": "${AWS::StackName}-saml-provider"}]},
"Export": {
"Name": {"Fn::Sub": "${AWS::StackName}-ProviderName"}
}
}
}
}
YAML
AWSTemplateFormatVersion: '2010-09-09'
Description: '[AWSDocs] IAM: tutorial_saml-idp'
Parameters:
IdentityProviderName:
Type: String
Description: Name of the SAML Identity Provider (leave empty for auto-generated name like '{StackName}-{UniqueId}')
Default: ""
AllowedPattern: '^$|^[a-zA-Z0-9._-]+$'
ConstraintDescription: 'Must be empty or contain only alphanumeric characters, periods, underscores, and hyphens'
IdentityProviderSAMLMetadataDocument:
Type: String
Description: SAML metadata document from identity provider
IdentityProviderAddPrivateKey:
Type: String
Description: Optional private key for decrypting SAML assertions. The private key must be a .pem file that uses AES-GCM or AES-CBC encryption algorithm to decrypt SAML assertions.
Default: ""
IdentityProviderAssertionEncryptionMode:
Type: String
Description: Optional, sets encryption mode for SAML assertions
Default: ""
AllowedValues:
- ""
- "Allowed"
- "Required"
Conditions:
HasPrivateKey: !Not [!Equals [!Ref IdentityProviderAddPrivateKey, ""]]
HasEncryptionMode: !Not [!Equals [!Ref IdentityProviderAssertionEncryptionMode, ""]]
HasCustomName: !Not [!Equals [!Ref IdentityProviderName, ""]]
Resources:
SAMLProvider:
Type: 'AWS::IAM::SAMLProvider'
Properties:
Name: !If
- HasCustomName
- !Ref IdentityProviderName
- !Ref AWS::NoValue
SamlMetadataDocument: !Ref IdentityProviderSAMLMetadataDocument
Tags:
- Key: Name
Value: !If
- HasCustomName
- !Ref IdentityProviderName
- !Sub '${AWS::StackName}-saml-provider'
AddPrivateKey: !If
- HasPrivateKey
- !Ref IdentityProviderAddPrivateKey
- !Ref AWS::NoValue
AssertionEncryptionMode: !If
- HasEncryptionMode
- !Ref IdentityProviderAssertionEncryptionMode
- !Ref AWS::NoValue
Outputs:
ProviderARN:
Description: 'ARN of the created SAML Identity Provider'
Value: !Ref SAMLProvider
Export:
Name: !Sub '${AWS::StackName}-ProviderARN'
ProviderName:
Description: 'Name of the SAML Identity Provider'
Value: !If
- HasCustomName
- !Ref IdentityProviderName
- !Sub '${AWS::StackName}-saml-provider'
Export:
Name: !Sub '${AWS::StackName}-ProviderName'