...

Source file src/cloud.google.com/go/storage/hmac.go

Documentation: cloud.google.com/go/storage

     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  
    15  package storage
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"time"
    22  
    23  	"cloud.google.com/go/storage/internal/apiv2/storagepb"
    24  	"google.golang.org/api/iterator"
    25  	raw "google.golang.org/api/storage/v1"
    26  )
    27  
    28  // HMACState is the state of the HMAC key.
    29  type HMACState string
    30  
    31  const (
    32  	// Active is the status for an active key that can be used to sign
    33  	// requests.
    34  	Active HMACState = "ACTIVE"
    35  
    36  	// Inactive is the status for an inactive key thus requests signed by
    37  	// this key will be denied.
    38  	Inactive HMACState = "INACTIVE"
    39  
    40  	// Deleted is the status for a key that is deleted.
    41  	// Once in this state the key cannot key cannot be recovered
    42  	// and does not count towards key limits. Deleted keys will be cleaned
    43  	// up later.
    44  	Deleted HMACState = "DELETED"
    45  )
    46  
    47  // HMACKey is the representation of a Google Cloud Storage HMAC key.
    48  //
    49  // HMAC keys are used to authenticate signed access to objects. To enable HMAC key
    50  // authentication, please visit https://cloud.google.com/storage/docs/migrating.
    51  type HMACKey struct {
    52  	// The HMAC's secret key.
    53  	Secret string
    54  
    55  	// AccessID is the ID of the HMAC key.
    56  	AccessID string
    57  
    58  	// Etag is the HTTP/1.1 Entity tag.
    59  	Etag string
    60  
    61  	// ID is the ID of the HMAC key, including the ProjectID and AccessID.
    62  	ID string
    63  
    64  	// ProjectID is the ID of the project that owns the
    65  	// service account to which the key authenticates.
    66  	ProjectID string
    67  
    68  	// ServiceAccountEmail is the email address
    69  	// of the key's associated service account.
    70  	ServiceAccountEmail string
    71  
    72  	// CreatedTime is the creation time of the HMAC key.
    73  	CreatedTime time.Time
    74  
    75  	// UpdatedTime is the last modification time of the HMAC key metadata.
    76  	UpdatedTime time.Time
    77  
    78  	// State is the state of the HMAC key.
    79  	// It can be one of StateActive, StateInactive or StateDeleted.
    80  	State HMACState
    81  }
    82  
    83  // HMACKeyHandle helps provide access and management for HMAC keys.
    84  type HMACKeyHandle struct {
    85  	projectID string
    86  	accessID  string
    87  	retry     *retryConfig
    88  	tc        storageClient
    89  }
    90  
    91  // HMACKeyHandle creates a handle that will be used for HMACKey operations.
    92  func (c *Client) HMACKeyHandle(projectID, accessID string) *HMACKeyHandle {
    93  	return &HMACKeyHandle{
    94  		projectID: projectID,
    95  		accessID:  accessID,
    96  		retry:     c.retry,
    97  		tc:        c.tc,
    98  	}
    99  }
   100  
   101  // Get invokes an RPC to retrieve the HMAC key referenced by the
   102  // HMACKeyHandle's accessID.
   103  //
   104  // Options such as UserProjectForHMACKeys can be used to set the
   105  // userProject to be billed against for operations.
   106  func (hkh *HMACKeyHandle) Get(ctx context.Context, opts ...HMACKeyOption) (*HMACKey, error) {
   107  	desc := new(hmacKeyDesc)
   108  	for _, opt := range opts {
   109  		opt.withHMACKeyDesc(desc)
   110  	}
   111  
   112  	o := makeStorageOpts(true, hkh.retry, desc.userProjectID)
   113  	hk, err := hkh.tc.GetHMACKey(ctx, hkh.projectID, hkh.accessID, o...)
   114  
   115  	return hk, err
   116  }
   117  
   118  // Delete invokes an RPC to delete the key referenced by accessID, on Google Cloud Storage.
   119  // Only inactive HMAC keys can be deleted.
   120  // After deletion, a key cannot be used to authenticate requests.
   121  func (hkh *HMACKeyHandle) Delete(ctx context.Context, opts ...HMACKeyOption) error {
   122  	desc := new(hmacKeyDesc)
   123  	for _, opt := range opts {
   124  		opt.withHMACKeyDesc(desc)
   125  	}
   126  
   127  	o := makeStorageOpts(true, hkh.retry, desc.userProjectID)
   128  	return hkh.tc.DeleteHMACKey(ctx, hkh.projectID, hkh.accessID, o...)
   129  }
   130  
   131  func toHMACKeyFromRaw(hk *raw.HmacKey, updatedTimeCanBeNil bool) (*HMACKey, error) {
   132  	hkmd := hk.Metadata
   133  	if hkmd == nil {
   134  		return nil, errors.New("field Metadata cannot be nil")
   135  	}
   136  	createdTime, err := time.Parse(time.RFC3339, hkmd.TimeCreated)
   137  	if err != nil {
   138  		return nil, fmt.Errorf("field CreatedTime: %w", err)
   139  	}
   140  	updatedTime, err := time.Parse(time.RFC3339, hkmd.Updated)
   141  	if err != nil && !updatedTimeCanBeNil {
   142  		return nil, fmt.Errorf("field UpdatedTime: %w", err)
   143  	}
   144  
   145  	hmKey := &HMACKey{
   146  		AccessID:    hkmd.AccessId,
   147  		Secret:      hk.Secret,
   148  		Etag:        hkmd.Etag,
   149  		ID:          hkmd.Id,
   150  		State:       HMACState(hkmd.State),
   151  		ProjectID:   hkmd.ProjectId,
   152  		CreatedTime: createdTime,
   153  		UpdatedTime: updatedTime,
   154  
   155  		ServiceAccountEmail: hkmd.ServiceAccountEmail,
   156  	}
   157  
   158  	return hmKey, nil
   159  }
   160  
   161  func toHMACKeyFromProto(pbmd *storagepb.HmacKeyMetadata) *HMACKey {
   162  	if pbmd == nil {
   163  		return nil
   164  	}
   165  
   166  	return &HMACKey{
   167  		AccessID:            pbmd.GetAccessId(),
   168  		ID:                  pbmd.GetId(),
   169  		State:               HMACState(pbmd.GetState()),
   170  		ProjectID:           pbmd.GetProject(),
   171  		CreatedTime:         convertProtoTime(pbmd.GetCreateTime()),
   172  		UpdatedTime:         convertProtoTime(pbmd.GetUpdateTime()),
   173  		ServiceAccountEmail: pbmd.GetServiceAccountEmail(),
   174  	}
   175  }
   176  
   177  // CreateHMACKey invokes an RPC for Google Cloud Storage to create a new HMACKey.
   178  func (c *Client) CreateHMACKey(ctx context.Context, projectID, serviceAccountEmail string, opts ...HMACKeyOption) (*HMACKey, error) {
   179  	if projectID == "" {
   180  		return nil, errors.New("storage: expecting a non-blank projectID")
   181  	}
   182  	if serviceAccountEmail == "" {
   183  		return nil, errors.New("storage: expecting a non-blank service account email")
   184  	}
   185  
   186  	desc := new(hmacKeyDesc)
   187  	for _, opt := range opts {
   188  		opt.withHMACKeyDesc(desc)
   189  	}
   190  
   191  	o := makeStorageOpts(false, c.retry, desc.userProjectID)
   192  	hk, err := c.tc.CreateHMACKey(ctx, projectID, serviceAccountEmail, o...)
   193  	return hk, err
   194  }
   195  
   196  // HMACKeyAttrsToUpdate defines the attributes of an HMACKey that will be updated.
   197  type HMACKeyAttrsToUpdate struct {
   198  	// State is required and must be either StateActive or StateInactive.
   199  	State HMACState
   200  
   201  	// Etag is an optional field and it is the HTTP/1.1 Entity tag.
   202  	Etag string
   203  }
   204  
   205  // Update mutates the HMACKey referred to by accessID.
   206  func (h *HMACKeyHandle) Update(ctx context.Context, au HMACKeyAttrsToUpdate, opts ...HMACKeyOption) (*HMACKey, error) {
   207  	if au.State != Active && au.State != Inactive {
   208  		return nil, fmt.Errorf("storage: invalid state %q for update, must be either %q or %q", au.State, Active, Inactive)
   209  	}
   210  
   211  	desc := new(hmacKeyDesc)
   212  	for _, opt := range opts {
   213  		opt.withHMACKeyDesc(desc)
   214  	}
   215  
   216  	isIdempotent := len(au.Etag) > 0
   217  	o := makeStorageOpts(isIdempotent, h.retry, desc.userProjectID)
   218  	hk, err := h.tc.UpdateHMACKey(ctx, h.projectID, desc.forServiceAccountEmail, h.accessID, &au, o...)
   219  	return hk, err
   220  }
   221  
   222  // An HMACKeysIterator is an iterator over HMACKeys.
   223  //
   224  // Note: This iterator is not safe for concurrent operations without explicit synchronization.
   225  type HMACKeysIterator struct {
   226  	ctx       context.Context
   227  	raw       *raw.ProjectsHmacKeysService
   228  	projectID string
   229  	hmacKeys  []*HMACKey
   230  	pageInfo  *iterator.PageInfo
   231  	nextFunc  func() error
   232  	index     int
   233  	desc      hmacKeyDesc
   234  	retry     *retryConfig
   235  }
   236  
   237  // ListHMACKeys returns an iterator for listing HMACKeys.
   238  //
   239  // Note: This iterator is not safe for concurrent operations without explicit synchronization.
   240  func (c *Client) ListHMACKeys(ctx context.Context, projectID string, opts ...HMACKeyOption) *HMACKeysIterator {
   241  	desc := new(hmacKeyDesc)
   242  	for _, opt := range opts {
   243  		opt.withHMACKeyDesc(desc)
   244  	}
   245  
   246  	o := makeStorageOpts(true, c.retry, desc.userProjectID)
   247  	return c.tc.ListHMACKeys(ctx, projectID, desc.forServiceAccountEmail, desc.showDeletedKeys, o...)
   248  }
   249  
   250  // Next returns the next result. Its second return value is iterator.Done if
   251  // there are no more results. Once Next returns iterator.Done, all subsequent
   252  // calls will return iterator.Done.
   253  //
   254  // Note: This iterator is not safe for concurrent operations without explicit synchronization.
   255  func (it *HMACKeysIterator) Next() (*HMACKey, error) {
   256  	if err := it.nextFunc(); err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	key := it.hmacKeys[it.index]
   261  	it.index++
   262  
   263  	return key, nil
   264  }
   265  
   266  // PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
   267  //
   268  // Note: This iterator is not safe for concurrent operations without explicit synchronization.
   269  func (it *HMACKeysIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
   270  
   271  func (it *HMACKeysIterator) fetch(pageSize int, pageToken string) (token string, err error) {
   272  	// TODO: Remove fetch method upon integration. This method is internalized into
   273  	// httpStorageClient.ListHMACKeys() as it is the only caller.
   274  	call := it.raw.List(it.projectID)
   275  	setClientHeader(call.Header())
   276  	if pageToken != "" {
   277  		call = call.PageToken(pageToken)
   278  	}
   279  	if it.desc.showDeletedKeys {
   280  		call = call.ShowDeletedKeys(true)
   281  	}
   282  	if it.desc.userProjectID != "" {
   283  		call = call.UserProject(it.desc.userProjectID)
   284  	}
   285  	if it.desc.forServiceAccountEmail != "" {
   286  		call = call.ServiceAccountEmail(it.desc.forServiceAccountEmail)
   287  	}
   288  	if pageSize > 0 {
   289  		call = call.MaxResults(int64(pageSize))
   290  	}
   291  
   292  	var resp *raw.HmacKeysMetadata
   293  	err = run(it.ctx, func(ctx context.Context) error {
   294  		resp, err = call.Context(ctx).Do()
   295  		return err
   296  	}, it.retry, true)
   297  	if err != nil {
   298  		return "", err
   299  	}
   300  
   301  	for _, metadata := range resp.Items {
   302  		hk := &raw.HmacKey{
   303  			Metadata: metadata,
   304  		}
   305  		hkey, err := toHMACKeyFromRaw(hk, true)
   306  		if err != nil {
   307  			return "", err
   308  		}
   309  		it.hmacKeys = append(it.hmacKeys, hkey)
   310  	}
   311  	return resp.NextPageToken, nil
   312  }
   313  
   314  type hmacKeyDesc struct {
   315  	forServiceAccountEmail string
   316  	showDeletedKeys        bool
   317  	userProjectID          string
   318  }
   319  
   320  // HMACKeyOption configures the behavior of HMACKey related methods and actions.
   321  type HMACKeyOption interface {
   322  	withHMACKeyDesc(*hmacKeyDesc)
   323  }
   324  
   325  type hmacKeyDescFunc func(*hmacKeyDesc)
   326  
   327  func (hkdf hmacKeyDescFunc) withHMACKeyDesc(hkd *hmacKeyDesc) {
   328  	hkdf(hkd)
   329  }
   330  
   331  // ForHMACKeyServiceAccountEmail returns HMAC Keys that are
   332  // associated with the email address of a service account in the project.
   333  //
   334  // Only one service account email can be used as a filter, so if multiple
   335  // of these options are applied, the last email to be set will be used.
   336  func ForHMACKeyServiceAccountEmail(serviceAccountEmail string) HMACKeyOption {
   337  	return hmacKeyDescFunc(func(hkd *hmacKeyDesc) {
   338  		hkd.forServiceAccountEmail = serviceAccountEmail
   339  	})
   340  }
   341  
   342  // ShowDeletedHMACKeys will also list keys whose state is "DELETED".
   343  func ShowDeletedHMACKeys() HMACKeyOption {
   344  	return hmacKeyDescFunc(func(hkd *hmacKeyDesc) {
   345  		hkd.showDeletedKeys = true
   346  	})
   347  }
   348  
   349  // UserProjectForHMACKeys will bill the request against userProjectID
   350  // if userProjectID is non-empty.
   351  //
   352  // Note: This is a noop right now and only provided for API compatibility.
   353  func UserProjectForHMACKeys(userProjectID string) HMACKeyOption {
   354  	return hmacKeyDescFunc(func(hkd *hmacKeyDesc) {
   355  		hkd.userProjectID = userProjectID
   356  	})
   357  }
   358  

View as plain text