하드코딩된 시크릿을 안전한 시크릿 스토리지로 옮기기

하드코딩된 시크릿을 안전한 시크릿 스토리지로 옮기기

이 코드 예제에서는 하드코딩된 시크릿을 AWS Secrets Manager로 옮기는 방법을 보여드려요.

출처: 문서

본문

다음 작업을 다루어요.

  • IAM 역할 생성
  • Secrets Manager에 시크릿 생성
  • 애플리케이션 코드 업데이트
  • 시크릿 업데이트
  • 리소스 정리

Bash (AWS CLI with Bash script)

Note GitHub에 더 많은 내용이 있어요. 전체 예제와 설정·실행 방법은 Sample developer tutorials 저장소에서 확인할 수 있어요.

이 스크립트는 시크릿 관리·조회 역할을 만들고, Secrets Manager에 시크릿을 생성하며, 최소 권한 리소스 정책을 적용하고, 시크릿을 검증·업데이트해요.

#!/bin/bash

# Script to move hardcoded secrets to AWS Secrets Manager
# This script demonstrates how to create IAM roles, store a secret in AWS Secrets Manager,
# and set up appropriate permissions

set -euo pipefail

# Set up logging
LOG_FILE="secrets_manager_tutorial.log"
exec > >(tee -a "$LOG_FILE") 2>&1

echo "Starting AWS Secrets Manager tutorial script at $(date)"
echo "======================================================"

# Function to check for errors in command output
check_error() {
	local output=$1
	local cmd=$2

	if echo "$output" | grep -qi "error"; then
		echo "ERROR: Command failed: $cmd"
		echo "$output"
		cleanup_resources
		exit 1
	fi
}

# Function to generate a random identifier using secure method
generate_random_id() {
	python3 -c "import secrets; print('sm' + secrets.token_hex(4))"
}

# Function to safely clean up resources
cleanup_resources() {
	echo ""
	echo "==========================================="
	echo "RESOURCES CREATED"
	echo "==========================================="

	if [ -n "${SECRET_NAME:-}" ]; then
		echo "Secret: $SECRET_NAME"
	fi

	if [ -n "${RUNTIME_ROLE_NAME:-}" ]; then
		echo "IAM Role: $RUNTIME_ROLE_NAME"
	fi

	if [ -n "${ADMIN_ROLE_NAME:-}" ]; then
		echo "IAM Role: $ADMIN_ROLE_NAME"
	fi

	echo ""
	echo "==========================================="
	echo "CLEANUP CONFIRMATION"
	echo "==========================================="
	echo "Cleaning up all created resources..."

	# Delete secret if it exists
	if [ -n "${SECRET_NAME:-}" ]; then
		echo "Deleting secret: $SECRET_NAME"
		aws secretsmanager delete-secret --secret-id "$SECRET_NAME" --force-delete-without-recovery 2>/dev/null || true
	fi

	# Detach policies and delete runtime role if it exists
	if [ -n "${RUNTIME_ROLE_NAME:-}" ]; then
		echo "Deleting inline policies from runtime role: $RUNTIME_ROLE_NAME"
		for policy in $(aws iam list-role-policies --role-name "$RUNTIME_ROLE_NAME" --query 'PolicyNames[]' --output text 2>/dev/null || true); do
			aws iam delete-role-policy --role-name "$RUNTIME_ROLE_NAME" --policy-name "$policy" 2>/dev/null || true
		done
		echo "Deleting IAM role: $RUNTIME_ROLE_NAME"
		aws iam delete-role --role-name "$RUNTIME_ROLE_NAME" 2>/dev/null || true
	fi

	# Detach policies and delete admin role if it exists
	if [ -n "${ADMIN_ROLE_NAME:-}" ]; then
		echo "Detaching policy from role: $ADMIN_ROLE_NAME"
		aws iam detach-role-policy --role-name "$ADMIN_ROLE_NAME" --policy-arn "arn:aws:iam::aws:policy/SecretsManagerReadWrite" 2>/dev/null || true

		for policy in $(aws iam list-role-policies --role-name "$ADMIN_ROLE_NAME" --query 'PolicyNames[]' --output text 2>/dev/null || true); do
			aws iam delete-role-policy --role-name "$ADMIN_ROLE_NAME" --policy-name "$policy" 2>/dev/null || true
		done

		echo "Deleting IAM role: $ADMIN_ROLE_NAME"
		aws iam delete-role --role-name "$ADMIN_ROLE_NAME" 2>/dev/null || true
	fi

	echo "Cleanup completed."
}

# Trap to ensure cleanup on script exit
trap 'echo "Script interrupted. Running cleanup..."; cleanup_resources' INT TERM EXIT

# Generate random identifiers for resources
ADMIN_ROLE_NAME="SecretsManagerAdmin-$(generate_random_id)"
RUNTIME_ROLE_NAME="RoleToRetrieveSecretAtRuntime-$(generate_random_id)"
SECRET_NAME="MyAPIKey-$(generate_random_id)"

echo "Using the following resource names:"
echo "Admin Role: $ADMIN_ROLE_NAME"
echo "Runtime Role: $RUNTIME_ROLE_NAME"
echo "Secret Name: $SECRET_NAME"
echo ""

# Step 1: Create IAM roles
echo "Creating IAM roles..."

# Create assume role policy document
ASSUME_ROLE_POLICY=$(cat <<'EOF'
{
	"Version":"2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Principal": {
				"Service": "ec2.amazonaws.com"
			},
			"Action": "sts:AssumeRole"
		}
	]
}
EOF
)

# Create the SecretsManagerAdmin role
echo "Creating admin role: $ADMIN_ROLE_NAME"
ADMIN_ROLE_OUTPUT=$(aws iam create-role \
	--role-name "$ADMIN_ROLE_NAME" \
	--assume-role-policy-document "$ASSUME_ROLE_POLICY" 2>&1)

check_error "$ADMIN_ROLE_OUTPUT" "create-role for admin"
echo "$ADMIN_ROLE_OUTPUT"
aws iam tag-role --role-name "$ADMIN_ROLE_NAME" --tags Key=project,Value=doc-smith Key=tutorial,Value=aws-secrets-manager-gs

# Attach the SecretsManagerReadWrite policy to the admin role
echo "Attaching SecretsManagerReadWrite policy to admin role"
ATTACH_POLICY_OUTPUT=$(aws iam attach-role-policy \
	--role-name "$ADMIN_ROLE_NAME" \
	--policy-arn "arn:aws:iam::aws:policy/SecretsManagerReadWrite" 2>&1)

check_error "$ATTACH_POLICY_OUTPUT" "attach-role-policy for admin"
echo "Policy attached successfully"

# Create the RoleToRetrieveSecretAtRuntime role
echo "Creating runtime role: $RUNTIME_ROLE_NAME"
RUNTIME_ROLE_OUTPUT=$(aws iam create-role \
	--role-name "$RUNTIME_ROLE_NAME" \
	--assume-role-policy-document "$ASSUME_ROLE_POLICY" 2>&1)

check_error "$RUNTIME_ROLE_OUTPUT" "create-role for runtime"
echo "$RUNTIME_ROLE_OUTPUT"
aws iam tag-role --role-name "$RUNTIME_ROLE_NAME" --tags Key=project,Value=doc-smith Key=tutorial,Value=aws-secrets-manager-gs

# Wait for roles to be fully created
echo "Waiting for IAM roles to be fully created..."
sleep 10

# Step 2: Create a secret in AWS Secrets Manager
echo "Creating secret in AWS Secrets Manager..."

# Generate secure secret value using environment variable or secure method
# WARNING: In production, use secure methods to inject secrets (AWS CodeBuild, parameter store, etc.)
if [ -z "${TUTORIAL_SECRET_VALUE:-}" ]; then
	SECRET_VALUE=$(python3 -c "import json; print(json.dumps({'ClientID':'my_client_id','ClientSecret':__import__('secrets').token_urlsafe(32)}))")
else
	SECRET_VALUE="$TUTORIAL_SECRET_VALUE"
fi

CREATE_SECRET_OUTPUT=$(aws secretsmanager create-secret \
	--name "$SECRET_NAME" \
	--description "API key for my application" \
	--secret-string "$SECRET_VALUE" \
	--add-replica-regions 'Region=us-east-1' \
	--tags Key=project,Value=doc-smith Key=tutorial,Value=aws-secrets-manager-gs 2>&1)

check_error "$CREATE_SECRET_OUTPUT" "create-secret"
echo "$CREATE_SECRET_OUTPUT"

# Get AWS account ID
echo "Getting AWS account ID..."
ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text 2>&1)
check_error "$ACCOUNT_ID" "get-caller-identity"
echo "Account ID: $ACCOUNT_ID"

# Get secret ARN for precise resource policy
echo "Getting secret ARN..."
SECRET_ARN=$(aws secretsmanager describe-secret \
	--secret-id "$SECRET_NAME" \
	--query 'ARN' \
	--output text 2>&1)
check_error "$SECRET_ARN" "describe-secret"

# Add resource policy to the secret with least privilege
echo "Adding resource policy to secret..."
RESOURCE_POLICY=$(cat <<EOF
{
	"Version":"2012-10-17",
	"Statement": [
		{
			"Sid": "AllowRuntimeRoleReadSecret",
			"Effect": "Allow",
			"Principal": {
				"AWS": "arn:aws:iam::$ACCOUNT_ID:role/$RUNTIME_ROLE_NAME"
			},
			"Action": "secretsmanager:GetSecretValue",
			"Resource": "$SECRET_ARN",
			"Condition": {
				"StringEquals": {
					"secretsmanager:VersionStage": "AWSCURRENT"
				}
			}
		}
	]
}
EOF
)

PUT_POLICY_OUTPUT=$(aws secretsmanager put-resource-policy \
	--secret-id "$SECRET_NAME" \
	--resource-policy "$RESOURCE_POLICY" \
	--block-public-policy 2>&1)

check_error "$PUT_POLICY_OUTPUT" "put-resource-policy"
echo "Resource policy added successfully"

# Enable rotation policy recommendation
echo "Enabling secret metadata tags for rotation tracking..."
aws secretsmanager tag-resource \
	--secret-id "$SECRET_NAME" \
	--tags Key=Purpose,Value=Tutorial Key=AutoRotation,Value=Recommended 2>/dev/null || true

# Step 3: Demonstrate retrieving the secret
echo "Retrieving the secret value (for demonstration purposes)..."
GET_SECRET_OUTPUT=$(aws secretsmanager get-secret-value \
	--secret-id "$SECRET_NAME" 2>&1)

check_error "$GET_SECRET_OUTPUT" "get-secret-value"
echo "Secret retrieved successfully. Secret metadata:"
echo "$GET_SECRET_OUTPUT" | jq '{ARN: .ARN, Name: .Name, LastUpdatedDate: .LastUpdatedDate, VersionIdsToStages: .VersionIdsToStages}' 2>/dev/null || echo "Secret metadata retrieved (jq not available)"

# Step 4: Update the secret with new values
echo "Updating the secret with new values..."
UPDATE_SECRET_VALUE=$(python3 -c "import json; print(json.dumps({'ClientID':'my_new_client_id','ClientSecret':__import__('secrets').token_urlsafe(32)}))")

UPDATE_SECRET_OUTPUT=$(aws secretsmanager update-secret \
	--secret-id "$SECRET_NAME" \
	--secret-string "$UPDATE_SECRET_VALUE" 2>&1)

check_error "$UPDATE_SECRET_OUTPUT" "update-secret"
echo "Secret updated successfully"

# Step 5: Verify the updated secret
echo "Verifying the updated secret..."
VERIFY_SECRET_OUTPUT=$(aws secretsmanager get-secret-value \
	--secret-id "$SECRET_NAME" 2>&1)

check_error "$VERIFY_SECRET_OUTPUT" "get-secret-value for verification"
echo "Updated secret retrieved successfully. Secret metadata:"
echo "$VERIFY_SECRET_OUTPUT" | jq '{ARN: .ARN, Name: .Name, LastUpdatedDate: .LastUpdatedDate, VersionIdsToStages: .VersionIdsToStages}' 2>/dev/null || echo "Secret metadata retrieved (jq not available)"

# Step 6: Display rotation recommendations
echo ""
echo "Rotation Configuration Recommendations:"
echo "========================================"
DESCRIBE_OUTPUT=$(aws secretsmanager describe-secret --secret-id "$SECRET_NAME" 2>&1)
if echo "$DESCRIBE_OUTPUT" | grep -q "RotationRules"; then
	echo "Current rotation configuration:"
	echo "$DESCRIBE_OUTPUT" | jq '.RotationRules' 2>/dev/null || echo "Rotation rules available"
else
	echo "No automatic rotation configured. Consider enabling rotation with:"
	echo "aws secretsmanager rotate-secret --secret-id $SECRET_NAME --rotation-lambda-arn arn:aws:lambda:REGION:ACCOUNT:function:FUNCTION_NAME --rotation-rules AutomaticallyAfterDays=30"
fi

echo ""
echo "======================================================"
echo "Tutorial completed successfully!"
echo ""
echo "Summary of what we did:"
echo "1. Created IAM roles for managing and retrieving secrets"
echo "2. Created a secret in AWS Secrets Manager with secure generation"
echo "3. Added a least-privilege resource policy to control access to the secret"
echo "4. Retrieved the secret value (simulating application access)"
echo "5. Updated the secret with cryptographically secure values"
echo "6. Verified the updated secret"
echo ""
echo "Security best practices applied:"
echo "- Used cryptographically secure random ID generation"
echo "- Applied least-privilege resource policies with version stages"
echo "- Tagged resources for rotation tracking"
echo "- Blocked public access to secrets"
echo "- Used ARN-specific permissions instead of wildcards"
echo ""
echo "Next steps you might want to consider:"
echo "- Enable automatic secret rotation with AWS Lambda"
echo "- Implement secret caching in your application"
echo "- Set up CloudTrail logging for secret access auditing"
echo "- Use AWS CodeGuru Reviewer to find hardcoded secrets in your code"
echo "- For multi-region applications, replicate your secrets across regions"
echo "- Configure VPC endpoints for private access to Secrets Manager"
echo ""

echo "Script completed at $(date)"
exit 0

API 상세는 AWS CLI Command Reference의 다음 항목을 참고해요.

  • AttachRolePolicy, CreateRole, CreateSecret, DeleteRole, DeleteSecret, DetachRolePolicy, GetCallerIdentity, GetSecretValue, PutResourcePolicy, UpdateSecret

더 알아보기 (Learn more)