1// Copyright 2019 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package storage 16 17import ( 18 "context" 19 "errors" 20 "fmt" 21 "time" 22 23 "google.golang.org/api/iterator" 24 raw "google.golang.org/api/storage/v1" 25) 26 27// HMACState is the state of the HMAC key. 28// 29// This type is EXPERIMENTAL and subject to change or removal without notice. 30type HMACState string 31 32const ( 33 // Active is the status for an active key that can be used to sign 34 // requests. 35 Active HMACState = "ACTIVE" 36 37 // Inactive is the status for an inactive key thus requests signed by 38 // this key will be denied. 39 Inactive HMACState = "INACTIVE" 40 41 // Deleted is the status for a key that is deleted. 42 // Once in this state the key cannot key cannot be recovered 43 // and does not count towards key limits. Deleted keys will be cleaned 44 // up later. 45 Deleted HMACState = "DELETED" 46) 47 48// HMACKey is the representation of a Google Cloud Storage HMAC key. 49// 50// HMAC keys are used to authenticate signed access to objects. To enable HMAC key 51// authentication, please visit https://cloud.google.com/storage/docs/migrating. 52// 53// This type is EXPERIMENTAL and subject to change or removal without notice. 54type HMACKey struct { 55 // The HMAC's secret key. 56 Secret string 57 58 // AccessID is the ID of the HMAC key. 59 AccessID string 60 61 // Etag is the HTTP/1.1 Entity tag. 62 Etag string 63 64 // ID is the ID of the HMAC key, including the ProjectID and AccessID. 65 ID string 66 67 // ProjectID is the ID of the project that owns the 68 // service account to which the key authenticates. 69 ProjectID string 70 71 // ServiceAccountEmail is the email address 72 // of the key's associated service account. 73 ServiceAccountEmail string 74 75 // CreatedTime is the creation time of the HMAC key. 76 CreatedTime time.Time 77 78 // UpdatedTime is the last modification time of the HMAC key metadata. 79 UpdatedTime time.Time 80 81 // State is the state of the HMAC key. 82 // It can be one of StateActive, StateInactive or StateDeleted. 83 State HMACState 84} 85 86// HMACKeyHandle helps provide access and management for HMAC keys. 87// 88// This type is EXPERIMENTAL and subject to change or removal without notice. 89type HMACKeyHandle struct { 90 projectID string 91 accessID string 92 93 raw *raw.ProjectsHmacKeysService 94} 95 96// HMACKeyHandle creates a handle that will be used for HMACKey operations. 97// 98// This method is EXPERIMENTAL and subject to change or removal without notice. 99func (c *Client) HMACKeyHandle(projectID, accessID string) *HMACKeyHandle { 100 return &HMACKeyHandle{ 101 projectID: projectID, 102 accessID: accessID, 103 raw: raw.NewProjectsHmacKeysService(c.raw), 104 } 105} 106 107// Get invokes an RPC to retrieve the HMAC key referenced by the 108// HMACKeyHandle's accessID. 109// 110// Options such as UserProjectForHMACKeys can be used to set the 111// userProject to be billed against for operations. 112// 113// This method is EXPERIMENTAL and subject to change or removal without notice. 114func (hkh *HMACKeyHandle) Get(ctx context.Context, opts ...HMACKeyOption) (*HMACKey, error) { 115 call := hkh.raw.Get(hkh.projectID, hkh.accessID) 116 117 desc := new(hmacKeyDesc) 118 for _, opt := range opts { 119 opt.withHMACKeyDesc(desc) 120 } 121 if desc.userProjectID != "" { 122 call = call.UserProject(desc.userProjectID) 123 } 124 125 setClientHeader(call.Header()) 126 127 var metadata *raw.HmacKeyMetadata 128 var err error 129 err = runWithRetry(ctx, func() error { 130 metadata, err = call.Context(ctx).Do() 131 return err 132 }) 133 if err != nil { 134 return nil, err 135 } 136 137 hkPb := &raw.HmacKey{ 138 Metadata: metadata, 139 } 140 return pbHmacKeyToHMACKey(hkPb, false) 141} 142 143// Delete invokes an RPC to delete the key referenced by accessID, on Google Cloud Storage. 144// Only inactive HMAC keys can be deleted. 145// After deletion, a key cannot be used to authenticate requests. 146// 147// This method is EXPERIMENTAL and subject to change or removal without notice. 148func (hkh *HMACKeyHandle) Delete(ctx context.Context, opts ...HMACKeyOption) error { 149 delCall := hkh.raw.Delete(hkh.projectID, hkh.accessID) 150 desc := new(hmacKeyDesc) 151 for _, opt := range opts { 152 opt.withHMACKeyDesc(desc) 153 } 154 if desc.userProjectID != "" { 155 delCall = delCall.UserProject(desc.userProjectID) 156 } 157 setClientHeader(delCall.Header()) 158 159 return runWithRetry(ctx, func() error { 160 return delCall.Context(ctx).Do() 161 }) 162} 163 164func pbHmacKeyToHMACKey(pb *raw.HmacKey, updatedTimeCanBeNil bool) (*HMACKey, error) { 165 pbmd := pb.Metadata 166 if pbmd == nil { 167 return nil, errors.New("field Metadata cannot be nil") 168 } 169 createdTime, err := time.Parse(time.RFC3339, pbmd.TimeCreated) 170 if err != nil { 171 return nil, fmt.Errorf("field CreatedTime: %v", err) 172 } 173 updatedTime, err := time.Parse(time.RFC3339, pbmd.Updated) 174 if err != nil && !updatedTimeCanBeNil { 175 return nil, fmt.Errorf("field UpdatedTime: %v", err) 176 } 177 178 hmk := &HMACKey{ 179 AccessID: pbmd.AccessId, 180 Secret: pb.Secret, 181 Etag: pbmd.Etag, 182 ID: pbmd.Id, 183 State: HMACState(pbmd.State), 184 ProjectID: pbmd.ProjectId, 185 CreatedTime: createdTime, 186 UpdatedTime: updatedTime, 187 188 ServiceAccountEmail: pbmd.ServiceAccountEmail, 189 } 190 191 return hmk, nil 192} 193 194// CreateHMACKey invokes an RPC for Google Cloud Storage to create a new HMACKey. 195// 196// This method is EXPERIMENTAL and subject to change or removal without notice. 197func (c *Client) CreateHMACKey(ctx context.Context, projectID, serviceAccountEmail string, opts ...HMACKeyOption) (*HMACKey, error) { 198 if projectID == "" { 199 return nil, errors.New("storage: expecting a non-blank projectID") 200 } 201 if serviceAccountEmail == "" { 202 return nil, errors.New("storage: expecting a non-blank service account email") 203 } 204 205 svc := raw.NewProjectsHmacKeysService(c.raw) 206 call := svc.Create(projectID, serviceAccountEmail) 207 desc := new(hmacKeyDesc) 208 for _, opt := range opts { 209 opt.withHMACKeyDesc(desc) 210 } 211 if desc.userProjectID != "" { 212 call = call.UserProject(desc.userProjectID) 213 } 214 215 setClientHeader(call.Header()) 216 217 hkPb, err := call.Context(ctx).Do() 218 if err != nil { 219 return nil, err 220 } 221 222 return pbHmacKeyToHMACKey(hkPb, true) 223} 224 225// HMACKeyAttrsToUpdate defines the attributes of an HMACKey that will be updated. 226// 227// This type is EXPERIMENTAL and subject to change or removal without notice. 228type HMACKeyAttrsToUpdate struct { 229 // State is required and must be either StateActive or StateInactive. 230 State HMACState 231 232 // Etag is an optional field and it is the HTTP/1.1 Entity tag. 233 Etag string 234} 235 236// Update mutates the HMACKey referred to by accessID. 237// 238// This method is EXPERIMENTAL and subject to change or removal without notice. 239func (h *HMACKeyHandle) Update(ctx context.Context, au HMACKeyAttrsToUpdate, opts ...HMACKeyOption) (*HMACKey, error) { 240 if au.State != Active && au.State != Inactive { 241 return nil, fmt.Errorf("storage: invalid state %q for update, must be either %q or %q", au.State, Active, Inactive) 242 } 243 244 call := h.raw.Update(h.projectID, h.accessID, &raw.HmacKeyMetadata{ 245 Etag: au.Etag, 246 State: string(au.State), 247 }) 248 249 desc := new(hmacKeyDesc) 250 for _, opt := range opts { 251 opt.withHMACKeyDesc(desc) 252 } 253 if desc.userProjectID != "" { 254 call = call.UserProject(desc.userProjectID) 255 } 256 setClientHeader(call.Header()) 257 258 var metadata *raw.HmacKeyMetadata 259 var err error 260 err = runWithRetry(ctx, func() error { 261 metadata, err = call.Context(ctx).Do() 262 return err 263 }) 264 265 if err != nil { 266 return nil, err 267 } 268 hkPb := &raw.HmacKey{ 269 Metadata: metadata, 270 } 271 return pbHmacKeyToHMACKey(hkPb, false) 272} 273 274// An HMACKeysIterator is an iterator over HMACKeys. 275// 276// Note: This iterator is not safe for concurrent operations without explicit synchronization. 277// 278// This type is EXPERIMENTAL and subject to change or removal without notice. 279type HMACKeysIterator struct { 280 ctx context.Context 281 raw *raw.ProjectsHmacKeysService 282 projectID string 283 hmacKeys []*HMACKey 284 pageInfo *iterator.PageInfo 285 nextFunc func() error 286 index int 287 desc hmacKeyDesc 288} 289 290// ListHMACKeys returns an iterator for listing HMACKeys. 291// 292// Note: This iterator is not safe for concurrent operations without explicit synchronization. 293// 294// This method is EXPERIMENTAL and subject to change or removal without notice. 295func (c *Client) ListHMACKeys(ctx context.Context, projectID string, opts ...HMACKeyOption) *HMACKeysIterator { 296 it := &HMACKeysIterator{ 297 ctx: ctx, 298 raw: raw.NewProjectsHmacKeysService(c.raw), 299 projectID: projectID, 300 } 301 302 for _, opt := range opts { 303 opt.withHMACKeyDesc(&it.desc) 304 } 305 306 it.pageInfo, it.nextFunc = iterator.NewPageInfo( 307 it.fetch, 308 func() int { return len(it.hmacKeys) - it.index }, 309 func() interface{} { 310 prev := it.hmacKeys 311 it.hmacKeys = it.hmacKeys[:0] 312 it.index = 0 313 return prev 314 }) 315 return it 316} 317 318// Next returns the next result. Its second return value is iterator.Done if 319// there are no more results. Once Next returns iterator.Done, all subsequent 320// calls will return iterator.Done. 321// 322// Note: This iterator is not safe for concurrent operations without explicit synchronization. 323// 324// This method is EXPERIMENTAL and subject to change or removal without notice. 325func (it *HMACKeysIterator) Next() (*HMACKey, error) { 326 if err := it.nextFunc(); err != nil { 327 return nil, err 328 } 329 330 key := it.hmacKeys[it.index] 331 it.index++ 332 333 return key, nil 334} 335 336// PageInfo supports pagination. See the google.golang.org/api/iterator package for details. 337// 338// Note: This iterator is not safe for concurrent operations without explicit synchronization. 339// 340// This method is EXPERIMENTAL and subject to change or removal without notice. 341func (it *HMACKeysIterator) PageInfo() *iterator.PageInfo { return it.pageInfo } 342 343func (it *HMACKeysIterator) fetch(pageSize int, pageToken string) (token string, err error) { 344 call := it.raw.List(it.projectID) 345 setClientHeader(call.Header()) 346 if pageToken != "" { 347 call = call.PageToken(pageToken) 348 } 349 if it.desc.showDeletedKeys { 350 call = call.ShowDeletedKeys(true) 351 } 352 if it.desc.userProjectID != "" { 353 call = call.UserProject(it.desc.userProjectID) 354 } 355 if it.desc.forServiceAccountEmail != "" { 356 call = call.ServiceAccountEmail(it.desc.forServiceAccountEmail) 357 } 358 if pageSize > 0 { 359 call = call.MaxResults(int64(pageSize)) 360 } 361 362 ctx := it.ctx 363 var resp *raw.HmacKeysMetadata 364 err = runWithRetry(it.ctx, func() error { 365 resp, err = call.Context(ctx).Do() 366 return err 367 }) 368 if err != nil { 369 return "", err 370 } 371 372 for _, metadata := range resp.Items { 373 hkPb := &raw.HmacKey{ 374 Metadata: metadata, 375 } 376 hkey, err := pbHmacKeyToHMACKey(hkPb, true) 377 if err != nil { 378 return "", err 379 } 380 it.hmacKeys = append(it.hmacKeys, hkey) 381 } 382 return resp.NextPageToken, nil 383} 384 385type hmacKeyDesc struct { 386 forServiceAccountEmail string 387 showDeletedKeys bool 388 userProjectID string 389} 390 391// HMACKeyOption configures the behavior of HMACKey related methods and actions. 392// 393// This interface is EXPERIMENTAL and subject to change or removal without notice. 394type HMACKeyOption interface { 395 withHMACKeyDesc(*hmacKeyDesc) 396} 397 398type hmacKeyDescFunc func(*hmacKeyDesc) 399 400func (hkdf hmacKeyDescFunc) withHMACKeyDesc(hkd *hmacKeyDesc) { 401 hkdf(hkd) 402} 403 404// ForHMACKeyServiceAccountEmail returns HMAC Keys that are 405// associated with the email address of a service account in the project. 406// 407// Only one service account email can be used as a filter, so if multiple 408// of these options are applied, the last email to be set will be used. 409// 410// This option is EXPERIMENTAL and subject to change or removal without notice. 411func ForHMACKeyServiceAccountEmail(serviceAccountEmail string) HMACKeyOption { 412 return hmacKeyDescFunc(func(hkd *hmacKeyDesc) { 413 hkd.forServiceAccountEmail = serviceAccountEmail 414 }) 415} 416 417// ShowDeletedHMACKeys will also list keys whose state is "DELETED". 418// 419// This option is EXPERIMENTAL and subject to change or removal without notice. 420func ShowDeletedHMACKeys() HMACKeyOption { 421 return hmacKeyDescFunc(func(hkd *hmacKeyDesc) { 422 hkd.showDeletedKeys = true 423 }) 424} 425 426// UserProjectForHMACKeys will bill the request against userProjectID 427// if userProjectID is non-empty. 428// 429// Note: This is a noop right now and only provided for API compatibility. 430// 431// This option is EXPERIMENTAL and subject to change or removal without notice. 432func UserProjectForHMACKeys(userProjectID string) HMACKeyOption { 433 return hmacKeyDescFunc(func(hkd *hmacKeyDesc) { 434 hkd.userProjectID = userProjectID 435 }) 436} 437