현재 진행 중인 팀 프로젝트에서 AWS 활용을 위한 액세스 키, 소셜 로그인을 위한 액세스 키 등 보안이 필요한 키들을 관리하여야 했습니다.
다만, 각 파트 별로 필요한 키들이 달랐고 저장 방식 또한 달랐기에 Notion에 키를 작성해두고 로컬에 가져와서 각자의 방식으로 관리하는 불편함이 있었습니다.
이 글에서는 불편함을 해결하기 위해 AWS Secret Manager를 도입하여 해결한 경험에 대해 소개하려고 합니다.
사용 환경
사용자 > 보안자격증명 > 액세스 키 > 액세스 키 만들기 로 접근하여 AWS CLI용 액세스 키를 생성합니다.
AWS CLI 설치 페이지에 들어가서 각자 환경에 맞는 CLI를 설치 해줍니다.
$ aws configure
AWS Access Key ID [None]: 아까_받은_액세스_키
AWS Secret Access Key [None]: 아까_받은_시크릿_키
설정 한 후에, 계정 연결이 잘 되었는지 체크해줍니다.
$ aws sts get-caller-identity
{
"UserId": "A.................",
"Account": "0...............",
"Arn": "arn:aws:iam::.........:user/user_name"
}
// BAD CODE
// src/app/_lib/AWSKeys.ts
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
const secret_name = 'YOUR_SECRET_NAME';
const client = new SecretsManagerClient({
region: 'ap-northeast-2',
});
export const getAWSKeys = async () => {
const response = await client.send(
new GetSecretValueCommand({
SecretId: secret_name,
VersionStage: 'AWSCURRENT',
}),
);
const secret = response.SecretString;
return secret;
};
클라이언트 사이드에서 AWS 서비스를 호출하려고 시도했던 코드 입니다.
위와 같은 코드로 페이지에서 호출을 할 경우
Credential is missing 이라는 에러를 만나게 됩니다.
브라우저 환경에서 AWS SDK를 직접 사용하는 경우, 보안상의 이유로 브라우저는 로컬 파일 시스템에 접근할 수 없습니다.
따라서 AWS CLI를 통해 설정한 로컬의 ~/.aws/credentials 파일에 저장된 인증 정보에 접근할 수 없습니다.
이를 해결하기 위한 방법으로는
등의 방법이 있습니다.
만약 백엔드를 활용할 수 없는 상황이라면 Cognito에 대한 활용 방법을 고려해보는게 좋은 해결책이 될 수 있습니다.
저는 Next.js 를 활용 중이기 때문에, API 라우트를 구성하여 서버리스 함수로 처리하였습니다.
const secrets = response.SecretString;
const keys = JSON.parse(secrets || '{}');
SecretString 값이 String으로 들어오기 때문에, {KEY_NAME: KEY_VALUE}
형식으로 관리해주기 위해 Object 형태로 변환해줍니다.
// src/app/api/s3-keys/getSecrets/route.ts
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
import { NextResponse } from 'next/server';
export async function GET() {
const secret_name = 'YOUR_SECRET_NAME';
const client = new SecretsManagerClient({
region: 'ap-northeast-2',
});
try {
const response = await client.send(
new GetSecretValueCommand({
SecretId: secret_name,
VersionStage: 'AWSCURRENT',
}),
);
const secrets = response.SecretString;
const keys = JSON.parse(secrets || '{}');
return NextResponse.json(keys);
} catch (error) {
console.error(error);
}
}
저는 API를 요청하는 함수를 따로 관리하고 있기 때문에 설정해두었습니다.
// src/app/_api/auth.ts
export const getSecretKeys = async () => {
try {
const response = await axios.get(
'http://localhost:3000/api/s3-keys/getSecrets',
);
return response.data;
} catch (error) {
console.error('error: ', error);
throw new Error('Get AWS keys failed');
}
};
저는 AWS, KAKAO, GOOGLE에서 사용되는 키 들을 AWS Secret Manager에 설정해 두었습니다. AWS Secret Manager에 보안 암호 키를 요청할 경우, 따로 지정해서 빼올 수 있는 것이 아니라 한꺼번에 모든 키값을 반환해줍니다.
따라서, 제가 필요한 키만 사용할 수 있도록 함수를 나누어 주었습니다.
return data.YOUR_SECRET_KEY
// src/app/_lib/keys.ts
import { getSecretKeys } from '../_api/auth';
export const getGoogleKeys = async () => {
const data = await getSecretKeys();
return data.GOOGLE_CLIENT_ID;
};
export const getKakaoKeys = async () => {
const data = await getSecretKeys();
return data.KAKAO_JS_KEY;
};
export const getAWSAccessKeys = async () => {
const data = await getSecretKeys();
const accessKey = data.AWS_ACCESS_KEY;
const secretAccessKey = data.AWS_SECRET_KEY;
return { accessKey, secretAccessKey };
};
AWS Secret Manager로 마이그레이션 함으로써, .env 파일을 팀원끼리 공유하고 직접 로컬로 가져오던 번거로움을 없애 생산성을 향상시킬 수 있었습니다.
다만, 초기 세팅의 번거로움이 있으며 도커 혹은 타 인스턴트로 배포할 경우 따로 AWS 인증 정보를 세팅해 주어야 한다는 단점이 있습니다.
새로운 기술을 도입할 때는 항상 트레이드 오프를 고려해야 합니다.
뭐가 더 이득인지 계산해주는 AI는 없나요?