/
grant.go
214 lines (176 loc) · 5.56 KB
/
grant.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// Copyright 2019 The Berglas Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package berglas
import (
"context"
"fmt"
"sort"
"cloud.google.com/go/iam"
"cloud.google.com/go/storage"
"github.com/sirupsen/logrus"
grpccodes "google.golang.org/grpc/codes"
grpcstatus "google.golang.org/grpc/status"
)
type grantRequest interface {
isGrantRequest()
}
// StorageGrantRequest is used as input to grant access to secrets backed Cloud
// Storage encrypted with Cloud KMS.
type StorageGrantRequest struct {
// Bucket is the name of the bucket where the secret lives.
Bucket string
// Object is the name of the object in Cloud Storage.
Object string
// Members is the list of membership bindings. This should be in the format
// described at https://godoc.org/google.golang.org/api/iam/v1#Binding.
Members []string
}
func (r *StorageGrantRequest) isGrantRequest() {}
// GrantRequest is an alias for StorageGrantRequest for
// backwards-compatibility. New clients should use StorageGrantRequest.
type GrantRequest = StorageGrantRequest
// SecretManagerGrantRequest is used as input to grant access to a secret in
// Secret Manager.
type SecretManagerGrantRequest struct {
// Project is the ID or number of the project where secrets live.
Project string
// Name is the name of the secret to access.
Name string
// Members is the list of membership bindings. This should be in the format
// described at https://godoc.org/google.golang.org/api/iam/v1#Binding.
Members []string
}
func (r *SecretManagerGrantRequest) isGrantRequest() {}
// Grant is a top-level package function for granting access to a secret. For
// large volumes of secrets, please create a client instead.
func Grant(ctx context.Context, i grantRequest) error {
client, err := New(ctx)
if err != nil {
return err
}
return client.Grant(ctx, i)
}
// Grant adds IAM permission to the given entity to the storage object and the
// underlying KMS key.
func (c *Client) Grant(ctx context.Context, i grantRequest) error {
if i == nil {
return fmt.Errorf("missing request")
}
switch t := i.(type) {
case *SecretManagerGrantRequest:
return c.secretManagerGrant(ctx, t)
case *StorageGrantRequest:
return c.storageGrant(ctx, t)
default:
return fmt.Errorf("unknown grant type %T", t)
}
}
func (c *Client) secretManagerGrant(ctx context.Context, i *SecretManagerGrantRequest) error {
project := i.Project
if project == "" {
return fmt.Errorf("missing project")
}
name := i.Name
if name == "" {
return fmt.Errorf("missing secret name")
}
members := i.Members
if len(members) == 0 {
return nil
}
sort.Strings(members)
logger := c.Logger().WithFields(logrus.Fields{
"project": project,
"name": name,
"members": members,
})
logger.Debug("grant.start")
defer logger.Debug("grant.finish")
logger.Debug("granting access to secret")
storageHandle := c.secretManagerIAM(project, name)
if err := updateIAMPolicy(ctx, storageHandle, func(p *iam.Policy) *iam.Policy {
for _, m := range members {
p.Add(m, iamSecretManagerAccessor)
}
return p
}); err != nil {
terr, ok := grpcstatus.FromError(err)
if ok && terr.Code() == grpccodes.NotFound {
return errSecretDoesNotExist
}
return fmt.Errorf("failed to update Secret Manager IAM policy for %s: %w", name, err)
}
return nil
}
func (c *Client) storageGrant(ctx context.Context, i *StorageGrantRequest) error {
bucket := i.Bucket
if bucket == "" {
return fmt.Errorf("missing bucket name")
}
object := i.Object
if object == "" {
return fmt.Errorf("missing object name")
}
members := i.Members
if len(members) == 0 {
return nil
}
sort.Strings(members)
logger := c.Logger().WithFields(logrus.Fields{
"bucket": bucket,
"object": object,
"members": members,
})
logger.Debug("grant.start")
defer logger.Debug("grant.finish")
// Get attributes to find the KMS key
logger.Debug("finding storage object")
objHandle := c.storageClient.Bucket(bucket).Object(object)
attrs, err := objHandle.Attrs(ctx)
if err == storage.ErrObjectNotExist {
return errSecretDoesNotExist
}
if err != nil {
return fmt.Errorf("failed to read secret metadata: %w", err)
}
if attrs.Metadata == nil || attrs.Metadata[MetadataKMSKey] == "" {
return fmt.Errorf("missing kms key in secret metadata")
}
key := attrs.Metadata[MetadataKMSKey]
logger = logger.WithField("key", key)
logger.Debug("found kms key")
// Grant access to storage
logger.Debug("granting access to storage")
storageHandle := c.storageIAM(bucket, object)
if err := updateIAMPolicy(ctx, storageHandle, func(p *iam.Policy) *iam.Policy {
for _, m := range members {
p.Add(m, iamObjectReader)
}
return p
}); err != nil {
return fmt.Errorf("failed to update Storage IAM policy for %s: %w", object, err)
}
// Grant access to KMS
logger.Debug("granting access to kms")
kmsHandle := c.kmsClient.ResourceIAM(key)
if err := updateIAMPolicy(ctx, kmsHandle, func(p *iam.Policy) *iam.Policy {
for _, m := range members {
p.Add(m, iamKMSDecrypt)
}
return p
}); err != nil {
return fmt.Errorf("failed to update KMS IAM policy for %s: %w", key, err)
}
return nil
}