2025. 8. 24. 14:39ㆍ볼트 (Vault)
AppRole Method
우리가 쉽게 접하는 인증 방식은 아이디와 비밀번호죠.

이 방식의 보편적인 관념은, 어플리케이션(사용자)이 비밀번호와 같은 민감한 정보를 항상 알고 있어야 한다는 점입니다.
그리고 그 비밀번호는 언제나 고정되어 있고, 바꾼다하더라도 어플리케이션이 직접 바꿔야 하죠.
하지만 AppRole 방식은 비밀번호를 알고 있는 주체를 어플리케이션에서 보안에 강한 관리자로 바꿉니다.
더 이상 어플리케이션이 민감한 정보를 들고 있지 않도록 조치하는 것이죠.

이 방식은 일전의 아이디와 비밀번호와 같이 Role ID, Secret ID로 정보를 가져옵니다.
다만 주요한 차이점은 생애 주기를 가지고 있는 토큰(token)을 가지고 관리자로부터 Secret ID를 발급 받아야 하죠.
이제 더 이상 어플리케이션은 Secret ID를 고정할 수도, 입맛대로 바꿀 수도 없습니다.
겉으로 보기에는 어플리케이션이 Secret ID를 가지고 있지 않기 때문에 참 안전한 듯 보입니다.
그러나 여전히 Secret ID를 쉽게 얻을 수 있는 토큰키를 가지고 있기 때문에 그냥 불필요한 중간 과정이 추가된 것 뿐일 수도 있죠.
그럼에도 불구하고 이 방식을 선호하는 이유는 무엇일까요?
Usage Workflow
아래 Vault에서 소개한 사용 예시를 자세히 들여다 볼까요?
Best practices for AppRole authentication | Vault | HashiCorp Developer
Follow best practices for AppRole authentication to secure access and validate application workload identity.
developer.hashicorp.com
위 예시에는 3가지 주체가 등장합니다. 이를 좀 보편화되게 각색해서 4가지 주체로 만들어봤습니다.
- Vault Cluster : 민감한 정보들을 저장하고 있는 서버입니다.
- Auth : 보안된 서버로 아무도 내용을 모릅니다.
- Runner : 실질적인 작업을 수행합니다.
- Manager : Runner를 관리합니다.
각 장의 더보기를 누르면 예시 코드가 등장합니다.
# %% setup
import requests
from requests import Response
VAULT_ADDRESS = ""
VAULT_USER = ""
VAULT_PASSWORD = ""
VAULT_ROLE = ""
def get_content(response: Response):
try:
return response.json()
except:
return response.text
1. Authentication and Token

당연하게도 누군가는 Vault에 접속하기 위해 로그인, 즉 인증을 해야 합니다.
다양한 인증 방식이 있지만, Manager나 Runner가 직접 인증해서 토큰을 받아오는 경우는 지양해야 합니다.
민감한 정보를 최대한 숨기려고 하는 일인데 이 둘 중 한 명이라도 들고 있으면 결국 본질 자체를 훼손하는 꼴이기 때문이죠.
(그럴바엔 그냥 직접 로그인하는 게 간단하고 쉽습니다)
따라서 별도의 Auth 서버를 두고 인증을 수행합니다.
Manager, Runner 모두 Auth 내의 어떠한 정보라도 볼 수 없습니다.
다만 Auth는 인증 후 특정 시간에 Token을 Manager에게 전달하죠. Manager는 이를 받아 잘 저장해둡니다.
# %% login by userpass
path = f"/v1/auth/userpass/login"
url = f"{VAULT_ADDRESS}{path}/{VAULT_USER}"
data = {
"password": VAULT_PASSWORD,
}
response = requests.post(url=url, data=data, verify=False)
print(f"status : {response.status_code}")
get_content(response)
# %% parse token
content = response.json()
token = content['auth']['client_token']
token_accessor = content['auth']['accessor']
2. Role ID

Manager는 필요한 Role을 이미 사전에 알고 있습니다.
해당 Role과 일전에 받은 Token과 함께 직접 Vault에게 Role ID를 요청합니다.
그리고 Vault는 Role ID를 응답으로 주겠죠.
이를 위해선 토큰에 대해 다음과 같은 정책(policy)가 등록되어야 합니다.
path "auth/approle/role/flower_app/role-id" {
capabilities = ["read"]
}
# %% get role id
path = f"/v1/auth/approle/role/{VAULT_ROLE}/role-id"
url = f"{VAULT_ADDRESS}{path}"
header = {"X-Vault-Token": token}
response = requests.get(url=url, headers=header, verify=False)
print(f"status : {response.status_code}")
get_content(response)
3. Wrapped Secret ID

Manager는 Role ID를 얻은 것과 비슷하게 Secret ID를 요청합니다.
이때 직접적으로 Secret ID를 얻는 것이 아니라 간접적으로 암호화된 Secret ID를 얻습니다.
이를 위해선 토큰에 대해 다음과 같은 정책(policy)가 등록되어야 합니다.
path "auth/approle/role/flower_app/secret-id" {
capabilities = ["create", "update", "read"]
min_wrapping_ttl = "100s"
max_wrapping_ttl = "300s"
}
# %% get wrapped secret id
path = f"/v1/auth/approle/role/{VAULT_ROLE}/secret-id"
url = f"{VAULT_ADDRESS}{path}"
header = {"X-Vault-Token": token, "X-Vault-Wrap-TTL": "120s"}
response = requests.post(url=url, data=data, headers=header, verify=False)
print(url)
print(f"status : {response.status_code}")
get_content(response)
# %% parse secret token
wrapped_secret_token = response.json()['wrap_info']['token']
wrapped_secret_token
4. Pass Role ID and Wrapped Secret ID

Manager는 여태까지 얻은 Role ID와 Wrapped Secret ID를 모두 Runner에게 전달합니다.
근데 여기서 궁금증이 2개 생기죠.
- Role ID는 고정되어 있어서 어차피 Runner가 애초부터 가지고 있으면 되는데, 건네줄 필요가 있을까?
- Secret ID는 그대로 주면되지, 왜 한 번 감싸서 줄까?
해답을 찾기 위해선 다시 AppRole을 사용하는 본질적인 이유로 돌아가야 합니다.
바로 어플리케이션이 민감한 정보를 들고 있지 않아야 되는 것이죠.
대안으로 나온 Role은 Vault에 접속하여 정보를 얻는 핵심입니다.
어플리케이션의 특정 정보에 대한 접근 권한은 바로 이 Role로 제어하게 되는 것이죠.
예를 들어 필요에 의해서 특정 Role에 대해 정책을 변경했다고 해봅니다.
이때 어플리케이션이 하나의 Role만 가졌다고 가정해보면, 일이 틀어졌을 때 '큰 재앙'을 맞이할 수 있습니다.
만일 생각이 있는 사용자라면 정책을 백업하거나 롤백하는 도구를 미리 만들어놨겠지만 - 이는 결국 복잡성을 증가시킵니다.
단순하고도 효과적인 해결책은, 그냥 Role을 두 개 만들어놓으면 됩니다.
최신 버전의 Role이 문제가 발생했을 때, 바로 다른 Role로 대신하면 되는 것이죠.
여기서 Runner는 두 개 Role을 모두 들고 있어서, 어떤 Role을 사용해야 올바른 지 결정하는 건 옳지 않습니다.
뭐... 그냥 본인의 로직만 잘 신경쓰면 되는 것도 중요하지만,
어떤 인증 자체가 필요한 지 아는 것 자체가 민감한 정보를 파헤쳐야 아는 것이기 때문입니다.
Secret ID는 평문 그대로 전송하면 로그나 네트워크 스니핑 등 유출될 우려가 매우 크죠.
반면에 Wrapped Secret ID는 유출되도 사용할 수 없어서 비교적 안전합니다.
그리고 일단 한 번 Wrapping하면 더 이상 사용이 불가능합니다. 즉, 일회성이라는 것이죠.
또한 짧은 생애 주기(TTL)를 가지고 있기 때문에 어디 모셔둘 수도 없습니다.
5. Secret ID

이제 Runner는 받은 Wrapped Secret ID를 Vault에 unwrap하도록 요청해서, 결국 평범한 Secret ID를 받아냅니다.
# %% unwrap secret id
path = f"/v1/sys/wrapping/unwrap"
url = f"{VAULT_ADDRESS}{path}"
header = {"X-Vault-Token": wrapped_secret_token}
response = requests.post(url=url, headers=header, verify=False)
print(f"status : {response.status_code}")
get_content(response)
이제 Runner는 보유한 Role ID와 Secret ID로 Vault에 접속해서 정보를 얻을 수 있습니다!
Binding CIDRs
Worker나 Runner가 사용하는 토큰이나 Secret ID는 비영구적이어야 합니다.
물론 재빠르게 사용하고 나서 만료시켜주면 걱정이 없겠지만, 현실은 그렇지 않죠.
따라서 만료 시기를 정해주는 장치를 만들어놓습니다. 이를 Time To Live, 줄여서 TTL이라고 하죠.
그리고 다수의 보안 장치를 뚫고, 결국 토큰이나 secret id를 탈취했다면 결국 vault에 접속하는 건 시간 문제죠.
따라서 어디서 사용할 것인지 ip 주소를 제한할 수 있습니다. 이를 Binding Classless Inter-Domain Routing, 줄여서 제목이 되죠.
$ 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"
위 예제와 같이 이 밖에도 최대 몇 개까지 만들 것인지 등을 설정할 수 있습니다.
AppRole을 만들 때 매개변수로 자세히 설명되어 있으니, 아래를 참고하면 좋습니다.
AppRole - Auth Methods - HTTP API | Vault | HashiCorp Developer
This is the API documentation for the Vault AppRole auth method.
developer.hashicorp.com
Auth Server
필자가 Vault를 처음 접한 건 실무에서였습니다.
역시 민감한 정보들을 다루는 데 나름 신경을 많이 썼더군요.
토큰이나 키들을 생애주기에 맞춰 바꿔끼는 행위를 로테이팅(rotating)이라고 합니다.
그러나 문제는 토큰이나 키를 바꾸지 않는 행위이죠.
대부분의 개발자들은 모두 어딘가에(?) 키들을 기록해놓고, 필요할 때마다 꺼내서 사용합니다.
이게 정말, 정말로 편한 게 그냥 누구나 접속해서 마음대로 헤집어도 되는 수준이었죠.
뭐, 수 년이 지나서야 로테이팅 정책을 만들긴 했는데 - 여전히 어딘가 부족한 건 사실입니다.

Vault를 잘 관리하기 위해서 몇 가지 생각을 해보자면,
첫 번째로는 Vault를 다루기 위한 툴이 CLI 여서는 안될 것 같습니다.
불편한 건 둘째치고, 휴먼 에러가 너무 많은 게 탈이죠.
이런 건 알려고도 하지마세요... 저도 이 세계는 잘 모릅니다.

두 번째는 Vault를 위한 자체 관리 도구를 만드는 것이죠.
Vault의 GUI는 관리자 권한 빼고는 접속하게 만들면 안됩니다.
실무에서도 느꼈지만 이 메뉴들 모두 자유롭게 왕래해서는 안되죠...

세 번째는 잘 만든 관리 도구를 활용해 정말로 로테이팅하는 것이 좋다는 것입니다.
'볼트 (Vault)' 카테고리의 다른 글
| [Vault] Secret 만들기 (0) | 2024.04.13 |
|---|---|
| [Vault] 사용자 계정 만들기 (0) | 2024.04.13 |
| [Vault] Helm를 이용해 Vault 배포하기 (0) | 2023.07.29 |