# -*- coding: utf-8 -*- # # Copyright 2020 Google LLC. All Rights Reserved. # # 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. """Update an existing secret.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals from googlecloudsdk.api_lib.secrets import api as secrets_api from googlecloudsdk.calliope import base from googlecloudsdk.calliope import exceptions as calliope_exceptions from googlecloudsdk.command_lib.secrets import args as secrets_args from googlecloudsdk.command_lib.secrets import exceptions from googlecloudsdk.command_lib.secrets import log as secrets_log @base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA) class Update(base.UpdateCommand): r"""Update a secret replica's metadata. Update a secret replica's metadata (e.g. cmek policy). This command will return an error if given a secret that does not exist or if given a location that the given secret doesn't exist in. The --remove-kms-key flag is only valid for Secrets that have an automatic replication policy or exist in a single location. To remove keys from a Secret with multiple user managed replicas, please use the set-replication command. ## EXAMPLES To remove CMEK from a secret called 'my-secret', run: $ {command} my-secret --remove-cmek To set the CMEK key on an automatic secret called my-secret to a specified KMS key, run: ${command} my-secret --set-kms-key=projects/my-project/locations/global/keyRings/my-keyring/cryptoKeys/my-key To set the CMEK key on a secret called my-secret to a specified KMS key in a specified location in its replication, run: ${command} my-secret --set-kms-key=projects/my-project/locations/us-central1/keyRings/my-keyring/cryptoKeys/my-key --location=us-central1 """ NO_CHANGES_MESSAGE = ( 'There are no changes to the secret [{secret}] for update.') SECRET_MISSING_MESSAGE = ( 'The secret [{secret}] cannot be updated because it does not exist. ' 'Please use the create command to create a new secret.') LOCATION_REQUIRED_MESSAGE = ( 'This secret has a user managed replication polciy. The location in ' 'which to set the customer managed encryption key must be set with ' '--location.') MISCONFIGURED_REPLICATION_MESSAGE = ( 'There was a problem updating replication for this secret. Please use ' 'the replication set command to perform this update.') LOCATION_AND_AUTOMATIC_MESSAGE = ( 'This secret has an automatic replication policy. To set its customer ' 'managed encryption key, please omit --locations.') LOCATION_NOT_IN_POLICY_MESSAGE = ( 'The secret does not have a replica in this location.') PARTIALLY_CMEK_MESSAGE = ( 'Either all replicas must use customer managed encryption or all ' 'replicas must use Google managed encryption. To add customer managed ' 'encryption to all replicas, please use the replication set command.') REMOVE_AND_SET_CMEK_MESSAGE = ( 'Cannot simultaneously set and remove a customer managed encryption key.') REMOVE_CMEK_AND_LOCATION_MESSAGE = ( 'Cannot remove customer managed encryption keys for just one location. ' 'To use Google managed encryption keys for all locations, please remove ' '--locations.') @staticmethod def Args(parser): secrets_args.AddSecret( parser, purpose='to update', positional=True, required=True) secrets_args.AddUpdateReplicationGroup(parser) def _RemoveCmek(self, secret_ref, secret): api_version = secrets_api.GetApiFromTrack(self.ReleaseTrack()) if secret.replication.automatic: updated_secret = secrets_api.Secrets( api_version=api_version ).SetReplication(secret_ref, 'automatic', [], []) secrets_log.Secrets().UpdatedReplication(secret_ref) return updated_secret if secret.replication.userManaged and secret.replication.userManaged.replicas: locations = [] for replica in secret.replication.userManaged.replicas: if not replica.location: raise exceptions.MisconfiguredReplicationError( self.MISCONFIGURED_REPLICATION_MESSAGE) locations.append(replica.location) updated_secret = secrets_api.Secrets( api_version=api_version ).SetReplication(secret_ref, 'user-managed', locations, []) secrets_log.Secrets().UpdatedReplication(secret_ref) return updated_secret raise exceptions.MisconfiguredReplicationError( self.MISCONFIGURED_REPLICATION_MESSAGE) def _SetKmsKey(self, secret_ref, secret, kms_key, location): api_version = secrets_api.GetApiFromTrack(self.ReleaseTrack()) if secret.replication.automatic: if location: raise calliope_exceptions.BadArgumentException( 'location', self.LOCATION_AND_AUTOMATIC_MESSAGE) updated_secret = secrets_api.Secrets( api_version=api_version ).SetReplication(secret_ref, 'automatic', [], [kms_key]) secrets_log.Secrets().UpdatedReplication(secret_ref) return updated_secret if secret.replication.userManaged and secret.replication.userManaged.replicas: if not location: raise calliope_exceptions.RequiredArgumentException( 'location', self.LOCATION_REQUIRED_MESSAGE) locations = [] keys = [] found_location = False for replica in secret.replication.userManaged.replicas: if not replica.location: raise exceptions.MisconfiguredReplicationError( self.MISCONFIGURED_REPLICATION_MESSAGE) locations.append(replica.location) if location == replica.location: found_location = True keys.append(kms_key) elif replica.customerManagedEncryption and replica.customerManagedEncryption.kmsKeyName: keys.append(replica.customerManagedEncryption.kmsKeyName) if not found_location: raise calliope_exceptions.InvalidArgumentException( 'location', self.LOCATION_NOT_IN_POLICY_MESSAGE) if len(locations) != len(keys): raise exceptions.MisconfiguredEncryptionError( self.PARTIALLY_CMEK_MESSAGE) updated_secret = secrets_api.Secrets( api_version=api_version ).SetReplication(secret_ref, 'user-managed', locations, keys) secrets_log.Secrets().UpdatedReplication(secret_ref) return updated_secret raise exceptions.MisconfiguredReplicationError( self.MISCONFIGURED_REPLICATION_MESSAGE) def Run(self, args): api_version = secrets_api.GetApiFromTrack(self.ReleaseTrack()) secret_ref = args.CONCEPTS.secret.Parse() if not args.remove_cmek and not args.set_kms_key: raise calliope_exceptions.MinimumArgumentException( ['--remove-cmek', '--set-kms-key']) if args.remove_cmek and args.set_kms_key: raise calliope_exceptions.ConflictingArgumentsException( self.REMOVE_AND_SET_CMEK_MESSAGE) if args.remove_cmek and args.location: raise calliope_exceptions.ConflictingArgumentsException( self.REMOVE_CMEK_AND_LOCATION_MESSAGE) # args.set_kms_key without args.location is allowed only if the secret has # a single replica. # Attempt to get the secret secret = secrets_api.Secrets(api_version=api_version).GetOrNone(secret_ref) # Secret does not exist if secret is None: raise calliope_exceptions.InvalidArgumentException( 'secret', self.SECRET_MISSING_MESSAGE.format(secret=secret_ref.Name())) if args.remove_cmek: return self._RemoveCmek(secret_ref, secret) return self._SetKmsKey(secret_ref, secret, args.set_kms_key, args.location)