Next.js 에서 AWS Secret Manager 활용하기

Ryan·2024년 4월 5일
1

1. 개요

현재 진행 중인 팀 프로젝트에서 AWS 활용을 위한 액세스 키, 소셜 로그인을 위한 액세스 키 등 보안이 필요한 키들을 관리하여야 했습니다.
다만, 각 파트 별로 필요한 키들이 달랐고 저장 방식 또한 달랐기에 Notion에 키를 작성해두고 로컬에 가져와서 각자의 방식으로 관리하는 불편함이 있었습니다.

이 글에서는 불편함을 해결하기 위해 AWS Secret Manager를 도입하여 해결한 경험에 대해 소개하려고 합니다.

사용 환경

  • NEXT.JS 14 APP ROUTER
  • AWS SDK FOR JAVASCRIPT V3

2. 설정

2.1. AWS CLI 용 액세스 키 설정

사용자 > 보안자격증명 > 액세스 키 > 액세스 키 만들기 로 접근하여 AWS CLI용 액세스 키를 생성합니다.

2.2. AWS CLI 설치

AWS CLI 설치 페이지에 들어가서 각자 환경에 맞는 CLI를 설치 해줍니다.

2.3. AWS 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"
}

3. 코드활용

3.1. 시행착오

// 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를 활용한 임시 인증
  • 서버리스 백엔드 사용
  • 직접 인증 코드를 환경 변수등을 사용해 작성

등의 방법이 있습니다.
만약 백엔드를 활용할 수 없는 상황이라면 Cognito에 대한 활용 방법을 고려해보는게 좋은 해결책이 될 수 있습니다.

3.2. API 라우트 활용

저는 Next.js 를 활용 중이기 때문에, API 라우트를 구성하여 서버리스 함수로 처리하였습니다.

3.2.1. 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);
  }
}

3.2.2. API 서비스 설정

저는 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');
  }
};

3.2.3. 각 키 값 가져오기


저는 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 };
};

4. 마무리

AWS Secret Manager로 마이그레이션 함으로써, .env 파일을 팀원끼리 공유하고 직접 로컬로 가져오던 번거로움을 없애 생산성을 향상시킬 수 있었습니다.

다만, 초기 세팅의 번거로움이 있으며 도커 혹은 타 인스턴트로 배포할 경우 따로 AWS 인증 정보를 세팅해 주어야 한다는 단점이 있습니다.

새로운 기술을 도입할 때는 항상 트레이드 오프를 고려해야 합니다.
뭐가 더 이득인지 계산해주는 AI는 없나요?

profile
창의적인 사용자 경험과 활발한 협업을 좋아하는 프론트엔드 지망생 입니다.

0개의 댓글

관련 채용 정보