본문 바로가기

AWS/IAM

AWS CLI환경에서 역할전환 Profile 자동화

안녕하세요. 오늘은 지난 번 포스팅에서 말씀드린 AWS CLI 환경에서 역할전환 Profile 입력을 자동으로

해주는 코드를 소개해드리면서 간단하게 사용법 설명드리고자 합니다.

예전에 글 썻던 MFA 임시세션토큰 자동화랑 유사한 내용이고, 코드만 약간 수정되었습니다 ^^

 

지난번 포스팅에서 말씀드린 것 처럼 역할을 사용하려면 그 역할과 신뢰관계를 맺고 있어야하고,

sts:AssumeRole 권한이 허용되어 있어야합니다.

 

또한, AssumeRole을 사용하여 역할을 호출하게되면, 임시세션토큰이 발급되는데요.

이러한 임시세션토큰은 Role의 경우에는 Default 1시간이고 최대 12시간까지 설정이 가능합니다.

 

임시세션토큰이 발급되면, AccessKey, SecretAccessKey, SessionToken 정보를 $HOME/.aws/credentials에

입력해주어야 하는데요. 최소 1시간, 또는 최대 12시간마다 이러한 정보를 새로 입력해주기는 너무나 불편합니다.

 

제가 IT일을 하면서 가슴속에 새긴 2가지 말이 있는데요.

'IT는 구글링과 구걸링', '최첨단 수동시스템으로 일하고 있다면 IT를 하지 못하는 것'

위 두 말을 항상 가슴속에 새기고 일하고 있습니다 ^^

 

'IT는 게으른 사람이 하는 것' 이라는 말 처럼, 내가 게을러지기 위해 끊임없이 최첨단 수동 프로세스를

자동화해야 한다고 생각합니다.

 

그럼 본격적으로 포스팅 시작하겠습니다 ^^

지난번 포스팅에서 다루었던 내용들은 대부분 생략할 예정이므로, 이해가 안가신다면 지난 번 포스팅

참고 부탁드립니다 ^^

 

junhyeong-jang.tistory.com/17


지난 포스팅에 이어서, 현재 AssumeRole-User가 생성되어 있고, 이러한 User에는 sts:AssumeRole 권한이

할당되어 있습니다.

또한, 테스트를 위해 AWSS3ReadOnlyAccess 권한이 부착되어 있는 Assume-S3-Read-Role 이 생성되어 있습니다.

 

위에서 말씀드린 AssumeRole-User의 AccessKey와 SecretAccessKey는 %HOME/.aws/credentials에

Assume 라는 이름으로 등록되어 있습니다.

 

역시 저번 포스팅과 마찬가지로 이러한 사용자의 Profile로 aws s3 ls 명령어를 사용했을때는 Access Deny가 발생합니다.

(당연하게도 sts:AssumeRole 권한밖에 없기때문입니다 ^^)

 

sts:AssumeRole 권한만 있는 사용자의 s3 명령어 결과

 

그러나, 신뢰관계를 맺고있는 역할을 호출하고, Response로 출력되는 AccessKey, SecretAccessKey와 SessionToken을

입력해주고 해당 Profile을 사용하게 되면 정상적으로 호출이 됩니다.

 

AssumeRole을 통해 임시세션토큰을 발급받고, Profile 입력 후 s3 명령어 사용

 

이렇게, 임시세션토큰을 발급받고 Profile을 생성하기에는 귀찮습니다.

 

Python을 사용하여 자동으로 Profile을 업데이트 하도록 해보겠습니다.

 

#!/usr/bin/python3
import sys
import configparser
import json
import os
from os.path import expanduser
import argparse


def main(region_args, profile_args, assume_profile_args):
    home = expanduser("~")

    awsConfig = configparser.ConfigParser()
    awsCred   = configparser.ConfigParser()

    awsConfig.read("%s/.aws/config" % home)
    awsCred.read('%s/.aws/credentials' % home)

    try:
        mfaARN = awsConfig[awsConfig["profile " + profile_args]['source_profile']]['mfa_arn']
    except KeyError:
        try:
            mfaARN = awsConfig['default']['mfa_arn']
        except KeyError:
            mfaARN = None

    try:
        RoleArn = awsConfig[awsConfig["profile " + profile_args]['source_profile']]['role_arn']
    except KeyError:
        try:
            RoleArn = awsConfig['default']['role_arn']
        except KeyError:
            exit("Need Role Arn in config file")

    profiles = set( awsCred.sections())
    configprofiles = set( awsConfig.sections())

    if( profile_args in profiles and "profile " + profile_args in configprofiles):
        print("Updating %s profile" % profile_args)
    else:
        if( "profile " + profile_args in configprofiles):
            print("Creating %s credentials profile" % profile_args)
            awsCred.add_section(profile_args)
        else:
            exit("No such profile \"%s\" in config" % profile_args )

    if mfaARN != None:
        try:
            OneTimeNumber = int(input("OTP from device: "))
        except ValueError:
            exit("OTP must be a number")


    if mfaARN != None:
        response = os.popen("aws --profile %s sts assume-role --role-arn %s --role-session-name %s --serial-number  %s --token-code %s" % ( awsConfig["profile " + profile_args]['source_profile'],
                                                                                                    RoleArn,
                                                                                                    profile_args,
                                                                                                    mfaARN,
                                                                                                    str(OneTimeNumber).zfill(6))).read()
    else:
        response = os.popen("aws --profile %s sts assume-role --role-arn %s --role-session-name %s" % ( awsConfig["profile " + profile_args]['source_profile'],
                                                                                                    RoleArn,
                                                                                                    profile_args)).read()
    try:
        myjson = json.loads(response)
    except json.decoder.JSONDecodeError:
        exit("AWS was not happy with that one")

    os.popen("aws configure set aws_access_key_id %s --profile %s" % (myjson['Credentials']['AccessKeyId'], assume_profile_args))
    os.popen("aws configure set aws_secret_access_key %s --profile %s" % (myjson['Credentials']['SecretAccessKey'], assume_profile_args))
    os.popen("aws configure set aws_session_token %s --profile %s" % (myjson['Credentials']['SessionToken'], assume_profile_args))
    os.popen("aws configure set region %s --profile %s" % (region_args, assume_profile_args))
    os.popen("aws configure set output json --profile %s" % assume_profile_args)
    

def get_arguments():
    parser = argparse.ArgumentParser()
    parser.add_argument('-p', required=False, default='default', help='Account Credential Name')
    parser.add_argument('-r', required=False, default='ap-northeast-2', help='Region')
    parser.add_argument('-t', required=False, default='Assume-Profile', help='Assume Token Profile')
    args = parser.parse_args()

    return args.r, args.p, args.t


if __name__ == "__main__":
    region_args, profile_args, assume_profile_args = get_arguments()
    main(region_args, profile_args, assume_profile_args)

 

간단하게 코드 읽어보시면 아시겠지만, $HOME/.aws/credentials 와 $HOME/.aws/config 파일을 열어

맵핑되는 정보를 읽어들인 후, 이 정보로 임시세션토큰을 요청하고 Profile을 업데이트 해주는 코드입니다.

그렇기 때문에 코드에서 맵핑시키려는 정보들로 aws profile을 미리 어느정도 작성을 해놔야하는데요.

 

$HOME/.aws/config 파일을 작성하도록 하겠습니다.

 

# $HOME/.aws/config

[profile Assume]
region = ap-northeast-2
output = json
source_profile = Assume

[Assume]
output = json
role_arn = arn:aws:iam::123456789012:role/Assume-S3-Read-Role

위와 같은 형식으로 $HOME/.aws/config 파일을 작성해놓습니다.

여기서 role_arn은 AWS Web Console에서 신뢰관계를 맺어놓은 Role의 ARN(Aws Resource Name) 입니다.

 

이렇게 작성을 하고, Python 코드를 다음과 같이 실행시켜보도록 하겠습니다.

저는 위의 Python코드의 이름을 assume-role.py 로 저장해놨습니다.

 

python assume-role.py -p Assume -r ap-northeast-2 -t S3Read

 

위와 같이 Python코드를 실행시키시면, Assume 이라는 Profile로 서울리전에서 config에 적혀있는 Role ARN으로

aws sts assume-role 명령어를 실행하고, 출력되는 정보를 S3Read 라는 이름의 Profile로 저장해줍니다.

 

확인해볼까요??

 

먼저 아래 사진에서 보시는 것 처럼 저는 default와 Assume Profile외에는 아무것도 없습니다.

$HOME/.aws/credentials

 

 

이 상태에서 python 명령어를 실행하겠습니다.

 

python assume-role.py -p Assume -r ap-northeast-2 -t S3Read

 

Python 코드 실행

 

그리고 나서 $HOME/.aws/credentials 파일과 $HOME/.aws/config 파일을 확인해보시면...

 

$HOME/.aws/credentials 파일과 $HOME/.aws/config

 

위와 같이 업데이트 되신 것을 확인하실 수 있습니다.

 

S3Read 역할의 임시세션토큰이 입력된 Profile(S3Read)을 사용하면 S3에 대해 읽을 수 있겠죠??

S3Read Profile로 s3 ls 명령어 사용

 

위처럼 정상적으로 명령어를 수행하신 것 확인할 수 있습니다.

 

또한, Code를 읽어보신 분은 아시겠지만, Role을 생성할 때 옵션중에 'MFA 필요' 라는 옵션이 존재합니다.

이때에는 $HOME/.aws/config에 Role을 사용하고자 하는 IAM User에게 할당된 MFA ARN을 추가하시면 됩니다.

 

즉 $HOME/.aws/config 파일이 아래와 같이 됩니다.

[profile Assume]
region = ap-northeast-2
output = json
source_profile = Assume

[Assume]
output = json
role_arn = arn:aws:iam::123456789012:role/Assume-S3-Read-Role
mfa_arn = arn:aws:iam::123456789012:mfa/AssumeRole-User

 

위와 같이 입력을 해놓으시면, 코드에서 알아서 읽어서 넣어준답니다!!

또한, 아까 위에서 잠깐 언급했던 AssumeRole의 경우에는 Default가 1시간, 최대 12시간 이라고 말씀드렸는데요.

 

해당 부분의 설정은 아래에서 조정 가능하십니다.

 

AWS Web Console - IAM - Roles

Role의 최대세션유지시간 조절

 

 

요즘 바쁘다는 핑계로 포스팅을 잘 못하고 있는데요..

더 부지런해져서 기술을 공유할 수 있는 시간을 자주 갖도록 하겠습니다..!!

 

질문이나 피드백은 언제나 감사합니다..!!^^

 

그럼 다음 포스팅때 인사드리겠습니다.