[Go] Vault를 사용해 시크릿 저장하고 불러오기 기초

⚠ Vault란?

HashiCorp Vault는 ID 기반 시크릿 및 암호화 관리 시스템입니다. 여기서 시크릿은 API 암호화 키, 비밀번호, 인증서 등 액세스를 엄격하게 제어하고자 하는 모든 것을 아우릅니다. Vault는 인증(authentication) 및 인가(authorization) 방법에 따라 게이트화된 암호화 서비스를 제공합니다. Vault의 UI, CLI 또는 HTTP API를 사용하여 기밀 및 기타 중요 데이터에 대한 액세스를 안전하게 저장 및 관리하고, 엄격하게 제어(restrict)하며, 감사(audit)할 수 있습니다.


초기화

$ go mod init vault-example
$ touch main.go
$ touch docker-compose.yml
$ go get github.com/hashicorp/vault/api

디렉터리 구조


docker-compose.yml

version: '3.9'
services:
  vault:
    image: vault:1.13.3 # 사용할 이미지 이름과 태그 지정
    container_name: vault # 컨테이너 이미지 지정
    ports:
      - 8200:8200 # 로컬호스트 포트:컨테이너 포트 바인딩
    cap_add:
      - IPC_LOCK # 저장된 시크릿이 디스크로 스왑되지 않도록 컨테이너에서 메모리를 잠금
    environment:
      VAULT_DEV_ROOT_TOKEN_ID: "root" # 루트 토큰 환경 변수 지정

main.go

package main

import (
	"context"
	"log"

	vault "github.com/hashicorp/vault/api"
)

func main() {
	config := vault.DefaultConfig()

	config.Address = "http://127.0.0.1:8200"

	client, err := vault.NewClient(config)
	if err != nil {
		log.Fatalf("Error creating vault client: %s", err)
	}

	client.SetToken("root")

	secretData := map[string]interface{}{
		"password": "Hashi123",
	}

	ctx := context.Background()

	_, err = client.KVv2("secret").Put(ctx, "my-secret-password", secretData)
	if err != nil {
		log.Fatalf("unable to write secret: %v", err)
	}

	log.Println("Secret written successfully.")

	secret, err := client.KVv2("secret").Get(ctx, "my-secret-password")
	if err != nil {
		log.Fatalf("unable to read secret: %v", err)
	}

	value, ok := secret.Data["password"].(string)
	if !ok {
		log.Fatalf("value type assertion failed: %T %#v", secret.Data["password"], secret.Data["password"])
	}

	if value != "Hashi123" {
		log.Fatalf("unexpected password value %q retrieved from vault", value)
	}

	log.Println("Access granted!")
}

main 함수 분해

클라이언트 생성

config := vault.DefaultConfig() // 기본 설정값 불러오기

config.Address = "http://127.0.0.1:8200" // vault 서버의 주소 지정

client, err := vault.NewClient(config) // 설정값을 사용해 vault 클라이언트 생성
if err != nil {
	log.Fatalf("Error creating vault client: %s", err)
}

client.SetToken("root") // 클라이언트의 토큰을 직접 지정

시크릿 쓰기

secretData := map[string]interface{}{
	"password": "Hashi123",
}

ctx := context.Background()

// 경로를 "my-secret-password"로 지정하여 "secret" 경로에 마운트된 KV v2 시크릿 엔진에 시크릿 추가
// Put은 추가하려는 값이 이미 존재할 경우, 새로운 버전으로 값을 추가한다.
// 기존의 값은 GetVersion을 사용해 불러올 수 있다.
_, err = client.KVv2("secret").Put(ctx, "my-secret-password", secretData)
if err != nil {
	log.Fatalf("unable to write secret: %v", err)
}

log.Println("Secret written successfully.")

시크릿 읽기

// "my-secret-password" 경로에 저장된 시크릿을 "secret" 경로에 마운트된 KV v2 엔진으로부터 읽기
// Get은 최신 버전의 시크릿을 읽어들인다.
// 만약 최신 버전이 삭제되었다면, 데이터 필드는 nil이 되고 삭제된 시간이 포함된다.
secret, err := client.KVv2("secret").Get(ctx, "my-secret-password")
if err != nil {
	log.Fatalf("unable to read secret: %v", err)
}

value, ok := secret.Data["password"].(string)
if !ok {
	log.Fatalf("value type assertion failed: %T %#v", secret.Data["password"], secret.Data["password"])
}

if value != "Hashi123" {
	log.Fatalf("unexpected password value %q retrieved from vault", value)
}

log.Println("Access granted!")

실행

docker compose 실행

$ docker compose up -d

main.go 실행

$ go run main.go 
2023/10/25 14:46:35 Secret written successfully.
2023/10/25 14:46:35 Access granted!

docker compose 종료

$ docker compose down

참고자료

글에서 수정이 필요한 부분이나 설명이 부족한 부분이 있다면 댓글로 남겨주세요!