Kong에서 JWT Plugin을 사용하여 토큰 인증 구성을 해두었다.
DB less mode라 secret key를 yaml에 하드코딩 해야하는 부분이 있어,
이 부분을 harshicorp의 Vault를 이용해보았다.
Kong은 OpenSource를 사용하고 있어 vault plugin을 사용 할 수 없다.
그래서 lua커스텀 플러그인을 구현해야한다.
Vault
UI, CLI 또는 HTTP API를 사용하여 토큰, 비밀번호, 인증서, 비밀을 보호하는 암호화 키 및 기타 민감한 데이터에 대한 액세스를 보호하고 저장하며 엄격하게 제어하는 도구이다. 아직 사용방법은 잘 모르겠다.
설치방법
이번 포스팅에서는 Docker를 이용하여 배포하고 백엔드 저장소는 구성하지 않았다.
vault-data폴더는 없으면 알아서 생성된다.
vault-config폴더는 생성하고 내부에 config.hcl파일을 설정한다.
Vault Config
# docker-vault/vault-config/config.hcl
storage "file" {
path = "/vault/file"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
disable_mlock = true
ui = true
Docker-compose
version: "3.8"
services:
vault:
image: hashicorp/vault:1.15
container_name: vault-file
ports:
- "8200:8200"
cap_add:
- IPC_LOCK
environment:
VAULT_ADDR: "http://0.0.0.0:8200"
volumes:
- ./vault-data:/vault/file
- ./vault-config:/vault/config
command: vault server -config=/vault/config/config.hcl
Secret 등록
5개의 키를 생성하고 3개의 키를 인증해야 해제되도록 구성 합니다.

생성된 키를 다운 받아서 보관합니다. 분실되면 복구할수없음
Token 1개 key 5개를 확인 할수 있는데, root token은 테스트를 위해서 vault 인증에 사용한다.


키를 넣으면 우측하단에 카운트 되기 시작한다.

Unseal 후 Root token으로 접속한다.

Secrets Engines를 선택하고 Enable new egine을 눌러 등록한다.

Key-Value를 선택

Path를 넣고 생성한다.
# 추후 새성 형태 예시:
https://127.0.0.1:8200/v1/django-secret/data/jwt-key


항목 | 설명 |
---|---|
Path for this secret | Secret이 저장될 하위 경로 (예: jwt-key 입력 시 /django-secret/data/jwt-key 로 저장됨) |
Secret data | "key": "value" 형식의 데이터 |
JSON 모드 | 여러 개의 key-value를 JSON으로 한 번에 입력 가능 (스위치 On) |

Kong 설정
테스트 환경에서는 db less 설정으로 구성해서 테스트해보고있다.
plugins 등록
kong의 plugins이 저장된 경로이다.
/usr/local/share/lua/5.1/kong/plugins/*
기본적으로 제공되는 Kong OSS bundled(plugin)리스트를 확인 할수있다.
(그래도 혹시 모르니 사용전에 라이선스 확인을 해야 할것 같긴 하다…)
acl, acme, aws-lambda, azure-functions, basic-auth 등...
custom plugin은 사용하기 위해서 kong 설정 파일에 plugins 항목에 추가해야한다.
주석처리된 내용을 보면 plugins는 기본적으로 plugins = bundled로 설정되어 있다.
테스트에서 새로추가할 플러그인의 이름은 vault-jwt이다.
plugins = bundled, vault-jwt 로 등록한다.
등록만하고 서버는 아직 적용 하지 않는다.
vi /etc/kong/kong.conf
# plugin add
# default: bundled
plugins = bundled, vault-jwt
vault-jwt폴더 하위에 handler.lua와 schema.lua를 생성한다.
- handler.lua – Vault에서 공개키를 가져오고 JWT 서명 검증
- schema.lua – 설정 항목 정의 (vault_addr, vault_token, vault_path)

$ sudo chown -R kong:kong /usr/local/share/lua/5.1/kong/plugins/vault-jwt
lua는 좀 생소한 언어지만, ChatGPT가 잘 도와줘서 꾸역꾸역 구현했다..
schema.lua 정의
# schema.lua
return {
name = "vault-jwt",
fields = {
{ config = {
type = "record",
fields = {
{ vault_addr = { type = "string", required = true }, },
{ vault_token = { type = "string", required = true }, },
{ vault_path = { type = "string", required = true }, },
{ vault_key_name = { type = "string", required = true }, },
},
},
},
}
}
handler.lua 정의
log생성 설정을 꼭 하도록 한다. 그래야 error발생시 error.log에 나타난다.
# 로그 위치
/usr/local/kong/logs/*
handler설정은 아래와 같다.
jwt.claims으로 설정해야하는데 jwt.payload로 구성해서 문제가 있었다…
(이부분은 추후 개선해야 될수도 있을 것 같다. base64인코딩으로 들어오는 것을 받아서 변환해야 될것 같기도…)
들어오는 형태를 보고 판단해야해서 로그설정이 필요하다.
# handler.lua
local jwt_decoder = require "kong.plugins.jwt.jwt_parser"
local http = require "resty.http"
local cjson = require "cjson.safe"
local VaultJWTHandler = {
PRIORITY = 1000,
VERSION = "1.0"
}
function VaultJWTHandler:access(conf)
-- 1. Authorization 헤더 확인
local auth_header = kong.request.get_header("Authorization")
if not auth_header then
ngx.log(ngx.ERR, "[vault-jwt] Missing Authorization header")
return kong.response.exit(401, { message = "Missing Authorization header" })
end
-- 2. Bearer 토큰 추출
local token = auth_header:match("Bearer%s+(.+)")
if not token then
ngx.log(ngx.ERR, "[vault-jwt] Invalid Bearer token format: ", auth_header)
return kong.response.exit(401, { message = "Invalid Bearer token format" })
end
ngx.log(ngx.ERR, "[vault-jwt] Received JWT token: ", token)
-- 3. JWT 디코딩
local jwt, err = jwt_decoder:new(token)
if not jwt or not jwt.claims then
ngx.log(ngx.ERR, "[vault-jwt] JWT parsing failed. Error: ", err or "unknown", " | jwt: ", cjson.encode(jwt))
return kong.response.exit(401, { message = "Invalid JWT" })
end
ngx.log(ngx.ERR, "[vault-jwt] Decoded JWT claims: ", cjson.encode(jwt.claims))
-- 4. Vault에서 secret 가져오기
local httpc = http.new()
local res, err = httpc:request_uri(conf.vault_addr .. "/v1/" .. conf.vault_path, {
method = "GET",
headers = {
["X-Vault-Token"] = conf.vault_token,
}
})
if not res then
ngx.log(ngx.ERR, "[vault-jwt] Vault request error: ", err)
return kong.response.exit(500, { message = "Vault request error: " .. err })
end
local body = cjson.decode(res.body)
local secret = body and body.data and body.data.data and body.data.data[conf.vault_key_name]
if not secret then
ngx.log(ngx.ERR, "[vault-jwt] Failed to parse secret from Vault response: ", res.body)
return kong.response.exit(500, { message = "Failed to parse secret from Vault" })
end
-- 5. JWT 서명 검증
local ok, err = jwt:verify_signature(secret, "HS256")
if not ok then
ngx.log(ngx.ERR, "[vault-jwt] Invalid JWT signature: ", err)
return kong.response.exit(401, { message = "Invalid JWT signature" })
end
-- 6. Kong에게 인증 처리 알리기 (consumer 매핑)
local iss = jwt.claims.iss or "anonymous"
kong.client.authenticate(nil, {
id = iss,
custom_id = iss
})
-- 7. 부가적인 헤더 추가 가능
kong.service.request.set_header("X-Verified-Sub", jwt.claims.sub or "")
end
return VaultJWTHandler
kong.yml 설정
Plugin을 등록하는 과정이 필요하다.
Plugin을 등록하고 적용할 Service와 매핑하면된다.
(기존에 jwt plugin에 하드코딩하던 것을 대체 할수 있다.그런데 결국 vault인증 정보가 들어있어야되는데…이게 맞나?)
plugins:
- name: vault-jwt # kong.conf에 등록된 plugins 이름
service: demo-be-blog-create
config:
vault_addr: http://192.168.0.24:8200
vault_token: hvs.4RyaIVtbARBRZgNWVrA9cuvk
vault_path: django-secret/data/jwt-key
vault_key_name: django-secret
- name: vault-jwt # kong.conf에 등록된 plugins 이름
service: demo-be-blog-detail
config:
vault_addr: http://192.168.0.24:8200
vault_token: hvs.4RyaIVtbARBRZgNWVrA9cuvk
vault_path: django-secret/data/jwt-key
vault_key_name: django-secret
주의사항
vault는 재부팅하면 unseal과정이 필요합니다.
웹접속후 해제하면 정상 동작됨.
이번 포스팅은 여기까지 입니다~!