AppRole 인증 모범 사례

AppRole 인증 모범 사례

Vault 사용의 핵심은 인증과 인가예요. Vault가 이것들을 클라이언트에 어떻게 제공하는지 이해하는 것이 Vault를 구성하고 관리하는 방법을 이해하는 열쇠예요.

  • Vault는 인증 방식을 사용해 클라이언트에 인증을 제공해요.
  • Vault는 정책을 사용해 클라이언트에 인가를 제공해요.

Vault는 여러 내부 및 외부 인증 방식을 제공해요. 외부 방식은 AWS, LDAP, GitHub 등과 같은 신뢰할 수 있는 제3자 인증자(trusted third-party authenticator) 라고 불러요. 신뢰할 수 있는 제3자 인증자가 제공되지 않는 상황에서는 Vault의 대안 접근 방식인 AppRole이 있어요. 신뢰할 수 있는 제3자 인증자로 다른 플랫폼 인증 방식이 가능하다면 AppRole 대신 그것을 사용하는 것이 모범 사례예요.

이 가이드는 Vault의 두 가지 근본 원칙에 크게 의존해요: 신원의 폭발 반경(blast-radius)과 인증 지속 시간을 모두 제한하는 것이에요.

출처: 문서

본문

신원의 폭발 반경 (Blast-radius of an identity)

Vault는 아이덴티티 기반 시크릿 관리 솔루션이며, 시크릿에 대한 접근은 클라이언트의 알려지고 검증된 신원을 기반으로 해요. Vault에 인증하는 신원이 식별 가능하고 사용자가 사용하는 시크릿에만 접근할 수 있는 것이 중요해요. 시크릿은 Vault와 시크릿 최종 사용자 사이에서 프록시되어서는 안 되고, 클라이언트는 자신이 최종 사용자가 아닌 시크릿에 접근할 수 없어야 해요.

인증 지속 시간 (Duration of authentication)

Vault가 엔티티의 신원을 검증하면, Vault는 해당 엔티티에 토큰을 제공해요. 클라이언트는 이후 Vault와의 모든 상호작용에서 이 토큰을 사용해 인증함을 증명하므로, 이 토큰은 안전하게 처리되고 제한된 수명을 가져야 해요. 토큰은 인가하는 시크릿에 대한 접근이 필요한 동안만 살아 있어야 해요.

용어 용어집 (Glossary of terms)

  • 인증 (Authentication) — 신원을 확인하는 과정. AuthN으로 줄여 부르기도 해요.
  • 인가 (Authorization) — 엔티티가 무엇에 어떤 수준으로 접근할 수 있는지 검증하는 과정. AuthZ로 줄여 부르기도 해요.
  • RoleID — Vault에 인증할 역할의 반-비밀 식별자예요. 인증 쌍의 사용자 이름 부분이라고 생각하면 돼요.
  • SecretID — Vault에 인증할 역할의 비밀 식별자예요. 인증 쌍의 비밀번호 부분이라고 생각하면 돼요.
  • AppRole 역할 — 인증의 인가 및 사용 파라미터를 포함하는 Vault에 구성된 역할이에요.

AppRole 인증 방식이란? (What is AppRole auth method?)

AppRole 인증 방식은 Vault에 대한 머신 인증을 위한 것이에요. AppRole은 유연하도록 설계되었기 때문에 구성 방법이 많아요. 다른 Vault 인증 방식과 달리 보안의 부담은 신뢰할 수 있는 제3자가 아니라 구성자에게 있어요.

AppRole은 신뢰할 수 있는 제3자 인증자가 아니라 신뢰할 수 있는 브로커(trusted broker) 방식이에요. 차이점은 AppRole 인증에서 신뢰의 부담은 클라이언트와 Vault 사이의 인증을 브로커링하는 안전하게 관리되는 브로커 시스템에 있다는 거예요.

이 보안의 핵심 원칙은 Vault로의 인증을 브로커링하는 동안 RoleID와 SecretID가 시크릿을 소비해야 하는 최종 사용자 시스템에서만 함께 있다는 것이에요.

AppRole 인증에는 세 명의 참여자가 있어요:

  • Vault — Vault 서비스
  • 브로커 (The broker) — 인증을 브로커링하는 신뢰되고 보안된 시스템
  • 시크릿 소비자 (The secret consumer) — Vault의 시크릿의 최종 소비자

플랫폼 크레덴셜 전달 방법 (Platform credential delivery method)

대상 클라이언트 외의 어떤 단일 시스템도 완전한 크레덴셜 세트(RoleID와 SecretID)를 얻지 못하게 하려면, 권장 구현은 그 값을 두 개의 다른 채널로 별도 전달하는 것이에요. 이를 통해 각 신뢰할 수 있는 오케스트레이터에 RoleID 또는 SecretID 중 하나에만 접근하는 좁은 범위의 토큰을 제공할 수 있어요.

RoleID 전달 모범 사례

RoleID는 다른 크레덴셜이 평가되는 AppRole을 선택하는 식별자예요. 애플리케이션의 사용자 이름이라고 생각하면 돼요. 따라서 RoleID는 비밀 값이 아니에요. 특정 역할 구성을 식별하는 정적 UUID예요. 일반적으로 애플리케이션마다 역할을 만들어 각 애플리케이션이 고유한 RoleID를 갖도록 해요.

비밀이 아니므로 RoleID 값을 텍스트 파일이나 환경 변수로 머신 이미지나 컨테이너에 임베드할 수 있어요.

예:

  • Packer로 RoleID를 환경 변수로 저장한 이미지를 빌드한다.
  • Terraform으로 RoleID가 임베드된 머신을 프로비저닝한다.

이 값을 전달하는 다양한 패턴이 있어요.

머신이나 컨테이너에서 실행되는 애플리케이션은 파일이나 환경 변수에서 RoleID를 읽어 Vault로 인증해요.

정책 요구 사항 (Policy requirement)

Vault에서 RoleID를 읽으려면 적절한 정책이 필요해요. 예를 들어 "jenkins"라는 역할의 RoleID를 얻으려면 정책은 아래와 같아야 해요.

# Grant 'read' permission on the 'auth/approle/role/<role_name>/role-id' path
path "auth/approle/role/jenkins/role-id" {
   capabilities = [ "read" ]
}

SecretID 전달 모범 사례

SecretID는 기본적으로 모든 로그인에 필요한 크레덴셜이며 항상 비밀로 유지되도록 의도된 크레덴셜이에요. RoleID가 사용자 이름과 유사한 반면 SecretID는 해당 RoleID의 비밀번호와 같아요.

SecretID는 비밀이고 보안되어 의도된 수신자만 읽을 수 있어야 하므로 분배할 때 두 가지 추가 고려 사항이 있어요.

  1. 바인딩 CIDR (Binding CIDRs)
  2. AppRole 응답 래핑 (AppRole response wrapping)
바인딩 CIDR (Binding CIDRs)

AppRole을 정의할 때 secretid_bound_cidrs 파라미터를 사용해 이 역할에 대해 로그인 작업을 수행할 수 있는 IP 주소 블록을 지정할 수 있어요. token_bound_cidrs로 토큰별 IP 범위를 추가로 제한할 수 있어요.

예시:

$ vault write auth/approle/role/jenkins \
      secret_id_bound_cidrs="0.0.0.0/0","127.0.0.1/32" \
      secret_id_ttl=60m \
      secret_id_num_uses=5 \
      enable_local_secret_ids=false \
      token_bound_cidrs="0.0.0.0/0","127.0.0.1/32" \
      token_num_uses=10 \
      token_ttl=1h \
      token_max_ttl=3h \
      token_type=default \
      period="" \
      policies="default","test"

CIDR 고려 사항: token_bound_cidrs 파라미터로 설정할 수 있는 CIDR 블록 수에는 하드 제한이 없지만 제한 요소가 있어요. 하나는 Vault가 IP를 제공된 목록과 비교하는 데 걸리는 시간이고, 다른 하나는 목록을 만들 때 HTTP의 최대 요청 크기예요.

AppRole 응답 래핑 (AppRole response wrapping)

SecretID의 기밀성, 무결성, 부인 방지를 보장하려면 SecretID 생성 시 -wrap-ttl 플래그를 사용할 수 있어요. 평문으로 SecretID를 제공하는 대신 사용 횟수가 1인 새 토큰의 Cubbyhole에 넣어요. 애플리케이션이 SecretID를 읽으려 할 때 이 애플리케이션만 읽을 수 있음을 보장할 수 있어요.

예시: 다음 CLI 명령은 "jenkins"라는 역할의 SecretID를 검색해요. 생성된 SecretID는 60초 동안 풀기(unwrap) 유효한 토큰에 래핑돼요.

$ vault write -wrap-ttl=60s -force auth/approle/role/jenkins/secret-id

Key                              Value
---                              -----
wrapping_token:                  s.yzbznr9NlZNzsgEtz3SI56pX
wrapping_accessor:               Smi4CO0Sdhn8FJvL8XvOT30y
wrapping_token_ttl:              1m
wrapping_token_creation_time:    2021-06-07 20:02:01.019838 -0700 PDT
wrapping_token_creation_path:    auth/approle/role/jenkins/secret-id

마지막으로 감사 로그를 모니터링해 SecretID의 읽기 시도를 확인할 수 있어요. 애플리케이션이 SecretID를 읽으려 할 때 Vault가 사용 한도 오류를 던지면 다른 누군가가 SecretID를 읽었다는 것을 알고 이에 대해 경고할 수 있어요. 감사 로그는 SecretID 읽기 시도가 어디서 시작되었는지 나타내요.

정책 요구 사항 (Policy requirement)

Vault에서 SecretID를 읽으려면 적절한 정책이 필요해요. 예를 들어 "jenkins"라는 역할의 SecretID를 얻으려면 정책은 아래와 같아야 해요.

# Grant 'update' permission on the 'auth/approle/role/<role_name>/secret-id' path
path "auth/approle/role/jenkins/secret-id" {
   capabilities = [ "update" ]
}

토큰 수명 고려 사항 (Token lifetime considerations)

토큰은 클라이언트 측에서 유지되어야 하고 만료 시 갱신될 수 있어요. 수명이 짧은 워크플로에서는 전통적으로 토큰이 평균 배포 시간과 일치하는 수명으로 생성되고 만료되도록 두며, 각 배포마다 새 토큰을 확보해 왔어요.

긴 토큰 time-to-live(TTL)는 수백만 개의 AppRole 리스를 정리할 때 메모리 부족을 유발할 수 있어요. 이를 피하려면 AppRole 토큰의 TTL을 줄이고 가능하면 토큰 갱신을 구현하는 것을 권장해요. Vault 서버의 메모리를 늘릴 수도 있지만 장기적인 해결책은 아니에요.

일반적으로 어떤 인증 방식에서든 애플리케이션은 매번 새 인증 대신 같은 Vault 토큰을 반복해서 시크릿을 가져오는 것이 선호돼요. 인증은 비용이 많이 드는 작업이고 결과적으로 Vault가 추적해야 하는 토큰이 생겨요. 초당 수천 건처럼 높은 인증 처리량이 예상된다면 메모리에서 발급되어 스토리지를 소비하지 않는 배치 토큰 사용을 권장해요.

Vault Agent

클라이언트 호스트에서 Vault Agent를 실행하고 에이전트가 토큰의 수명 주기를 관리하게 하는 것을 고려하세요. Vault Agent는 클라이언트 애플리케이션이 사용하는 토큰 수를 줄여요. 또한 필요시 Vault API를 구현해 인증하고 토큰 TTL을 갱신할 필요를 없애요.

Vault Agent에 대해 더 알아보려면 다음 튜토리얼을 읽어보세요.

Jenkins CI/CD

Jenkins를 CI 도구로 사용할 때 Jenkins 자체에 신원이 필요해요. 그러나 Jenkins가 Vault에 로그인해 워크플로를 통해 애플리케이션에 클라이언트 토큰을 전달해서는 안 돼요. Jenkins는 애플리케이션에 자체 신원을 주어 애플리케이션이 자체 시크릿을 얻도록 해야 해요. 모범 사례는 Jenkins와 함께 Vault Agent를 최대한 많이 사용해 Vault 토큰이 Jenkins에 의해 관리되지 않도록 하는 것이에요. 매일 아침 또는 실행 전마다 x회 사용에 대해 SecretID를 전달할 수 있어요. Vault Agent가 Vault로 인증해 Jenkins를 위한 토큰을 얻게 하세요. 그러면 Jenkins는 Vault에 대해 x회 작업에 그 토큰을 사용해요.

애플리케이션에 대한 AppRole의 핵심 이점은 플랫폼 간 애플리케이션 마이그레이션을 더 쉽게 한다는 점이에요.

애플리케이션에 AppRole을 사용할 때 모범 사례는 Jenkins에서 RoleID를 숨기되 Jenkins가 애플리케이션에 래핑된 SecretID를 전달하도록 허용하는 것이에요.

사용 워크플로 (Usage workflow)

Jenkins는 Vault에 저장된 'secret'으로 분류된 일부 데이터가 필요한 작업을 실행해야 해요. 작업을 단기간 실행되는 컨테이너 러너에서 실행하는 마스터와 워커 노드가 있어요.

과정은 다음과 같아요:

  1. Jenkins 워커가 Vault로 인증한다.
  2. Vault가 토큰을 반환한다.
  3. 워커가 토큰을 사용해 시작할 작업의 역할에 대한 래핑된 SecretID를 검색한다.
  4. Vault가 래핑된 SecretID를 반환한다.
  5. 워커가 작업 러너를 시작하고 래핑된 SecretID를 작업에 변수로 전달한다.
  6. 러너 컨테이너가 SecretID의 풀기(unwrap)를 요청한다.
  7. Vault가 SecretID를 반환한다.
  8. 러너가 RoleID와 SecretID를 사용해 Vault로 인증한다.
  9. Vault가 필요한 시크릿의 읽기를 허용하는 정책이 있는 토큰을 반환한다.
  10. 러너가 토큰을 사용해 Vault에서 시크릿을 얻는다.

과정의 더 복잡한 단계에 대한 자세한 내용은 아래에 있어요.

시크릿 래핑: 시크릿 래핑에 익숙하지 않다면 응답 래핑 문서를 참고하세요.

CI 워커가 Vault로 인증

CI 워커는 시작할 작업의 AppRole에 대한 래핑된 SecretID를 검색하려면 Vault로 인증해야 해요.

워커가 플랫폼 인증 방식을 사용할 수 있다면 워커는 그것을 사용해야 해요. 그렇지 않으면 워커를 다른 방식으로 Vault에 사전 인증하는 것만이 선택지예요.

Vault가 토큰을 반환

워커의 Vault 토큰은 제한된 범위여야 하고 래핑된 SecretID만 검색해야 해요. 이 때문에 워커는 장수명 Vault 토큰으로 사전 시드되거나 하드코딩된 RoleID와 SecretID를 사용할 수 있어요. 이는 사소한 위험만 제시하니까요.

워커가 가져야 할 정책은:

path "auth/approle/role/+/secret*" {
  capabilities = [ "create", "read", "update" ]
  min_wrapping_ttl = "100s"
  max_wrapping_ttl = "300s"
}
워커가 토큰을 사용해 래핑된 SecretID 검색

CI 워커는 이제 래핑된 SecretID를 검색할 수 있어야 해요. 이 명령은 아래와 같은 형태예요.

$ vault write -wrap-ttl=120s -f auth/approle/role/my-role/secret-id

워커는 시작하는 작업의 역할만 알면 되고 RoleID는 알 필요가 없음을 주목하세요. 위 예시에서는 my-role이에요.

워커가 작업 러너를 시작하고 래핑된 SecretID 전달

이것은 래핑된 토큰을 환경 변수로 전달해 달성할 수 있어요. Jenkins에서 이를 수행하는 예시:

environment {
   WRAPPED_SID = """$s{sh(
                    returnStdout: true,
                    Script: ‘curl --header "X-Vault-Token: $VAULT_TOKEN"
       --header "X-Vault-Namespace: ${PROJ_NAME}_namespace"
       --header "X-Vault-Wrap-Ttl: 300s"
         $VAULT_ADDR/v1/auth/approle/role/$JOB_NAME/secret-id’
         | jq -r '.wrap_info.token'
                )}
"""
  }
러너가 RoleID와 SecretID를 사용해 Vault로 인증

러너는 Vault로 인증하고 정확히 필요한 시크릿을 읽는 정책만 받아요. 다른 것은 얻을 수 없어요. 예시 정책:

path "kv/my-role_secrets/*" {
  capabilities = [ "read" ]
}
구현 세부 사항 (Implementation specifics)

추가 보안 조치로 앱에 필요한 역할을 만들 때 다음을 염두에 두세요:

  • secret_id_bound_cidrs (array: []) — 설정하면 로그인 작업을 수행할 수 있는 IP 주소 블록을 지정하는 쉼표 구분 문자열 또는 CIDR 블록 목록.
  • secret_id_num_uses (integer: 0) — 특정 SecretID가 이 AppRole에서 토큰을 가져오는 데 사용될 수 있는 횟수. 이후 SecretID가 만료돼요. 0 값은 무제한 사용을 허용해요.

권장 사항: 최상의 보안을 위해 secret_id_num_uses를 1회 사용으로 설정하세요. 또한 secret_id_bound_cidrs를 변경해 연결 장치의 소스 IP 범위를 제한하는 것도 고려하세요.

안티 패턴 (Anti-patterns)

Vault의 AppRole 인증 방식을 사용할 때 다음 안티 패턴을 피하는 것이 좋아요.

CI 워커가 시크릿을 검색

CI 워커가 Vault로 인증해 작업의 시크릿을 검색해 러너에 전달할 수도 있어요. 그러나 이것은 위에서 나열한 두 모범 사례 중 첫 번째를 위반해요.

CI 워커는 아마 많은 유형의 작업을 실행해야 하고, 그 중 다수는 시크릿을 필요로 해요. 이 방법을 사용하면 워커는 소비자가 아닌 많은 시크릿을 검색하는 인가(정책)를 가져야 해요. 또한 단일 시크릿이 손상되면 그에 신원을 연결하고 그 신원에 대해 브레이크 글라스 절차를 시작할 방법이 없어요. 따라서 모든 시크릿이 손상된 것으로 간주해야 해요.

CI 워커가 RoleID와 SecretID를 러너에 전달

워커는 RoleID와 SecretID를 검색하고 둘 다 러너에 전달하도록 Vault에 인가될 수 있어요. 이는 워커가 모든 시크릿을 검색하는 Vault 인가를 갖는 것을 막지만, RoleID와 SecretID 둘 다를 가지므로 그 능력이 있어요. 이는 모범 사례에 반대예요.

CI 워커가 Vault 토큰을 러너에 전달

워커는 파이프라인을 위한 시크릿을 검색하는 인가를 가진 자식 토큰을 생성하도록 Vault에 인가될 수 있어요.

다시 말해 이것은 워커가 시크릿을 검색하는 Vault 인가를 피하지만, 워커는 인가를 가진 자식 토큰에 접근할 수 있으므로 모범 사례에 반대예요.

보안 고려 사항 (Security considerations)

어떤 신뢰할 수 있는 브로커 상황에서도 브로커(이 경우 Jenkins 워커)는 보안되고 중요 시스템으로 취급되어야 해요. 즉 사용자는 브로커에 최소 접근을 가져야 하고 접근은 면밀히 모니터링되고 감사되어야 해요.

또한 Vault 감사 로그가 타임스탬프 이벤트를 제공하므로 두 이벤트에 대한 경고로 전체 과정을 모니터링하세요:

  • AppRole에 대해 래핑된 SecretID가 요청되었는데 실행 중인 Jenkins 작업이 없을 때
  • Jenkins 에이전트가 토큰을 풀려 할 때 토큰이 이미 사용되어 Vault가 거부할 때

두 경우 모두 신뢰할 수 있는 브로커 워크플로가 손상되었을 가능성을 보여주며 이벤트를 조사해야 해요.

참고 자료 (Reference materials)

더 알아보기 (Learn more)

  • AppRole 인증 방식 전체 API는 AppRole API 문서를 참고하세요.
  • AppRole 인증 방식을 처음 시작하려면 AppRole 인증 문서를 참고하세요.