aboutsummaryrefslogtreecommitdiff
path: root/secrets
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2022-12-23 22:21:50 -0800
committerMike Crute <mike@crute.us>2022-12-23 22:21:50 -0800
commit15ef537afcded7561f59d1890df14af7f8277752 (patch)
tree5b1607aba55f6dacc1e4c86ab05a136cd8267390 /secrets
parentd4a56ba3c37ea7141854d0c1196ffee41fbaca6d (diff)
downloadgolib-15ef537afcded7561f59d1890df14af7f8277752.tar.bz2
golib-15ef537afcded7561f59d1890df14af7f8277752.tar.xz
golib-15ef537afcded7561f59d1890df14af7f8277752.zip
secrets: add config file clientsecrets/v0.3.1
Diffstat (limited to 'secrets')
-rw-r--r--secrets/client.go14
-rw-r--r--secrets/config_file_client.go157
-rw-r--r--secrets/config_file_client_test.go122
-rw-r--r--secrets/go.mod5
-rw-r--r--secrets/go.sum14
5 files changed, 302 insertions, 10 deletions
diff --git a/secrets/client.go b/secrets/client.go
index 779f28c..aaa45d7 100644
--- a/secrets/client.go
+++ b/secrets/client.go
@@ -25,22 +25,22 @@ type Renewal struct {
25} 25}
26 26
27type Credential struct { 27type Credential struct {
28 Username string `json:"username" mapstructure:"username"` 28 Username string `json:"username" mapstructure:"username" yaml:"username"`
29 Password string `json:"password" mapstructure:"password"` 29 Password string `json:"password" mapstructure:"password" yaml:"password"`
30} 30}
31 31
32type ApiKey struct { 32type ApiKey struct {
33 Key string `json:"key" mapstructure:"key"` 33 Key string `json:"key" mapstructure:"key" yaml:"key"`
34} 34}
35 35
36type AWSCredential struct { 36type AWSCredential struct {
37 AccessKeyId string `json:"access_key" mapstructure:"access_key"` 37 AccessKeyId string `json:"access_key" mapstructure:"access_key" yaml:"access_key"`
38 SecretAccessKey string `json:"secret_key" mapstructure:"secret_key"` 38 SecretAccessKey string `json:"secret_key" mapstructure:"secret_key" yaml:"secret_key"`
39 SessionToken string `json:"security_token" mapstructure:"security_token"` 39 SessionToken string `json:"security_token" mapstructure:"security_token" yaml:"security_token"`
40} 40}
41 41
42type RSAKey struct { 42type RSAKey struct {
43 Key string `json:"key" mapstructure:"key"` 43 Key string `json:"key" mapstructure:"key" yaml:"key"`
44} 44}
45 45
46func (k *RSAKey) RSAPrivateKey() (*rsa.PrivateKey, error) { 46func (k *RSAKey) RSAPrivateKey() (*rsa.PrivateKey, error) {
diff --git a/secrets/config_file_client.go b/secrets/config_file_client.go
new file mode 100644
index 0000000..ef2907e
--- /dev/null
+++ b/secrets/config_file_client.go
@@ -0,0 +1,157 @@
1package secrets
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io/fs"
8 "path/filepath"
9 "sync"
10 "time"
11
12 "github.com/mitchellh/mapstructure"
13 "gopkg.in/yaml.v2"
14)
15
16type ConfigFileHandle struct{}
17
18func (h *ConfigFileHandle) Reference() string { return "NOOP" }
19
20var _ Handle = (*ConfigFileHandle)(nil)
21
22// ConfigFileClient returns secrets from a JSON or YAML configuration
23// file. This mode isn't as secure as using Vault or some other secret
24// management service but can be useful for users who don't have access
25// to such a service.
26//
27// Writes to this secret client will silently succeed while doing
28// nothing.
29type ConfigFileClient struct {
30 c chan Renewal
31 cfg map[string]any
32}
33
34var _ Client = (*ConfigFileClient)(nil)
35var _ ClientManager = (*ConfigFileClient)(nil)
36
37// NewConfigFileClient creates a new ConfigFileClient by loading a named
38// config file from a filesystem and unmarshalling it. The config file
39// can be in JSON or YAML format, determined by a .json, .yaml, or .yml
40// extension. The configuration must be nested within a key in that file
41// to support sharing the file with other subsystems.
42//
43// Credentials should be stored in the config file in a format that
44// matches their definitions in client.go
45func NewConfigFileClient(filesystem fs.FS, name, key string) (ClientManager, error) {
46 fp, err := filesystem.Open(name)
47 if err != nil {
48 return nil, err
49 }
50 defer fp.Close()
51
52 fc := map[string]any{}
53
54 switch e := filepath.Ext(name); e {
55 case ".yaml", ".yml":
56 if err := yaml.NewDecoder(fp).Decode(&fc); err != nil {
57 return nil, err
58 }
59 case ".json":
60 if err := json.NewDecoder(fp).Decode(&fc); err != nil {
61 return nil, err
62 }
63 default:
64 return nil, fmt.Errorf("Config files with extension %s are not supported", e)
65 }
66
67 ck, ok := fc[key]
68 if !ok {
69 return nil, fmt.Errorf("Key %s does not exist in config file", key)
70 }
71
72 cfg := map[string]any{}
73 if err := mapstructure.Decode(ck, &cfg); err != nil {
74 return nil, fmt.Errorf("Config file key was not of correct type: %w", err)
75 }
76
77 return &ConfigFileClient{
78 c: make(chan Renewal),
79 cfg: cfg,
80 }, nil
81}
82
83func (c *ConfigFileClient) DatabaseCredential(ctx context.Context, path string) (*Credential, Handle, error) {
84 out := Credential{}
85 hnd, err := c.RawSecret(ctx, path, &out)
86 if err != nil {
87 return nil, nil, err
88 }
89 return &out, hnd, nil
90}
91
92func (c *ConfigFileClient) Secret(ctx context.Context, path string, out any) (Handle, error) {
93 return c.RawSecret(ctx, path, out)
94}
95
96func (c *ConfigFileClient) RawSecret(ctx context.Context, path string, out any) (Handle, error) {
97 v, ok := c.cfg[path]
98 if !ok {
99 return nil, fmt.Errorf("Secret %s was not found in config file", path)
100 }
101
102 if err := mapstructure.Decode(v, &out); err != nil {
103 return nil, err
104 }
105
106 return &ConfigFileHandle{}, nil
107}
108
109func (c *ConfigFileClient) AWSIAMUser(ctx context.Context, name string) (*AWSCredential, Handle, error) {
110 out := AWSCredential{}
111 hnd, err := c.RawSecret(ctx, name, &out)
112 if err != nil {
113 return nil, nil, err
114 }
115 return &out, hnd, nil
116}
117
118func (c *ConfigFileClient) AWSAssumeRoleSimple(ctx context.Context, name string) (*AWSCredential, Handle, error) {
119 return c.AWSAssumeRole(ctx, name, "", time.Second)
120}
121
122func (c *ConfigFileClient) AWSAssumeRole(ctx context.Context, name string, sessionName string, ttl time.Duration) (*AWSCredential, Handle, error) {
123 return nil, nil, fmt.Errorf("Assuming AWS roles is not supported by ConfigFileClient")
124}
125
126func (c *ConfigFileClient) WriteSecret(ctx context.Context, path string, in any) error {
127 return nil
128}
129
130func (c *ConfigFileClient) Destroy(h Handle) error {
131 return nil
132}
133
134func (c *ConfigFileClient) MakeNonCritical(h Handle) error {
135 return nil
136}
137
138func (c *ConfigFileClient) Authenticate(ctx context.Context) error {
139 return nil
140}
141
142func (c *ConfigFileClient) Notifications() <-chan Renewal {
143 return c.c
144}
145
146func (c *ConfigFileClient) Run(ctx context.Context, wg *sync.WaitGroup) error {
147 wg.Add(1)
148 defer wg.Done()
149
150 for {
151 select {
152 case <-ctx.Done():
153 close(c.c)
154 return nil
155 }
156 }
157}
diff --git a/secrets/config_file_client_test.go b/secrets/config_file_client_test.go
new file mode 100644
index 0000000..e4ba448
--- /dev/null
+++ b/secrets/config_file_client_test.go
@@ -0,0 +1,122 @@
1package secrets
2
3import (
4 "context"
5 "testing"
6 "testing/fstest"
7 "time"
8
9 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/suite"
11)
12
13var testFs fstest.MapFS
14
15var jsonTestConfig = []byte(`{
16 "secrets": {
17 "database": {
18 "username": "dbuser",
19 "password": "dbpass"
20 },
21 "iam": {
22 "access_key": "accesskey",
23 "secret_key": "secretkey"
24 },
25 "apikey": {
26 "key": "api"
27 }
28 }
29}`)
30
31var yamlTestConfig = []byte(`
32secrets:
33 database:
34 username: dbuser
35 password: dbpass
36 iam:
37 access_key: accesskey
38 secret_key: secretkey
39 apikey:
40 key: api
41`)
42
43func init() {
44 testFs = fstest.MapFS{}
45 testFs["secrets.foo"] = &fstest.MapFile{Data: jsonTestConfig}
46 testFs["secrets.json"] = &fstest.MapFile{Data: jsonTestConfig}
47 testFs["secrets.yaml"] = &fstest.MapFile{Data: yamlTestConfig}
48 testFs["secrets.yml"] = &fstest.MapFile{Data: yamlTestConfig}
49}
50
51type ConfigFileClientSuite struct {
52 suite.Suite
53 c Client
54}
55
56func (s *ConfigFileClientSuite) SetupTest() {
57 var err error
58 s.c, err = NewConfigFileClient(testFs, "secrets.yaml", "secrets")
59 assert.NoError(s.T(), err)
60}
61
62func (s *ConfigFileClientSuite) TestFromJSON() {
63 _, err := NewConfigFileClient(testFs, "secrets.json", "secrets")
64 assert.NoError(s.T(), err)
65}
66
67func (s *ConfigFileClientSuite) TestFromYAML() {
68 _, err := NewConfigFileClient(testFs, "secrets.yaml", "secrets")
69 assert.NoError(s.T(), err)
70 _, err = NewConfigFileClient(testFs, "secrets.yml", "secrets")
71 assert.NoError(s.T(), err)
72}
73
74func (s *ConfigFileClientSuite) TestFromInvalidExtension() {
75 _, err := NewConfigFileClient(testFs, "secrets.foo", "secrets")
76 assert.ErrorContains(s.T(), err, "extension .foo are not supported")
77}
78
79func (s *ConfigFileClientSuite) TestFromInvalidFile() {
80 _, err := NewConfigFileClient(testFs, "secrets.foo-", "secrets")
81 assert.ErrorContains(s.T(), err, "file does not exist")
82}
83
84func (s *ConfigFileClientSuite) TestFromInvalidKey() {
85 _, err := NewConfigFileClient(testFs, "secrets.json", "sekrets")
86 assert.ErrorContains(s.T(), err, "Key sekrets does not exist")
87}
88
89func (s *ConfigFileClientSuite) TestDatabaseCredential() {
90 db, hnd, err := s.c.DatabaseCredential(context.TODO(), "database")
91 assert.NoError(s.T(), err)
92 assert.NotNil(s.T(), hnd)
93 assert.Equal(s.T(), db.Username, "dbuser")
94 assert.Equal(s.T(), db.Password, "dbpass")
95}
96
97func (s *ConfigFileClientSuite) TestSecret() {
98 key := ApiKey{}
99 hnd, err := s.c.Secret(context.TODO(), "apikey", &key)
100 assert.NoError(s.T(), err)
101 assert.NotNil(s.T(), hnd)
102 assert.Equal(s.T(), key.Key, "api")
103}
104
105func (s *ConfigFileClientSuite) TestAWSIAMUser() {
106 iam, hnd, err := s.c.AWSIAMUser(context.TODO(), "iam")
107 assert.NoError(s.T(), err)
108 assert.NotNil(s.T(), hnd)
109 assert.Equal(s.T(), iam.AccessKeyId, "accesskey")
110 assert.Equal(s.T(), iam.SecretAccessKey, "secretkey")
111}
112
113func (s *ConfigFileClientSuite) TestAWSAssumeRole() {
114 _, _, err := s.c.AWSAssumeRole(context.TODO(), "role", "name", time.Second)
115 assert.ErrorContains(s.T(), err, "not supported")
116 _, _, err = s.c.AWSAssumeRoleSimple(context.TODO(), "role")
117 assert.ErrorContains(s.T(), err, "not supported")
118}
119
120func TestConfigFileClientSuite(t *testing.T) {
121 suite.Run(t, &ConfigFileClientSuite{})
122}
diff --git a/secrets/go.mod b/secrets/go.mod
index ca23257..89a03d8 100644
--- a/secrets/go.mod
+++ b/secrets/go.mod
@@ -7,12 +7,15 @@ require (
7 github.com/hashicorp/vault/api v1.8.0 7 github.com/hashicorp/vault/api v1.8.0
8 github.com/hashicorp/vault/api/auth/approle v0.3.0 8 github.com/hashicorp/vault/api/auth/approle v0.3.0
9 github.com/mitchellh/mapstructure v1.5.0 9 github.com/mitchellh/mapstructure v1.5.0
10 github.com/stretchr/testify v1.8.1
11 gopkg.in/yaml.v2 v2.2.5
10) 12)
11 13
12require ( 14require (
13 github.com/armon/go-metrics v0.3.9 // indirect 15 github.com/armon/go-metrics v0.3.9 // indirect
14 github.com/armon/go-radix v1.0.0 // indirect 16 github.com/armon/go-radix v1.0.0 // indirect
15 github.com/cenkalti/backoff/v3 v3.0.0 // indirect 17 github.com/cenkalti/backoff/v3 v3.0.0 // indirect
18 github.com/davecgh/go-spew v1.1.1 // indirect
16 github.com/fatih/color v1.7.0 // indirect 19 github.com/fatih/color v1.7.0 // indirect
17 github.com/golang/protobuf v1.5.2 // indirect 20 github.com/golang/protobuf v1.5.2 // indirect
18 github.com/golang/snappy v0.0.4 // indirect 21 github.com/golang/snappy v0.0.4 // indirect
@@ -42,6 +45,7 @@ require (
42 github.com/mitchellh/reflectwalk v1.0.0 // indirect 45 github.com/mitchellh/reflectwalk v1.0.0 // indirect
43 github.com/oklog/run v1.0.0 // indirect 46 github.com/oklog/run v1.0.0 // indirect
44 github.com/pierrec/lz4 v2.5.2+incompatible // indirect 47 github.com/pierrec/lz4 v2.5.2+incompatible // indirect
48 github.com/pmezard/go-difflib v1.0.0 // indirect
45 github.com/ryanuber/go-glob v1.0.0 // indirect 49 github.com/ryanuber/go-glob v1.0.0 // indirect
46 go.uber.org/atomic v1.9.0 // indirect 50 go.uber.org/atomic v1.9.0 // indirect
47 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect 51 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
@@ -53,4 +57,5 @@ require (
53 google.golang.org/grpc v1.41.0 // indirect 57 google.golang.org/grpc v1.41.0 // indirect
54 google.golang.org/protobuf v1.26.0 // indirect 58 google.golang.org/protobuf v1.26.0 // indirect
55 gopkg.in/square/go-jose.v2 v2.5.1 // indirect 59 gopkg.in/square/go-jose.v2 v2.5.1 // indirect
60 gopkg.in/yaml.v3 v3.0.1 // indirect
56) 61)
diff --git a/secrets/go.sum b/secrets/go.sum
index b27d5e2..46fa656 100644
--- a/secrets/go.sum
+++ b/secrets/go.sum
@@ -219,14 +219,19 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH
219github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 219github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
220github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 220github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
221github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 221github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
222github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
223github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 222github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
223github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
224github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
225github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
224github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 226github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
225github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 227github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
226github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 228github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
227github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 229github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
228github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
229github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 230github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
231github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
232github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
233github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
234github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
230github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= 235github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
231go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 236go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
232go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 237go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
@@ -322,6 +327,7 @@ google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/l
322google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 327google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
323gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 328gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
324gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 329gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
330gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
325gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 331gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
326gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= 332gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
327gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 333gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
@@ -329,8 +335,10 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
329gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 335gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
330gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 336gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
331gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 337gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
338gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
332gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 339gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
333gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
334gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 340gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
341gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
342gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
335honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 343honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
336honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 344honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=