HashiCorp Vault 프로덕션 환경 배포 가이드 - (3) Policy/Auth Management

Juhwan Song·2025년 6월 25일
0
post-thumbnail

TL;DR

이번 포스팅에서는 Vault의 사용 권한 관리를 위한 개념, 방법 및 유의사항을 알아봅니다.

이 포스트를 다 읽고 나면 다음과 같은 지식을 습득할 수 있습니다.

  • LDAP AuthN Method 연동 방법
  • Vault 관리자 권한 부여
  • Vault Policy 개념
  • Identity와 Alias 관리
  • ACL Policy Best Practice
  • ACL Policy 구성 시 주의사항
  • AppRole AuthN 개념
  • CIDR로 AppRole 인증하기

LDAP AuthN Method 연동하기

Vault UI에 Root token으로 로그인 하면 화면 좌측에서 Access 메뉴를 확인할 수 있습니다.

Main Menu > Access > Authentication Methods 로 접근합니다.

Enable new methods 를 선택합니다.

Infra -> LDAP 를 선택합니다.

여기서 Path를 변경할 수 있지만, Vault CLI나 API를 사용할 때 mountpoint를 별도로 설정해 줘야 합니다. 여러 인증 소스를 사용하지 않는다면 기본값으로 두는 것이 좋습니다.

LDAP object 에서 Configure LDAP를 선택하여 구성 페이지로 진입합니다.

  • URL: LDAP 서버 주소 (콤마 구분 리스트)
  • Connection Timeout: 서버 접속 시의 타임아웃 (기본값: 30초, 설정: 5초)
  • Request Timeout: 인증 요청 시의 타임아웃 (기본값: 90초, 설정: 30초)
  • LDAP Options (LDAP 연결 관련 설정)

  • Issue StartTLS: 연결 수립 후 TLS로 전환
  • Insecure TLS: TLS 인증서 검증을 무시함 (안전하지 않으니 사용하지 말 것)
  • User Attribute: 인증 시 전달된 사용자 이름과 일치하는 속성 (기본값: CN, 설정: sAMAccountName)

  • User Principal (UPN) Domain: 사용자가 도메인 주소를 입력하여 로그인하는 것을 허용 (설정: 도메인 주소)

LDAP 쿼리를 위한 사용자 계정은 BindDN과 BindPass를 통해 구성합니다.

  • Name of Object to bind (binddn): 사용자 검색을 위해 사용할 계정
  • User DN: 사용자 검색을 시작할 DN
  • Bindpass: BindDN의 계정 패스워드

LDAP 연동에 사용되는 계정은 일반 사용자 계정이 아닌, 별도의 서비스 계정을 생성하고, 최소한의 권한을 부여해야 합니다.

최소 권한 서비스 사용자 생성 방법에 대한 설명은 다음 링크에서 확인할 수 있습니다.

Active Directory Service Account Creation

LDAP 사용자 등록

LDAP 연동 후, Vault에 LDAP로 로그인 하면 자동으로 그 사용자에 대한 Entity와 Alias 항목이 추가됩니다. 자동 생성된 Entity는 default policy를 할당받으며, 랜덤한 문자열의 이름으로 식별됩니다.

만약 자동 생성을 원하지 않는다면 관리자가 수동으로 계정 연동 정보를 미리 등록해야 하며, 따라서 온보딩 워크플로우에 Vault 계정 생성 및 권한 부여 작업을 통합하는 것이 바람직합니다.

Entitiy와 Alias

Entity는 Vault 내에서 개별 사용자를 식별하는 기준입니다. Vault에 접근하기 위해 한 사용자가 여러 identity source를 사용하는 경우가 많기 때문에, 개별 identity source 내의 계정을 Alias로 등록하고, Alias를 Entity와 매핑함으로써 모든 identity source에 대한 일관성 있는 사용자 권한 관리를 가능하게 합니다.

따라서, 사용자의 권한을 제어하는 ACL Policy는 Entity에 할당됩니다.

관리자 권한 할당

가장 먼저, 관리자로 사용할 계정을 이용해 LDAP로 로그인하여 Entity를 생성합니다.
그리고 다시 Root Token으로 로그인 한 뒤, Admin ACL Policy를 생성합니다.

Main Menu > Policies > ACL Policies

Create ACL policy 를 선택한 뒤, 다음과 같은 Admin 정책을 구성합니다.

# Allow managing leases
path "sys/leases/*"
{
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

# Manage auth methods broadly across Vault
path "auth/*"
{
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

# Create, update, and delete auth methods
path "sys/auth/*"
{
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

# List existing policies
path "sys/policies/acl"
{
  capabilities = ["read","list"]
}

# Create and manage ACL policies
path "sys/policies/acl/*"
{
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

# List, create, update, and delete key/value secrets
path "secret/*"
{
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

# Manage secret engines
path "sys/mounts/*"
{
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

# List existing secret engines.
path "sys/mounts"
{
  capabilities = ["read"]
}

# Read health checks
path "sys/health"
{
  capabilities = ["read", "sudo"]
}

# All permissions for Identity
path "identity/*"
{
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

위 정책에서는 Secret Engine의 기본 mountpoint로 /secret 을 사용합니다. 만약 다른 mountpoint를 사용한다면, 정책의 경로를 적절하게 변경하십시오.

Admin Policy를 생성했다면, 이제 Policy를 Entity에 등록해야 합니다.

Main Menu > Access > Organization > Entities

Entity 항목이 여러 개일 경우, Alias를 통해 사용자 이름을 확인할 수 있습니다.

Edit entity 를 클릭하여 Entity에 Policy를 할당합니다.

이제 Root Token 세션을 로그아웃 한 뒤, 관리자 계정으로 다시 로그인합니다. 시스템 메뉴가 모두 표시된다면 성공적으로 관리자 권한이 부여된 것입니다.

관리자 계정을 생성하였다면, Root Token은 즉시 Revoke 되어야 합니다.

Root Token은 Vault의 모든 기능에 대한 제한 없는 액세스 권한을 가지고 있으며, 사용 완료 후 반드시 Revoke 할 것을 권고합니다. Root Token의 Revoke는 다음과 같은 CLI 명령으로 수행할 수 있습니다.

vault token revoke "<token>"

만약 Root Token이 필요한 일이 생긴다면, Unseal Key를 이용해 재생성 할 수 있습니다.

Vault Policy

Vault에서 권한을 제어하는 방법은 두 가지가 있습니다. 기본적이고 가장 많이 사용되는 방식이 ACL Policy, Enterprise 에서만 제공되며, programmatic 하게 권한을 제어할 수 있는 EGP Policy가 있습니다.

EGP Policy는 코드를 이용해 권한 할당/해제를 동적으로 제어할 수 있지만, 그에 비례하여 정책의 복잡도가 급속도로 올라가는 문제가 있습니다.
Vault Enterprise를 사용하더라도, 권한 제어는 가능한 한 ACL Policy를 사용하고, ACL Policy로는 달성할 수 없는 요구사항이 있을 때만 EGP Policy를 사용해야 합니다.

이 글에서는 OSS 버전의 Vault를 사용하므로, ACL Policy를 중점적으로 다룹니다.

ACL Policy Best Practice

ACL Policy의 핵심 개념은 경로 (Path) 입니다. Vault는 ACL Policy에 정의된 경로와 Capability 권한을 보고 요청을 허용/거부합니다.
따라서, Policy와 Secret Engine을 구성할 때 가장 중요한 것은 '어떤 경로를 통해 Secret에 접근할 것인가?' 입니다.

이해를 돕기 위해, 개별 PC의 암호를 저장하는 Key-Value 타입의 Secret Engine이 필요하다고 가정해 보겠습니다.
Secret Engine의 mountpoint는 secret/password 이며, 이 Secret Engine에 접근할 때는 /v1/secret/password/ 를 사용하게 됩니다.

당연히 개별 PC당 하나의 식별자가 필요할 테니, 어떤 것을 식별자로 사용할 지 결정해야 합니다. 저희 회사에서는 자동화와 여러 편의성을 고려해 FQDN을 사용했지만, 다른 값이 되어도 문제는 없습니다.

이제 Secret Engine 내의 항목에 접근하려면 다음과 같은 경로를 사용해야 한다는 결정을 내릴 수 있습니다.

/v1/secret/password/<FQDN>

여기에 Username 식별자를 추가하면 다음과 같이 되겠죠.

/v1/secret/password/<FQDN>/<Username>

부서 구분을 추가한다면 아래와 같습니다.

/v1/secret/password/<Team>/<FQDN>/<Username>

ACL Policy에서 HQ-DEV 팀 내의 공용 사용자인 user 계정 패스워드에 대한 읽기 권한을 할당하는 실제 예제는 다음과 같습니다.

# KV2 Secret Engine Example
## Enumerate secrets in Secret Engine
path "secret/password/metadata/"
{
  capabilities = ["list"]
}

path "secret/password/metadata/HQ-DEV/*"
{
  capabilities = ["list"]
}
## Permit HQ-DEV Shared password objects only
path "secret/password/data/HQ-DEV/+/user"
{
  capabilities = ["list", "read"]
}

여기서 +는 하나의 세그먼트와 매칭되는 와일드카드입니다.

* 은 그 뒤의 모든 문자열과 매칭되는 와일드카드이며, 경로에 따라서 권한 제어를 세분화하하기 위해서는 +를 사용하는 것이 좋습니다.

이렇듯, Vault를 잘 사용하려면 최초 설계 단계에서부터 ACL Policy를 할당하기 위한 Path를 고려하여 Secret Engine과 URL Hierarchy를 구성해야 합니다.

URL Hierarchy의 깊이가 깊으면 권한 관리의 세분성은 좋아지지만, ACL Policy의 관리가 어려워집니다.

이러한 트레이드오프 관계를 고려하여 Vault Secret Engine 구조를 설계하는 것이 중요합니다.

회사의 규모가 크고, 팀 별 권한 분리가 필요한 경우에는 Vault Enterprise의 Namespace 기능을 사용할 수 있습니다.

AppRole AuthN

이제 LDAP를 통해 Vault에 저장된 Secret을 사용할 수 있게 되었습니다.
하지만, 사람이 아닌 애플리케이션과 자동화된 파이프라인에서 Vault에 저장된 Secret이 필요하다면 어떻게 해야 할까요?

모든 작업마다 사람이 수동으로 LDAP 인증 정보를 입력해 줄 수는 없습니다. LDAP 인증 정보를 어딘가에 기록해두고, 그것을 참조하게 하는 것도 안전하지 않습니다.

이러한 문제를 해결하기 위해 Vault는 AppRole 이라는 인증 수단을 지원합니다.

출처: HashiCorp - How (and Why) to Use AppRole Correctly in HashiCorp Vault

간단히 설명하면, App은 Role ID라는 공개된 값을 가지고 Trusted Entity에게 Secret ID를 요청하면, Trusted Entity가 App의 요청을 위임받아 Vault에게 암호화된 Secret ID를 받고, 그것을 App 에게 전달합니다.

마지막으로 App은 Secret ID를 Unwrap 하여, Role ID + Secret ID 를 가지고 본인을 인증, Vault 토큰을 획득합니다.

이렇게 복잡한 과정을 거치는 이유는 Secret ID가 전달되는 과정에서 누군가 트래픽을 엿보더라도 Vault Token을 획득할 수 없게 하기 위함입니다.

그런데, 여기에는 닭과 달걀의 문제가 있습니다.

Trusted Entity는 어떻게 인증하는가?

"Trusted Entity는 Vault에게 자기 자신을 어떻게 인증하는가?" 라는 의문이 자연스럽게 떠오릅니다.

HashiCorp 에서는 기본적으로 플랫폼 기반 인증을 사용할 것을 권고하며, 예시로는 Kubernetes, IAM 등이 있습니다.

하지만 타겟 시스템에 Vault 플러그인이 존재하지 않는다면? 우리는 다른 대안을 찾아야 합니다.

CIDR 기반 인증

임의의 플랫폼을 Trusted Entity로 만들기 위한 방법으로, 저는 CIDR Only 인증을 사용할 것을 제안합니다.

즉, 플랫폼은 자신의 IP를 통해 자기 자신을 인증하고, Secret ID를 요청할 수 있는 권한을 획득합니다.
또는, Secret Engine에 직접 접근할 수 있는 권한을 획득할 수도 있습니다.

이 방법은 일반적인 인증 수단보다 '덜 안전한' 것으로 간주된다는 점을 기억하십시오.

안전하게 CIDR 기반 인증을 사용하기 위해서는 전체 네트워크에서 IP Spoofing이 불가능해야 하며, 해당 호스트에 접근할 수 있는 사용자/수단이 엄격하게 제한되어야 합니다.

그리고 다음과 같은 요구사항을 만족해야 합니다.

  • 로드 밸런서는 L7 Proxy로 구성되어야 합니다
  • 로드 밸런서는 X-Forwarded For 헤더를 이용해 Origin IP를 보존해야 합니다
  • Vault는 로드 밸런서의 X-Forwarded For 헤더를 신뢰하도록 구성되어야 합니다

직전의 프로덕션 배포 가이드를 잘 따라왔다면, 이미 위 조건을 만족한 상태일 것입니다. 이제 CIDR 인증을 이용해 AppRole 인증을 수행하고, Vault 토큰을 획득하는 예제를 보여드리겠습니다.

CIDR AppRole 인증 구성

가장 먼저, AppRole 인증 기능을 활성화해야 합니다.

# Enable AppRole Auth Method
vault auth enable approle

그리고 AppRole 인증을 생성합니다.

GUI에서의 AppRole 구성이 제한되기 때문에, 이 작업은 반드시 CLI나 API로 수행해야 합니다.

vault write auth/approle/role/ansible-hq-infra \
  role_id=ansible-hq-infra \
  bind_secret_id=false \
  token_bound_cidrs="<AnsibleServerIP_CIDR>" \
  token_no_default_policy=true \
  secret_id_bound_cidrs="<AnsibleServerIP_CIDR>" \
  secret_id_ttl=60m \
  secret_id_num_uses=1 \
  token_num_uses=0 \
  token_ttl=2h \
  policies="approle-ansible-hq-infra" \
  token_policies="approle-ansible-hq-infra"

위 명령어는 Role ID로 ansible-hq-infra 를 할당하고, CIDR를 기준으로 토큰 발행을 허용하는 예제입니다.
이 인증을 사용하면 approle-ansible-hq-infra 라는 Policy를 할당받게 됩니다.

권한 부여에 대해서, 해당 토큰에 Write 권한을 부여하는 것은 일반적으로 좋은 생각이 아닙니다.

특정 시스템에 대한 접근 권한을 얻은 공격자는 CIDR 인증을 활용해 Vault를 과부하 시킬 수 있으며, KV Store를 쓰레기 값으로 채워 Vault의 동작을 중단시킬 수 있습니다.

만약 Write 권한이 반드시 필요하다면, 운영자 개입 없이 자동으로 수행되어야 하는, 접속 보안이 강화된 서비스들에게만 선택적으로 적용되어야 합니다.

AppRole Policy 예제

# Random Password Generator
path"sys/policies/password/password_generate_policy/generate" {
  capabilities = [ "read" ]
}
# Service Password KV Store
path "secret/password/HQ-INFRA/*" {
   capabilities = ["create", "read", "update"]
}

path "secret/password/HQ-DEV/*" {
   capabilities = ["create", "read", "update"]
}

approle-ansible-hq-infra Policy의 예제입니다.

Random Password Generator는 Vault에서 제공하는 Password Generate Policy 기능을 활용한 것으로, 패스워드 로테이션 자동화 예제를 다루면서 알아볼 것입니다.

CIDR 인증 쉘 스크립트 예제

#!/bin/bash
# Get token and store it ENV, (usage: source vault_login.sh)
# check jq installation
if ! command -v jq &> /dev/null; then
    echo "Error: jq is not installed. Please install jq to parse the Vault response."
    echo "For example, on Debian/Ubuntu: sudo apt-get install jq"
    echo "On macOS: brew install jq"
    exit 1
fi

# Vault 주소 설정 (환경 변수 VAULT_ADDR이 설정되어 있지 않으면 기본값 사용 또는 직접 입력)
DEFAULT_VAULT_ADDR="https://vault.sjuhwan.lab"
VAULT_ADDR=${VAULT_ADDR:-$DEFAULT_VAULT_ADDR}
APP_NAME="ansible-hq-infra"

echo "Attempting to log in to Vault ($VAULT_ADDR) as AppRole Policy ..."

# Vault AppRole 로그인 API 경로 (일반적으로 /v1/auth/approle/login/)
# Vault의 LDAP 인증 메소드 설정에 따라 login 경로 뒤에 username이 붙거나, payload에 username을 포함해야 할 수 있습니다.
# 이 예제는 username이 경로에 포함되는 일반적인 경우를 따릅니다.
APPROLE_URL="$VAULT_ADDR/v1/auth/approle/role/$APP_NAME"
LOGIN_URL="$VAULT_ADDR/v1/auth/approle/login"

echo "Login to Vault with AppRole CIDR Auth..."


# curl을 사용하여 CIDR 인증으로 Client Token 요청 및 응답 저장
RESPONSE=$(curl --silent --insecure --fail --show-error \
                --data "{\"role_id\": \"$APP_NAME\"}" \
                "$LOGIN_URL" 2>&1) # stderr도 캡처하여 오류 확인

# curl 요청 성공 여부 확인
if [ $? -ne 0 ]; then
  echo "Error during Vault login:"
  echo "$RESPONSE" # print error messages
  exit 1
fi

# Extract client token from the response
# Vault response format: {"auth": {"client_token": "s.xxxx", ...}}
echo "Extract client_token from Response..."
CLIENT_TOKEN=$(echo "$RESPONSE" | jq -r '.auth.client_token')

# Check success
if [ -z "$CLIENT_TOKEN" ] || [ "$CLIENT_TOKEN" == "null" ]; then
  echo "Error: Could not extract role id from Vault response."
  echo "Vault response:"
  echo "$RESPONSE"
  exit 1
fi

export ANSIBLE_HASHI_VAULT_TOKEN="$CLIENT_TOKEN"

echo ""
echo "Successfully logged in to Vault!"
echo "VAULT_TOKEN environment variable has been set."
echo ""
echo "You can now use this token for subsequent Vault commands in this shell session."
echo "To verify, run: echo \$ANSIBLE_HASHI_VAULT_TOKEN"
echo "Or try a Vault command, e.g., vault token lookup"

role_id로 ansible-hq-infra 를 전달하고, 바로 인증을 수행합니다. CIDR 인증이 잘 구성되었다면 Vault Token이 생성됩니다.

마치며

이것으로 Vault에서 LDAP/AppRole AuthN을 구성하는 방법과 policy를 작성하는 방법, 익명 CIDR 인증으로 AppRole 인증을 수행하는 예제를 알아보았습니다.

다음 포스팅에서는 지금까지 구성한 환경을 가지고, Ansible로 Linux/Windows의 패스워드 로테이션을 수행하는 실제 예제를 다룰 것입니다.

profile
Virtualization / Network / Storage / Server Hardware and.. Linux

0개의 댓글