HashiCorp Vault Secure Storage Backend in Apache APISIX Ecosystem
API7.ai
January 24, 2022
마이크로서비스 기반 아키텍처의 부상으로 인해 보안을 유지하는 것이 이전보다 훨씬 더 어려워졌습니다. 우리는 이제 백엔드 서버의 100개 인스턴스가 단일 정적 비밀 자격 증명으로 데이터베이스 서버에 접근하는 시점을 훨씬 넘어섰습니다. 왜냐하면 자격 증명이 유출될 경우 전체 시스템이 위험에 빠지고, 해당 자격 증명을 취소하면 대규모 서비스 중단이 발생하기 때문입니다(이제 인스턴스를 재구성하지 않는 한 아무도 아무것에 접근할 수 없습니다). 보안 침해 가능성을 완전히 제거할 수는 없습니다. 때로는 예상치 못한 일이 발생하기 때문입니다. 대신, 이러한 상황에서 피해 범위를 통제하는 것은 전적으로 우리에게 달려 있습니다. 이러한 시나리오를 해결하기 위해 프로덕션 환경에서는 HashiCorp Vault와 같은 인기 있는 솔루션이 등장하여 ID 기반 비밀 및 암호화 관리 시스템으로 작동합니다. 이 글에서는 Vault를 Apache APISIX(클라우드 네이티브 API 게이트웨이)의 jwt-auth 플러그인과 통합하여 두 세계의 장점을 효과적으로 활용하는 방법을 설명합니다.
Vault란 무엇인가
HashiCorp Vault는 조직이 비밀에 대한 접근을 관리하고 이를 안전하게 전송할 수 있도록 설계되었습니다. 비밀은 엄격하게 통제되고 모니터링되어야 하는 민감한 자격 증명의 형태로 정의되며, 민감한 정보를 잠금 해제하는 데 사용될 수 있습니다. 비밀은 비밀번호, API 키, SSH 키, RSA 토큰 또는 OTP 형태일 수 있습니다. 실제 세계에서는 비밀이 설정 파일에 저장되거나 실제 프로그램 코드의 변수로 저장되어 버전 관리 시스템(GitHub, BitBucket 또는 GitLab)에 올라가는 경우가 흔히 발생하며, 이는 보안에 큰 위협이 됩니다. Vault는 이러한 문제를 비밀을 중앙 집중화하여 해결합니다. Vault는 정적 비밀을 위한 암호화된 저장소, TTL 임대 기간이 있는 동적 비밀 생성, 사용자(기계 또는 인간)의 인증을 제공하여 특정 비밀에 접근할 권한이 있는지 확인하는 등의 기능을 제공합니다. 따라서 보안 침해가 발생하더라도 피해 범위가 훨씬 작고 제한적입니다.
Vault는 인프라 내의 모든 비밀을 관리할 수 있는 단일 인터페이스를 제공하여 접근을 쉽게 제어하고 관리할 수 있게 합니다. 또한, 상세한 감사 로그를 생성하고 누가 무엇에 접근했는지 추적할 수 있는 유연성도 제공합니다.
APISIX jwt-auth 플러그인 정보
이 플러그인은 APISIX 라우트에 첨부되어 요청이 업스트림 URI로 전달되기 전에 JWT(JSON 웹 토큰, 자세히 알아보기) 인증을 수행할 수 있는 인증 플러그인입니다. 간단히 말해, 이는 중요한 리소스에 대한 권한 부여로 이어지는 안전한 인증 메커니즘입니다. 일반적으로 발급자는 JWT에 서명하기 위해 개인 키 또는 텍스트 비밀을 사용합니다. JWT의 수신자는 서명을 검증하여 토큰이 발급자에 의해 서명된 후 변경되지 않았는지 확인합니다. 전체 JWT 메커니즘의 무결성은 서명 비밀(텍스트 비밀 또는 RSA 키 쌍)에 달려 있습니다. 이는 인증되지 않은 소스가 서명 키를 추측하고 JWT 내의 클레임을 변경하려는 시도를 어렵게 만듭니다.
따라서 이러한 키를 안전한 환경에 저장하는 것은 매우 중요합니다. 잘못된 손에 들어가면 전체 인프라의 보안이 위험에 빠질 수 있습니다. APISIX 측에서는 표준 SecOps 관행을 따르기 위해 모든 수단을 다하고 있지만, 프로덕션 환경에서는 HashiCorp Vault와 같은 중앙 집중식 키 관리 솔루션을 사용하여 상세한 감사 추적, 주기적인 키 순환, 키 취소 등을 수행하는 것이 일반적입니다. 그리고 인프라 전체에서 키 순환이 발생할 때마다 Apache APISIX 구성을 업데이트해야 한다면 상당히 번거로운 문제가 될 것입니다.
Apache APISIX와 Vault 사용 단계
Vault와 통합하기 위해 Apache APISIX는 config.yaml에 Vault 구성을 로드해야 합니다.
내부적으로 APISIX는 Vault 서버의 KV 비밀 엔진 v1 HTTP API와 통신합니다. 대부분의 엔터프라이즈 솔루션은 프로덕션 환경에서 KV Secrets Engine - Version 1을 고수하는 것을 선호하기 때문에, APISIX-Vault 지원의 초기 단계에서는 버전 1만 지원했습니다. 이후 릴리스에서는 K/V 버전 2 지원을 추가할 예정입니다.
Vault를 사용하는 주요 아이디어는 낮은 신뢰 환경에서의 보안 문제 때문입니다. APISIX 개발자들은 여러분의 우선순위를 진지하게 이해합니다. 그래서 우리는 APISIX 서버에 제한된 접근 권한을 부여할 수 있는 짧은 범위의 Vault 액세스 토큰을 사용할 것을 권장합니다.
Vault 구성
이미 필요한 권한으로 실행 중인 Vault 인스턴스가 있다면 이 섹션을 건너뛰어도 됩니다. 이 섹션은 Apache APISIX 생태계 내에서 Vault를 사용하는 최선의 방법을 공유합니다. 아래 단계를 따르십시오.
1단계: Vault 서버 실행
여기서는 여러 옵션이 있습니다. Docker, 사전 컴파일된 바이너리 또는 소스에서 빌드 중에서 자유롭게 선택할 수 있습니다. Vault 서버와 통신하려면 Vault CLI 클라이언트가 필요하므로, Docker 방식보다는 사전 컴파일된 바이너리를 사용하는 것을 선호합니다. 어쨌든, 이는 전적으로 여러분의 선택입니다(Vault 공식 설치 문서를 참조하십시오). 개발 서버를 실행하려면 다음 명령을 실행하십시오.
$ vault server -dev -dev-root-token-id=root
…
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.
You may need to set the following environment variable:
export VAULT_ADDR='http://127.0.0.1:8200'
The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.
Unseal Key: 12hURx2eDPKK1tzK+8TkgH9pPhPNJFpyfc/imCLgJKY=
Root Token: root
Development mode should NOT be used in production installations!
현재 CLI에 올바른 환경 변수를 설정하십시오.
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='root'
적절한 경로 접두사로 Vault k/v 버전 1 비밀 엔진 백엔드를 활성화하십시오. 이 데모에서는 kv
경로를 선택하여 Vault 기본 비밀 경로와 충돌하지 않도록 합니다.
$ vault secrets enable -path=kv -version=1 kv
Success! Enabled the kv secrets engine at: kv/
# 상태를 다시 확인하려면 실행
$ vault secrets list
Path Type Accessor Description
---- ---- -------- -----------
cubbyhole/ cubbyhole cubbyhole_4eeb394c per-token private secret storage
identity/ identity identity_5ca6201e identity store
kv/ kv kv_92cd6d37 n/a
secret/ kv kv_6dd46a53 key/value secret storage
sys/ system system_2045ddb1 system endpoints used for control, policy and debugging
2단계: APISIX를 위한 Vault 액세스 토큰 생성
이 글은 jwt-auth
플러그인 관점에서 Vault를 사용하는 것에 관한 것입니다. 따라서 APISIX 소비자(APISIX 생태계에서 소비자에 익숙하지 않다면 Apache APISIX 소비자 문서를 읽어보십시오)의 사용자 이름이 jack
인 경우, jwt-auth
플러그인은 (Vault 구성이 활성화된 경우) Vault kv 저장소에서 <config.yaml 내부의 vault.prefix>/consumer/<consumer.username>/jwt-auth
경로의 비밀을 찾습니다. 이 맥락에서, config.yaml
내부의 vault.prefix
로 kv/apisix
네임스페이스(Vault 경로)를 할당하는 경우, kv/apisix/consumer/
. 경로에 대한 정책을 생성하는 것을 권장합니다. 끝의 별표()는 정책이 kv/apisix/consumer
접두사를 가진 모든 경로에 대한 읽기 권한을 허용하도록 합니다.
HashiCorp 구성 언어(HCL)로 정책 파일을 생성하십시오.
$ tee apisix-policy.hcl << EOF
path "kv/apisix/consumer/*" {
capabilities = ["read"]
}
EOF
정책을 Vault 인스턴스에 적용하십시오.
$ vault policy write apisix-policy apisix-policy.hcl
Success! Uploaded policy: apisix-policy
작은 접근 범위로 구성된 새로 정의된 정책으로 토큰을 생성하십시오.
$ vault token create -policy="apisix-policy"
Key Value
--- -----
token s.KUWFVhIXgoRuQbbp3j1eMVGa
token_accessor nPXT3q0mfZkLmhshfioOyx8L
token_duration 768h
token_renewable true
token_policies ["apisix-policy" "default"]
identity_policies []
policies ["apisix-policy" "default"]
이 데모에서 s.KUWFVhIXgoRuQbbp3j1eMVGa
는 여러분의 액세스 토큰입니다.
Apache APISIX에 Vault 구성 추가
앞서 논의한 대로, Apache APISIX는 Vault HTTP API를 통해 Vault 인스턴스와 통신합니다. 필요한 구성은 config.yaml에 추가되어야 합니다. 다음은 사용할 수 있는 다양한 필드에 대한 간략한 정보입니다:
- host: Vault 서버가 실행 중인 호스트 주소입니다.
- timeout: 각 요청에 대한 HTTP 시간 초과입니다.
- token: Vault 인스턴스에서 생성된 토큰으로, Vault에서 데이터를 읽을 수 있는 권한을 부여합니다.
- prefix: 접두사를 활성화하면 정책을 더 잘 적용하고, 제한된 범위의 토큰을 생성하며, APISIX에서 접근할 수 있는 데이터를 엄격히 제어할 수 있습니다. 유효한 접두사는 (
kv/apisix
,secret
등)입니다.
vault:
host: 'http://0.0.0.0:8200'
timeout: 10
token: 's.KUWFVhIXgoRuQbbp3j1eMVGa'
prefix: 'kv/apisix'
APISIX 소비자 생성
APISIX는 인증 시나리오와 함께 사용할 수 있는 소비자 수준의 추상화를 제공합니다. APISIX 라우트에 인증을 활성화하려면 해당 인증 서비스에 대한 적절한 구성이 있는 소비자가 필요합니다. 그런 다음에만 APISIX는 소비자 구성에 따라 인증을 성공적으로 수행한 후 요청을 업스트림 URI로 전달할 수 있습니다. APISIX 소비자에는 두 가지 필드가 있습니다. 하나는 username
(필수)로, 다른 소비자와 구별하기 위한 것이고, 다른 하나는 소비자별 플러그인 구성을 포함하는 plugins
입니다.
이 글에서는 jwt-auth
플러그인을 사용하여 소비자를 생성할 것입니다. 이 플러그인은 해당 라우트 또는 서비스에 대해 JWT 인증을 수행합니다.
Vault 구성을 사용하여 jwt-auth
를 활성화하려면 다음 요청을 보내십시오:
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"username": "jack",
"plugins": {
"jwt-auth": {
"key": "test-key",
"vault": {}
}
}
}'
여기서 플러그인은 소비자 jack
에 대해 Vault 경로(<conf.yaml의 vault.prefix>/consumer/jack/jwt-auth
)에서 키 비밀을 찾고, 이를 후속 서명 및 JWT 검증에 사용합니다. 동일한 경로에서 키를 찾을 수 없는 경우, 플러그인은 오류를 기록하고 JWT 인증을 수행하지 못합니다.
테스트 업스트림 서버 설정
동작을 테스트하기 위해 업스트림(단순한 핑 핸들러로 "pong"을 반환)에 대한 라우트를 생성할 수 있습니다. 일반 Go HTTP 서버로 설정할 수 있습니다.
// 간단한 업스트림 서버
package main
import "net/http"
func ping(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("secure/pong\n"))
}
func main() {
http.HandleFunc("/secure/ping", ping)
http.ListenAndServe(":9999", nil)
}
인증이 활성화된 APISIX 라우트 생성
이 보안 핑 HTTP 서버와 jwt-auth
인증 플러그인이 활성화된 APISIX 라우트를 생성하십시오.
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"plugins": {
"jwt-auth": {}
},
"upstream": {
"nodes": {
"127.0.0.1:9999": 1
},
"type": "roundrobin"
},
"uri": "/secure/ping"
}'
jwt-auth 플러그인에서 토큰 생성
이제 APISIX에서 JWT 비밀을 서명하여 APISIX 서버의 [http://localhost:9080/secure/ping](http://localhost:9080/secure/ping)
프록시 라우트에 요청을 보낼 때 사용할 수 있습니다.
$ curl http://127.0.0.1:9080/apisix/plugin/jwt/sign\?key\=test-key -i
HTTP/1.1 200 OK
Date: Tue, 18 Jan 2022 07:50:57 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.11.0
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODY1N30.nkyev1_KUapVgY_QVYETsSApA6gEkDWS8tsHFV1EpD8
이전 단계에서 failed to sign jwt
와 같은 메시지가 표시되면 Vault kv/apisix/consumers/jack/jwt-auth
경로에 비밀 키가 저장되어 있는지 확인하십시오.
# 예시
$ vault kv put kv/apisix/consumer/jack/jwt-auth secret=$ecr3t-c0d3
Success! Data written to: kv/apisix/consumer/jack/jwt-auth
APISIX 서버에 요청
이제 APISIX 프록시의 /secure/ping
라우트에 요청을 보냅니다. 성공적인 검증 후, 요청은 Go HTTP 서버로 전달됩니다.
$ curl http://127.0.0.1:9080/secure/ping -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODU5M30.IYudBr7FTgRme70u4rEBoYNtGmGByzgfGlt8hctI__Q' -i
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Connection: keep-alive
Date: Tue, 18 Jan 2022 08:00:04 GMT
Server: APISIX/2.11.0
secure/pong
유효한 JWT가 없는 요청은 HTTP 401 Unauthorized
오류를 발생시킵니다.
$ curl http://127.0.0.1:9080/secure/ping -i
HTTP/1.1 401 Unauthorized
Date: Tue, 18 Jan 2022 08:00:33 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.11.0
{"message":"Missing JWT token in request"}
Vault를 APISIX jwt-auth 플러그인과 통합할 수 있는 다양한 사용 사례
Apache APISIX jwt-auth
플러그인은 Vault 저장소에서 단순 텍스트 비밀 키와 RS256 공개-개인 키 쌍을 모두 가져오도록 구성할 수 있습니다.
:::note 이 통합 지원의 초기 버전에서는 플러그인이 Vault 경로에 저장된 비밀의 키 이름이 [ secret
, public_key
, private_key
] 중 하나일 것으로 기대합니다. 향후 릴리스에서는 사용자 정의 이름의 키를 참조하는 기능을 추가할 예정입니다. :::
-
Vault 내부에 HS256 서명 비밀을 저장하고 이를 JWT 서명 및 검증에 사용하려는 경우.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "jack", "plugins": { "jwt-auth": { "key": "key-1", "vault": {} } } }'
여기서 플러그인은 소비자
jack
에 대해 Vault 경로(<conf.yaml의 vault.prefix>/consumer/jack/jwt-auth
)에서secret
키를 찾고, 이를 후속 서명 및 JWT 검증에 사용합니다. 동일한 경로에서 키를 찾을 수 없는 경우, 플러그인은 오류를 기록하고 JWT 인증을 수행하지 못합니다. -
RS256 RSA 키 쌍, 공개 및 개인 키가 Vault에 저장된 경우.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "jim", "plugins": { "jwt-auth": { "key": "rsa-keypair", "algorithm": "RS256", "vault": {} } } }'
플러그인은 플러그인 Vault 구성 내에 언급된
jim
에 대해 Vault kv 경로(<conf.yaml의 vault.prefix>/consumer/jim/jwt-auth
)에서public_key
및private_key
키를 찾습니다. 찾을 수 없는 경우 인증이 실패합니다.Vault kv 저장소에 공개 및 개인 키를 저장하는 방법이 확실하지 않은 경우, 다음 명령을 사용하십시오.
# 현재 디렉토리에 "public.pem" 및 "private.pem" 파일이 있는 경우 $ vault kv put kv/apisix/consumer/jim/jwt-auth public_key=@public.pem private_key=@private.pem Success! Data written to: kv/apisix/consumer/jim/jwt-auth
-
소비자 구성에 공개 키가 있고, 개인 키는 Vault에 있는 경우.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "username": "john", "plugins": { "jwt-auth": { "key": "user-key", "algorithm": "RS256", "public_key": "-----BEGIN PUBLIC KEY-----\n……\n-----END PUBLIC KEY-----" "vault": {} } } }'
이 플러그인은 소비자 구성에서 RSA 공개 키를 사용하고, Vault에서 직접 가져온 개인 키를 사용합니다.
플러그인에서 Vault 비활성화
이제 jwt-auth
플러그인에서 Vault 조회를 비활성화하려면 소비자 플러그인 구성(이 경우 jack
)에서 빈 Vault 객체를 제거하십시오. 이렇게 하면 JWT 플러그인이 플러그인 구성에서 서명 비밀(HS256/HS512 또는 RS512 키 쌍)을 조회하여 jwt-auth
구성이 활성화된 URI 라우트에 대한 후속 요청을 수행합니다. APISIX config.yaml
에서 Vault 구성이 활성화되어 있더라도 Vault 서버에 요청이 전송되지 않습니다.
APISIX 플러그인은 핫 리로드되므로 APISIX를 재시작할 필요가 없습니다.
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"username": "jack",
"plugins": {
"jwt-auth": {
"key": "test-key",
"secret": "my-secret-key"
}
}
}'
요약
이 글은 Vault-Apache APISIX 통합의 다가오는 릴리스와 관련된 세부 정보를 제공합니다.
GitHub Discussions에서 토론을 시작하거나 메일링 리스트를 통해 소통해 주십시오.