Sionの技術ブログ

SREとして日々の学習を書いて行きます。twitterは@sion_cojp

Go - AWS SSM Parameter Storeのデータを復号化とmockテストの書き方

SSMとは?

インフラ運用を便利にするサービス

SSM ParameterStoreとは?

パスワードなど値を管理

SecureStringsは裏でKMSを使って文字列を暗号化して保持も出来るよ!

(料金かかるけど、そんなたいしたことない。前提知識として持っててね! 料金 - AWS Key Management Service (KMS)| AWS

DBのパスワードを保管して呼び出す、みたいなので使えそう





実際にやってみよう

1. ParameterStoreの登録

SystemManager->Parameter Store->パラメーターの作成

安全な文字列にチェックをいれて"hogehoge"と入れてみます

f:id:sion_cojp:20180115210244p:plain




2. aws-cliで試してみる

### --with-decryptionを外すと暗号化された文字が出力されます
$ aws ssm get-parameters --region ap-northeast-1 --name sion_test --with-decryption
{
    "Parameters": [
        {
            "Name": "sion_test",
            "Type": "SecureString",
            "Value": "hogehoge",
            "Version": 1
        }
    ],
    "InvalidParameters": []
}




3. Goで動かす

あとでテストでSSM mockを使うので、ssmiface.SSMAPIを使います

package main

import (
    "fmt"
    "os"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/ssm"
    "github.com/aws/aws-sdk-go/service/ssm/ssmiface"
)

func main() {
    d := newssmDecrypter()
    s, err := d.decrypt("sion_test")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    fmt.Println(s)
}

// ssmDecrypter stores the AWS Session used for SSM decrypter.
type ssmDecrypter struct {
    sess *session.Session
    svc  ssmiface.SSMAPI
}

// newssmDecrypter returns a new ssmDecrypter.
func newssmDecrypter() *ssmDecrypter {
    sess := session.New()
    svc := ssm.New(sess)
    return &ssmDecrypter{sess, svc}
}

// decrypt decrypts string.
func (d *ssmDecrypter) decrypt(encrypted string) (string, error) {
    params := &ssm.GetParameterInput{
        Name:           aws.String(encrypted),
        WithDecryption: aws.Bool(true),
    }
    resp, err := d.svc.GetParameter(params)
    if err != nil {
        return "", err
    }
    return *resp.Parameter.Value, nil
}

// $ export AWS_REGION=ap-northeast-1 && export AWS_PROFILE=xxxxxx && go run main.go
// hogehoge




4. テストを書く

SSMのmockを使います

aws-sdk-go/interface.go at master · aws/aws-sdk-go · GitHub

こちらをみればわかると思いますが、色々とinterfaceが用意されてる + サンプルコードも書いてます

今回はGetParameterメソッドをmock用に作ります("decrypted"の文字列を返すようにする)

package main

import (
    "reflect"
    "testing"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/service/ssm"
    "github.com/aws/aws-sdk-go/service/ssm/ssmiface"
)

// mockSSMClient stores SSM interface for mock
type mockSSMClient struct {
    ssmiface.SSMAPI
}

// newTestssmDecrypter returns a new ssmDecrypter for mock.
func newTestssmDecrypter(mock ssmiface.SSMAPI) *ssmDecrypter {
    return &ssmDecrypter{
        svc: mock,
    }
}

// GetParameter returns "decrypted" that is Decrypted SSM parameter.
func (m *mockSSMClient) GetParameter(i *ssm.GetParameterInput) (*ssm.GetParameterOutput, error) {
    parameter := &ssm.Parameter{
        Value: aws.String("decrypted"),
    }

    return &ssm.GetParameterOutput{
        Parameter: parameter,
    }, nil
}

func TestDecrypt(t *testing.T) {
    mock := &mockSSMClient{}
    d := newTestssmDecrypter(mock)

    cases := []struct {
        value    string
        expected interface{}
    }{
        // string decrypt
        {
            "a",
            "decrypted",
        },
    }
    for _, c := range cases {
        s, err := d.decrypt(c.value)
        if err != nil {
            t.Fatalf("failed decrypt: %s", err)
        }

        if !reflect.DeepEqual(c.expected, s) {
            t.Errorf("want %s got %s", c.expected, s)
        }
    }
}
$ go test -v .
s-koyama-m:hoho s-koyama$ go test -v .
=== RUN   TestDecrypt
--- PASS: TestDecrypt (0.00s)
PASS
ok      my/ssm  0.017s