최신글

AWS CodeBuild에서 Github 토큰도 없이 어떻게 private repository를 clone할까?

반응형

이 글에서는 Codebuild가 Github access token없이 GitHub private repository에 접근하는 방법을 설명합니다.

 

Codebuild Connection

AWS CodeBuild에서 GitHub private repository를 source로 사용하려면, CodeBuild가 그 repository를 읽을 수 있어야 합니다.

방법은 크게 두 가지로 나뉩니다. 하나는 GitHub Personal Access Token(PAT)처럼 오래 유지되는 자격증명을 CodeBuild에 등록하는 방식이고, 다른 하나는 GitHub App connection을 만들어 AWS CodeConnections가 repository 접근을 중개하게 하는 방식입니다.

 

Github App connection은 Codebuild Connection메뉴에서 생성할 수 있습니다.

 

 

CodeBuild는 GitHub 토큰을 어디서 받아오나요?

핵심부터 말하면, AWS CodeConnections는 PAT를 저장해두고 쓰는 게 아닙니다. GitHub App의 installation access token(단기 토큰)을 그때그때 발급받아 씁니다.

 

제가 정리한 흐름은 이렇습니다.

  1. connection을 만들 때 GitHub에 AWS Connector for GitHub 앱을 설치(install)합니다. 이때 제 계정/org에 installation이 하나 생기고, AWS는 그 installation ID와 connection을 묶어둡니다.
  2. 이 GitHub App에는 private key가 있는데, 이건 제가 아니라 AWS(CodeConnections 서비스)가 보관합니다. 그래서 저는 토큰이나 키를 손에 쥐지 않습니다.
  3. CodeBuild가 repository에 접근해야 할 때, CodeConnections가 그 private key로 JWT를 서명해서 앱 자체를 인증하고, GitHub에 POST /app/installations/{id}/access_tokens를 호출합니다.
  4. GitHub가 installation access token을 돌려줍니다. 이게 임시 자격증명입니다. TTL이 약 1시간이고, 앱이 설치된 특정 repository로만 scope가 제한됩니다.
  5. CodeBuild가 이 토큰을 git credential로 써서 clone합니다.

 

이 흐름을 이해하려면 먼저 한 가지를 짚어야 합니다. 키를 AWS가 들고 있다는 GitHub App은 대체 무엇일까요?

 

그래서 GitHub App이 뭔가요?

GitHub App의 핵심은 “앱 자체가 하나의 독립된 행위자(actor)” 라는 점입니다. 사람 계정에 묶인 PAT와 달리, 앱이 자기 신원으로 행동합니다. 그래서 사람 입력이 필요 없는 자동화 워크플로에 잘 맞습니다. 특징을 정리하면 이렇습니다.

  • 앱을 org/계정에 설치(install)하면, 그 설치 단위마다 installation ID가 생깁니다.
  • 권한을 repository 단위, 작업 단위(코드 읽기/쓰기, webhook 등)로 잘게 제한할 수 있습니다.
  • 장기 토큰을 들고 다니지 않고, 그때그때 단기 토큰을 발급받습니다.

 

CodeConnections가 쓰는 AWS Connector for GitHub가 바로 이 GitHub App이고, GitHub Marketplace에서 게시자 신원이 검증된 앱입니다. 즉 제가 connection을 만들 때 한 일은, 사실 AWS가 만들어둔 GitHub App을 제 GitHub 계정에 설치해준 것이었습니다.

 

private key를 AWS가 보관한다는 건 어떻게 알 수 있나요?

솔직하게 말하면, AWS가 “우리가 private key를 보관한다”고 명시한 문장은 저도 못 찾았습니다. 이건 제가 GitHub App의 동작 원리에서 역추론한 부분이라, 단정이 아니라 “구조상 그렇게 될 수밖에 없다”가 맞는 표현입니다. 근거는 두 가지였습니다.

 

첫째, GitHub 공식 문서상 installation access token을 만들려면 반드시 JWT를 RS256으로 서명해야 하고, 서명에는 앱의 private key가 필요합니다. 둘째, CodeConnections 설치 흐름에서 고객인 저는 .pem private key를 받지 않습니다. 앱을 install하고 connection ARN을 받는 게 전부였고, 어디에도 키를 다운로드하는 단계가 없었습니다.

이 둘을 합치면 결론이 나옵니다. JWT 서명은 제 손이 아니라 앱 소유자(AWS) 쪽 백엔드에서 일어날 수밖에 없습니다. GitHub App을 만들면 GitHub가 키 쌍을 생성하고 앱 소유자에게 private key를 .pem으로 딱 한 번 내려주는데, AWS Connector for GitHub의 소유자는 AWS이므로 그 키는 AWS가 자기 보안 인프라(보통 KMS/HSM)에 보관합니다. 앱을 설치한 저는 그 키를 절대 볼 수 없고, 제가 가진 통제권은 “어느 repository에 접근을 허용할지”뿐이었습니다.

 

Github app은 어떻게 Github에게 인증을 받나요?

Github app은 자기 private key로 JWT를 직접 만들어 서명합니다. 그 JWT를 GitHub에 보내서 “나 이 앱 맞다”고 증명하면, GitHub가 보관 중인 public key로 서명을 검증합니다.

왜 안전한지 정리하면, private key는 백엔드 밖으로 절대 안 나가고 네트워크에는 서명된 JWT만 오갑니다. 그리고 이 JWT는 수명이 최대 10분으로 아주 짧고, JWT 자체로는 repository에 접근하지도 못합니다. JWT는 “앱 인증” 용도일 뿐이고, 이걸로 다시 repository scope가 걸린 installation access token(약 1시간)을 받아야 비로소 clone이 됩니다.

 

AWS 쪽에서 토큰을 막 받아올 수 있나요?

아닙니다. 여기에 제가 놓치기 쉬웠던 한 겹이 더 있었습니다. “CodeBuild가 이 connection 토큰을 받을 자격이 있나”를 AWS IAM이 한 번 더 통제합니다.

CodeBuild project는 service role로 실행되는데, 이 role에 connection 사용 권한이 없으면 임시 GitHub 토큰을 받는 단계까지 가지도 못합니다. 즉 GitHub가 토큰을 발급하기 전에 AWS IAM 인가가 한 겹 먼저 있는 구조입니다. 저는 아래처럼 connection ARN으로 범위를 좁힌 권한만 줬습니다.

data "aws_iam_policy_document" "codebuild_connection" {
  statement {
    effect = "Allow"

    actions = [
      "codeconnections:GetConnection",
      "codeconnections:GetConnectionToken",
      "codeconnections:UseConnection",
    ]

    resources = [var.github_connection_arn]
  }
}

 

CodeBuild project source에는 clone 대상 repository URL과 connection ARN을 함께 넣습니다. URL은 “무엇을 clone할지”, connection ARN은 “어떤 인증 경로로 접근할지”를 정하기 때문에, 둘 중 하나만 있어서는 private repository clone이 동작하지 않습니다.

resource "aws_codebuild_project" "github_app" {
  name         = var.project_name
  service_role = aws_iam_role.codebuild.arn

  source {
    type            = "GITHUB"
    location        = var.github_repository_url
    git_clone_depth = 1

    auth {
      type     = "CODECONNECTIONS"
      resource = var.github_connection_arn
    }
  }

  source_version = var.github_branch
}

 

그래서 connection 상태가 AVAILABLE이어도 GitHub App installation에 대상 repository가 빠져 있으면 clone이 실패하고, 반대로 repository URL이 맞아도 service role이 connection을 못 쓰면 GitHub source에 접근하지 못합니다. 인증 경로가 GitHub 쪽과 AWS 쪽에서 각각 한 번씩 걸러지는 셈입니다.

 

실습

실습은 테라폼으로 진행했고 저의 github에 정리했습니다.

 

정리하면

Codebuild connection의 임시 자격증명의 출처는 GitHub App private key(AWS 보관) → JWT 서명 → GitHub가 발급한 1시간짜리 installation token이고, 그걸 받을 자격은 CodeBuild의 IAM role이 결정합니다.

PAT 방식과의 차이가 여기서 갈립니다. PAT는 장기 토큰을 사람이 직접 들고 있어야 하지만, GitHub App connection은 단기 토큰입니다. 그래서 토큰 유출 위험과 권한 범위가 훨씬 작아집니다. 새 CodeBuild-GitHub 연동을 한다면 저는 GitHub App connection을 먼저 검토할 것 같습니다.

 

참고자료

반응형