概要
これまで紹介したHashiCorp Vaultの使い方はCLIを使うのがメインでしたが、実際はアプリケーション内で秘密情報を扱うケースが多々あります。
VaultはGoのライブラリを提供しているので、様々なログイン方法を紹介しつつ秘密情報にアクセスしてみます。
環境
- Vault 0.10.3
- golang 1.10.3
CLIから秘密情報を登録する
あらかじめKey/Valueに秘密情報を登録しておきます。
$ vault kv put secret/my-secret my-value=s3cr3t Key Value --- ----- created_time 2018-07-13T15:01:30.440745605Z deletion_time n/a destroyed false version 2
登録されているか確認します。
$ vault kv get secret/my-secret ====== Metadata ====== Key Value --- ----- created_time 2018-07-13T15:01:30.440745605Z deletion_time n/a destroyed false version 2 ====== Data ====== Key Value --- ----- my-value s3cr3t
登録されてますね。
Goでアクセス
通常の使い方(固定トークン)
github.com/hashicorp/vault/apiというライブラリを提供しているのでそれを使います。
クライアントを用意し、それにトークンを渡せばCLIのようなコマンドが各種使えます。
まずは基本的な固定トークンでの使用方法です。
const ( vaultAddr = "http://YOUR_VAULT_ADDR:8200" staticToken = "YOUR_STATIC_TOKEN" ) var httpClient = &http.Client{ Timeout: 10 * time.Second, } func main() { token := staticToken client, err := api.NewClient(&api.Config{Address: vaultAddr, HttpClient: httpClient}) if err != nil { panic(err) } client.SetToken(token) data, err := client.Logical().Read("secret/data/my-secret") if err != nil { panic(err) } b, _ := json.Marshal(data.Data) fmt.Println(string(b)) }
実行
$ go run main.go | jq . { "data": { "my-value": "s3cr3t" }, "metadata": { "created_time": "2018-07-13T15:01:30.440745605Z", "deletion_time": "", "destroyed": false, "version": 2 } }
ちゃんと秘密情報を取得できました。
User&Passwordでログイン
固定トークンでなく、userpass authで認証してからトークンを発行し、それを使うようにしてみます。
func main() { token, err := userpassLogin() if err != nil { panic(err) } client, err := api.NewClient(&api.Config{Address: vaultAddr, HttpClient: httpClient}) if err != nil { panic(err) } client.SetToken(token) data, err := client.Logical().Read("secret/data/my-secret") if err != nil { panic(err) } b, _ := json.Marshal(data.Data) fmt.Println(string(b)) } const ( username = "jun06t" password = "foo" ) func userpassLogin() (string, error) { // create a vault client client, err := api.NewClient(&api.Config{Address: vaultAddr, HttpClient: httpClient}) if err != nil { return "", err } // to pass the password options := map[string]interface{}{ "password": password, } path := fmt.Sprintf("auth/userpass/login/%s", username) // PUT call to get a token secret, err := client.Logical().Write(path, options) if err != nil { return "", err } token := secret.Auth.ClientToken return token, nil }
実行
$ go run main.go | jq . { "data": { "my-value": "s3cr3t" }, "metadata": { "created_time": "2018-07-13T15:01:30.440745605Z", "deletion_time": "", "destroyed": false, "version": 2 } }
こちらも問題なく取得できました。
AWSのIAMでログイン
前回ローカルのCLIからIAMを使ってログインする方法を紹介しました。
今回はGoからログインします。
func main() { token, err := awsLogin() if err != nil { panic(err) } client, err := api.NewClient(&api.Config{Address: vaultAddr, HttpClient: httpClient}) if err != nil { panic(err) } client.SetToken(token) data, err := client.Logical().Read("secret/data/my-secret") if err != nil { panic(err) } b, _ := json.Marshal(data.Data) fmt.Println(string(b)) } const ( accessKey = "" secretKey = "" sessionToken = "" headerValue = "" ) func awsLogin() (string, error) { // get aws credential data, err := awsauth.GenerateLoginData(accessKey, secretKey, sessionToken, headerValue) if err != nil { return "", err } // create a vault client client, err := api.NewClient(&api.Config{Address: vaultAddr, HttpClient: httpClient}) if err != nil { return "", err } // PUT call to get a token secret, err := client.Logical().Write("auth/aws/login", data) if err != nil { return "", err } token := secret.Auth.ClientToken return token, nil }
実行
こちらも問題なく取得できました。
$ go run main.go | jq . { "data": { "my-value": "s3cr3t" }, "metadata": { "created_time": "2018-07-13T15:01:30.440745605Z", "deletion_time": "", "destroyed": false, "version": 2 } }
クレデンシャル情報は予め取得して置く必要がある
上のコードから分かるように、github.com/hashicorp/vault/builtin/credential/awsのGenerateLoginDataというメソッドを使っています。
CLIのコマンドだと
$ vault login -method=aws
で済んだので、パス(auth/aws/login
)さえ指定すれば良いのかなと思いましたがダメでした。
auditログを見てみると
CLIのログ
{ "time": "2018-07-13T01:37:57.465134866Z", "type": "request", "auth": { "client_token": "", "accessor": "", "display_name": "", "policies": null, "metadata": null, "entity_id": "" }, "request": { "id": "b5a58f9a-8dc8-90bd-34e7-5f55b22518ab", "operation": "update", "client_token": "", "client_token_accessor": "", "path": "auth/aws/login", "data": { "iam_http_request_method": "hmac-sha256:386d03a363ce39bc27a97ceb9f507ae5ad2251a2564d333ba2e13efcd8010384", "iam_request_body": "hmac-sha256:e53839a5a1f36971746b894c1b15d2f3f5dadbfa14f2967f0d5450c8bd27ead6", "iam_request_headers": "hmac-sha256:cea3204364b8295fb82512b7cc18065f004c18beaace6e11591d51d8da79125d", "iam_request_url": "hmac-sha256:8bfaab975daaf27f7e5cdfc10e51c50c50fe0f40243f821116d3e9c6a9657db7", "role": "hmac-sha256:6e72c3db169bfab3b7939815a865933969bfcef9925e07c6a8cb91a0cd4c735e" }, "policy_override": false, "remote_address": "10.74.0.242", "wrap_ttl": 0, "headers": {} }, "error": "" }
GenerateLoginData()
を使わなかったGoのログ
{ "time": "2018-07-13T01:36:49.420007917Z", "type": "request", "auth": { "client_token": "", "accessor": "", "display_name": "", "policies": null, "metadata": null, "entity_id": "" }, "request": { "id": "864c6c6e-f9ce-4097-174a-5261bda3c42b", "operation": "update", "client_token": "", "client_token_accessor": "", "path": "auth/aws/login", "data": null, "policy_override": false, "remote_address": "10.74.0.242", "wrap_ttl": 0, "headers": {} }, "error": "" }
このようにクレデンシャルが無いことが分かります。
なのでクレデンシャルを取得するメソッドがないかなぁと探したところ上記のメソッドを見つけました。
ソース上はどうなってる?
このメソッドのソースを見てみると
func GenerateLoginData(accessKey, secretKey, sessionToken, headerValue string) (map[string]interface{}, error) { loginData := make(map[string]interface{}) credConfig := &awsutil.CredentialsConfig{ AccessKey: accessKey, SecretKey: secretKey, SessionToken: sessionToken, } creds, err := credConfig.GenerateCredentialChain()
vault/cli.go at bd9ba940ef4180af5790325e0eaff7e7c3ce1cc2 · hashicorp/vault · GitHub
となっており、accessKey
等が明示的にあればそれを使いますが、無ければGenerateCredentialChain
ではaws-sdk-goのデフォルトのクレデンシャルの扱いのように
- EnvProvider:環境変数
- SharedCredentialsProvider:
~/.aws/credentials
- RemoteCredProvider:IAM Role
といったProviderを使ってクレデンシャルを取得するようになっています。
vault/generate_credentials.go at ce1b43cd487a3fa47734af3a68e270d15fa02f77 · hashicorp/vault · GitHub
なのでローカル開発では~/.aws/credentials
を使い、実際のサーバ上ではEC2 Roleを使えるのでアプリケーションコードのどこにもトークン等をべた書きする必要がありません。
まとめ
Goからvault serverにログインして秘密情報を取得する方法を紹介しました。
特に一番最後の方法では「結局vault用のトークンやuser&passがどこかにべた書きされてしまう」といった問題が無く、かつローカル開発でも支障なく使えるメリットがあるのでどんどん使っていきたいですね。
今回のコードは以下のgistにまとめてあります。
Golang Vault Login Sample · GitHub