feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""The command group for all of the Cloud KMS API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class CloudKms(base.Group):
"""Manage cryptographic keys in the cloud.
The gcloud kms command group lets you generate, use, rotate and destroy
Google Cloud KMS keys.
Cloud KMS is a cloud-hosted key management service that lets you manage
encryption for your cloud services the same way you do on-premises. You can
generate, use, rotate and destroy AES256 encryption keys. Cloud KMS is
integrated with IAM and Cloud Audit Logging so that you can manage
permissions on individual keys, and monitor how these are used. Use Cloud
KMS to protect secrets and other sensitive data which you need to store in
Google Cloud Platform.
More information on Cloud KMS can be found here:
https://cloud.google.com/kms/ and detailed documentation can be found here:
https://cloud.google.com/kms/docs/
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
def Filter(self, context, args):
# TODO(b/190535383): Determine if command group works with project number
base.RequireProjectID(args)
del context, args
base.DisableUserProjectQuota()
self.EnableSelfSignedJwtForTracks(
[base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA]
)

View File

@@ -0,0 +1,129 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Decrypt an input file using an asymmetric-encryption key version."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import crc32c
from googlecloudsdk.command_lib.kms import e2e_integrity
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import files
class AsymmetricDecrypt(base.Command):
r"""Decrypt an input file using an asymmetric-encryption key version.
Decrypts the given ciphertext file using the provided asymmetric-encryption
key version and saves the decrypted data to the plaintext file.
By default, the command performs integrity verification on data sent to and
received from Cloud KMS. Use `--skip-integrity-verification` to disable
integrity verification.
## EXAMPLES
The following command will read the file '/tmp/my/secret.file.enc', decrypt it
using the asymmetric CryptoKey `dont-panic` Version 3 and write the plaintext
to '/tmp/my/secret.file.dec'.
$ {command} \
--location=us-central1 \
--keyring=hitchhiker \
--key=dont-panic \
--version=3 \
--ciphertext-file=/tmp/my/secret.file.enc \
--plaintext-file=/tmp/my/secret.file.dec
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser, 'to use for asymmetric-decryption.')
flags.AddCryptoKeyVersionFlag(parser, 'to use for asymmetric-decryption')
flags.AddCiphertextFileFlag(parser, 'to decrypt')
flags.AddPlaintextFileFlag(parser, 'to output')
flags.AddSkipIntegrityVerification(parser)
def _PerformIntegrityVerification(self, args):
return not args.skip_integrity_verification
def _CreateAsymmetricDecryptRequest(self, args):
try:
ciphertext = console_io.ReadFromFileOrStdin(
args.ciphertext_file, binary=True)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read ciphertext file [{0}]: {1}'.format(
args.ciphertext_file, e))
messages = cloudkms_base.GetMessagesModule()
crypto_key_ref = flags.ParseCryptoKeyVersionName(args)
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsAsymmetricDecryptRequest( # pylint: disable=line-too-long
name=crypto_key_ref.RelativeName())
if self._PerformIntegrityVerification(args):
ciphertext_crc32c = crc32c.Crc32c(ciphertext)
req.asymmetricDecryptRequest = messages.AsymmetricDecryptRequest(
ciphertext=ciphertext, ciphertextCrc32c=ciphertext_crc32c)
else:
req.asymmetricDecryptRequest = messages.AsymmetricDecryptRequest(
ciphertext=ciphertext)
return req
def _VerifyResponseIntegrityFields(self, req, resp):
"""Verifies integrity fields in response."""
# ciphertext_crc32c was verified server-side.
if not resp.verifiedCiphertextCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage())
# Verify plaintext checksum.
if not crc32c.Crc32cMatches(resp.plaintext, resp.plaintextCrc32c):
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetResponseFromServerCorruptedErrorMessage())
def Run(self, args):
req = self._CreateAsymmetricDecryptRequest(args)
client = cloudkms_base.GetClientInstance()
try:
resp = (
client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions
.AsymmetricDecrypt(req))
# Intercept INVALID_ARGUMENT errors related to checksum verification to
# present a user-friendly message. All other errors are surfaced as-is.
except apitools_exceptions.HttpBadRequestError as error:
e2e_integrity.ProcessHttpBadRequestError(error)
if self._PerformIntegrityVerification(args):
self._VerifyResponseIntegrityFields(req, resp)
try:
log.WriteToFileOrStdout(
args.plaintext_file,
resp.plaintext or '',
overwrite=True,
binary=True,
private=True)
except files.Error as e:
raise exceptions.BadFileException(e)

View File

@@ -0,0 +1,200 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Sign a user input file using an asymmetric-signing key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import crc32c
from googlecloudsdk.command_lib.kms import e2e_integrity
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import get_digest
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
class AsymmetricSign(base.Command):
r"""Sign a user input file using an asymmetric-signing key version.
Creates a digital signature of the input file using the provided
asymmetric-signing key version and saves the base64 encoded signature.
The required flag `signature-file` indicates the path to store signature.
By default, the command performs integrity verification on data sent to and
received from Cloud KMS. Use `--skip-integrity-verification` to disable
integrity verification.
## EXAMPLES
The following command will read the file '/tmp/my/file.to.sign', digest it
with the digest algorithm 'sha256' and sign it using the asymmetric CryptoKey
`dont-panic` Version 3, and save the signature in base64 format to
'/tmp/my/signature'.
$ {command} \
--location=us-central1 \
--keyring=hitchhiker \
--key=dont-panic \
--version=3 \
--digest-algorithm=sha256 \
--input-file=/tmp/my/file.to.sign \
--signature-file=/tmp/my/signature
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser, 'to use for signing.')
flags.AddCryptoKeyVersionFlag(parser, 'to use for signing')
flags.AddDigestAlgorithmFlag(parser, 'The algorithm to digest the input.')
flags.AddInputFileFlag(parser, 'to sign')
flags.AddSignatureFileFlag(parser, 'to output')
flags.AddSkipIntegrityVerification(parser)
def _PerformIntegrityVerification(self, args):
return not args.skip_integrity_verification
def _SignOnDigest(self, args):
return args.digest_algorithm is not None
def _ReadBinaryFile(self, path, max_bytes):
data = files.ReadBinaryFileContents(path)
if len(data) > max_bytes:
raise exceptions.BadFileException(
'The file [{0}] is larger than the maximum size of {1} bytes.'.format(
path, max_bytes))
return data
def _CreateAsymmetricSignRequestOnDigest(self, args):
try:
digest = get_digest.GetDigest(args.digest_algorithm, args.input_file)
except EnvironmentError as e:
raise exceptions.BadFileException(
'Failed to read input file [{0}]: {1}'.format(args.input_file, e))
messages = cloudkms_base.GetMessagesModule()
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsAsymmetricSignRequest( # pylint: disable=line-too-long
name=flags.ParseCryptoKeyVersionName(args).RelativeName())
if self._PerformIntegrityVerification(args):
# args.digest_algorithm has been verified in get_digest.GetDigest()
digest_crc32c = crc32c.Crc32c(getattr(digest, args.digest_algorithm))
req.asymmetricSignRequest = messages.AsymmetricSignRequest(
digest=digest, digestCrc32c=digest_crc32c)
else:
req.asymmetricSignRequest = messages.AsymmetricSignRequest(digest=digest)
return req
def _CreateAsymmetricSignRequestOnData(self, args):
"""Returns an AsymmetricSignRequest for use with a data input.
Populates an AsymmetricSignRequest with its data field populated by data
read from args.input_file. dataCrc32c is populated if integrity verification
is not skipped.
Args:
args: Input arguments.
Returns:
An AsymmetricSignRequest with data populated and dataCrc32c populated if
integrity verification is not skipped.
Raises:
exceptions.BadFileException: An error occurred reading the input file.
This can occur if the file can't be read or if the file is larger than
64 KiB.
"""
try:
# The Asymmetric Sign API limits the data input to 64KiB.
data = self._ReadBinaryFile(args.input_file, max_bytes=65536)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read input file [{0}]: {1}'.format(args.input_file, e))
messages = cloudkms_base.GetMessagesModule()
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsAsymmetricSignRequest( # pylint: disable=line-too-long
name=flags.ParseCryptoKeyVersionName(args).RelativeName())
if self._PerformIntegrityVerification(args):
data_crc32c = crc32c.Crc32c(data)
req.asymmetricSignRequest = messages.AsymmetricSignRequest(
data=data, dataCrc32c=data_crc32c)
else:
req.asymmetricSignRequest = messages.AsymmetricSignRequest(data=data)
return req
def _CreateAsymmetricSignRequest(self, args):
if self._SignOnDigest(args):
return self._CreateAsymmetricSignRequestOnDigest(args)
else:
return self._CreateAsymmetricSignRequestOnData(args)
def _VerifyResponseIntegrityFields(self, req, resp, use_digest=True):
"""Verifies integrity fields in AsymmetricSignResponse."""
# Verify resource name.
if req.name != resp.name:
raise e2e_integrity.ResourceNameVerificationError(
e2e_integrity.GetResourceNameMismatchErrorMessage(
req.name, resp.name))
if use_digest:
# digest_crc32c was verified server-side.
if not resp.verifiedDigestCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage())
else:
# data_crc32c was verified server-side.
if not resp.verifiedDataCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage())
# Verify signature checksum.
if not crc32c.Crc32cMatches(resp.signature, resp.signatureCrc32c):
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetResponseFromServerCorruptedErrorMessage())
def Run(self, args):
client = cloudkms_base.GetClientInstance()
req = self._CreateAsymmetricSignRequest(args)
try:
resp = (
client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions
.AsymmetricSign(req))
# Intercept INVALID_ARGUMENT errors related to checksum verification, to
# present a user-friendly message. All other errors are surfaced as-is.
except apitools_exceptions.HttpBadRequestError as error:
e2e_integrity.ProcessHttpBadRequestError(error)
if self._PerformIntegrityVerification(args):
self._VerifyResponseIntegrityFields(
req, resp, use_digest=self._SignOnDigest(args))
try:
log.WriteToFileOrStdout(
args.signature_file,
resp.signature,
overwrite=True,
binary=True,
private=True)
except files.Error as e:
raise exceptions.BadFileException(e)

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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.
"""The command group for autokey config."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import resources
@base.UniverseCompatible
@base.ReleaseTracks(
base.ReleaseTrack.GA, base.ReleaseTrack.BETA, base.ReleaseTrack.ALPHA
)
class AutokeyConfig(base.Group):
"""Update and retrieve the AutokeyConfig.
Autokey config is a folder level resource which configures the key project
for the folder and the resources inside it.
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
@staticmethod
def Args(parser):
pass

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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.
"""Describe the AutokeyConfig of a folder."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
@base.UniverseCompatible
class Describe(base.DescribeCommand):
r"""Describe the AutokeyConfig of a folder.
{command} can be used to retrieve the AutokeyConfig of a folder.
## EXAMPLES
The following command retrieves the AutokeyConfig of a folder having folder-id
`123`:
$ {command} --folder=123
"""
@staticmethod
def Args(parser):
flags.AddFolderIdFlag(parser, True)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
autokey_config_name = 'folders/{0}/autokeyConfig'.format(
args.folder)
return client.folders.GetAutokeyConfig(
messages.CloudkmsFoldersGetAutokeyConfigRequest(
name=autokey_config_name))

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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.
"""Gets the effective Cloud KMS AutokeyConfig for a given project."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
@base.UniverseCompatible
class ShowEffectiveConfig(base.Command):
r"""Gets the effective Cloud KMS AutokeyConfig for a given project.
{command} can be used to get the effective Cloud KMS AutokeyConfig for a given
project.
## EXAMPLES
The following command retrieves the effective Cloud KMS AutokeyConfig for a
given project `my-project`:
$ {command} --project=my-project
If --project flag is not provided, then the current project will be used.
"""
@staticmethod
def Args(parser):
resource_args.AddKmsProjectResourceArgForKMS(parser, True, 'project')
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
project_ref = args.CONCEPTS.project.Parse()
return client.projects.ShowEffectiveAutokeyConfig(
messages.CloudkmsProjectsShowEffectiveAutokeyConfigRequest(
parent=project_ref.RelativeName()))

View File

@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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.
"""Describe the AutokeyConfig of a folder."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import parsing
@base.UniverseCompatible
class Describe(base.DescribeCommand):
r"""Updates the AutokeyConfig for a folder.
{command} can be used to update the AutokeyConfig of a folder.
## EXAMPLES
The following command updates the AutokeyConfig for the folder mentioned in
the config.yaml file:
$ {command} config.yaml
"""
@staticmethod
def Args(parser):
flags.AddAutokeyConfigFileFlag(parser)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
name, key_project, etag = parsing.ReadAutokeyConfigFromConfigFile(
args.CONFIG_FILE
)
if not etag:
return client.folders.UpdateAutokeyConfig(
messages.CloudkmsFoldersUpdateAutokeyConfigRequest(
autokeyConfig=messages.AutokeyConfig(
name=name, keyProject=key_project
),
name=name,
updateMask="keyProject",
),
)
return client.folders.UpdateAutokeyConfig(
messages.CloudkmsFoldersUpdateAutokeyConfigRequest(
autokeyConfig=messages.AutokeyConfig(
name=name, keyProject=key_project, etag=etag
),
name=name,
updateMask="keyProject",
)
)

View File

@@ -0,0 +1,134 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Decapsulate an input file using a key-encapsulation key version."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import crc32c
from googlecloudsdk.command_lib.kms import e2e_integrity
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import files
@base.UniverseCompatible
class Decapsulate(base.Command):
r"""Decapsulate an input file using a key-encapsulation key version.
Decapsulates the given ciphertext file using the provided key-encapsulation
key version and saves the decapsulated shared secret to the shared secret file.
By default, the command performs integrity verification on data sent to and
received from Cloud KMS. Use `--skip-integrity-verification` to disable
integrity verification.
## EXAMPLES
The following command will read the file '/tmp/my/secret.file.enc', decapsulate it
using the key encapsulation CryptoKey `my-key` Version 3 and write the shared secret
to '/tmp/my/secret.file.dec'.
$ {command} \
--location=us-central1 \
--keyring=my-keyring \
--key=my-key \
--version=3 \
--ciphertext-file=/tmp/my/secret.file.enc \
--shared-secret-file=/tmp/my/secret.file.dec
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser, 'to use for decapsulation.')
flags.AddCryptoKeyVersionFlag(parser, 'to use for decapsulation')
flags.AddCiphertextFileFlag(parser, 'to decapsulate')
flags.AddSharedSecretFileFlag(parser, 'to output')
flags.AddSkipIntegrityVerification(parser)
def _PerformIntegrityVerification(self, args):
return not args.skip_integrity_verification
def _CreateDecapsulateRequest(self, args):
try:
ciphertext = console_io.ReadFromFileOrStdin(
args.ciphertext_file, binary=True)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read ciphertext file [{0}]: {1}'.format(
args.ciphertext_file, e))
messages = cloudkms_base.GetMessagesModule()
crypto_key_ref = flags.ParseCryptoKeyVersionName(args)
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsDecapsulateRequest( # pylint: disable=line-too-long
name=crypto_key_ref.RelativeName()
)
if self._PerformIntegrityVerification(args):
ciphertext_crc32c = crc32c.Crc32c(ciphertext)
req.decapsulateRequest = messages.DecapsulateRequest(
ciphertext=ciphertext, ciphertextCrc32c=ciphertext_crc32c
)
else:
req.decapsulateRequest = messages.DecapsulateRequest(
ciphertext=ciphertext
)
return req
def _VerifyResponseIntegrityFields(self, req, resp):
"""Verifies integrity fields in response."""
# ciphertext_crc32c was verified server-side.
if not resp.verifiedCiphertextCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage())
# Verify plaintext checksum.
if not crc32c.Crc32cMatches(resp.sharedSecret, resp.sharedSecretCrc32c):
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetResponseFromServerCorruptedErrorMessage())
def Run(self, args):
req = self._CreateDecapsulateRequest(args)
client = cloudkms_base.GetClientInstance()
try:
resp = client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions.Decapsulate(
req
)
if self._PerformIntegrityVerification(args):
self._VerifyResponseIntegrityFields(req, resp)
log.WriteToFileOrStdout(
args.shared_secret_file,
resp.sharedSecret or '',
overwrite=True,
binary=True,
private=True,
)
# Intercept INVALID_ARGUMENT errors related to checksum verification to
# present a user-friendly message. All other errors are surfaced as-is.
except apitools_exceptions.HttpBadRequestError as error:
e2e_integrity.ProcessHttpBadRequestError(error)
except files.Error as e:
raise exceptions.BadFileException(e)

View File

@@ -0,0 +1,207 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Decrypt a ciphertext file using a key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import crc32c
from googlecloudsdk.command_lib.kms import e2e_integrity
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import files
class Decrypt(base.Command):
r"""Decrypt a ciphertext file using a Cloud KMS key.
`{command}` decrypts the given ciphertext file using the given Cloud KMS key
and writes the result to the named plaintext file. Note that to permit users
to decrypt using a key, they must be have at least one of the following IAM
roles for that key: `roles/cloudkms.cryptoKeyDecrypter`,
`roles/cloudkms.cryptoKeyEncrypterDecrypter`.
Additional authenticated data (AAD) is used as an additional check by Cloud
KMS to authenticate a decryption request. If an additional authenticated data
file is provided, its contents must match the additional authenticated data
provided during encryption and must not be larger than 64KiB. If you don't
provide a value for `--additional-authenticated-data-file`, an empty string is
used. For a thorough explanation of AAD, refer to this
guide: https://cloud.google.com/kms/docs/additional-authenticated-data
If `--ciphertext-file` or `--additional-authenticated-data-file` is set to
'-', that file is read from stdin. Note that both files cannot be read from
stdin. Similarly, if `--plaintext-file` is set to '-', the decrypted plaintext
is written to stdout.
By default, the command performs integrity verification on data sent to and
received from Cloud KMS. Use `--skip-integrity-verification` to disable
integrity verification.
## EXAMPLES
To decrypt the file 'path/to/ciphertext' using the key `frodo` with key
ring `fellowship` and location `global` and write the plaintext
to 'path/to/plaintext.dec', run:
$ {command} \
--key=frodo \
--keyring=fellowship \
--location=global \
--ciphertext-file=path/to/input/ciphertext \
--plaintext-file=path/to/output/plaintext.dec
To decrypt the file 'path/to/ciphertext' using the key `frodo` and the
additional authenticated data that was used to encrypt the ciphertext, and
write the decrypted plaintext to stdout, run:
$ {command} \
--key=frodo \
--keyring=fellowship \
--location=global \
--additional-authenticated-data-file=path/to/aad \
--ciphertext-file=path/to/input/ciphertext \
--plaintext-file='-'
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(
parser, 'Cloud KMS key to use for decryption.\n'
'* For symmetric keys, Cloud KMS detects the decryption key version '
'from the ciphertext. If you specify a key version as part of a '
'symmetric decryption request, an error is logged and decryption '
'fails.\n'
'* For asymmetric keys, the encryption key version can\'t be detected '
'automatically. You must keep track of this information and provide '
'the key version in the decryption request. The key version itself '
'is not sensitive data and does not need to be encrypted.')
flags.AddCiphertextFileFlag(
parser, 'to decrypt. This file should contain the result of encrypting '
'a file with `gcloud kms encrypt`')
flags.AddPlaintextFileFlag(parser, 'to output')
flags.AddAadFileFlag(parser)
flags.AddSkipIntegrityVerification(parser)
def _ReadFileOrStdin(self, path, max_bytes):
data = console_io.ReadFromFileOrStdin(path, binary=True)
if len(data) > max_bytes:
raise exceptions.BadFileException(
'The file [{0}] is larger than the maximum size of {1} bytes.'.format(
path, max_bytes))
return data
def _PerformIntegrityVerification(self, args):
return not args.skip_integrity_verification
def _CreateDecryptRequest(self, args):
if (args.ciphertext_file == '-' and
args.additional_authenticated_data_file == '-'):
raise exceptions.InvalidArgumentException(
'--ciphertext-file',
'--ciphertext-file and --additional-authenticated-data-file cannot '
'both read from stdin.')
try:
# The Encrypt API has a limit of 64K; the output ciphertext files will be
# slightly larger. Check proactively (but generously) to avoid attempting
# to buffer and send obviously oversized files to KMS.
ciphertext = self._ReadFileOrStdin(
args.ciphertext_file, max_bytes=2 * 65536)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read ciphertext file [{0}]: {1}'.format(
args.ciphertext_file, e))
aad = None
if args.additional_authenticated_data_file:
try:
# The Encrypt API limits the AAD to 64KiB.
aad = self._ReadFileOrStdin(
args.additional_authenticated_data_file, max_bytes=65536)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read additional authenticated data file [{0}]: {1}'
.format(args.additional_authenticated_data_file, e))
crypto_key_ref = flags.ParseCryptoKeyName(args)
# Check that the key id does not include /cryptoKeyVersion/ which may occur
# as encrypt command does allow version, so it is easy for user to make a
# mistake here.
if '/cryptoKeyVersions/' in crypto_key_ref.cryptoKeysId:
raise exceptions.InvalidArgumentException(
'--key', '{} includes cryptoKeyVersion which is not valid for '
'decrypt.'.format(crypto_key_ref.cryptoKeysId))
messages = cloudkms_base.GetMessagesModule()
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysDecryptRequest(
name=crypto_key_ref.RelativeName())
# Populate request integrity fields.
if self._PerformIntegrityVerification(args):
ciphertext_crc32c = crc32c.Crc32c(ciphertext)
# Set checksum if AAD is not provided for consistency.
aad_crc32c = crc32c.Crc32c(aad) if aad is not None else crc32c.Crc32c(b'')
req.decryptRequest = messages.DecryptRequest(
ciphertext=ciphertext,
additionalAuthenticatedData=aad,
ciphertextCrc32c=ciphertext_crc32c,
additionalAuthenticatedDataCrc32c=aad_crc32c)
else:
req.decryptRequest = messages.DecryptRequest(
ciphertext=ciphertext, additionalAuthenticatedData=aad)
return req
def _VerifyResponseIntegrityFields(self, req, resp):
"""Verifies integrity fields in response."""
# Verify plaintext checksum.
if not crc32c.Crc32cMatches(resp.plaintext, resp.plaintextCrc32c):
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetResponseFromServerCorruptedErrorMessage())
def Run(self, args):
req = self._CreateDecryptRequest(args)
client = cloudkms_base.GetClientInstance()
try:
resp = client.projects_locations_keyRings_cryptoKeys.Decrypt(req)
# Intercept INVALID_ARGUMENT errors related to checksum verification to
# present a user-friendly message. All other errors are surfaced as-is.
except apitools_exceptions.HttpBadRequestError as error:
e2e_integrity.ProcessHttpBadRequestError(error)
if self._PerformIntegrityVerification(args):
self._VerifyResponseIntegrityFields(req, resp)
try:
if resp.plaintext is None:
with files.FileWriter(args.plaintext_file):
# to create an empty file
pass
log.Print('Decrypted file is empty')
else:
log.WriteToFileOrStdout(
args.plaintext_file, resp.plaintext, binary=True, overwrite=True)
except files.Error as e:
raise exceptions.BadFileException(e)

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""The command group for ekm config."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import resources
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class EkmConfig(base.Group):
"""Update and retrieve the EkmConfig.
EkmConfig is a singleton resource that represents configuration
parameters that apply to all CryptoKeys and CryptoKeyVersions of EXTERNAL_VPC
protecion level.
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
@staticmethod
def Args(parser):
pass

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Add IAM Policy Binding for EkmConfig."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import iam
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.iam import iam_util
from googlecloudsdk.command_lib.kms import resource_args
class AddIamPolicyBinding(base.Command):
"""Add IAM policy binding to an EkmConfig.
Adds a policy binding to the IAM policy of a kms EkmConfig. A binding consists
of at least one member, a role, and an optional condition.
## EXAMPLES
To add an IAM policy binding for the role of 'roles/editor' for the user
`test-user@gmail.com` on the EkmConfig with location `us-central1`, run:
$ {command} --location='us-central1' --member='user:test-user@gmail.com'
--role='roles/editor'
To add an IAM policy binding which expires at the end of the year 2022 for the
role of 'roles/editor' and the user `test-user@gmail.com` and location
`us-central1`, run:
$ {command} --location='us-central1' --member='user:test-user@gmail.com'
--role='roles/editor' --condition='expression=request.time <
timestamp("2023-01-01T00:00:00Z"),title=expires_end_of_2022,description=Expires
at midnight on 2022-12-31'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
"""
@staticmethod
def Args(parser):
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
iam_util.AddArgsForAddIamPolicyBinding(parser, add_condition=True)
def Run(self, args):
location_ref = args.CONCEPTS.location.Parse()
ekm_config_name = 'projects/{0}/locations/{1}/ekmConfig'.format(
location_ref.projectsId, location_ref.locationsId)
result = iam.AddPolicyBindingToEkmConfig(ekm_config_name, args.member,
args.role)
iam_util.LogSetIamPolicy(ekm_config_name, 'EkmConfig')
return result

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Describe the EkmConfig."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
class Describe(base.DescribeCommand):
r"""Describe the EkmConfig.
{command} can be used to retrieve the EkmConfig.
## EXAMPLES
The following command retrieves the EkmConfig in `us-east1` for the current
project:
$ {command} --location=us-east1
The following command retrieves the EkmConfig for its project `foo` and
location `us-east1`:
$ {command} --location="projects/foo/locations/us-east1"
"""
@staticmethod
def Args(parser):
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
location_ref = args.CONCEPTS.location.Parse()
ekm_config_name = 'projects/{0}/locations/{1}/ekmConfig'.format(
location_ref.projectsId, location_ref.locationsId)
return client.projects_locations.GetEkmConfig(
messages.CloudkmsProjectsLocationsGetEkmConfigRequest(
name=ekm_config_name))

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Fetch the IAM policy for an EkmConfig."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import iam
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
class GetIamPolicy(base.ListCommand):
"""Get the IAM policy for an EkmConfig.
Gets the IAM policy for the given location.
Returns an empty policy if the resource does not have a policy set.
## EXAMPLES
The following command gets the IAM policy for the EkmConfig
within the location `us-central1`:
$ {command} --location=us-central1
"""
@staticmethod
def Args(parser):
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
base.URI_FLAG.RemoveFromParser(parser)
def Run(self, args):
location_ref = args.CONCEPTS.location.Parse()
ekm_config_name = 'projects/{0}/locations/{1}/ekmConfig'.format(
location_ref.projectsId, location_ref.locationsId)
return iam.GetEkmConfigIamPolicy(ekm_config_name)

View File

@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Remove IAM Policy Binding for EkmConfig."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import iam
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.iam import iam_util
from googlecloudsdk.command_lib.kms import resource_args
class RemoveIamPolicyBinding(base.Command):
"""Remove IAM policy binding from an EkmConfig.
Removes a policy binding from the IAM policy of a kms EkmConfig. A binding
consists of at
least one member, a role, and an optional condition.
## EXAMPLES
To remove an IAM policy binding for the role of 'roles/editor' for the user
'test-user@gmail.com' on the EkmConfig with location us-central1, run:
$ {command} --location='us-central1' --member='user:test-user@gmail.com'
--role='roles/editor'
To remove an IAM policy binding with a condition of
expression='request.time < timestamp("2023-01-01T00:00:00Z")',
title='expires_end_of_2022',
and description='Expires at midnight on 2022-12-31' for the role of
'roles/editor'
for the user 'test-user@gmail.com' on the EkmConfig with location us-central1,
run:
$ {command} --location='us-central1' --member='user:test-user@gmail.com'
--role='roles/editor' --condition='expression=request.time <
timestamp("2023-01-01T00:00:00Z"),title=expires_end_of_2022,description=Expires
at midnight on 2022-12-31'
To remove all IAM policy bindings regardless of the condition for the role of
'roles/editor' and for the user 'test-user@gmail.com' on the EkmConfig with
location us-central1, run:
$ {command} laplace --location='us-central1'
--member='user:test-user@gmail.com' --role='roles/editor' --all
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
"""
@staticmethod
def Args(parser):
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
iam_util.AddArgsForRemoveIamPolicyBinding(parser, add_condition=True)
def Run(self, args):
location_ref = args.CONCEPTS.location.Parse()
ekm_config_name = 'projects/{0}/locations/{1}/ekmConfig'.format(
location_ref.projectsId, location_ref.locationsId)
result = iam.RemovePolicyBindingFromEkmConfig(ekm_config_name, args.member,
args.role)
iam_util.LogSetIamPolicy(ekm_config_name, 'EkmConfig')
return result

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Set the IAM policy for EkmConfig."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.api_lib.cloudkms import iam
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.iam import iam_util
from googlecloudsdk.command_lib.kms import resource_args
class SetIamPolicy(base.Command):
"""Set the IAM policy for an EkmConfig.
Sets the IAM policy for the EkmConfig in a location as defined in a JSON or
YAML file.
See https://cloud.google.com/iam/docs/managing-policies for details of
the policy file format and contents.
## EXAMPLES
The following command will read am IAM policy defined in a JSON file
'policy.json' and set it for the EkmConfig with location `us-central1`:
$ {command} policy.json --location=us-central1
"""
@staticmethod
def Args(parser):
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
parser.add_argument(
'policy_file', help=('JSON or YAML file with '
'the IAM policy'))
def Run(self, args):
messages = cloudkms_base.GetMessagesModule()
policy, update_mask = iam_util.ParseYamlOrJsonPolicyFile(
args.policy_file, messages.Policy)
location_ref = args.CONCEPTS.location.Parse()
ekm_config_name = 'projects/{0}/locations/{1}/ekmConfig'.format(
location_ref.projectsId, location_ref.locationsId)
result = iam.SetEkmConfigIamPolicy(ekm_config_name, policy, update_mask)
iam_util.LogSetIamPolicy(ekm_config_name, 'EkmConfig')
return result

View File

@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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 the EkmConfig."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import resource_args
class Update(base.UpdateCommand):
r"""Update the EkmConfig.
{command} can be used to update the EkmConfig. Applies to all CryptoKeys and
CryptoKeyVersions with a `protection level` of `external vpc`.
## EXAMPLES
The following command sets the default ekm-connection to `laplace` for its
project `foo` and location `us-east1`:
$ {command} --location=us-east1
--default-ekm-connection="projects/foo/locations/us-east1/ekmConnections/laplace"
The following command removes the default-ekm-connection in `us-east1` for the
current project:
$ {command} --location=us-east1 --default-ekm-connection=""
"""
@staticmethod
def Args(parser):
flags.AddDefaultEkmConnectionFlag(parser)
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
def CreateRequest(self, messages, ec_name, ekm_config):
ekm_config_to_update = ekm_config
ekm_config_to_update.defaultEkmConnection = ec_name
req = messages.CloudkmsProjectsLocationsUpdateEkmConfigRequest(
name=ekm_config.name, ekmConfig=ekm_config_to_update)
req.updateMask = 'defaultEkmConnection'
return req
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
loc_ref = args.CONCEPTS.location.Parse()
# Currently default_ekm_connection is the only field so it must be specified
# but it can be an empty string to remove the default ekm connection from
# the config.
if args.default_ekm_connection is None:
raise exceptions.RequiredArgumentException('--default-ekm-connection',
'Must be specified.')
# Try to get the ekmConfig and raise an exception if it doesn't exist.
ekm_config_name = 'projects/{0}/locations/{1}/ekmConfig'.format(
loc_ref.projectsId, loc_ref.locationsId)
ekm_config = client.projects_locations.GetEkmConfig(
messages.CloudkmsProjectsLocationsGetEkmConfigRequest(
name=ekm_config_name))
# Make update request
update_req = self.CreateRequest(messages, args.default_ekm_connection,
ekm_config)
return client.projects_locations.UpdateEkmConfig(update_req)

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""The command group for ekm connections."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import resources
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class EkmConnections(base.Group):
"""Create and manage ekm connections.
Ekm Connections are used to control the connection settings for an
EXTERNAL_VPC CryptoKey.
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
@staticmethod
def Args(parser):
parser.display_info.AddUriFunc(
cloudkms_base.MakeGetUriFunc(flags.EKM_CONNECTION_COLLECTION))

View File

@@ -0,0 +1,34 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Add IAM policy binding for a kms ekm connection.
description: |
Adds a policy binding to the IAM policy of a kms ekm connection. A binding consists of at least
one member, a role, and an optional condition.
examples: |
To add an IAM policy binding for the role of 'roles/editor' for the user 'test-user@gmail.com'
on the ekm connection laplace with location global, run:
$ {command} laplace --location='global' --member='user:test-user@gmail.com' --role='roles/editor'
To add an IAM policy binding which expires at the end of the year 2022 for the role of
'roles/editor' and the user 'test-user@gmail.com' on the laplace fellowship and
location global, run:
$ {command} laplace --location='global' --member='user:test-user@gmail.com' --role='roles/editor' --condition='expression=request.time < timestamp("2023-01-01T00:00:00Z"),title=expires_end_of_2022,description=Expires at midnight on 2022-12-31'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: cloudkms.projects.locations.ekmConnections
arguments:
resource:
help_text: The ekm connection to add the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:ekm_connection
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""Create a new ekm connection."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import certs
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import maps
from googlecloudsdk.command_lib.kms import resource_args
class Create(base.CreateCommand):
r"""Create a new ekm connection.
Creates a new connection within the given location.
## EXAMPLES
The following command creates an ekm connection named `laplace` within the
location `us-central1`:
$ {command} laplace \
--location=us-central1 \
--service-directory-service="foo" \
--endpoint-filter="foo > bar" \
--hostname="hostname.foo" \
--server-certificates-files=foo.pem,bar.pem
The following command creates an ekm connection named `laplace` within the
location `us-central1` in `cloud-kms` key management mode with the required
crypto-space-path :
$ {command} laplace \
--location=us-central1 \
--service-directory-service="foo" \
--endpoint-filter="foo > bar" \
--hostname="hostname.foo" \
--key-management-mode=cloud-kms
--crypto-space-path="foo"
--server-certificates-files=foo.pem,bar.pem
"""
@staticmethod
def Args(parser):
resource_args.AddKmsEkmConnectionResourceArgForKMS(parser, True,
'ekm_connection')
flags.AddServiceDirectoryServiceFlag(parser, True)
flags.AddEndpointFilterFlag(parser)
flags.AddHostnameFlag(parser, True)
flags.AddServerCertificatesFilesFlag(parser, True)
flags.AddKeyManagementModeFlags(parser)
parser.display_info.AddCacheUpdater(flags.EkmConnectionCompleter)
def _CreateRequest(self, args):
messages = cloudkms_base.GetMessagesModule()
ekm_connection_ref = args.CONCEPTS.ekm_connection.Parse()
parent_ref = ekm_connection_ref.Parent()
if args.key_management_mode == 'cloud-kms':
if not args.crypto_space_path:
raise exceptions.RequiredArgumentException(
'--crypto-space-path',
'Must be supplied when --key-management-mode is cloud-kms.')
certificate_list = []
for cert_file in args.server_certificates_files:
try:
certificate_list.append(
messages.Certificate(rawDer=certs.GetDerCertificate(cert_file)))
except Exception as e:
raise exceptions.BadArgumentException(
'--server-certificates-files',
'Error while attempting to read file {} : {}'.format(cert_file, e))
req = messages.CloudkmsProjectsLocationsEkmConnectionsCreateRequest(
parent=parent_ref.RelativeName(),
ekmConnectionId=ekm_connection_ref.Name(),
ekmConnection=messages.EkmConnection(
keyManagementMode=maps.KEY_MANAGEMENT_MODE_MAPPER.GetEnumForChoice(
args.key_management_mode),
cryptoSpacePath=args.crypto_space_path,
serviceResolvers=[
messages.ServiceResolver(
serviceDirectoryService=args.service_directory_service,
endpointFilter=args.endpoint_filter,
hostname=args.hostname,
serverCertificates=certificate_list)
]))
return req
def Run(self, args):
client = cloudkms_base.GetClientInstance()
return client.projects_locations_ekmConnections.Create(
self._CreateRequest(args))

View File

@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""Describe an ekmconnection."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import resource_args
class Describe(base.DescribeCommand):
"""Get metadata for an ekmconnection.
Returns metadata for the given ekmconnection.
## EXAMPLES
The following command returns the metadata for the ekmconnection `laplace`
in the location `us-east1`:
$ {command} laplace --location=us-east1
"""
@staticmethod
def Args(parser):
resource_args.AddKmsEkmConnectionResourceArgForKMS(parser, True,
'ekm_connection')
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
ekm_connection_ref = args.CONCEPTS.ekm_connection.Parse()
if not ekm_connection_ref.Name():
raise exceptions.InvalidArgumentException(
'ekmconnection', 'ekmconnection id must be non-empty.')
return client.projects_locations_ekmConnections.Get(
messages.CloudkmsProjectsLocationsEkmConnectionsGetRequest(
name=ekm_connection_ref.RelativeName()))

View File

@@ -0,0 +1,27 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Get the IAM policy for an ekm connection.
description: |
Displays the IAM policy associated with an ekm connection.
If formatted as JSON, the output can be edited and used as
a policy file for *set-iam-policy*. The output includes an "etag"
field identifying the version emitted and allowing detection of
concurrent policy updates;
see $ {parent_command} set-iam-policy for additional details.
examples: |
To print the IAM policy for a given ekm connection, run:
$ {command} --location=my-location my-ekmconnection
request:
collection: cloudkms.projects.locations.ekmConnections
arguments:
resource:
help_text: The ekm connection for which to get the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:ekm_connection
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""List ekmconnections within a location."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
class List(base.ListCommand):
"""List ekmconnections within a location.
Lists all ekmconnections within the given location.
## EXAMPLES
The following command lists a maximum of five ekmconnections in the location
`global`:
$ {command} --location=global --limit=5
"""
@staticmethod
def Args(parser):
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
# Service resolvers is currently restricted to only have one.
parser.display_info.AddFormat("""
table(
name,
service_resolvers[0].serviceDirectoryService,
service_resolvers[0].hostname)
""")
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
location_ref = args.CONCEPTS.location.Parse()
request = messages.CloudkmsProjectsLocationsEkmConnectionsListRequest(
parent=location_ref.RelativeName())
return list_pager.YieldFromList(
client.projects_locations_ekmConnections,
request,
field='ekmConnections',
limit=args.limit,
batch_size_attribute='pageSize')

View File

@@ -0,0 +1,41 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Remove IAM policy binding for a kms ekm connection.
description: |
Removes a policy binding from the IAM policy of a kms ekm connection. A binding consists of at
least one member, a role, and an optional condition.
examples: |
To remove an IAM policy binding for the role of 'roles/editor' for the user
'test-user@gmail.com' on the ekm connection laplace with location global, run:
$ {command} laplace --location='global' --member='user:test-user@gmail.com' --role='roles/editor'
To remove an IAM policy binding with a condition of
expression='request.time < timestamp("2023-01-01T00:00:00Z")', title='expires_end_of_2022',
and description='Expires at midnight on 2022-12-31' for the role of 'roles/editor'
for the user 'test-user@gmail.com' on the ekm connection laplace with location global, run:
$ {command} laplace --location='global' --member='user:test-user@gmail.com' --role='roles/editor' --condition='expression=request.time < timestamp("2023-01-01T00:00:00Z"),title=expires_end_of_2022,description=Expires at midnight on 2022-12-31'
To remove all IAM policy bindings regardless of the condition for the role of
'roles/editor' and for the user 'test-user@gmail.com' on the ekm connection laplace with
location global, run:
$ {command} laplace --location='global' --member='user:test-user@gmail.com' --role='roles/editor' --all
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: cloudkms.projects.locations.ekmConnections
arguments:
resource:
help_text: The ekm connection to remove the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:ekm_connection
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,30 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Set the IAM policy binding for a KMS ekm connection.
description: |
Sets the IAM policy for the given ekm connection as defined in a JSON or YAML file.
See https://cloud.google.com/iam/docs/managing-policies for details of
the policy file format and contents.
examples: |
The following command will read an IAM policy defined in a JSON file
'policy.json' and set it for the ekm connection 'laplace' with the location 'global':
$ {command} laplace policy.json --location=global
See https://cloud.google.com/iam/docs/managing-policies for details of the
policy file format and contents.
request:
collection: cloudkms.projects.locations.ekmConnections
modify_request_hooks:
- googlecloudsdk.command_lib.iam.hooks:UseMaxRequestedPolicyVersion:api_field=setIamPolicyRequest.policy.version
- googlecloudsdk.command_lib.iam.hooks:AddVersionToUpdateMaskIfNotPresent:update_mask_path=setIamPolicyRequest.updateMask
arguments:
resource:
help_text: The ekm connection for which to set the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:ekm_connection
iam:
policy_version: 3

View File

@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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 ekmconnection."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import certs
from googlecloudsdk.command_lib.kms import exceptions as kms_exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import maps
from googlecloudsdk.command_lib.kms import resource_args
class Update(base.UpdateCommand):
r"""Update an ekmconnection.
{command} can be used to update the ekmconnection. Updates can be made to the
ekmconnection's service resolver's fields.
## EXAMPLES
The following command updates an ekm-connection named `laplace` service
resolver's hostname within location `us-east1`:
$ {command} laplace --location=us-east1 \
--hostname=newhostname.foo
The following command updates an ekm-connection named `laplace` service
resolver's service_directory_service, endpoint_filter, hostname, and
server_certificates within location `us-east1`:
$ {command} laplace --location=us-east1 \
--service-directory-service="foo" \
--endpoint-filter="foo > bar" \
--hostname="newhostname.foo" \
--server-certificates-files=foo.pem,bar.pem
The following command updates an ekm-connection named `laplace`
key_management_mode within location `us-east1`:
$ {command} laplace --location=us-east1 \
--key-management-mode=manual
"""
@staticmethod
def Args(parser):
resource_args.AddKmsEkmConnectionResourceArgForKMS(parser, True,
'ekm_connection')
flags.AddServiceDirectoryServiceFlag(parser)
flags.AddEndpointFilterFlag(parser)
flags.AddHostnameFlag(parser)
flags.AddKeyManagementModeFlags(parser)
flags.AddServerCertificatesFilesFlag(parser)
def CreateUpdateMask(self, args):
update_mask = []
if (args.service_directory_service or args.endpoint_filter or
args.hostname or args.server_certificates_files):
update_mask.append('serviceResolvers')
if args.key_management_mode:
update_mask.append('keyManagementMode')
if args.crypto_space_path:
update_mask.append('cryptoSpacePath')
return ','.join(update_mask)
def CreateRequest(self, args, messages, ekm_connection_to_update):
ec_ref = flags.ParseEkmConnectionName(args)
service_resolver_to_update = ekm_connection_to_update.serviceResolvers[0]
if args.service_directory_service:
service_resolver_to_update.serviceDirectoryService = args.service_directory_service
if args.endpoint_filter:
service_resolver_to_update.endpointFilter = args.endpoint_filter
if args.hostname:
service_resolver_to_update.hostname = args.hostname
if args.key_management_mode:
ekm_connection_to_update.keyManagementMode = (
maps.KEY_MANAGEMENT_MODE_MAPPER.GetEnumForChoice(
args.key_management_mode))
if args.crypto_space_path:
ekm_connection_to_update.cryptoSpacePath = args.crypto_space_path
certificate_list = []
if args.server_certificates_files:
for cert_file in args.server_certificates_files:
try:
certificate_list.append(
messages.Certificate(rawDer=certs.GetDerCertificate(cert_file)))
except Exception as e:
raise exceptions.BadArgumentException(
'--server-certificates-files',
'Error while attempting to read file {} : {}'.format(
cert_file, e))
service_resolver_to_update.serverCertificates = certificate_list
req = messages.CloudkmsProjectsLocationsEkmConnectionsPatchRequest(
name=ec_ref.RelativeName(), ekmConnection=ekm_connection_to_update)
req.updateMask = self.CreateUpdateMask(args)
return req
def Run(self, args):
if not (args.service_directory_service or args.endpoint_filter or
args.hostname or args.server_certificates_files or
args.key_management_mode or args.crypto_space_path):
raise kms_exceptions.UpdateError(
'An error occured: At least one of --service-directory-service or '
'--endpoint-filter or --hostname or --server-certificates-files or '
'--key-management-mode or --crypto-space-path must be specified.')
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
ec_ref = flags.ParseEkmConnectionName(args)
# Try to get the ekmConnection and raise an exception if it doesn't exist.
ekm_connection = client.projects_locations_ekmConnections.Get(
messages.CloudkmsProjectsLocationsEkmConnectionsGetRequest(
name=ec_ref.RelativeName()))
# Make update request
update_req = self.CreateRequest(args, messages, ekm_connection)
return client.projects_locations_ekmConnections.Patch(update_req)

View File

@@ -0,0 +1,218 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Encrypt a plaintext file using a key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import crc32c
from googlecloudsdk.command_lib.kms import e2e_integrity
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import files
class Encrypt(base.Command):
r"""Encrypt a plaintext file using a key.
Encrypts the given plaintext file using the given CryptoKey and writes the
result to the named ciphertext file. The plaintext file must not be larger
than 64KiB.
If an additional authenticated data file is provided, its contents must also
be provided during decryption. The file must not be larger than 64KiB.
The flag `--version` indicates the version of the key to use for
encryption. By default, the primary version is used.
If `--plaintext-file` or `--additional-authenticated-data-file` is set to '-',
that file is read from stdin. Similarly, if `--ciphertext-file` is set to '-',
the ciphertext is written to stdout.
By default, the command performs integrity verification on data sent to and
received from Cloud KMS. Use `--skip-integrity-verification` to disable
integrity verification.
## EXAMPLES
The following command will read the file 'path/to/plaintext', encrypt it using
the CryptoKey `frodo` with the KeyRing `fellowship` and Location `global`, and
write the ciphertext to 'path/to/ciphertext'.
$ {command} \
--key=frodo \
--keyring=fellowship \
--location=global \
--plaintext-file=path/to/input/plaintext \
--ciphertext-file=path/to/output/ciphertext
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser, 'The key to use for encryption.')
flags.AddCryptoKeyVersionFlag(parser, 'to use for encryption')
flags.AddPlaintextFileFlag(parser, 'to encrypt')
flags.AddCiphertextFileFlag(parser, 'to output')
flags.AddAadFileFlag(parser)
flags.AddSkipIntegrityVerification(parser)
def _ReadFileOrStdin(self, path, max_bytes):
data = console_io.ReadFromFileOrStdin(path, binary=True)
if len(data) > max_bytes:
raise exceptions.BadFileException(
'The file [{0}] is larger than the maximum size of {1} bytes.'.format(
path, max_bytes))
return data
def _PerformIntegrityVerification(self, args):
return not args.skip_integrity_verification
def _MaybeStripResourceVersion(self, req_name, resp_name):
"""Strips the trailing '/cryptoKeyVersions/xx' from Response's resource name.
If request's resource name is a key and not a version, returns response's
resource name with the trailing '/cryptoKeyVersions/xx' suffix stripped.
Args:
req_name: String.
CloudkmsProjectsLocationsKeyRingsCryptoKeysEncryptRequest.name.
resp_name: String. EncryptResponse.name.
Returns:
resp_resource_name with '/cryptoKeyVersions/xx' suffix stripped.
"""
if req_name.find('/cryptoKeyVersions/') == -1:
return resp_name.partition('/cryptoKeyVersions/')[0]
else:
return resp_name
def _CreateEncryptRequest(self, args):
if (args.plaintext_file == '-' and
args.additional_authenticated_data_file == '-'):
raise exceptions.InvalidArgumentException(
'--plaintext-file',
'--plaintext-file and --additional-authenticated-data-file cannot '
'both read from stdin.')
try:
# The Encrypt API limits the plaintext to 64KiB.
plaintext = self._ReadFileOrStdin(args.plaintext_file, max_bytes=65536)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read plaintext file [{0}]: {1}'.format(
args.plaintext_file, e))
aad = None
if args.additional_authenticated_data_file:
try:
# The Encrypt API limits the AAD to 64KiB.
aad = self._ReadFileOrStdin(
args.additional_authenticated_data_file, max_bytes=65536)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read additional authenticated data file [{0}]: {1}'
.format(args.additional_authenticated_data_file, e))
if args.version:
crypto_key_ref = flags.ParseCryptoKeyVersionName(args)
else:
crypto_key_ref = flags.ParseCryptoKeyName(args)
messages = cloudkms_base.GetMessagesModule()
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysEncryptRequest(
name=crypto_key_ref.RelativeName())
# Populate request integrity fields.
if self._PerformIntegrityVerification(args):
plaintext_crc32c = crc32c.Crc32c(plaintext)
# Set checksum if AAD is not provided for simpler response verification.
aad_crc32c = crc32c.Crc32c(aad) if aad is not None else crc32c.Crc32c(b'')
req.encryptRequest = messages.EncryptRequest(
plaintext=plaintext,
additionalAuthenticatedData=aad,
plaintextCrc32c=plaintext_crc32c,
additionalAuthenticatedDataCrc32c=aad_crc32c)
else:
req.encryptRequest = messages.EncryptRequest(
plaintext=plaintext, additionalAuthenticatedData=aad)
return req
def _VerifyResponseIntegrityFields(self, req, resp):
"""Verifies integrity fields in EncryptResponse.
Note: This methods assumes that self._PerformIntegrityVerification() is True
and that all request CRC32C fields were pupolated.
Args:
req: messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysEncryptRequest()
object
resp: messages.EncryptResponse() object.
Returns:
Void.
Raises:
e2e_integrity.ServerSideIntegrityVerificationError if the server reports
request integrity verification error.
e2e_integrity.ClientSideIntegrityVerificationError if response integrity
verification fails.
"""
# Verify resource name.
# Strip version from resp.name if --key was provided.
resp_name = self._MaybeStripResourceVersion(req.name, resp.name)
if req.name != resp_name:
raise e2e_integrity.ResourceNameVerificationError(
e2e_integrity.GetResourceNameMismatchErrorMessage(
req.name, resp_name))
# plaintext_crc32c was verified server-side.
if not resp.verifiedPlaintextCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage())
# additional_authenticated_data_crc32c was verified server-side.
if not resp.verifiedAdditionalAuthenticatedDataCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage())
# Verify ciphertext checksum.
if not crc32c.Crc32cMatches(resp.ciphertext, resp.ciphertextCrc32c):
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetResponseFromServerCorruptedErrorMessage())
def Run(self, args):
client = cloudkms_base.GetClientInstance()
req = self._CreateEncryptRequest(args)
try:
resp = client.projects_locations_keyRings_cryptoKeys.Encrypt(req)
# Intercept INVALID_ARGUMENT errors related to checksum verification, to
# present a user-friendly message. All other errors are surfaced as-is.
except apitools_exceptions.HttpBadRequestError as error:
e2e_integrity.ProcessHttpBadRequestError(error)
if self._PerformIntegrityVerification(args):
self._VerifyResponseIntegrityFields(req, resp)
try:
log.WriteToFileOrStdout(
args.ciphertext_file, resp.ciphertext, binary=True, overwrite=True)
except files.Error as e:
raise exceptions.BadFileException(e)

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 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.
"""The command group for import jobs."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
class ImportJobs(base.Group):
"""Create and manage import jobs.
Import jobs can be used to create CryptoKeyVersions using
pre-existing key material, generated outside of Cloud KMS.
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
@classmethod
def Args(cls, parser):
parser.display_info.AddUriFunc(
cloudkms_base.MakeGetUriFunc(flags.IMPORT_JOB_COLLECTION))

View File

@@ -0,0 +1,28 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Add IAM policy binding to a KMS import job.
description: |
Adds a policy binding to the IAM policy of a KMS import job. One binding consists of a member
and a role.
examples: |
To add an IAM policy binding for the role of 'roles/viewer' for the user 'test-user@gmail.com'
on the import job frodo with the keyring fellowship and location global, run:
$ {command} frodo --keyring='fellowship' --location='global' --member='user:test-user@gmail.com' --role='roles/viewer'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: cloudkms.projects.locations.keyRings.importJobs
arguments:
resource:
help_text: The import job to add the IAM policy binding to.
spec: !REF googlecloudsdk.command_lib.kms.resources:import_job
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""Create a new import job."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import maps
@base.DefaultUniverseOnly
class Create(base.CreateCommand):
r"""Create a new import job.
Creates a new import job within the given keyring.
## EXAMPLES
The following command creates a new import job named `strider` within the
`fellowship` keyring, and `us-central1` location:
$ {command} strider --location=us-central1 \
--keyring=fellowship --import-method=rsa-oaep-3072-sha256-aes-256 \
--protection-level=hsm
The following command creates a new import job named `strider` within the
`fellowship` keyring, and `us-central1` location:
$ {command} strider --location=us-central1 \
--keyring=fellowship --import-method=rsa-oaep-3072-sha256-aes-256 \
--protection-level=hsm-single-tenant --single-tenant-hsm-instance=my_sthi
"""
@staticmethod
def Args(parser):
flags.AddSingleTenantHsmInstanceFlag(parser)
flags.AddLocationFlag(parser, 'import job')
flags.AddKeyRingFlag(parser, 'import job')
flags.AddRequiredProtectionLevelFlag(parser)
flags.AddRequiredImportMethodFlag(parser)
flags.AddPositionalImportJobArgument(parser, 'to create')
parser.display_info.AddCacheUpdater(flags.KeyRingCompleter)
def _CreateRequest(self, args):
messages = cloudkms_base.GetMessagesModule()
if not args.protection_level:
raise exceptions.ArgumentError(
'--protection-level needs to be specified when creating an import job'
)
if not args.import_method:
raise exceptions.ArgumentError(
'--import-method needs to be specified when creating an import job')
import_job_ref = flags.ParseImportJobName(args)
parent_ref = flags.ParseParentFromResource(import_job_ref)
if args.protection_level == 'hsm-single-tenant':
if not args.single_tenant_hsm_instance:
raise exceptions.BadArgumentException(
'--single-tenant-hsm-instance',
'Single tenant HSM instance must be specified when protection level'
' is hsm-single-tenant',
)
if args.single_tenant_hsm_instance:
single_tenant_hsm_instance_ref = flags.ParseSingleTenantHsmInstanceName(
args
)
if (
single_tenant_hsm_instance_ref.Parent().RelativeName()
!= import_job_ref.Parent().Parent().RelativeName()
):
raise exceptions.BadArgumentException(
'--single-tenant-hsm-instance',
'Single tenant HSM instance must be in the same location as the'
' import job',
)
return messages.CloudkmsProjectsLocationsKeyRingsImportJobsCreateRequest(
parent=parent_ref.RelativeName(),
importJobId=import_job_ref.Name(),
importJob=messages.ImportJob(
protectionLevel=maps.IMPORT_PROTECTION_LEVEL_MAPPER.GetEnumForChoice(
args.protection_level
),
importMethod=maps.IMPORT_METHOD_MAPPER.GetEnumForChoice(
args.import_method
),
cryptoKeyBackend=args.single_tenant_hsm_instance,
),
)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
return client.projects_locations_keyRings_importJobs.Create(
self._CreateRequest(args))

View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 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.
"""Describe a version."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import exceptions as kms_exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
class Describe(base.DescribeCommand):
r"""Get metadata for a given import job.
Returns metadata for the given import job.
The optional flag `--attestation-file` specifies file to write the attestation
into. The attestation enables the user to verify the integrity and provenance
of the key. See https://cloud.google.com/kms/docs/attest-key for more
information about attestations.
## EXAMPLES
The following command returns metadata for import job 'strider' within the
keyring 'fellowship' in the location 'us-central1':
$ {command} strider --keyring=fellowship --location=us-central1
For import jobs with protection level `HSM`, use the `--attestation-file`
flag to save the attestation to a local file.
$ {command} strider --keyring=fellowship --location=us-central1 \
--attestation-file=path/to/attestation.dat
"""
@staticmethod
def Args(parser):
flags.AddKeyRingFlag(parser, 'import job')
flags.AddLocationFlag(parser, 'import job')
flags.AddPositionalImportJobArgument(parser, 'to describe')
flags.AddAttestationFileFlag(parser)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
import_job_ref = flags.ParseImportJobName(args)
if not import_job_ref.Name():
raise exceptions.InvalidArgumentException(
'import_job', 'import job id must be non-empty.')
import_job = client.projects_locations_keyRings_importJobs.Get( # pylint: disable=line-too-long
messages.CloudkmsProjectsLocationsKeyRingsImportJobsGetRequest(
name=import_job_ref.RelativeName()))
# Raise exception if --attestation-file is provided for software
# import jobs.
if (args.attestation_file and import_job.protectionLevel !=
messages.ImportJob.ProtectionLevelValueValuesEnum.HSM):
raise kms_exceptions.ArgumentError(
'Attestations are only available for HSM import jobs.')
if (args.attestation_file and import_job.state == messages.ImportJob
.StateValueValuesEnum.PENDING_GENERATION):
raise kms_exceptions.ArgumentError(
'The attestation is unavailable until the import job is generated.')
if args.attestation_file and import_job.attestation is not None:
try:
log.WriteToFileOrStdout(
args.attestation_file,
import_job.attestation.content,
overwrite=True,
binary=True)
except files.Error as e:
raise exceptions.BadFileException(e)
if import_job.attestation is not None:
# Suppress the attestation content in the printed output. Users can use
# --attestation-file to obtain it, instead.
import_job.attestation.content = None
return import_job

View File

@@ -0,0 +1,27 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Get the IAM policy for an import job.
description: |
Displays the IAM policy associated with an import job.
If formatted as JSON, the output can be edited and used as
a policy file for *set-iam-policy*. The output includes an "etag"
field identifying the version emitted and allowing detection of
concurrent policy updates;
see $ {parent_command} set-iam-policy for additional details.
examples: |
To print the IAM policy for a given import job, run:
$ {command} --keyring=my-key-ring --location=my-location my-importjob
request:
collection: cloudkms.projects.locations.keyRings.importJobs
arguments:
resource:
help_text: The import job for which to get the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:import_job
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 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.
"""List the import jobs within a keyring."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
class List(base.ListCommand):
"""Lists import jobs within a keyring.
Lists all import jobs within the given keyring.
## EXAMPLES
The following command lists a maximum of five import jobs within the
keyring 'fellowship' and location 'global':
$ {command} --keyring=fellowship --location=global
"""
@staticmethod
def Args(parser):
flags.AddKeyRingFlag(parser, 'import job')
flags.AddLocationFlag(parser, 'import job')
parser.display_info.AddFormat("""
table(
name,
state,
import_method,
protection_level,
labels.list())
""")
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
key_ring_ref = flags.ParseKeyRingName(args)
request = messages.CloudkmsProjectsLocationsKeyRingsImportJobsListRequest(
parent=key_ring_ref.RelativeName())
return list_pager.YieldFromList(
client.projects_locations_keyRings_importJobs,
request,
field='importJobs',
limit=args.limit,
batch_size_attribute='pageSize')

View File

@@ -0,0 +1,29 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Remove IAM policy binding for a KMS import job.
description: |
Removes a policy binding from the IAM policy of a KMS import job. One binding consists of a
member and a role.
examples: |
To remove an IAM policy binding for the role of 'roles/viewer' for the user
'test-user@gmail.com' on the import job frodo with the keyring fellowship and location global,
run:
$ {command} frodo --keyring='fellowship' --location='global' --member='user:test-user@gmail.com' --role='roles/viewer'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: cloudkms.projects.locations.keyRings.importJobs
arguments:
resource:
help_text: The import job from which to remove the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:import_job
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,31 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Set the IAM policy binding for a KMS import job.
description: |
Sets the IAM policy for the given import job as defined in a JSON or YAML file.
See https://cloud.google.com/iam/docs/managing-policies for details of
the policy file format and contents.
examples: |
The following command will read an IAM policy defined in a JSON file
'policy.json' and set it for the import job 'frodo' with the keyring
'fellowship' and location 'global':
$ {command} frodo policy.json --keyring=fellowship --location=global
See https://cloud.google.com/iam/docs/managing-policies for details of the
policy file format and contents.
request:
collection: cloudkms.projects.locations.keyRings.importJobs
modify_request_hooks:
- googlecloudsdk.command_lib.iam.hooks:UseMaxRequestedPolicyVersion:api_field=setIamPolicyRequest.policy.version
- googlecloudsdk.command_lib.iam.hooks:AddVersionToUpdateMaskIfNotPresent:update_mask_path=setIamPolicyRequest.updateMask
arguments:
resource:
help_text: The import job for which to set the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:import_job
iam:
policy_version: 3

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""The command group for all of the Cloud KMS API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(
base.ReleaseTrack.GA, base.ReleaseTrack.BETA, base.ReleaseTrack.ALPHA
)
class KmsInventory(base.Group):
"""Manages the KMS Inventory and Key Tracking commands."""
category = base.IDENTITY_AND_SECURITY_CATEGORY
def Filter(self, context, args):
del context, args
# Fixes billing issues, overrides kms disabling quota
base.EnableUserProjectQuota()

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""Gets the protected resources summary."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.kmsinventory import inventory
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
DETAILED_HELP = {
'DESCRIPTION': """
*{command}* returns a summary of the resources a key is protecting.
The summary includes how many projects contain protected resources,
how many protected resources there are, what are the types of protected
resources, and the count for each type of protected resource.
""",
'EXAMPLES': """
To view the summary of protected resources for the key `puppy`, run:
$ {command} --keyname=puppy
""",
}
class GetProtectedResourcesSummary(base.Command):
"""Gets the protected resources summary."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
resource_args.AddKmsKeyResourceArgForKMS(parser, True, '--keyname')
def Run(self, args):
keyname = args.keyname
return inventory.GetProtectedResourcesSummary(keyname)

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""List the keys within a keyring."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.kmsinventory import inventory
from googlecloudsdk.calliope import base
from googlecloudsdk.core import properties
DETAILED_HELP = {
'DESCRIPTION': """
*{command}* lists the keys in the specified project.
""",
'EXAMPLES': """
To view the keys in the default project, run:
$ {command}
To view the keys in project `jellyfish`, run:
$ {command} --project=jellyfish
""",
}
class ListKeys(base.ListCommand):
"""Lists the keys in a project."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
pass
def Run(self, args):
# NOMUTANTS--no good way to test
project = properties.VALUES.core.project.Get(required=True)
return inventory.ListKeys(project, args)

View File

@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""Searches the protected resources."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.kmsinventory import inventory
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
from googlecloudsdk.command_lib.resource_manager import completers
DETAILED_HELP = {
'DESCRIPTION': """
*{command}* returns a list of the resources a key is protecting
within the specified organization.
""",
'EXAMPLES': """
To view the protected resources for the key `puppy` and organization
number `1234` run:
$ {command} --keyname=puppy --scope=1234
""",
}
RESOURCE_TYPE_HELP = """\
A list of resource types that this request searches for. If empty, it will
search all the [trackable resource types](https://cloud.google.com/kms/docs/view-key-usage#tracked-resource-types).
Regular expressions are also supported. For example:
* ``compute.googleapis.com.*'' snapshots resources whose type
starts with ``compute.googleapis.com''.
* ``.*Image'' snapshots resources whose type ends with
``Image''.
* ``.*Image.*'' snapshots resources whose type contains
``Image''.
See [RE2](https://github.com/google/re2/wiki/Syntax) for all supported
regular expression syntax. If the regular expression does not match any
supported resource type, an ``INVALID_ARGUMENT'' error will be returned.
"""
class SearchProtectedResources(base.ListCommand):
"""Searches the resources protected by a key."""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
resource_args.AddKmsKeyResourceArgForKMS(parser, True, '--keyname')
parser.add_argument(
'--scope',
metavar='ORGANIZATION_ID',
completer=completers.OrganizationCompleter,
required=True,
help='Organization ID.')
parser.add_argument(
'--resource-types',
metavar='RESOURCE_TYPES',
type=arg_parsers.ArgList(),
help=RESOURCE_TYPE_HELP,
)
def Run(self, args):
key_name = args.keyname
org = args.scope
resource_types = args.resource_types
if not resource_types:
resource_types = []
return inventory.SearchProtectedResources(
scope=org, key_name=key_name, resource_types=resource_types, args=args
)

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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.
"""The command group for KeyHandle resources."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import resources
@base.UniverseCompatible
@base.ReleaseTracks(
base.ReleaseTrack.GA, base.ReleaseTrack.BETA, base.ReleaseTrack.ALPHA
)
class KeyHandles(base.Group):
"""Create and manage KeyHandle resources.
A KeyHandle is a resource which contains a reference to a KMS CryptoKey
resource that can be used through existing CMEK channels
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
@classmethod
def Args(cls, parser):
parser.display_info.AddUriFunc(
cloudkms_base.MakeGetUriFunc(flags.KEY_HANDLE_COLLECTION)
)

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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.
"""Create a key handle."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
@base.UniverseCompatible
class Create(base.CreateCommand):
"""Create a new KeyHandle.
Creates a new KeyHandle, triggering the provisioning of a new CryptoKey for
CMEK use with the given resource type in the configured key project and the
same location
## EXAMPLES
The following command creates a KeyHandle named `my-key-handle` within the
location `global` for the resource type `compute.googleapis.com/Disk`:
$ {command} --key-handle-id=my-key-handle --my-key-handle --location=global
--resource-type=compute.googleapis.com/Disk
In case we want to generate a random KeyHandle id, we can use the
`--generate-key-handle-id` flag instead of the `--key-handle-id` flag.
"""
@staticmethod
def Args(parser):
flags.AddCreateKeyHandleFlags(parser)
parser.display_info.AddCacheUpdater(flags.KeyHandleCompleter)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
location_ref = args.CONCEPTS.location.Parse()
if args.key_handle_id:
req = messages.CloudkmsProjectsLocationsKeyHandlesCreateRequest(
parent=location_ref.RelativeName(),
keyHandleId=args.key_handle_id,
keyHandle=messages.KeyHandle(resourceTypeSelector=args.resource_type),
)
else:
req = messages.CloudkmsProjectsLocationsKeyHandlesCreateRequest(
parent=location_ref.RelativeName(),
keyHandle=messages.KeyHandle(resourceTypeSelector=args.resource_type),
)
operation = client.projects_locations_keyHandles.Create(req)
operation_ref = resources.REGISTRY.ParseRelativeName(
operation.name, collection='cloudkms.projects.locations.operations'
)
created_key_handle = waiter.WaitFor(
waiter.CloudOperationPoller(
client.projects_locations_keyHandles,
client.projects_locations_operations,
),
operation_ref,
'Waiting for KeyHandle to be created.',
)
log.CreatedResource(created_key_handle.name, kind='KeyHandle')
log.status.Print(
'The corresponding CryptoKey is: {0}'.format(created_key_handle.kmsKey)
)
return created_key_handle

View File

@@ -0,0 +1,18 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Get metadata for a KeyHandle.
description: Get metadata for a KeyHandle.
examples: |
The following command gets metadata for a KeyHandle named `my-key-handle` in the locations
`us-central1`.
$ {command} my-key-handle --location=us-central1
request:
collection: cloudkms.projects.locations.keyHandles
arguments:
resource:
help_text: The KeyHandle to get metadata for.
spec: !REF googlecloudsdk.command_lib.kms.resources:key_handle

View File

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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.
"""List keyhandles within a project and location."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import resource_args
@base.UniverseCompatible
class List(base.ListCommand):
"""List KeyHandle resources within a project and location.
Lists all KeyHandle resources within a given project and location.
Addtionally, can filter the list.
## EXAMPLES
The following command lists a maximum of five KeyHandle resources in the
location `global`:
$ {command} --location=global --limit=5
The following command lists all KeyHandle resources in the location `global`
that have a resource type selector of `compute.googleapis.com/Instance`:
$ {command} --location=global
--resource-type=compute.googleapis.com/Instance
"""
@staticmethod
def Args(parser):
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
flags.AddResourceTypeSelectorFlag(parser, True)
parser.display_info.AddFormat('table(name, kmsKey, resourceTypeSelector)')
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
location_ref = args.CONCEPTS.location.Parse()
filter_str = f'resource_type_selector="{args.resource_type}"'
request = messages.CloudkmsProjectsLocationsKeyHandlesListRequest(
parent=location_ref.RelativeName(), filter=filter_str
)
return list_pager.YieldFromList(
client.projects_locations_keyHandles,
request,
field='keyHandles',
limit=args.limit,
batch_size_attribute='pageSize',
)

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""The command group for keyrings."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import resources
class KeyRings(base.Group):
"""Create and manage keyrings.
A keyring is a toplevel logical grouping of keys.
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
@staticmethod
def Args(parser):
parser.display_info.AddUriFunc(
cloudkms_base.MakeGetUriFunc(flags.KEY_RING_COLLECTION))

View File

@@ -0,0 +1,34 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Add IAM policy binding for a kms keyring.
description: |
Adds a policy binding to the IAM policy of a kms keyring. A binding consists of at least one
member, a role, and an optional condition.
examples: |
To add an IAM policy binding for the role of 'roles/editor' for the user 'test-user@gmail.com'
on the keyring fellowship with location global, run:
$ {command} fellowship --location='global' --member='user:test-user@gmail.com' --role='roles/editor'
To add an IAM policy binding which expires at the end of the year 2018 for the role of
'roles/cloudkms.signer' and the user 'test-user@gmail.com' on the keyring fellowship and
location global, run:
$ {command} fellowship --location='global' --member='user:test-user@gmail.com' --role='roles/cloudkms.signer' --condition='expression=request.time < timestamp("2019-01-01T00:00:00Z"),title=expires_end_of_2018,description=Expires at midnight on 2018-12-31'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: cloudkms.projects.locations.keyRings
arguments:
resource:
help_text: The keyring to add the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:key_ring
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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.
"""Command group for managing Cloud KMS key ring configurations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class Config(base.Group):
"""Manage Cloud KMS key ring configurations."""

View File

@@ -0,0 +1,38 @@
release_tracks: [ALPHA]
command_type: CONFIG_EXPORT
help_text:
brief: Export the configuration for a Cloud KMS key ring.
description: |
*{command}* exports the configuration for a Cloud KMS key ring.
Key ring configurations can be exported in
Kubernetes Resource Model (krm) or Terraform HCL formats. The
default format is `krm`.
Specifying `--all` allows you to export the configurations for all
key rings within the project.
Specifying `--path` allows you to export the configuration(s) to
a local directory.
examples: |
To export the configuration for a key ring, run:
$ {command} my-key-ring
To export the configuration for a key ring to a file, run:
$ {command} my-key-ring --path=/path/to/dir/
To export the configuration for a key ring in Terraform
HCL format, run:
$ {command} my-key-ring --resource-format=terraform
To export the configurations for all key rings within a
project, run:
$ {command} --all
arguments:
resource:
help_text: Key ring to export the configuration for.
spec: !REF googlecloudsdk.command_lib.kms.resources:key_ring

View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Create a keyring."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import resource_args
class Create(base.CreateCommand):
"""Create a new keyring.
Creates a new keyring within the given location.
## Examples
The following command creates a keyring named `fellowship` within the
location `global`:
$ {command} fellowship --location=global
"""
@staticmethod
def Args(parser):
resource_args.AddKmsKeyringResourceArgForKMS(parser, True, 'keyring')
parser.display_info.AddCacheUpdater(flags.KeyRingCompleter)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
key_ring_ref = args.CONCEPTS.keyring.Parse()
parent_ref = key_ring_ref.Parent()
req = messages.CloudkmsProjectsLocationsKeyRingsCreateRequest(
parent=parent_ref.RelativeName(),
keyRingId=key_ring_ref.Name(),
keyRing=messages.KeyRing())
return client.projects_locations_keyRings.Create(req)

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Describe a keyring."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import resource_args
class Describe(base.DescribeCommand):
"""Get metadata for a keyring.
Returns metadata for the given keyring.
## EXAMPLES
The following command returns the metadata for the keyring `towers`
in the location `us-east1`:
$ {command} towers --location=us-east1
"""
@staticmethod
def Args(parser):
resource_args.AddKmsKeyringResourceArgForKMS(parser, True, 'keyring')
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
key_ring_ref = args.CONCEPTS.keyring.Parse()
if not key_ring_ref.Name():
raise exceptions.InvalidArgumentException('keyring',
'keyring id must be non-empty.')
return client.projects_locations_keyRings.Get(
messages.CloudkmsProjectsLocationsKeyRingsGetRequest(
name=key_ring_ref.RelativeName()))

View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Fetch the IAM policy for a keyring."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import iam
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
class GetIamPolicy(base.ListCommand):
"""Get the IAM policy for a keyring.
Gets the IAM policy for the given keyring.
Returns an empty policy if the resource does not have a policy set.
## EXAMPLES
The following command gets the IAM policy for the keyring `fellowship`
within the location `us-central1`:
$ {command} fellowship --location=us-central1
"""
@staticmethod
def Args(parser):
flags.AddLocationFlag(parser, 'keyring')
flags.AddKeyRingArgument(parser, 'whose IAM policy to fetch')
base.URI_FLAG.RemoveFromParser(parser)
def Run(self, args):
return iam.GetKeyRingIamPolicy(flags.ParseKeyRingName(args))

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""List keyrings within a location."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
class List(base.ListCommand):
"""List keyrings within a location.
Lists all keyrings within the given location.
## EXAMPLES
The following command lists a maximum of five keyrings in the location
`global`:
$ {command} --location=global --limit=5
"""
@staticmethod
def Args(parser):
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
parser.display_info.AddFormat('table(name)')
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
location_ref = args.CONCEPTS.location.Parse()
request = messages.CloudkmsProjectsLocationsKeyRingsListRequest(
parent=location_ref.RelativeName())
return list_pager.YieldFromList(
client.projects_locations_keyRings,
request,
field='keyRings',
limit=args.limit,
batch_size_attribute='pageSize')

View File

@@ -0,0 +1,41 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Remove IAM policy binding for a kms keyring.
description: |
Removes a policy binding from the IAM policy of a kms keyring. A binding consists of at least
one member, a role, and an optional condition.
examples: |
To remove an IAM policy binding for the role of 'roles/cloudkms.signer' for the user
'test-user@gmail.com' on the keyring fellowship with location global, run:
$ {command} fellowship --location='global' --member='user:test-user@gmail.com' --role='roles/cloudkms.signer'
To remove an IAM policy binding with a condition of
expression='request.time < timestamp("2019-01-01T00:00:00Z")', title='expires_end_of_2018',
and description='Expires at midnight on 2018-12-31' for the role of 'roles/cloudkms.signer'
for the user 'test-user@gmail.com' on the keyring fellowship with location global, run:
$ {command} fellowship --location='global' --member='user:test-user@gmail.com' --role='roles/cloudkms.signer' --condition='expression=request.time < timestamp("2019-01-01T00:00:00Z"),title=expires_end_of_2018,description=Expires at midnight on 2018-12-31'
To remove all IAM policy bindings regardless of the condition for the role of
'roles/cloudkms.signer' and for the user 'test-user@gmail.com' on the keyring fellowship with
location global, run:
$ {command} fellowship --location='global' --member='user:test-user@gmail.com' --role='roles/cloudkms.signer' --all
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: cloudkms.projects.locations.keyRings
arguments:
resource:
help_text: The keyring to remove the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:key_ring
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Set the IAM policy for a keyring."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.api_lib.cloudkms import iam
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.iam import iam_util
from googlecloudsdk.command_lib.kms import flags
class SetIamPolicy(base.Command):
"""Set the IAM policy for a keyring.
Sets the IAM policy for the given keyring as defined in a JSON or YAML file.
See https://cloud.google.com/iam/docs/managing-policies for details of
the policy file format and contents.
## EXAMPLES
The following command will read am IAM policy defined in a JSON file
'policy.json' and set it for the keyring `fellowship` with location `global`:
$ {command} fellowship policy.json --location=global
"""
@staticmethod
def Args(parser):
flags.AddLocationFlag(parser, 'keyring')
flags.AddKeyRingArgument(parser, 'whose IAM policy to update')
parser.add_argument('policy_file', help=('JSON or YAML file with '
'the IAM policy'))
def Run(self, args):
messages = cloudkms_base.GetMessagesModule()
policy, update_mask = iam_util.ParseYamlOrJsonPolicyFile(args.policy_file,
messages.Policy)
keyring_ref = flags.ParseKeyRingName(args)
result = iam.SetKeyRingIamPolicy(keyring_ref, policy, update_mask)
iam_util.LogSetIamPolicy(keyring_ref.Name(), 'keyring')
return result

View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""The command group for keys."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import resources
class Keys(base.Group):
"""Create and manage keys.
A key represents a logical key that can be used for cryptographic
operations.
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
@classmethod
def Args(cls, parser):
parser.display_info.AddUriFunc(
cloudkms_base.MakeGetUriFunc(flags.CRYPTO_KEY_COLLECTION))

View File

@@ -0,0 +1,35 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Add IAM policy binding for a kms key.
description: |
Adds a policy binding to the IAM policy of a kms key. A binding consists of at least one
member, a role, and an optional condition.
examples: |
To add an IAM policy binding for the role of 'roles/editor' for the user 'test-user@gmail.com'
on the key frodo with the keyring fellowship and location global, run:
$ {command} frodo --keyring='fellowship' --location='global' --member='user:test-user@gmail.com' --role='roles/editor'
To add an IAM policy binding which expires at the end of the year 2018 for the role of
'roles/cloudkms.signer' and the user 'test-user@gmail.com' on a the key frodo with the keyring
fellowship and location global, run:
$ {command} frodo --keyring='fellowship' --location='global' --member='user:test-user@gmail.com' --role='roles/cloudkms.signer' --condition='expression=request.time < timestamp("2019-01-01T00:00:00Z"),title=expires_end_of_2018,description=Expires at midnight on 2018-12-31'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: cloudkms.projects.locations.keyRings.cryptoKeys
arguments:
resource:
help_text: The key to add the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:key
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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.
"""Command group for managing Cloud KMS crypto key configurations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class Config(base.Group):
"""Manage Cloud KMS crypto key configurations."""

View File

@@ -0,0 +1,38 @@
release_tracks: [ALPHA]
command_type: CONFIG_EXPORT
help_text:
brief: Export the configuration for a Cloud KMS crypto key.
description: |
*{command}* exports the configuration for a Cloud KMS crypto key.
Crypto key configurations can be exported in
Kubernetes Resource Model (krm) or Terraform HCL formats. The
default format is `krm`.
Specifying `--all` allows you to export the configurations for all
crypto keys within the project.
Specifying `--path` allows you to export the configuration(s) to
a local directory.
examples: |
To export the configuration for a crypto key, run:
$ {command} my-crypto-key
To export the configuration for a crypto key to a file, run:
$ {command} my-crypto-key --path=/path/to/dir/
To export the configuration for a crypto key in Terraform
HCL format, run:
$ {command} my-crypto-key --resource-format=terraform
To export the configurations for all crypto keys within a
project, run:
$ {command} --all
arguments:
resource:
help_text: Crypto key to export the configuration for.
spec: !REF googlecloudsdk.command_lib.kms.resources:key

View File

@@ -0,0 +1,244 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Create a key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import exceptions as kms_exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import maps
from googlecloudsdk.command_lib.kms import resource_args
from googlecloudsdk.command_lib.util.args import labels_util
@base.UniverseCompatible
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class Create(base.CreateCommand):
r"""Create a new key.
Creates a new key within the given keyring.
The flag `--purpose` is always required when creating a key.
The flag `--default-algorithm` is required when creating a symmetric signing
key, an asymmetric key, or an external key. Algorithm and purpose should be
compatible.
The optional flags `--rotation-period` and `--next-rotation-time` define a
rotation schedule for the key. A schedule can also be defined
by the `--create-rotation-schedule` command.
The flag `--next-rotation-time` must be in ISO 8601 or RFC3339 format,
and `rotation-period` must be in the form INTEGER[UNIT], where units
can be one of seconds (s), minutes (m), hours (h) or days (d).
The optional flag `--protection-level` specifies the physical environment
where crypto operations with the key happen. The default is ``software''; use
``hsm'' to create a hardware-backed key, ``external'' to create an
externally backed key, or ``external-vpc'' to create an external key over vpc.
The optional flag `--labels` defines a user specified key/value pair for the
given key.
The flag `--skip-initial-version-creation` creates a CryptoKey with no
versions. If you import into the CryptoKey, or create a new version in that
CryptoKey, there will be no primary version until one is set using the
`--set-primary-version` command. You must include
`--skip-initial-version-creation` when creating a CryptoKey with protection
level ``external'' or ``external-vpc''.
The optional flag `--import-only` restricts the key to imported key versions
only. To do so, the flag `--skip-initial-version-creation` must also be set.
The optional flag `--destroy-scheduled-duration` defines the destroy schedule
for the key, and must be in the form INTEGER[UNIT], where units can be one of
seconds (s), minutes (m), hours (h) or days (d).
The flag `--crypto-key-backend` defines the resource name for the
backend where the key resides. Required for ``external-vpc'' keys.
The optional flag `--allowed-access-reasons` defines the Key Access
Justifications Policy for the key, and is specified as a comma separated list
of zero or more justification codes defined in
https://cloud.google.com/assured-workloads/key-access-justifications/docs/justification-codes.
The key must be enrolled in Key Access Justifications to use this flag.
## EXAMPLES
The following command creates a key named ``frodo'' with protection level
``software'' within the keyring ``fellowship'' and location ``us-east1'':
$ {command} frodo \
--location=us-east1 \
--keyring=fellowship \
--purpose=encryption
The following command creates a key named ``strider'' with protection level
``software'' within the keyring ``rangers'' and location ``global'' with a
specified rotation schedule:
$ {command} strider \
--location=global --keyring=rangers \
--purpose=encryption \
--rotation-period=30d \
--next-rotation-time=2017-10-12T12:34:56.1234Z
The following command creates a raw encryption key named ``foo'' with
protection level ``software'' within the keyring ``fellowship'' and location
``us-east1'' with two specified labels:
$ {command} foo \
--location=us-east1 \
--keyring=fellowship \
--purpose=raw-encryption \
--default-algorithm=aes-128-cbc
--labels=env=prod,team=kms
The following command creates an asymmetric key named ``samwise'' with
protection level ``software'' and default algorithm ``ec-sign-p256-sha256''
within the keyring ``fellowship'' and location ``us-east1'':
$ {command} samwise \
--location=us-east1 \
--keyring=fellowship \
--purpose=asymmetric-signing \
--default-algorithm=ec-sign-p256-sha256
The following command creates a key named ``gimli'' with protection level
``hsm'' and default algorithm ``google-symmetric-encryption'' within the
keyring ``fellowship'' and location ``us-east1'':
$ {command} gimli \
--location=us-east1 \
--keyring=fellowship \
--purpose=encryption \
--protection-level=hsm
The following command creates a key named ``legolas'' with protection level
``external'' and default algorithm ``external-symmetric-encryption'' within
the keyring ``fellowship'' and location ``us-central1'':
$ {command} legolas \
--location=us-central1 \
--keyring=fellowship \
--purpose=encryption \
--default-algorithm=external-symmetric-encryption \
--protection-level=external
--skip-initial-version-creation
The following command creates a key named ``bilbo'' with protection level
``external-vpc'' and default algorithm ``external-symmetric-encryption'' and
an EkmConnection of ``eagles'' within the keyring ``fellowship'' and location
``us-central1'':
$ {command} bilbo \
--location=us-central1 \
--keyring=fellowship \
--purpose=encryption \
--default-algorithm=external-symmetric-encryption \
--protection-level=external-vpc
--skip-initial-version-creation
--crypto-key-backend="projects/$(gcloud config get project)/
locations/us-central1/ekmConnections/eagles"
The following command creates a key named ``arwen'' with protection level
``software'' within the keyring ``fellowship'' and location ``us-east1'' with
a Key Access Justifications policy that allows access reasons
``customer-initiated-access'' and ``google-initiated-system-operation'':
$ {command} arwen \
--location=us-east1 \
--keyring=fellowship \
--purpose=encryption \
--allowed-access-reasons=customer-initiated-access,google-initiated-system-operation
"""
@staticmethod
def Args(parser):
resource_args.AddKmsKeyResourceArgForKMS(parser, True, 'key')
flags.AddRotationPeriodFlag(parser)
flags.AddNextRotationTimeFlag(parser)
flags.AddSkipInitialVersionCreationFlag(parser)
labels_util.AddCreateLabelsFlags(parser)
parser.add_argument(
'--purpose',
choices=sorted(maps.PURPOSE_MAP.keys()),
required=True,
help='The "purpose" of the key.')
parser.display_info.AddCacheUpdater(flags.KeyRingCompleter)
flags.AddProtectionLevelFlag(parser)
flags.AddDefaultAlgorithmFlag(parser)
flags.AddImportOnlyFlag(parser)
flags.AddDestroyScheduledDurationFlag(parser)
flags.AddCryptoKeyBackendFlag(parser)
flags.AddAllowedAccessReasonsFlag(parser)
def _CreateRequest(self, args):
messages = cloudkms_base.GetMessagesModule()
purpose = maps.PURPOSE_MAP[args.purpose]
valid_algorithms = maps.VALID_ALGORITHMS_MAP[purpose]
# Check default algorithm has been specified for non-symmetric-encryption
# keys. For backward compatibility, the algorithm is
# google-symmetric-encryption by default if the purpose is encryption.
if not args.default_algorithm:
if args.purpose != 'encryption':
raise kms_exceptions.ArgumentError(
'--default-algorithm needs to be specified when creating a key with'
' --purpose={}. The valid algorithms are: {}'.format(
args.purpose, ', '.join(valid_algorithms)))
args.default_algorithm = 'google-symmetric-encryption'
# Check default algorithm and purpose are compatible.
if args.default_algorithm not in valid_algorithms:
raise kms_exceptions.ArgumentError(
'Default algorithm and purpose are incompatible. Here are the valid '
'algorithms for --purpose={}: {}'.format(args.purpose,
', '.join(valid_algorithms)))
crypto_key_ref = args.CONCEPTS.key.Parse()
parent_ref = crypto_key_ref.Parent()
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCreateRequest(
parent=parent_ref.RelativeName(),
cryptoKeyId=crypto_key_ref.Name(),
cryptoKey=messages.CryptoKey(
purpose=purpose,
versionTemplate=messages.CryptoKeyVersionTemplate(
protectionLevel=maps.PROTECTION_LEVEL_MAPPER.GetEnumForChoice(
args.protection_level),
algorithm=maps.ALGORITHM_MAPPER.GetEnumForChoice(
args.default_algorithm)),
labels=labels_util.ParseCreateArgs(args,
messages.CryptoKey.LabelsValue),
importOnly=args.import_only,
cryptoKeyBackend=args.crypto_key_backend),
skipInitialVersionCreation=args.skip_initial_version_creation)
flags.SetNextRotationTime(args, req.cryptoKey)
flags.SetRotationPeriod(args, req.cryptoKey)
flags.SetDestroyScheduledDuration(args, req.cryptoKey)
flags.SetKeyAccessJustificationsPolicy(args, req.cryptoKey)
return req
def Run(self, args):
client = cloudkms_base.GetClientInstance()
return client.projects_locations_keyRings_cryptoKeys.Create(
self._CreateRequest(args))

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Describe a key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import resource_args
class Describe(base.DescribeCommand):
"""Get metadata for a given key.
Returns metadata for the given key.
## EXAMPLES
The following command returns metadata for the key `frodo` within
the keyring `fellowship` in the location `us-east1`:
$ {command} frodo --keyring=fellowship --location=us-east1
"""
@staticmethod
def Args(parser):
resource_args.AddKmsKeyResourceArgForKMS(parser, True, 'key')
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
crypto_key_ref = args.CONCEPTS.key.Parse()
if not crypto_key_ref.Name():
raise exceptions.InvalidArgumentException('key',
'key id must be non-empty.')
resp = client.projects_locations_keyRings_cryptoKeys.Get(
messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysGetRequest(
name=crypto_key_ref.RelativeName()))
# Suppress the attestation in the response, if there is one. Users can use
# keys versions describe --attestation-file to obtain it, instead.
if resp.primary and resp.primary.attestation:
resp.primary.attestation = None
return resp

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Fetch the IAM policy for a key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import iam
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import resource_args
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class GetIamPolicy(base.ListCommand):
"""Get the IAM policy for a key.
Gets the IAM policy for the given key.
Returns an empty policy if the resource does not have a policy
set.
## EXAMPLES
The following command gets the IAM policy for the key `frodo` within
the keyring `fellowship` and location `global`:
$ {command} frodo --keyring=fellowship --location=global
"""
@staticmethod
def Args(parser):
resource_args.AddKmsKeyResourceArgForKMS(parser, True, 'key')
base.URI_FLAG.RemoveFromParser(parser)
def Run(self, args):
return iam.GetCryptoKeyIamPolicy(flags.ParseCryptoKeyName(args))

View File

@@ -0,0 +1,27 @@
- release_tracks: [ALPHA]
help_text:
brief: Get the IAM policy for a key.
description: |
*{command}* displays the IAM policy associated with a key.
If formatted as JSON, the output can be edited and used as
a policy file for *set-iam-policy*. The output includes an "etag"
field identifying the version emitted and allowing detection of
concurrent policy updates;
see $ {parent_command} set-iam-policy for additional details.
examples: |
To print the IAM policy for a given cluster, run:
$ {command} --keyring=my-key-ring --location=my-location my-key
request:
collection: cloudkms.projects.locations.keyRings.cryptoKeys
arguments:
resource:
help_text: The key for which to display the IAM policy.
spec: !REF googlecloudsdk.command_lib.kms.resources:key
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""List the keys within a keyring."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
class List(base.ListCommand):
"""List the keys within a keyring.
Lists all keys within the given keyring.
## EXAMPLES
The following command lists all keys within the
keyring `fellowship` and location `global`:
$ {command} --keyring=fellowship --location=global
"""
@staticmethod
def Args(parser):
# The format of a CryptoKeyVersion name is:
# 'projects/*/locations/*/keyRings/*/cryptoKeys/*/cryptoKeyVersions/*'
# The CryptoKeyVersionId is captured by segment(9).
resource_args.AddKmsKeyringResourceArgForKMS(parser, True, '--keyring')
parser.display_info.AddFormat("""
table(
name,
purpose,
version_template.algorithm,
version_template.protection_level,
labels.list(),
primary.name.segment(9):label=PRIMARY_ID,
primary.state:label=PRIMARY_STATE)
""")
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
key_ring_ref = args.CONCEPTS.keyring.Parse()
request = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysListRequest(
parent=key_ring_ref.RelativeName())
return list_pager.YieldFromList(
client.projects_locations_keyRings_cryptoKeys,
request,
field='cryptoKeys',
limit=args.limit,
batch_size_attribute='pageSize')

View File

@@ -0,0 +1,42 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Remove IAM policy binding for a kms key.
description: |
Removes a policy binding from the IAM policy of a kms key. A binding consists of at least one
member, a role, and an optional condition.
examples: |
To remove an IAM policy binding for the role of 'roles/editor' for the user
'test-user@gmail.com' on the key frodo with the keyring fellowship and location global, run:
$ {command} frodo --keyring='fellowship' --location='global' --member='user:test-user@gmail.com' --role='roles/editor'
To remove an IAM policy binding with a condition of
expression='request.time < timestamp("2019-01-01T00:00:00Z")', title='expires_end_of_2018',
and description='Expires at midnight on 2018-12-31' for the role of 'roles/cloudkms.signer'
for the user 'test-user@gmail.com' on a the key frodo with the keyring fellowship and location
global, run:
$ {command} frodo --keyring='fellowship' --location='global' --member='user:test-user@gmail.com' --role='roles/cloudkms.signer' --condition='expression=request.time < timestamp("2019-01-01T00:00:00Z"),title=expires_end_of_2018,description=Expires at midnight on 2018-12-31'
To remove all IAM policy bindings regardless of the condition for the role of
'roles/cloudkms.signer' and for the user 'test-user@gmail.com' on a the key frodo with the
keyring fellowship and location global, run:
$ {command} frodo --keyring='fellowship' --location='global' --member='user:test-user@gmail.com' --role='roles/cloudkms.signer' --all
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: cloudkms.projects.locations.keyRings.cryptoKeys
arguments:
resource:
help_text: The key to remove the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.kms.resources:key
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Remove a rotation schedule."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import resource_args
class RemoveRotationSchedule(base.UpdateCommand):
r"""Remove the rotation schedule for a key.
Removes the rotation schedule for the given key.
## EXAMPLES
The following command removes the rotation schedule for the key
named `frodo` within the keyring `fellowship` and location `global`:
$ {command} frodo \
--location=global \
--keyring=fellowship
"""
@staticmethod
def Args(parser):
resource_args.AddKmsKeyResourceArgForKMS(parser, True, 'key')
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
crypto_key_ref = flags.ParseCryptoKeyName(args)
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysPatchRequest(
name=crypto_key_ref.RelativeName(),
cryptoKey=messages.CryptoKey(),
updateMask='rotationPeriod,nextRotationTime')
return client.projects_locations_keyRings_cryptoKeys.Patch(req)

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Set the IAM policy for a key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.api_lib.cloudkms import iam
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.iam import iam_util
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import resource_args
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class SetIamPolicy(base.Command):
"""Set the IAM policy for a key.
Sets the IAM policy for the given key as defined in a JSON or YAML file.
See https://cloud.google.com/iam/docs/managing-policies for details of
the policy file format and contents.
## EXAMPLES
The following command will read am IAM policy defined in a JSON file
'policy.json' and set it for the key `frodo` with the keyring
`fellowship` and location `global`:
$ {command} frodo policy.json --keyring=fellowship --location=global
"""
# Text from above based on output of function call below
# detailed_help = iam_util.GetDetailedHelpForSetIamPolicy(
# flags.CRYPTO_KEY_COLLECTION, 'example-crypto-key-1')
@staticmethod
def Args(parser):
resource_args.AddKmsKeyResourceArgForKMS(parser, True, 'key')
parser.add_argument('policy_file', help=('JSON or YAML '
'file with the IAM policy'))
def Run(self, args):
messages = cloudkms_base.GetMessagesModule()
policy, update_mask = iam_util.ParseYamlOrJsonPolicyFile(args.policy_file,
messages.Policy)
crypto_key_ref = flags.ParseCryptoKeyName(args)
result = iam.SetCryptoKeyIamPolicy(crypto_key_ref, policy, update_mask)
iam_util.LogSetIamPolicy(crypto_key_ref.Name(), 'key')
return result

View File

@@ -0,0 +1,31 @@
- release_tracks: [ALPHA]
help_text:
brief: Set the IAM policy for a key.
description: |
Sets the IAM policy for the given key as defined in a JSON or YAML file.
See https://cloud.google.com/iam/docs/managing-policies for details of
the policy file format and contents.
examples: |
The following command will read am IAM policy defined in a JSON file
'policy.json' and set it for the key `frodo` with the keyring
`fellowship` and location `global`:
$ {command} frodo policy.json --keyring fellowship --location global
See https://cloud.google.com/iam/docs/managing-policies for details of the
policy file format and contents.
request:
collection: cloudkms.projects.locations.keyRings.cryptoKeys
modify_request_hooks:
- googlecloudsdk.command_lib.iam.hooks:UseMaxRequestedPolicyVersion:api_field=setIamPolicyRequest.policy.version
- googlecloudsdk.command_lib.iam.hooks:AddVersionToUpdateMaskIfNotPresent:update_mask_path=setIamPolicyRequest.updateMask
arguments:
resource:
help_text: The key whose IAM policy to update.
spec: !REF googlecloudsdk.command_lib.kms.resources:key
iam:
policy_version: 3

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Set the primary version of a key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import resource_args
class SetPrimaryVersion(base.Command):
"""Set the primary version of a key.
Sets the specified version as the primary version of the given key.
The version is specified by its version number assigned on creation.
## EXAMPLES
The following command sets version 9 as the primary version of the
key `samwise` within keyring `fellowship` and location `global`:
$ {command} samwise --version=9 --keyring=fellowship --location=global
"""
@staticmethod
def Args(parser):
resource_args.AddKmsKeyResourceArgForKMS(parser, True, 'key')
flags.AddCryptoKeyVersionFlag(parser, 'to make primary', required=True)
def Run(self, args):
# pylint: disable=line-too-long
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
version_ref = flags.ParseCryptoKeyVersionName(args)
key_ref = flags.ParseCryptoKeyName(args)
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysUpdatePrimaryVersionRequest(
name=key_ref.RelativeName(),
updateCryptoKeyPrimaryVersionRequest=(
messages.UpdateCryptoKeyPrimaryVersionRequest(
cryptoKeyVersionId=version_ref.cryptoKeyVersionsId)))
return client.projects_locations_keyRings_cryptoKeys.UpdatePrimaryVersion(
req)

View File

@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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 a rotation schedule on a key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import resource_args
class SetRotationSchedule(base.UpdateCommand):
r"""Update the rotation schedule for a key.
Updates the rotation schedule for the given key. The schedule
automatically creates a new primary version for the key
according to the `--next-rotation-time` and `--rotation-period` flags.
The flag `--next-rotation-time` must be in ISO or RFC3339 format,
and `--rotation-period` must be in the form INTEGER[UNIT], where units
can be one of seconds (s), minutes (m), hours (h) or days (d).
Key rotations performed manually via `update-primary-version` and the
version `create` do not affect the stored `--next-rotation-time`.
## EXAMPLES
The following command sets a 30 day rotation period for the key
named `frodo` within the keyring `fellowship` and location `global`
starting at the specified time:
$ {command} frodo \
--location=global \
--keyring=fellowship \
--rotation-period=30d \
--next-rotation-time=2017-10-12T12:34:56.1234Z
"""
@staticmethod
def Args(parser):
resource_args.AddKmsKeyResourceArgForKMS(parser, True, 'key')
flags.AddRotationPeriodFlag(parser)
flags.AddNextRotationTimeFlag(parser)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
crypto_key_ref = flags.ParseCryptoKeyName(args)
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysPatchRequest(
name=crypto_key_ref.RelativeName(),
cryptoKey=messages.CryptoKey())
flags.SetNextRotationTime(args, req.cryptoKey)
flags.SetRotationPeriod(args, req.cryptoKey)
fields_to_update = []
if args.rotation_period is not None:
fields_to_update.append('rotationPeriod')
if args.next_rotation_time is not None:
fields_to_update.append('nextRotationTime')
if not fields_to_update:
raise exceptions.ArgumentError(
'At least one of --next-rotation-time or --rotation-period must be '
'specified.')
req.updateMask = ','.join(fields_to_update)
return client.projects_locations_keyRings_cryptoKeys.Patch(req)

View File

@@ -0,0 +1,339 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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 a key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import exceptions as kms_exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import maps
from googlecloudsdk.command_lib.kms import resource_args
from googlecloudsdk.command_lib.util.args import labels_util
@base.UniverseCompatible
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class Update(base.UpdateCommand):
r"""Update a key.
1. Update the rotation schedule for the given key.
Updates the rotation schedule for the given key. The schedule
automatically creates a new primary version for the key
according to `next-rotation-time` and `rotation-period` flags.
Flag `next-rotation-time` must be in ISO 8601 or RFC3339 format,
and `rotation-period` must be in the form INTEGER[UNIT], where units
can be one of seconds (s), minutes (m), hours (h) or days (d).
Key rotations performed manually via `update-primary-version` and the
version `create` do not affect the stored `next-rotation-time`.
2. Remove the rotation schedule for the given key with
`remove-rotation-schedule` flag.
3. Update/Remove the labels for the given key with `update-labels` and/or
`remove-labels` flags.
4. Update the primary version for the given key with `primary-version` flag.
5. Update the Key Access Justifications policy for the given key with
`allowed-access-reasons` flag to allow specified reasons. The key must be
enrolled in Key Access Justifications to use this flag.
6. Remove the Key Access Justifications policy for the given key with
`remove-key-access-justifications-policy` flag. The key must be enrolled in
Key Access Justifications to use this flag.
7. Update the Key Access Justifications policy for the given key with
`allowed_access_reasons` flag to allow zero access reasons. This effectively
disables the key, because a policy is configured to reject all access reasons.
The key must be enrolled in Key Access Justifications to use this flag.
## EXAMPLES
The following command sets a 30 day rotation period for the key
named `frodo` within the keyring `fellowship` and location `global`
starting at the specified time:
$ {command} frodo \
--location=global \
--keyring=fellowship \
--rotation-period=30d \
--next-rotation-time=2017-10-12T12:34:56.1234Z
The following command removes the rotation schedule for the key
named `frodo` within the keyring `fellowship` and location `global`:
$ {command} frodo \
--location=global \
--keyring=fellowship \
--remove-rotation-schedule
The following command updates the labels value for the key
named `frodo` within the keyring `fellowship` and location `global`. If the
label key does not exist at the time, it will be added:
$ {command} frodo \
--location=global \
--keyring=fellowship \
--update-labels=k1=v1
The following command removes labels k1 and k2 from the key
named `frodo` within the keyring `fellowship` and location `global`:
$ {command} frodo \
--location=global \
--keyring=fellowship \
--remove-labels=k1,k2
The following command updates the primary version for the key
named `frodo` within the keyring `fellowship` and location `global`:
$ {command} frodo \
--location=global \
--keyring=fellowship \
--primary-version=1
The following command updates the default algorithm for the key named `frodo`
within the keyring `fellowship` and location `global`, assuming the key
originally has purpose 'asymmetric-encryption' and algorithm
'rsa-decrypt-oaep-2048-sha256':
$ {command} frodo \
--location=global \
--keyring=fellowship \
--default-algorithm=rsa-decrypt-oaep-4096-sha256
The following command updates the Key Access Justifications policy for the key
named `frodo` within the keyring ``fellowship'' and location ``global'' to
allow only ``customer-initiated-access'' and
``google-initiated-system-operation'':
$ {command} frodo \
--location=global \
--keyring=fellowship \
--allowed-access-reasons=customer-initiated-access,google-initiated-system-operation
The following command removes the Key Access Justifications policy for the key
named `frodo` within the keyring ``fellowship'' and location ``global'', which
results in all access reasons being allowed:
$ {command} frodo \
--location=global \
--keyring=fellowship \
--remove-key-access-justifications-policy
The following command updates the Key Access Justifications policy for the key
named `frodo` within the keyring ``fellowship'' and location ``global'' to
allow only zero access reasons, effectively disabling the key:
$ {command} frodo \
--location=global \
--keyring=fellowship \
--allowed-access-reasons=
"""
@staticmethod
def Args(parser):
resource_args.AddKmsKeyResourceArgForKMS(parser, True, 'key')
flags.AddRotationPeriodFlag(parser)
flags.AddNextRotationTimeFlag(parser)
flags.AddRemoveRotationScheduleFlag(parser)
flags.AddCryptoKeyPrimaryVersionFlag(parser, 'to make primary')
labels_util.AddUpdateLabelsFlags(parser)
flags.AddDefaultAlgorithmFlag(parser)
flags.AddAllowedAccessReasonsFlag(parser)
flags.AddRemoveKeyAccessJustificationsPolicyFlag(parser)
def ProcessFlags(self, args):
fields_to_update = []
labels_diff = labels_util.Diff.FromUpdateArgs(args)
if labels_diff.MayHaveUpdates():
fields_to_update.append('labels')
if args.remove_rotation_schedule:
if args.rotation_period or args.next_rotation_time:
raise kms_exceptions.ArgumentError(
'You cannot set and remove rotation schedule at the same time.')
fields_to_update.append('rotationPeriod')
fields_to_update.append('nextRotationTime')
if args.rotation_period:
fields_to_update.append('rotationPeriod')
if args.next_rotation_time:
fields_to_update.append('nextRotationTime')
if args.default_algorithm:
fields_to_update.append('versionTemplate.algorithm')
if (
args.allowed_access_reasons is not None
and args.remove_key_access_justifications_policy
):
raise kms_exceptions.ArgumentError(
'You cannot set and remove a Key Access Justifications policy at the '
'same time.'
)
if (
args.allowed_access_reasons is not None
or args.remove_key_access_justifications_policy
):
fields_to_update.append('keyAccessJustificationsPolicy')
# Raise an exception when no update field is specified.
if not args.primary_version and not fields_to_update:
raise kms_exceptions.UpdateError(
'At least one of --primary-version or --update-labels or '
'--remove-labels or --clear-labels or --rotation-period or '
'--next-rotation-time or --remove-rotation-schedule or '
'--default-algorithm or --allowed-access-reasons or '
'--remove-key-access-justifications-policy must be specified.'
)
return fields_to_update
def UpdatePrimaryVersion(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
crypto_key_ref = args.CONCEPTS.key.Parse()
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysUpdatePrimaryVersionRequest( # pylint: disable=line-too-long
name=crypto_key_ref.RelativeName(),
updateCryptoKeyPrimaryVersionRequest=(
messages.UpdateCryptoKeyPrimaryVersionRequest(
cryptoKeyVersionId=args.primary_version)))
try:
response = client.projects_locations_keyRings_cryptoKeys.UpdatePrimaryVersion( # pylint: disable=line-too-long
req)
except apitools_exceptions.HttpError:
return None
return response
def UpdateOthers(self, args, crypto_key, fields_to_update):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
crypto_key_ref = args.CONCEPTS.key.Parse()
labels_update = labels_util.Diff.FromUpdateArgs(args).Apply(
messages.CryptoKey.LabelsValue, crypto_key.labels)
if labels_update.needs_update:
new_labels = labels_update.labels
else:
new_labels = crypto_key.labels
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysPatchRequest(
name=crypto_key_ref.RelativeName(),
cryptoKey=messages.CryptoKey(
labels=new_labels))
req.updateMask = ','.join(fields_to_update)
flags.SetNextRotationTime(args, req.cryptoKey)
flags.SetRotationPeriod(args, req.cryptoKey)
if args.default_algorithm:
valid_algorithms = maps.VALID_ALGORITHMS_MAP[crypto_key.purpose]
if args.default_algorithm not in valid_algorithms:
raise kms_exceptions.UpdateError(
'Update failed: Algorithm {algorithm} is not valid. Here are the '
'valid algorithm(s) for purpose {purpose}: {all_algorithms}'.format(
algorithm=args.default_algorithm,
purpose=crypto_key.purpose,
all_algorithms=', '.join(valid_algorithms)))
req.cryptoKey.versionTemplate = messages.CryptoKeyVersionTemplate(
algorithm=maps.ALGORITHM_MAPPER.GetEnumForChoice(
args.default_algorithm))
if not args.remove_key_access_justifications_policy:
flags.SetKeyAccessJustificationsPolicy(args, req.cryptoKey)
try:
response = client.projects_locations_keyRings_cryptoKeys.Patch(req)
except apitools_exceptions.HttpError:
return None
return response
def HandleErrors(self, args, set_primary_version_succeeds,
other_updates_succeed, fields_to_update):
"""Handles various errors that may occur during any update stage.
Never returns without an exception.
Args:
args: Input arguments.
set_primary_version_succeeds: True if the primary verion is updated
successfully.
other_updates_succeed: True if all other updates (besides primary verions)
is updated successfully.
fields_to_update: A list of fields to be updated.
Raises:
ToolException: An exception raised when there is error during any update
stage.
"""
err = 'An Error occurred:'
if not set_primary_version_succeeds:
err += " Failed to update field 'primaryVersion'."
elif args.primary_version:
err += " Field 'primaryVersion' was updated."
if not other_updates_succeed:
err += " Failed to update field(s) '{}'.".format(
"', '".join(fields_to_update))
elif fields_to_update:
err += " Field(s) '{}' were updated.".format(
"', '".join(fields_to_update))
raise kms_exceptions.UpdateError(err)
def Run(self, args):
"""Updates the relevant fields (of a CryptoKey) from the flags."""
# Check the flags and raise an exception if any check fails.
fields_to_update = self.ProcessFlags(args)
# Try to get the cryptoKey and raise an exception if the key doesn't exist.
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
crypto_key_ref = args.CONCEPTS.key.Parse()
crypto_key = client.projects_locations_keyRings_cryptoKeys.Get(
messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysGetRequest(
name=crypto_key_ref.RelativeName()))
# Try to update the key's primary version.
set_primary_version_succeeds = True
if args.primary_version:
response = self.UpdatePrimaryVersion(args)
if response:
crypto_key = response # If call succeeds, update the crypto_key.
else:
set_primary_version_succeeds = False
# Try other updates.
other_updates_succeed = True
if fields_to_update:
response = self.UpdateOthers(args, crypto_key, fields_to_update)
if response:
crypto_key = response # If call succeeds, update the crypto_key.
else:
other_updates_succeed = False
if not set_primary_version_succeeds or not other_updates_succeed:
self.HandleErrors(args, set_primary_version_succeeds,
other_updates_succeed, fields_to_update)
else:
return crypto_key

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""The command group for versions."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import resources
class Versions(base.Group):
"""Create and manage versions.
A version represents an individual cryptographic key and the
associated key material.
"""
@staticmethod
def Args(parser):
parser.display_info.AddUriFunc(
cloudkms_base.MakeGetUriFunc(flags.CRYPTO_KEY_VERSION_COLLECTION))

View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Create a new version."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import exceptions as kms_exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Create(base.CreateCommand):
r"""Create a new version.
Creates a new version within the given key.
## EXAMPLES
The following command creates a new version within the `frodo`
key, `fellowship` keyring, and `global` location and sets it as
the primary version:
$ {command} --location=global \
--keyring=fellowship \
--key=frodo --primary
The following command creates a new version within the `legolas`
key, `fellowship` keyring, `us-central1` location,
`https://example.kms/v0/some/key/path` as the address for its external key,
and sets it as the key's primary version:
$ {command} --location=us-central1 \
--keyring=fellowship \
--key=legolas \
--external-key-uri=https://example.kms/v0/some/key/path \
--primary
The following command creates a new version within the `bilbo`
key, `fellowship` keyring, `us-central1` location,
`v0/some/key/path` as the ekm connection key path for its external key,
and sets it as the key's primary version:
$ {command} --location=us-central1 \
--keyring=fellowship \
--key=bilbo \
--ekm-connection-key-path=v0/some/key/path \
--primary
"""
GOOGLE_SYMMETRIC_ENCRYPTION = (
cloudkms_base.GetMessagesModule().CryptoKeyVersion.AlgorithmValueValuesEnum.GOOGLE_SYMMETRIC_ENCRYPTION
)
SYMMETRIC_NEW_PRIMARY_MESSAGE = (
'Successfully created key version [{version}] and set it as the primary '
'version. Future encryption requests will use [{version}] until the next '
'key rotation. Data that was encrypted with an older key version can '
'still be decrypted, and Cloud KMS will automatically choose the correct '
'key for decryption based on the ciphertext.')
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser)
flags.AddExternalKeyUriFlag(parser)
flags.AddEkmConnectionKeyPathFlag(parser)
parser.add_argument(
'--primary',
action='store_true',
help=(
'If specified, immediately makes the new version primary. '
'This should only be used with the `encryption` purpose.'
),
)
def _CreateCreateCKVRequest(self, args):
# pylint: disable=line-too-long
messages = cloudkms_base.GetMessagesModule()
crypto_key_ref = flags.ParseCryptoKeyName(args)
if args.external_key_uri and args.ekm_connection_key_path:
raise kms_exceptions.ArgumentError(
'Can not specify both --external-key-uri and '
'--ekm-connection-key-path.')
if args.external_key_uri or args.ekm_connection_key_path:
return messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsCreateRequest(
parent=crypto_key_ref.RelativeName(),
cryptoKeyVersion=messages.CryptoKeyVersion(
externalProtectionLevelOptions=messages
.ExternalProtectionLevelOptions(
externalKeyUri=args.external_key_uri,
ekmConnectionKeyPath=args.ekm_connection_key_path)))
return messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsCreateRequest(
parent=crypto_key_ref.RelativeName())
def Run(self, args):
# pylint: disable=line-too-long
client = cloudkms_base.GetClientInstance()
ckv = client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions
new_ckv = ckv.Create(self._CreateCreateCKVRequest(args))
if args.primary:
version_id = new_ckv.name.split('/')[-1]
crypto_key_ref = flags.ParseCryptoKeyName(args)
messages = cloudkms_base.GetMessagesModule()
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysUpdatePrimaryVersionRequest(
name=crypto_key_ref.RelativeName(),
updateCryptoKeyPrimaryVersionRequest=(
messages.UpdateCryptoKeyPrimaryVersionRequest(
cryptoKeyVersionId=version_id)))
client.projects_locations_keyRings_cryptoKeys.UpdatePrimaryVersion(req)
if new_ckv.algorithm == self.GOOGLE_SYMMETRIC_ENCRYPTION:
log.err.Print(
self.SYMMETRIC_NEW_PRIMARY_MESSAGE.format(version=version_id))
return new_ckv

View File

@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Describe a version."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import exceptions as kms_exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
@base.UniverseCompatible
class Describe(base.DescribeCommand):
r"""Get metadata for a given version.
Returns metadata for the given version.
The optional flag `attestation-file` specifies file to write the attestation
object into. The attestation object enables the user to verify the integrity
and provenance of the key. See https://cloud.google.com/kms/docs/attest-key
for more information about attestations.
## EXAMPLES
The following command returns metadata for version 2 within key `frodo`
within the keyring `fellowship` in the location `us-east1`:
$ {command} 2 --key=frodo --keyring=fellowship --location=us-east1
For key versions with protection level `HSM`, use the `--attestation-file`
flag to save the attestation to a local file.
$ {command} 2 --key=frodo --keyring=fellowship --location=us-east1 \
--attestation-file=path/to/attestation.dat
"""
@staticmethod
def Args(parser):
flags.AddKeyVersionResourceArgument(parser, 'to describe')
flags.AddAttestationFileFlag(parser)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
version_ref = flags.ParseCryptoKeyVersionName(args)
if not version_ref.Name():
raise exceptions.InvalidArgumentException(
'version', 'version id must be non-empty.')
version = client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions.Get( # pylint: disable=line-too-long
messages.
CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsGetRequest(
name=version_ref.RelativeName()))
# Raise exception if --attestation-file is provided for software
# key versions.
hsm = messages.CryptoKeyVersion.ProtectionLevelValueValuesEnum.HSM
hsm_st = (
messages.CryptoKeyVersion.ProtectionLevelValueValuesEnum.HSM_SINGLE_TENANT
)
if version.protectionLevel not in [hsm, hsm_st] and args.attestation_file:
raise kms_exceptions.ArgumentError(
'Attestations are only available for HSM or HSM_SINGLE_TENANT key '
'versions.'
)
if (args.attestation_file and version.state ==
messages.CryptoKeyVersion.StateValueValuesEnum.PENDING_GENERATION):
raise kms_exceptions.ArgumentError(
'The attestation is unavailable until the version is generated.')
if args.attestation_file and version.attestation is not None:
try:
log.WriteToFileOrStdout(
args.attestation_file,
version.attestation.content,
overwrite=True,
binary=True)
except files.Error as e:
raise exceptions.BadFileException(e)
if version.attestation is not None:
# Suppress the attestation content in the printed output. Users can use
# --attestation-file to obtain it, instead.
version.attestation.content = None
# Suppress the attestation content in the printed output. Users can use
# get-certificate-chain to obtain it, instead.
version.attestation.certChains = None
return version

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Destroy a version."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
class Destroy(base.UpdateCommand):
"""Schedule a version to be destroyed.
Schedules the given version for destruction in 24 hours.
After that time period passes it is automatically destroyed. Once
destroyed, the key material is removed but the version number can
not be reused.
Only versions which are Enabled or Disabled can be Scheduled
for destruction.
## EXAMPLES
The following command schedules version 9 of key `frodo` within
keyring `fellowship` and location `us-east1` for destruction:
$ {command} 9 --location=us-east1 --keyring=fellowship --key=frodo
"""
@staticmethod
def Args(parser):
flags.AddKeyVersionResourceArgument(parser, 'to destroy')
def Run(self, args):
# pylint: disable=line-too-long
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
version_ref = flags.ParseCryptoKeyVersionName(args)
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsDestroyRequest(
name=version_ref.RelativeName())
ckv = client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions
return ckv.Destroy(req)

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Make a version deactivated."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.api_lib.cloudkms import cryptokeyversions
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
class Disable(base.Command):
"""Disable a given version.
Disables the specified version within the given key.
Only a version which is Enabled can be Disabled.
## EXAMPLES
The following command disables version 3 of key `frodo` within
keyring `fellowship` and location `us-east1`:
$ {command} 3 --location=us-east1 --keyring=fellowship --key=frodo
"""
@staticmethod
def Args(parser):
flags.AddKeyVersionResourceArgument(parser, 'to disable')
def Run(self, args):
messages = cloudkms_base.GetMessagesModule()
version_ref = flags.ParseCryptoKeyVersionName(args)
return cryptokeyversions.SetState(
version_ref, messages.CryptoKeyVersion.StateValueValuesEnum.DISABLED)

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Make a version active."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.api_lib.cloudkms import cryptokeyversions
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
class Enable(base.Command):
"""Enable a given version.
Enables the specified version within the given key.
Only a version which is Disabled can be Enabled.
## EXAMPLES
The following command enables version 3 of key `frodo` within
keyring `fellowship` and location `us-east1`:
$ {command} 3 --location=us-east1 --keyring=fellowship --key=frodo
"""
@staticmethod
def Args(parser):
flags.AddKeyVersionResourceArgument(parser, 'to enable')
def Run(self, args):
messages = cloudkms_base.GetMessagesModule()
version_ref = flags.ParseCryptoKeyVersionName(args)
return cryptokeyversions.SetState(
version_ref, messages.CryptoKeyVersion.StateValueValuesEnum.ENABLED)

View File

@@ -0,0 +1,124 @@
# -*- 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.
"""Get a PEM-format certificate chain for a given version."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import exceptions as kms_exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
DETAILED_HELP = {
'EXAMPLES':
"""\
The following command saves the Cavium certificate chain for
CryptoKey ``frodo'' Version 2 to ``/tmp/my/cavium.pem'':
$ {command} 2 --key=frodo --keyring=fellowship --location=us-east1 --certificate-chain-type=cavium --output-file=/tmp/my/cavium.pem
""",
}
def _GetCertificateChainPem(chains, chain_type):
"""Returns the specified certificate chain(s) from a CertChains object.
Args:
chains: a KeyOperationAttestation.CertChains object.
chain_type: a string specifying the chain(s) to retrieve.
Returns:
A string containing the PEM-encoded certificate chain(s).
Raises:
exceptions.InvalidArgumentException if chain_type is not a valid chain type.
"""
if chain_type == 'cavium':
return ''.join(chains.caviumCerts)
elif chain_type == 'google-card':
return ''.join(chains.googleCardCerts)
elif chain_type == 'google-partition':
return ''.join(chains.googlePartitionCerts)
elif chain_type == 'all':
return ''.join(
chains.caviumCerts
+ chains.googlePartitionCerts
+ chains.googleCardCerts
)
raise exceptions.InvalidArgumentException(
'certificate_chain_type',
'{} is not a valid chain type.'.format(chain_type),
)
@base.DefaultUniverseOnly
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class GetCertificateChain(base.DescribeCommand):
r"""Get a certificate chain for a given version.
Returns the PEM-format certificate chain for the specified key version.
The optional flag `output-file` indicates the path to store the PEM. If not
specified, the PEM will be printed to stdout.
"""
detailed_help = DETAILED_HELP
@staticmethod
def Args(parser):
flags.AddKeyVersionResourceArgument(
parser, 'from which to get the certificate chain')
flags.AddCertificateChainFlag(parser)
flags.AddOutputFileFlag(parser, 'to store PEM')
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
version_ref = flags.ParseCryptoKeyVersionName(args)
if not version_ref.Name():
raise exceptions.InvalidArgumentException(
'version', 'version id must be non-empty.')
versions = client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions
version = versions.Get(
messages
.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsGetRequest(
name=version_ref.RelativeName()))
hsm = messages.CryptoKeyVersion.ProtectionLevelValueValuesEnum.HSM
hsm_st = (
messages.CryptoKeyVersion.ProtectionLevelValueValuesEnum.HSM_SINGLE_TENANT
)
if version.protectionLevel not in [hsm, hsm_st]:
raise kms_exceptions.ArgumentError(
'Certificate chains are only available for HSM and HSM_SINGLE_TENANT '
'key versions.'
)
if (version.state ==
messages.CryptoKeyVersion.StateValueValuesEnum.PENDING_GENERATION):
raise kms_exceptions.ArgumentError(
'Certificate chains are unavailable until the version is generated.')
try:
log.WriteToFileOrStdout(
args.output_file if args.output_file else '-',
_GetCertificateChainPem(version.attestation.certChains,
args.certificate_chain_type),
overwrite=True,
binary=False)
except files.Error as e:
raise exceptions.BadFileException(e)

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Get the public key for a given version."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import maps
from googlecloudsdk.core import log
@base.UniverseCompatible
class GetPublicKey(base.DescribeCommand):
r"""Get the public key for a given version.
Returns the public key of the given asymmetric key version in the specified format.
The optional flag `output-file` indicates the path to store the public key.
If not specified, the public key will be printed to stdout.
The optional flag `public-key-format` indicates the format in which the
public key will be returned. For the NIST PQC algorithms, this must be
specified and set to `nist-pqc`. For kem-xwing this must be specified and set to
`xwing-raw-bytes`. For all other algorithms, this flag is
optional and can be either `pem` or `der`; the default value is `pem`.
See "Retrieve a public key" in the Cloud KMS
documentation (https://cloud.google.com/kms/help/get-public-key) for more
information about the supported formats.
## EXAMPLES
The following command saves the public key for CryptoKey `frodo` Version 2
to '/tmp/my/public_key.file':
$ {command} 2 \
--key=frodo \
--keyring=fellowship \
--location=us-east1 \
--public-key-format=pem \
--output-file=/tmp/my/public_key.file
"""
@staticmethod
def Args(parser):
flags.AddKeyVersionResourceArgument(parser, 'to get public key')
flags.AddOutputFileFlag(parser, 'to store public key')
flags.AddPublicKeyFormatFlag(parser)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
version_ref = flags.ParseCryptoKeyVersionName(args)
if not version_ref.Name():
raise exceptions.InvalidArgumentException(
'version', 'version id must be non-empty.'
)
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsGetPublicKeyRequest( # pylint: disable=line-too-long
name=version_ref.RelativeName(),
)
if args.public_key_format:
if args.public_key_format not in [
'pem',
'der',
'nist-pqc',
'xwing-raw-bytes',
]:
raise exceptions.InvalidArgumentException(
'--public-key-format',
'Invalid value for public key format. Supported values are "der",'
' "pem", "xwing-raw-bytes" and "nist-pqc".',
)
req.publicKeyFormat = maps.PUBLIC_KEY_FORMAT_MAPPER.GetEnumForChoice(
args.public_key_format
)
resp = client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions.GetPublicKey(
req
)
# DER, NIST_PQC and XWING_RAW_BYTES are in raw byte format.
# So the output is in binary format.
write_binary_file = args.public_key_format in [
'der',
'nist-pqc',
'xwing-raw-bytes',
]
log.WriteToFileOrStdout(
args.output_file if args.output_file else '-',
resp.pem if resp.pem else resp.publicKey.data,
overwrite=True,
binary=write_binary_file,
private=True,
)

View File

@@ -0,0 +1,242 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 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.
"""Import a provided key from file into KMS using an Import Job."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
import sys
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import maps
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
class Import(base.Command):
r"""Import a version into an existing crypto key.
Imports wrapped key material into a new version within an existing crypto key
following the import procedure documented at
https://cloud.google.com/kms/docs/importing-a-key.
## EXAMPLES
The following command will read the files 'path/to/ephemeral/key' and
'path/to/target/key' and use them to create a new version with algorithm
'google-symmetric-encryption' within the 'frodo' crypto key, 'fellowship'
keyring, and 'us-central1' location using import job 'strider' to unwrap the
provided key material.
$ {command} --location=global \
--keyring=fellowship \
--key=frodo \
--import-job=strider \
--wrapped-key-file=path/to/target/key \
--algorithm=google-symmetric-encryption
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser, 'The containing key to import into.')
flags.AddCryptoKeyVersionFlag(
parser, 'to re-import into. Omit this field for first-time import')
flags.AddRsaAesWrappedKeyFileFlag(parser, 'to import')
flags.AddWrappedKeyFileFlag(parser, 'to import')
flags.AddImportedVersionAlgorithmFlag(parser)
flags.AddRequiredImportJobArgument(parser, 'to import from')
flags.AddPublicKeyFileFlag(parser)
flags.AddTargetKeyFileFlag(parser)
def _ReadFile(self, path, max_bytes):
data = files.ReadBinaryFileContents(path)
if len(data) > max_bytes:
raise exceptions.BadFileException(
'The file is larger than the maximum size of {0} bytes.'.format(
max_bytes))
return data
def _IsSha2ImportMethod(self, import_method, messages):
return import_method in (
messages.ImportJob.ImportMethodValueValuesEnum.RSA_OAEP_3072_SHA256,
messages.ImportJob.ImportMethodValueValuesEnum.RSA_OAEP_4096_SHA256,
messages.ImportJob.ImportMethodValueValuesEnum
.RSA_OAEP_3072_SHA256_AES_256, messages.ImportJob
.ImportMethodValueValuesEnum.RSA_OAEP_4096_SHA256_AES_256)
def _IsRsaAesWrappingImportMethod(self, import_method, messages):
return import_method in (messages.ImportJob.ImportMethodValueValuesEnum
.RSA_OAEP_3072_SHA1_AES_256,
messages.ImportJob.ImportMethodValueValuesEnum
.RSA_OAEP_4096_SHA1_AES_256,
messages.ImportJob.ImportMethodValueValuesEnum
.RSA_OAEP_3072_SHA256_AES_256,
messages.ImportJob.ImportMethodValueValuesEnum
.RSA_OAEP_4096_SHA256_AES_256)
def _ReadPublicKeyBytes(self, args):
try:
return self._ReadFile(args.public_key_file, max_bytes=65536)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read public key file [{0}]: {1}'.format(
args.public_key_file, e))
def _FetchImportJob(self, args, import_job_name, client, messages):
import_job = client.projects_locations_keyRings_importJobs.Get(
messages.CloudkmsProjectsLocationsKeyRingsImportJobsGetRequest(
name=import_job_name))
if import_job.state != messages.ImportJob.StateValueValuesEnum.ACTIVE:
raise exceptions.BadArgumentException(
'import-job', 'Import job [{0}] is not active (state is {1}).'.format(
import_job_name, import_job.state))
return import_job
def _CkmRsaAesKeyWrap(self, import_method, public_key_bytes, target_key_bytes,
client, messages):
try:
# TODO(b/141249289): Move imports to the top of the file. In the
# meantime, until we're sure that all Cloud SDK users have the
# cryptography module available, let's not error out if we can't load the
# module unless we're actually going down this code path.
# pylint: disable=g-import-not-at-top
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import keywrap
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
except ImportError:
log.err.Print('Cannot load the Pyca cryptography library. Either the '
'library is not installed, or site packages are not '
'enabled for the Google Cloud SDK. Please consult '
'https://cloud.google.com/kms/docs/crypto for further '
'instructions.')
sys.exit(1)
sha = hashes.SHA1()
if self._IsSha2ImportMethod(import_method, messages):
sha = hashes.SHA256()
# RSA-OAEP import methods have a maximum target key size that's a function
# of the RSA modulus size.
if not self._IsRsaAesWrappingImportMethod(import_method, messages):
if (
import_method
== messages.ImportJob.ImportMethodValueValuesEnum.RSA_OAEP_3072_SHA256
):
modulus_byte_length = 3072 // 8
elif (
import_method
== messages.ImportJob.ImportMethodValueValuesEnum.RSA_OAEP_4096_SHA256
):
modulus_byte_length = 4096 // 8
else:
raise ValueError('unexpected import method: {0}'.format(import_method))
# per go/rfc/8017#section-7.1.1
max_target_key_size = modulus_byte_length - (2 * sha.digest_size) - 2
if len(target_key_bytes) > max_target_key_size:
raise exceptions.BadFileException(
'target-key-file',
"The file is larger than the import method's maximum size of {0} "
'bytes.'.format(max_target_key_size),
)
aes_wrapped_key = b''
to_be_rsa_wrapped_key = target_key_bytes
public_key = serialization.load_pem_public_key(
public_key_bytes, backend=default_backend())
if self._IsRsaAesWrappingImportMethod(import_method, messages):
to_be_rsa_wrapped_key = os.urandom(32) # an ephemeral key
aes_wrapped_key = keywrap.aes_key_wrap_with_padding(
to_be_rsa_wrapped_key, target_key_bytes, default_backend())
rsa_wrapped_key = public_key.encrypt(
to_be_rsa_wrapped_key,
padding.OAEP(mgf=padding.MGF1(sha), algorithm=sha, label=None))
return rsa_wrapped_key + aes_wrapped_key
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
import_job_name = flags.ParseImportJobName(args).RelativeName()
# set wrapped_key_file to wrapped_key_file or rsa_aes_wrapped_key_file
wrapped_key_file = None
if args.wrapped_key_file:
wrapped_key_file = args.wrapped_key_file
if args.rsa_aes_wrapped_key_file:
raise exceptions.OneOfArgumentsRequiredException(
('--wrapped-key-file', '--rsa-aes-wrapped-key-file'),
'Either wrapped-key-file or rsa-aes-wrapped-key-file should be provided.') # pylint: disable=line-too-long
else:
wrapped_key_file = args.rsa_aes_wrapped_key_file
if bool(wrapped_key_file) == bool(args.target_key_file):
raise exceptions.OneOfArgumentsRequiredException(
('--target-key-file', '--wrapped-key-file/--rsa-aes-wrapped-key-file'), # pylint: disable=line-too-long
'Either a pre-wrapped key or a key to be wrapped must be provided.')
wrapped_key_bytes = None
if wrapped_key_file:
try:
# This should be less than 64KiB.
wrapped_key_bytes = self._ReadFile(wrapped_key_file, max_bytes=65536)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read wrapped key file [{0}]: {1}'.format(
wrapped_key_file, e))
import_job = self._FetchImportJob(args, import_job_name, client, messages)
if args.target_key_file:
target_key_bytes = None
try:
# This should be less than 64KiB.
target_key_bytes = self._ReadFile(args.target_key_file, max_bytes=8192)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read target key file [{0}]: {1}'.format(
args.target_key_file, e))
# Read the public key off disk if provided, otherwise, fetch it from KMS.
public_key_bytes = None
if args.public_key_file:
public_key_bytes = self._ReadPublicKeyBytes(args)
else:
public_key_bytes = import_job.publicKey.pem.encode('ascii')
wrapped_key_bytes = self._CkmRsaAesKeyWrap(import_job.importMethod,
public_key_bytes,
target_key_bytes, client,
messages)
# Send the request to KMS.
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsImportRequest( # pylint: disable=line-too-long
parent=flags.ParseCryptoKeyName(args).RelativeName())
req.importCryptoKeyVersionRequest = messages.ImportCryptoKeyVersionRequest(
algorithm=maps.ALGORITHM_MAPPER_FOR_IMPORT.GetEnumForChoice(
args.algorithm),
importJob=import_job_name,
wrappedKey=wrapped_key_bytes)
if args.version:
req.importCryptoKeyVersionRequest.cryptoKeyVersion = flags.ParseCryptoKeyVersionName(
args).RelativeName()
return client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions.Import(
req)

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""List the versions within a key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
class List(base.ListCommand):
r"""List the versions within a key.
Lists all of the versions within the given key.
## EXAMPLES
The following command lists all versions within the
key `frodo`, keyring `fellowship`, and location `global`:
$ {command} --location=global \
--keyring=fellowship \
--key=frodo
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser)
parser.display_info.AddFormat('table(name, state)')
def Run(self, args):
# pylint: disable=line-too-long
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
crypto_key_ref = flags.ParseCryptoKeyName(args)
request = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsListRequest(
parent=crypto_key_ref.RelativeName())
return list_pager.YieldFromList(
client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions,
request,
field='cryptoKeyVersions',
limit=args.limit,
batch_size_attribute='pageSize')

View File

@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""Restore a version."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
class Restore(base.UpdateCommand):
"""Restore a version scheduled for destruction.
Restores the given version that was scheduled to be destroyed.
This moves the version from Scheduled for destruction to Disabled.
Only versions which are Scheduled for destruction can be Restored.
## EXAMPLES
The following command restores version 9 of key `frodo` within
keyring `fellowship` and location `us-east1` which was previously scheduled
for destruction:
$ {command} 9 --location=us-east1 --keyring=fellowship --key=frodo
"""
@staticmethod
def Args(parser):
flags.AddKeyVersionResourceArgument(parser, 'to restore')
def Run(self, args):
# pylint: disable=line-too-long
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
version_ref = flags.ParseCryptoKeyVersionName(args)
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsRestoreRequest(
name=version_ref.RelativeName())
ckv = client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions
return ckv.Restore(req)

View File

@@ -0,0 +1,155 @@
# -*- 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 a key version."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import exceptions as kms_exceptions
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.command_lib.kms import maps
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Update(base.UpdateCommand):
r"""Update a key version.
{command} can be used to update the key versions. Updates can be made to the
the key versions's state (enabling or disabling it), to its external key
URI (if the key version has protection level EXTERNAL), or to its ekm
connection key path (if the key version has protection level EXTERNAL_VPC).
## EXAMPLES
The following command enables the key version 8 of key `frodo`
within keyring `fellowship` and location `us-east1`:
$ {command} 8 --location=us-east1 \
--keyring=fellowship \
--key=frodo \
--state=enabled
The following command disables the key version 8 of key `frodo`
within keyring `fellowship` and location `us-east1`:
$ {command} 8 --location=us-east1 \
--keyring=fellowship \
--key=frodo \
--state=disabled
The following command updates the external key URI of version 8 of key `frodo`
within keyring `fellowship` and location `us-east1`:
$ {command} 8 --location=us-east1 \
--keyring=fellowship \
--key=frodo \
--external-key-uri=https://example.kms/v0/some/key/path
The following command updates the ekm connection key path of version 8 of key
`bilbo` within keyring `fellowship` and location `us-east1`:
$ {command} 8 --location=us-east1 \
--keyring=fellowship \
--key=bilbo \
--ekm-connection-key-path=v0/some/key/path
"""
@staticmethod
def Args(parser):
flags.AddKeyVersionResourceArgument(parser, 'to describe')
flags.AddExternalKeyUriFlag(parser)
flags.AddEkmConnectionKeyPathFlag(parser)
flags.AddStateFlag(parser)
def ProcessFlags(self, args):
fields_to_update = []
if args.external_key_uri:
fields_to_update.append('externalProtectionLevelOptions.externalKeyUri')
if args.ekm_connection_key_path:
fields_to_update.append(
'externalProtectionLevelOptions.ekmConnectionKeyPath')
if args.state:
fields_to_update.append('state')
# Raise an exception when no update field is specified.
if not fields_to_update:
raise kms_exceptions.UpdateError(
'An error occurred: --external-key-uri or --ekm-connection-key-path'
' or --state must be specified.')
return fields_to_update
def CreateRequest(self, args, messages, fields_to_update):
# pylint: disable=line-too-long
version_ref = flags.ParseCryptoKeyVersionName(args)
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsPatchRequest(
name=version_ref.RelativeName(),
cryptoKeyVersion=messages.CryptoKeyVersion(
state=maps.CRYPTO_KEY_VERSION_STATE_MAPPER.GetEnumForChoice(
args.state),
externalProtectionLevelOptions=messages
.ExternalProtectionLevelOptions(
externalKeyUri=args.external_key_uri,
ekmConnectionKeyPath=args.ekm_connection_key_path)))
req.updateMask = ','.join(fields_to_update)
return req
def CheckKeyIsExternal(self, key_version, messages):
if (key_version.protectionLevel !=
messages.CryptoKeyVersion.ProtectionLevelValueValuesEnum.EXTERNAL):
raise kms_exceptions.UpdateError(
'External key URI updates are only available for key versions '
'with EXTERNAL protection level')
def CheckKeyIsExternalVpc(self, key_version, messages):
if (key_version.protectionLevel !=
messages.CryptoKeyVersion.ProtectionLevelValueValuesEnum.EXTERNAL_VPC):
raise kms_exceptions.UpdateError(
'EkmConnection key path updates are only available for key versions '
'with EXTERNAL_VPC protection level')
def Run(self, args):
# pylint: disable=line-too-long
fields_to_update = self.ProcessFlags(args)
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
version_ref = flags.ParseCryptoKeyVersionName(args)
# Try to get the cryptoKeyVersion and raise an exception if it doesn't exist.
key_version = client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions.Get(
messages
.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsGetRequest(
name=version_ref.RelativeName()))
# Check that this key version's ProtectionLevel is EXTERNAL
if args.external_key_uri:
self.CheckKeyIsExternal(key_version, messages)
if args.ekm_connection_key_path:
self.CheckKeyIsExternalVpc(key_version, messages)
# Make update request
update_req = self.CreateRequest(args, messages, fields_to_update)
return client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions.Patch(
update_req)

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""The command group for locations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
class Locations(base.Group):
"""View locations available for a project."""
category = base.IDENTITY_AND_SECURITY_CATEGORY

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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.
"""List locations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import properties
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List the project's locations.
Lists all locations available for this project.
"""
@staticmethod
def Args(parser):
parser.display_info.AddFormat(
'table(locationId, metadata.hsmAvailable, metadata.ekmAvailable)')
parser.display_info.AddUriFunc(
cloudkms_base.MakeGetUriFunc(flags.LOCATION_COLLECTION))
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
project = properties.VALUES.core.project.Get(required=True)
request = messages.CloudkmsProjectsLocationsListRequest(
name='projects/' + project)
return list_pager.YieldFromList(
client.projects_locations,
request,
field='locations',
limit=args.limit,
batch_size_attribute='pageSize')

View File

@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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.
"""Sign a user input file using a MAC signing key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import crc32c
from googlecloudsdk.command_lib.kms import e2e_integrity
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import files
class MacSign(base.Command):
r"""Sign a user input file using a MAC key version.
Creates a digital signature of the input file using the provided
MAC signing key version and saves the base64 encoded signature.
The required flag `signature-file` indicates the path to store signature.
By default, the command performs integrity verification on data sent to and
received from Cloud KMS. Use --skip-integrity-verification to disable
integrity verification.
## EXAMPLES
The following command will read the file '/tmp/my/file.to.sign', and sign it
using the symmetric MAC CryptoKey `dont-panic` Version 3, and save the
signature in base64 format to '/tmp/my/signature'.
$ {command} \
--location=us-central1 \
--keyring=hitchhiker \
--key=dont-panic \
--version=3 \
--input-file=/tmp/my/file.to.sign \
--signature-file=/tmp/my/signature
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser, 'to use for signing.')
flags.AddCryptoKeyVersionFlag(parser, 'to use for signing')
flags.AddInputFileFlag(parser, 'to sign')
flags.AddSignatureFileFlag(parser, 'to output')
flags.AddSkipIntegrityVerification(parser)
def _ReadFileOrStdin(self, path, max_bytes):
data = console_io.ReadFromFileOrStdin(path, binary=True)
if len(data) > max_bytes:
raise exceptions.BadFileException(
'The file [{0}] is larger than the maximum size of {1} bytes.'.format(
path, max_bytes))
return data
def _PerformIntegrityVerification(self, args):
return not args.skip_integrity_verification
def _CreateMacSignRequest(self, args):
try:
# The MacSign API limits the input data to 64KiB.
data = self._ReadFileOrStdin(args.input_file, max_bytes=65536)
except EnvironmentError as e:
raise exceptions.BadFileException(
'Failed to read input file [{0}]: {1}'.format(args.input_file, e))
messages = cloudkms_base.GetMessagesModule()
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsMacSignRequest( # pylint: disable=line-too-long
name=flags.ParseCryptoKeyVersionName(args).RelativeName())
if self._PerformIntegrityVerification(args):
data_crc32c = crc32c.Crc32c(data)
req.macSignRequest = messages.MacSignRequest(
data=data, dataCrc32c=data_crc32c)
else:
req.macSignRequest = messages.MacSignRequest(data=data)
return req
def _VerifyResponseIntegrityFields(self, req, resp):
"""Verifies integrity fields in MacSignResponse."""
# Verify resource name.
if req.name != resp.name:
raise e2e_integrity.ResourceNameVerificationError(
e2e_integrity.GetResourceNameMismatchErrorMessage(
req.name, resp.name))
# data_crc32c was verified server-side.
if not resp.verifiedDataCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage())
# Verify mac checksum.
if not crc32c.Crc32cMatches(resp.mac, resp.macCrc32c):
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetResponseFromServerCorruptedErrorMessage())
def Run(self, args):
client = cloudkms_base.GetClientInstance()
req = self._CreateMacSignRequest(args)
try:
resp = (
client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions
.MacSign(req))
# Intercept INVALID_ARGUMENT errors related to checksum verification, to
# present a user-friendly message. All other errors are surfaced as-is.
except apitools_exceptions.HttpBadRequestError as error:
e2e_integrity.ProcessHttpBadRequestError(error)
if self._PerformIntegrityVerification(args):
self._VerifyResponseIntegrityFields(req, resp)
try:
log.WriteToFileOrStdout(
args.signature_file,
resp.mac,
overwrite=True,
binary=True,
private=True)
except files.Error as e:
raise exceptions.BadFileException(e)

View File

@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 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.
"""Verify a user signature file using a MAC signing key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import crc32c
from googlecloudsdk.command_lib.kms import e2e_integrity
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
class MacVerify(base.Command):
r"""Verify a user signature file using a MAC key version.
Verifies a digital signature using the provided MAC signing key version.
By default, the command performs integrity verification on data sent to and
received from Cloud KMS. Use --skip-integrity-verification to disable
integrity verification.
## EXAMPLES
The following command will read the file '/tmp/my/file.to.verify', and verify
it using the symmetric MAC CryptoKey `dont-panic` Version 3 and the file
used previously to generate the MAC tag ('/tmp/my/original.data.file').
$ {command} \
--location=us-central1 \
--keyring=hitchhiker \
--key=dont-panic \
--version=3 \
--input-file=/tmp/my/original.data.file \
--signature-file=/tmp/my/file.to.verify
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser, 'to use for signing.')
flags.AddCryptoKeyVersionFlag(parser, 'to use for signing')
flags.AddInputFileFlag(parser, 'to use for verification')
flags.AddSignatureFileFlag(parser, 'to be verified')
flags.AddSkipIntegrityVerification(parser)
def _ReadFileOrStdin(self, path, max_bytes):
data = console_io.ReadFromFileOrStdin(path, binary=True)
if len(data) > max_bytes:
raise exceptions.BadFileException(
'The file [{0}] is larger than the maximum size of {1} bytes.'.format(
path, max_bytes))
return data
def _PerformIntegrityVerification(self, args):
return not args.skip_integrity_verification
def _CreateMacVerifyRequest(self, args):
try:
# The MacVerify API limits the input data to 64KiB.
data = self._ReadFileOrStdin(args.input_file, max_bytes=65536)
except EnvironmentError as e:
raise exceptions.BadFileException(
'Failed to read input file [{0}]: {1}'.format(args.input_file, e))
try:
# We currently only support signatures up to SHA512 length (64 bytes).
mac = self._ReadFileOrStdin(args.signature_file, max_bytes=64)
except EnvironmentError as e:
raise exceptions.BadFileException(
'Failed to read input file [{0}]: {1}'.format(args.input_file, e))
messages = cloudkms_base.GetMessagesModule()
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsMacVerifyRequest( # pylint: disable=line-too-long
name=flags.ParseCryptoKeyVersionName(args).RelativeName())
if self._PerformIntegrityVerification(args):
data_crc32c = crc32c.Crc32c(data)
mac_crc32c = crc32c.Crc32c(mac)
req.macVerifyRequest = messages.MacVerifyRequest(
data=data, mac=mac, dataCrc32c=data_crc32c, macCrc32c=mac_crc32c)
else:
req.macVerifyRequest = messages.MacVerifyRequest(data=data, mac=mac)
return req
def _VerifyResponseIntegrityFields(self, req, resp):
"""Verifies integrity fields in MacVerifyResponse."""
# Verify resource name.
if req.name != resp.name:
raise e2e_integrity.ResourceNameVerificationError(
e2e_integrity.GetResourceNameMismatchErrorMessage(
req.name, resp.name))
# data_crc32c was verified server-side.
if not resp.verifiedDataCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage())
# mac_crc32c was verified server-side.
if not resp.verifiedMacCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage())
# Verify mac integrity field.
if resp.success != resp.verifiedSuccessIntegrity:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetResponseFromServerCorruptedErrorMessage())
def Run(self, args):
client = cloudkms_base.GetClientInstance()
req = self._CreateMacVerifyRequest(args)
try:
resp = (
client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions
.MacVerify(req))
# Intercept INVALID_ARGUMENT errors related to checksum verification, to
# present a user-friendly message. All other errors are surfaced as-is.
except apitools_exceptions.HttpBadRequestError as error:
e2e_integrity.ProcessHttpBadRequestError(error)
if self._PerformIntegrityVerification(args):
self._VerifyResponseIntegrityFields(req, resp)
log.WriteToFileOrStdout(
'-', # Write to stdout.
resp.success,
binary=False)

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import resources
@base.DefaultUniverseOnly
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class Operations(base.Group):
"""Commands for managing operations."""
category = base.IDENTITY_AND_SECURITY_CATEGORY
@staticmethod
def Args(parser):
pass

View File

@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Describe operation command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
@base.DefaultUniverseOnly
class Describe(base.Command):
"""View the details of an operation.
View the details of an operation.
## EXAMPLES
To view the details of an operation, run:
$ {command} operation_id
--location=us-central1
"""
@staticmethod
def Args(parser):
resource_args.AddKmsOperationResourceArgForKMS(parser, True, 'operation')
def Run(self, args):
"""View the details of an operation."""
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
operation_ref = args.CONCEPTS.operation.Parse()
get_request = messages.CloudkmsProjectsLocationsOperationsGetRequest(
name=operation_ref.RelativeName()
)
operation = client.projects_locations_operations.Get(get_request)
return operation

View File

@@ -0,0 +1,254 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Decrypt a ciphertext file using a raw key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import crc32c
from googlecloudsdk.command_lib.kms import e2e_integrity
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import files
CBC_CTR_IV_SIZE = 16
class RawDecrypt(base.Command):
r"""Decrypt a ciphertext file using a raw key.
`{command}` decrypts the given ciphertext file using the given CryptoKey
containing a raw key and writes the result to the named plaintext file.
The ciphertext file must not be larger than 64KiB.
The supported algorithms are: `AES-128-GCM`, `AES-256-GCM`, `AES-128-CBC`,
`AES-256-CBC`, `AES-128-CTR`, `and AES-256-CTR`.
`AES-GCM` provides authentication which means that it accepts additional
authenticated data (AAD). So, the flag `--additional-authenticated-data-file`
is only valid with `AES-128-GCM` and `AES-256-GCM` algorithms. If AAD is
provided during encryption, it must be provided during decryption too.
The file must not be larger than 64KiB.
If `--plaintext-file` or `--additional-authenticated-data-file` or
`--initialization-vector-file` is set to '-', that file is read from stdin.
Similarly, if `--ciphertext-file` is set to '-', the ciphertext is written
to stdout.
By default, the command performs integrity verification on data sent to and
received from Cloud KMS. Use `--skip-integrity-verification` to disable
integrity verification.
## EXAMPLES
The following command reads and decrypts the file `path/to/input/ciphertext`.
The file will be decrypted using the CryptoKey `KEYNAME` containing a raw key,
from the KeyRing `KEYRING` in the `global` location. It uses the additional
authenticated data file `path/to/input/aad` (only valid with the `AES-GCM`
algorithms) and the initialization vector file `path/to/input/iv`.
The resulting plaintext will be written to `path/to/output/plaintext`.
$ {command} \
--key=KEYNAME \
--keyring=KEYRING \
--location=global \
--ciphertext-file=path/to/input/ciphertext \
--additional-authenticated-data-file=path/to/input/aad \
--initialization-vector-file=path/to/input/iv \
--plaintext-file=path/to/output/plaintext
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser, 'The (raw) key to use for decryption.')
flags.AddCryptoKeyVersionFlag(parser, 'to use for decryption', True)
flags.AddPlaintextFileFlag(parser, 'to store the decrypted data')
flags.AddCiphertextFileFlag(parser, 'to decrypt')
flags.AddIvFileFlag(parser, 'for decryption')
flags.AddAadFileFlag(parser)
flags.AddSkipIntegrityVerification(parser)
def _ReadFileOrStdin(self, path, max_bytes):
data = console_io.ReadFromFileOrStdin(path, binary=True)
if len(data) > max_bytes:
raise exceptions.BadFileException(
'The file [{0}] is larger than the maximum size of {1} bytes.'.format(
path, max_bytes
)
)
return data
def _PerformIntegrityVerification(self, args):
return not args.skip_integrity_verification
def _CreateRawDecryptRequest(self, args):
if args.ciphertext_file == '-' and args.initialization_vector_file == '-':
raise exceptions.InvalidArgumentException(
'--ciphertext-file and --initialization-vector-file',
"both parameters can't be read from stdin.",
)
if (
args.ciphertext_file == '-'
and args.additional_authenticated_data_file == '-'
):
raise exceptions.InvalidArgumentException(
'--ciphertext-file and --additional-authenticated-data-file',
"both parameters can't be read from stdin.",
)
if (
args.initialization_vector_file == '-'
and args.additional_authenticated_data_file == '-'
):
raise exceptions.InvalidArgumentException(
'--initialization-vector-file and'
' --additional-authenticated-data-file',
"both parameters can't be read from stdin.",
)
try:
# The Encrypt API has a limit of 64KB; the output ciphertext files will be
# slightly larger. Check proactively (but generously) to avoid attempting
# to buffer and send obviously oversized files to KMS.
ciphertext = self._ReadFileOrStdin(
args.ciphertext_file, max_bytes=2 * 65536
)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read ciphertext file [{0}]: {1}'.format(
args.ciphertext_file, e
)
)
try:
# The RawDecrypt API limits the IV to 16B.
iv = self._ReadFileOrStdin(
args.initialization_vector_file, max_bytes=CBC_CTR_IV_SIZE
)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read initialization vector file [{0}]: {1}'.format(
args.initialization_vector_file, e
)
)
if len(iv) != CBC_CTR_IV_SIZE:
raise exceptions.BadFileException(
'--initialization-vector-file',
'the IV size must be {0} bytes.'.format(CBC_CTR_IV_SIZE),
)
aad = b''
if args.additional_authenticated_data_file:
try:
# The RawDecrypt API limits the AAD to 64KiB.
aad = self._ReadFileOrStdin(
args.additional_authenticated_data_file, max_bytes=65536
)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read additional authenticated data file [{0}]: {1}'
.format(args.additional_authenticated_data_file, e)
)
crypto_key_ref = flags.ParseCryptoKeyVersionName(args)
messages = cloudkms_base.GetMessagesModule()
request = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsRawDecryptRequest( # pylint: disable=line-too-long
name=crypto_key_ref.RelativeName()
)
# Populate request integrity fields.
if self._PerformIntegrityVerification(args):
ciphertext_crc32c = crc32c.Crc32c(ciphertext)
iv_crc32c = crc32c.Crc32c(iv)
aad_crc32c = crc32c.Crc32c(aad)
request.rawDecryptRequest = messages.RawDecryptRequest(
ciphertext=ciphertext,
initializationVector=iv,
additionalAuthenticatedData=aad,
ciphertextCrc32c=ciphertext_crc32c,
initializationVectorCrc32c=iv_crc32c,
additionalAuthenticatedDataCrc32c=aad_crc32c,
)
else:
request.rawDecryptRequest = messages.RawDecryptRequest(
ciphertext=ciphertext,
initializationVector=iv,
additionalAuthenticatedData=aad,
)
return request
def _VerifyResponseIntegrityFields(self, resp):
"""Verifies integrity fields in response."""
# plaintext_crc32c was verified server-side.
if not resp.verifiedCiphertextCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage()
)
# additional_authenticated_data_crc32c was verified server-side.
if not resp.verifiedAdditionalAuthenticatedDataCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage()
)
# initialization_vector_crc32c was verified server-side.
if not resp.verifiedInitializationVectorCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage()
)
# Verify decrypted plaintext checksum.
if not crc32c.Crc32cMatches(resp.plaintext, resp.plaintextCrc32c):
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetResponseFromServerCorruptedErrorMessage()
)
def Run(self, args):
response = None
request = self._CreateRawDecryptRequest(args)
client = cloudkms_base.GetClientInstance()
try:
response = client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions.RawDecrypt(
request
) # pylint: disable=line-too-long
# Intercept INVALID_ARGUMENT errors related to checksum verification to
# present a user-friendly message. All other errors are surfaced as-is.
except apitools_exceptions.HttpBadRequestError as error:
e2e_integrity.ProcessHttpBadRequestError(error)
if self._PerformIntegrityVerification(args):
self._VerifyResponseIntegrityFields(response)
try:
if response.plaintext is None:
with files.FileWriter(args.plaintext_file):
# to create an empty file
pass
log.Print('Decrypted file is empty')
else:
log.WriteToFileOrStdout(
args.plaintext_file, response.plaintext, binary=True, overwrite=True
)
except files.Error as e:
raise exceptions.BadFileException(e)

View File

@@ -0,0 +1,295 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Encrypt a plaintext file using a raw key."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import uuid
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import crc32c
from googlecloudsdk.command_lib.kms import e2e_integrity
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import files
CBC_CTR_IV_SIZE = 16
class RawEncrypt(base.Command):
r"""Encrypt a plaintext file using a raw key.
Encrypts the given plaintext file using the given CryptoKey containing a raw
key and writes the result to the named ciphertext file.
The plaintext file must not be larger than 64KiB.
For the AES-CBC algorithms, no server-side padding is being done,
so the plaintext must be a multiple of the block size.
The supported algorithms are: `AES-128-GCM`, `AES-256-GCM`, `AES-128-CBC`,
`AES-256-CBC`, `AES-128-CTR`, `and AES-256-CTR`.
`AES-GCM` provides authentication which means that it accepts additional
authenticated data (AAD). So, the flag `--additional-authenticated-data-file`
is only valid with `AES-128-GCM` and `AES-256-GCM` algorithms.
The initialization vector (flag `--initialization-vector-file`) is only
supported for `AES-CBC` and `AES-CTR` algorithms, and must be 16B in length.
Therefore, both additional authenticated data and initialization vector can't
be provided during encryption. If an additional authenticated data file is
provided, its contents must also be provided during decryption.
The file must not be larger than 64KiB.
The flag `--version` indicates the version of the key to use for
encryption.
If `--plaintext-file` or `--additional-authenticated-data-file` or
`--initialization-vector-file` is set to '-', that file is read from stdin.
Similarly, if `--ciphertext-file` is set to '-', the ciphertext is written
to stdout.
By default, the command performs integrity verification on data sent to and
received from Cloud KMS. Use `--skip-integrity-verification` to disable
integrity verification.
## EXAMPLES
The following command reads and encrypts the file `path/to/input/plaintext`.
The file will be encrypted using the `AES-GCM` CryptoKey `KEYNAME` from the
KeyRing `KEYRING` in the `global` location using the additional authenticated
data file `path/to/input/aad`.
The resulting ciphertext will be written to `path/to/output/ciphertext`.
$ {command} \
--key=KEYNAME \
--keyring=KEYRING \
--location=global \
--plaintext-file=path/to/input/plaintext \
--additional-authenticated-data-file=path/to/input/aad \
--ciphertext-file=path/to/output/ciphertext
The following command reads and encrypts the file `path/to/input/plaintext`.
The file will be encrypted using the `AES-CBC` CryptoKey `KEYNAME` from the
KeyRing `KEYRING` in the `global` location using the initialization vector
stored at `path/to/input/aad`.
The resulting ciphertext will be written to `path/to/output/ciphertext`.
$ {command} \
--key=KEYNAME \
--keyring=KEYRING \
--location=global \
--plaintext-file=path/to/input/plaintext \
--initialization-vector-file=path/to/input/iv \
--ciphertext-file=path/to/output/ciphertext
"""
@staticmethod
def Args(parser):
flags.AddKeyResourceFlags(parser, 'The key to use for encryption.')
flags.AddCryptoKeyVersionFlag(parser, 'to use for encryption', True)
flags.AddPlaintextFileFlag(parser, 'to encrypt')
flags.AddCiphertextFileFlag(parser, 'to output')
flags.AddIvFileFlag(parser, 'for encryption')
flags.AddAadFileFlag(parser)
flags.AddSkipIntegrityVerification(parser)
def _ReadFileOrStdin(self, path, max_bytes):
data = console_io.ReadFromFileOrStdin(path, binary=True)
if len(data) > max_bytes:
raise exceptions.BadFileException(
'The file [{0}] is larger than the maximum size of {1} bytes.'.format(
path, max_bytes
)
)
return data
def _PerformIntegrityVerification(self, args):
return not args.skip_integrity_verification
def _CreateRawEncryptRequest(self, args):
if (
args.initialization_vector_file
and args.additional_authenticated_data_file
):
raise exceptions.InvalidArgumentException(
'--initialization-vector-file and'
' --additional-authenticated-data-file',
'both parameters cannot be provided simultaneously.',
)
if args.plaintext_file == '-' and (
args.initialization_vector_file == '-'
or args.additional_authenticated_data_file == '-'
):
raise exceptions.InvalidArgumentException(
'--plaintext-file', 'multiple parameters cannot be read from stdin.'
)
try:
# The RawEncrypt API limits the plaintext to 64KiB.
plaintext = self._ReadFileOrStdin(args.plaintext_file, max_bytes=65536)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read plaintext file [{0}]: {1}'.format(
args.plaintext_file, e
)
)
aad = b''
if args.additional_authenticated_data_file:
try:
# The RawEncrypt API limits the AAD to 64KiB.
aad = self._ReadFileOrStdin(
args.additional_authenticated_data_file, max_bytes=65536
)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read additional authenticated data file [{0}]: {1}'
.format(args.additional_authenticated_data_file, e)
)
iv = b''
if args.initialization_vector_file:
try:
# The RawEncrypt API limits the IV to CBC_CTR_IV_SIZE bytes.
iv = self._ReadFileOrStdin(
args.initialization_vector_file, max_bytes=CBC_CTR_IV_SIZE
)
except files.Error as e:
raise exceptions.BadFileException(
'Failed to read initialization vector file [{0}]: {1}'.format(
args.initialization_vector_file, e
)
)
if len(iv) != CBC_CTR_IV_SIZE:
raise exceptions.BadFileException(
'--initialization-vector-file',
'the IV size must be {0} bytes.'.format(CBC_CTR_IV_SIZE),
)
crypto_key_ref = flags.ParseCryptoKeyVersionName(args)
messages = cloudkms_base.GetMessagesModule()
req = messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsRawEncryptRequest( # pylint: disable=line-too-long
name=crypto_key_ref.RelativeName()
)
# Populate request integrity fields.
if self._PerformIntegrityVerification(args):
plaintext_crc32c = crc32c.Crc32c(plaintext)
iv_crc32c = crc32c.Crc32c(iv)
aad_crc32c = crc32c.Crc32c(aad)
req.rawEncryptRequest = messages.RawEncryptRequest(
plaintext=plaintext,
initializationVector=iv,
additionalAuthenticatedData=aad,
plaintextCrc32c=plaintext_crc32c,
initializationVectorCrc32c=iv_crc32c,
additionalAuthenticatedDataCrc32c=aad_crc32c,
)
else:
req.rawEncryptRequest = messages.RawEncryptRequest(
plaintext=plaintext,
initializationVector=iv,
additionalAuthenticatedData=aad,
)
return req
def _VerifyResponseIntegrityFields(self, req, resp):
"""Verifies integrity fields in RawEncryptResponse.
Note: This methods assumes that self._PerformIntegrityVerification() is True
and that all request CRC32C fields were pupolated.
Args:
req:
messages.CloudkmsProjectsLocationsKeyRingsCryptoKeysCryptoKeyVersionsRawEncryptRequest()
object
resp: messages.RawEncryptResponse() object.
Returns:
Void.
Raises:
e2e_integrity.ServerSideIntegrityVerificationError if the server reports
request integrity verification error.
e2e_integrity.ClientSideIntegrityVerificationError if response integrity
verification fails.
"""
# Verify resource name.
if req.name != resp.name:
raise e2e_integrity.ResourceNameVerificationError(
e2e_integrity.GetResourceNameMismatchErrorMessage(req.name, resp.name)
)
# plaintext_crc32c was verified server-side.
if not resp.verifiedPlaintextCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage()
)
# additional_authenticated_data_crc32c was verified server-side.
if not resp.verifiedAdditionalAuthenticatedDataCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage()
)
# initialization_vector_crc32c was verified server-side.
if not resp.verifiedInitializationVectorCrc32c:
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetRequestToServerCorruptedErrorMessage()
)
# Verify ciphertext checksum.
if not crc32c.Crc32cMatches(resp.ciphertext, resp.ciphertextCrc32c):
raise e2e_integrity.ClientSideIntegrityVerificationError(
e2e_integrity.GetResponseFromServerCorruptedErrorMessage()
)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
req = self._CreateRawEncryptRequest(args)
resp = None
try:
resp = client.projects_locations_keyRings_cryptoKeys_cryptoKeyVersions.RawEncrypt(
req
)
# Intercept INVALID_ARGUMENT errors related to checksum verification, to
# present a user-friendly message. All other errors are surfaced as-is.
except apitools_exceptions.HttpBadRequestError as error:
e2e_integrity.ProcessHttpBadRequestError(error)
if self._PerformIntegrityVerification(args):
self._VerifyResponseIntegrityFields(req, resp)
try:
log.WriteToFileOrStdout(
args.ciphertext_file, resp.ciphertext, binary=True, overwrite=True
)
# If an initialization vector file is not provided,
# store the one created during the encrypt in a randomly named file.
if not args.initialization_vector_file and resp.initializationVector:
iv_file_name = './inialization_vector_' + str(uuid.uuid4())[:8]
files.WriteBinaryFileContents(
iv_file_name,
resp.initializationVector,
overwrite=True,
)
except files.Error as e:
raise exceptions.BadFileException(e)

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Commands for managing single tenant HSM instances."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import resources
@base.DefaultUniverseOnly
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class SingleTenantHsm(base.Group):
"""Commands for managing single tenant HSM instances.
single tenant hsm instances are a new feature that allow customers to create
a dedicated HSM instance for their use.
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
@staticmethod
def Args(parser):
pass

View File

@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Create a single tenant HSM instance."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import resource_args
@base.DefaultUniverseOnly
class Create(base.Command):
r"""Create a single tenant HSM instance.
## EXAMPLES
The following command creates a single tenant HSM instance
within the location `us-central1` with a total approver count of 3:
$ {command} --location=us-central1 \
--total-approver-count=3
The following command creates a single tenant HSM instance within the location
`us-central1` with a total approver count of 3, and the single tenant HSM
instance ID `my_stchi`:
$ {command} --location=us-central1 \
--total-approver-count=3 \
--single-tenant-hsm-instance-id=my_stchi
"""
@staticmethod
def Args(parser):
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
parser.add_argument(
'--total-approver-count',
type=int,
required=True,
help=(
'The total number of approvers. This is the N value used for M of N'
' quorum auth. Must be greater than or equal to 3 and less than or'
' equal to 16.'
),
)
parser.add_argument(
'--single-tenant-hsm-instance-id',
type=str,
required=False,
help=(
'Specify an ID for the single tenant HSM instance. It must be'
' unique within a location and match the regular expression'
' `[a-zA-Z0-9_-]{1,63}`.'
),
)
def CreateRequest(self, args):
messages = cloudkms_base.GetMessagesModule()
location_ref = args.CONCEPTS.location.Parse()
parent = location_ref.RelativeName()
if args.total_approver_count < 3 or args.total_approver_count > 16:
raise exceptions.BadArgumentException(
'--total-approver-count',
'The total approver count must be between 3 and 16.',
)
stchi_id = (
args.single_tenant_hsm_instance_id
if args.single_tenant_hsm_instance_id
else None
)
return (
messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesCreateRequest(
parent=parent,
singleTenantHsmInstanceId=stchi_id,
singleTenantHsmInstance=messages.SingleTenantHsmInstance(
quorumAuth=messages.QuorumAuth(
totalApproverCount=int(args.total_approver_count),
),
),
)
)
def Run(self, args):
"""Create a single tenant HSM instance."""
client = cloudkms_base.GetClientInstance()
return client.projects_locations_singleTenantHsmInstances.Create(
self.CreateRequest(args)
)

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Describe a single tenant HSM instance."""
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import resource_args
@base.DefaultUniverseOnly
class Describe(base.DescribeCommand):
"""Get metadata for a single tenant HSM instance.
Returns metadata for the given single tenant HSM instance.
## EXAMPLES
The following command returns the metadata for the single tenant HSM instance
with the name `my_sthi` in the location `us-east1`using the fully specified
name:
$ {command}
projects/my-project/locations/us-east1/singleTenantHsmInstances/mysthi
The following command returns the metadata for the singletenanthsm instance
with the name `mysthi` in the location `us-east1` using the location and
resource id:
$ {command} mysthi --location=us-east1
"""
@staticmethod
def Args(parser):
resource_args.AddKmsSingleTenantHsmInstanceResourceArgForKMS(
parser, True, 'single_tenant_hsm_instance'
)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
sthi_ref = args.CONCEPTS.single_tenant_hsm_instance.Parse()
if not sthi_ref.Name():
raise exceptions.InvalidArgumentException(
'single_tenant_hsm_instance',
'single_tenant_hsm_instance id must be non-empty.',
)
return client.projects_locations_singleTenantHsmInstances.Get(
messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesGetRequest(
name=sthi_ref.RelativeName()
)
)

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""List single tenant HSM instances within a location."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
@base.DefaultUniverseOnly
class List(base.ListCommand):
r"""List single tenant HSM instances within a location.
## EXAMPLES
To list all single tenant HSM instances in a location:
$ {command} --location={location}
"""
@staticmethod
def Args(parser):
resource_args.AddKmsLocationResourceArgForKMS(parser, True, '--location')
parser.display_info.AddFormat("""
table(
name,
state,
quorum_auth.required_approver_count,
quorum_auth.total_approver_count)
""")
def Run(self, args):
"""List single tenant HSM instances."""
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
location_ref = args.CONCEPTS.location.Parse()
request = (
messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesListRequest(
parent=location_ref.RelativeName(),
)
)
return list_pager.YieldFromList(
client.projects_locations_singleTenantHsmInstances,
request,
field='singleTenantHsmInstances',
limit=args.limit,
batch_size=args.page_size,
batch_size_attribute='pageSize',
)

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Commands for managing single tenant HSM instances."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import flags
from googlecloudsdk.core import resources
@base.DefaultUniverseOnly
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class SingleTenantHsmInstanceProposal(base.Group):
"""Commands for managing single tenant HSM instance proposals.
single tenant hsm instance proposals represent a proposal to perform an
operation on a single tenant HSM instance.
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
@staticmethod
def Args(parser):
pass

View File

@@ -0,0 +1,226 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Approve a single tenant HSM instance proposal."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import ast
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import pems
from googlecloudsdk.command_lib.kms import resource_args
from googlecloudsdk.core import exceptions as core_exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
def tuple_list_type(arg):
try:
return ast.literal_eval(arg)
except ValueError as e:
raise exceptions.BadArgumentException(
'--challenge_replies',
'Error while attempting to parse challenge replies: {}'.format(e),
)
def _parse_challenge_replies(messages, challenge_reply_tuples):
"""Parses file paths from tuples into a list of ChallengeReply messages."""
challenge_replies = []
if not challenge_reply_tuples:
return challenge_replies
for challenge_reply in challenge_reply_tuples:
if not isinstance(challenge_reply, tuple) or len(challenge_reply) != 2:
raise exceptions.BadArgumentException(
'challenge replies',
'Each challenge reply must be a tuple of (signed_challenge_file,'
' public_key_file).',
)
signed_challenge_file, public_key_file = challenge_reply
if not signed_challenge_file:
raise exceptions.BadArgumentException(
'challenge replies',
'signed_challenge_file must be specified.',
)
if not public_key_file:
raise exceptions.BadArgumentException(
'challenge replies',
'public_key_file must be specified.',
)
try:
public_key = pems.GetPemPublicKey(public_key_file)
except Exception as e:
raise exceptions.BadArgumentException(
'challenge replies',
'Error while attempting to read public key file {}: {}'.format(
public_key_file, e
),
)
try:
signed_challenge = files.ReadBinaryFileContents(signed_challenge_file)
except Exception as e:
raise exceptions.BadArgumentException(
'challenge replies',
'Error while attempting to read signed challenge file {}: {}'.format(
signed_challenge_file, e
),
)
challenge_replies.append(
messages.ChallengeReply(
signedChallenge=signed_challenge,
publicKeyPem=public_key,
)
)
return challenge_replies
@base.DefaultUniverseOnly
class Approve(base.Command):
r"""Approve a single tenant HSM instance proposal.
## EXAMPLES
The following command approves a single tenant HSM instance proposal with
quorum challenge replies:
$ {command} projects/my-project/locations/us-east1/singleTenantHsmInstances/
my_sthi/proposals/my_proposal \
--quorum-challenge-replies="[('signed_challenge_1.txt','public_key_1.pem'),
('signed_challenge_2.txt','public_key_2.pem'),
('signed_challenge_3.txt','public_key_3.pem')]"
To approve a proposal with required challenges and quorum challenges:
$ {command} projects/my-project/locations/us-east1/singleTenantHsmInstances/
my_sthi/proposals/my_proposal \
--required-challenge-replies="[('required_challenge.txt','public_key_1.pem')]" \
--quorum-challenge-replies="[('quorum_challenge_1.txt','public_key_2.pem'),
('quorum_challenge_2.txt','public_key_3.pem')]"
"""
@staticmethod
def Args(parser):
resource_args.AddKmsSingleTenantHsmInstanceProposalResourceArgForKMS(
parser, True, 'single_tenant_hsm_instance_proposal'
)
quorum_group = parser.add_group(
required=True, help='Approval payload for the proposal.'
)
quorum_group.add_argument(
'--quorum-challenge-replies',
type=tuple_list_type,
help=(
'The challenge replies to approve the proposal. Challenge replies '
'can be sent across multiple requests. Each tuple should be '
'("signed_challenge_file", "public_key_file").'
),
)
quorum_group.add_argument(
'--required-challenge-replies',
type=tuple_list_type,
help=(
'A list of tuples, each containing the file paths for a required '
'challenge reply. Each tuple should be '
'("signed_challenge_file", "public_key_file").'
),
)
def ApproveRequest(self, args):
messages = cloudkms_base.GetMessagesModule()
single_tenant_hsm_instance_proposal_ref = (
args.CONCEPTS.single_tenant_hsm_instance_proposal.Parse()
)
kms_client = cloudkms_base.GetClientInstance()
proposal = kms_client.projects_locations_singleTenantHsmInstances_proposals.Get(
messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesProposalsGetRequest(
name=single_tenant_hsm_instance_proposal_ref.RelativeName()
)
)
approve_request = messages.ApproveSingleTenantHsmInstanceProposalRequest()
if proposal.quorumParameters is not None:
if args.quorum_challenge_replies is None:
raise exceptions.RequiredArgumentException(
'--quorum-challenge-replies',
'--quorum-challenge-replies must be specified for proposals'
' requiring quorum parameters.',
)
if args.required_challenge_replies is not None:
raise exceptions.BadArgumentException(
'--required-challenge-replies',
'This argument cannot be used with proposals requiring quorum'
' parameters.',
)
challenge_replies = _parse_challenge_replies(
messages, args.quorum_challenge_replies
)
approve_request.quorumReply = messages.QuorumReply(
challengeReplies=challenge_replies
)
elif proposal.requiredActionQuorumParameters is not None:
if (
args.required_challenge_replies is None
and args.quorum_challenge_replies is None
):
raise exceptions.RequiredArgumentException(
'--required-challenge-replies and --quorum-challenge-replies',
'At least one of --required-challenge-replies and'
' --quorum-challenge-replies must be specified for proposals'
' requiring required action quorum parameters.',
)
required_challenge_replies = _parse_challenge_replies(
messages, args.required_challenge_replies
)
quorum_challenge_replies = _parse_challenge_replies(
messages, args.quorum_challenge_replies
)
approve_request.requiredActionQuorumReply = (
messages.RequiredActionQuorumReply(
requiredChallengeReplies=required_challenge_replies,
quorumChallengeReplies=quorum_challenge_replies,
)
)
else:
raise core_exceptions.Error(
'Unsupported or missing approval parameters in proposal: {}'.format(
proposal.name
)
)
req = messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesProposalsApproveRequest(
name=single_tenant_hsm_instance_proposal_ref.RelativeName(),
approveSingleTenantHsmInstanceProposalRequest=approve_request,
)
return req
def Run(self, args):
"""Approve a single tenant HSM instance proposal."""
client = cloudkms_base.GetClientInstance()
result = (
client.projects_locations_singleTenantHsmInstances_proposals.Approve(
self.ApproveRequest(args)
)
)
if result is not Exception:
log.status.Print('Approved proposal successfully.')
return result

View File

@@ -0,0 +1,226 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Create a single tenant HSM instance proposal."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import pems
from googlecloudsdk.command_lib.kms import resource_args
@base.DefaultUniverseOnly
class Create(base.Command):
r"""Create a single tenant HSM instance proposal.
$ {command}
my_stchi
--location=us-central1 \
--required-approver-count=1 \
--two-factor-public-key-pems=public_key_1.pem,public_key_2.pem
"""
@staticmethod
def Args(parser):
resource_args.AddKmsSingleTenantHsmInstanceResourceArgForKMS(
parser, True, 'single_tenant_hsm_instance'
)
parser.add_argument(
'--operation-type',
type=str,
required=True,
help=(
'The type of operation for the single tenant HSM instance proposal.'
),
)
parser.add_argument(
'--required-approver-count',
type=int,
required=False,
help=(
'The number of approvers required for the single tenant HSM'
' instance. This is the M value used for M of N quorum. Must be'
' greater than or equal to 1 and less than or equal to the total'
' approver count of the single tenant HSM instance minus 1. This'
' field is required for the register_2fa_keys operation type.'
),
)
parser.add_argument(
'--two-factor-public-key-pems',
type=arg_parsers.ArgList(),
required=False,
metavar='PEM_FILE_PATH',
help=(
'The PEM files containing the two factor public keys 2FA keys for'
' M of N quorum auth tenant HSM instance. This field is required'
' for register_2fa_keys operation type.'
),
)
parser.add_argument(
'--member-public-key-pem',
type=str,
required=False,
help=(
'The PEM file containing the public key of the quorum member to'
' add or remove. This field is required for add_quorum_member and'
' remove_quorum_member operation types.'
),
)
parser.add_argument(
'--single-tenant-hsm-instance-proposal-id',
required=False,
help=(
'The ID to use for the single tenant HSM instance proposal, which'
' will become the final component of the single tenant HSM'
' instance resource name.'
),
)
def validate_required_approver_count(self, args, messages, sthi_ref):
if args.required_approver_count is None:
raise exceptions.BadArgumentException(
'--required-approver-count',
'The required approver count must be specified for'
' register_2fa_keys operation type.',
)
get_request = (
messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesGetRequest(
name=sthi_ref.RelativeName(),
)
)
client = cloudkms_base.GetClientInstance()
sthi = client.projects_locations_singleTenantHsmInstances.Get(get_request)
max_required_approver_count = sthi.quorumAuth.totalApproverCount - 1
if (
args.required_approver_count < 2
or args.required_approver_count > max_required_approver_count
):
raise exceptions.BadArgumentException(
'--required-approver-count',
'The required approver count must be between 2 and {}.'.format(
max_required_approver_count
),
)
def public_key_pem_files_to_list(self, args):
if args.two_factor_public_key_pems is None:
raise exceptions.BadArgumentException(
'--two-factor-public-key-pems',
'The two factor public key pems must be specified for'
' register_2fa_keys operation type.',
)
pem_list = []
for pem_file in args.two_factor_public_key_pems:
try:
pem_list.append(pems.GetPemPublicKey(pem_file))
except Exception as e:
raise exceptions.BadArgumentException(
'--two-factor-public-key-pems',
'Error while attempting to read file {} : {}'.format(pem_file, e),
)
return pem_list
def CreateRequest(self, args):
messages = cloudkms_base.GetMessagesModule()
sthi_ref = args.CONCEPTS.single_tenant_hsm_instance.Parse()
sthi_proposal_id = (
args.single_tenant_hsm_instance_proposal_id
if args.single_tenant_hsm_instance_proposal_id
else None
)
req = messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesProposalsCreateRequest(
parent=sthi_ref.RelativeName(),
singleTenantHsmInstanceProposal=messages.SingleTenantHsmInstanceProposal(),
singleTenantHsmInstanceProposalId=sthi_proposal_id,
)
if args.operation_type == 'register_2fa_keys':
self.validate_required_approver_count(args, messages, sthi_ref)
pem_list = self.public_key_pem_files_to_list(args)
req.singleTenantHsmInstanceProposal.registerTwoFactorAuthKeys = (
messages.RegisterTwoFactorAuthKeys(
requiredApproverCount=args.required_approver_count,
twoFactorPublicKeyPems=pem_list,
)
)
elif args.operation_type == 'disable_sthi':
req.singleTenantHsmInstanceProposal.disableSingleTenantHsmInstance = (
messages.DisableSingleTenantHsmInstance()
)
elif args.operation_type == 'enable_sthi':
req.singleTenantHsmInstanceProposal.enableSingleTenantHsmInstance = (
messages.EnableSingleTenantHsmInstance()
)
elif args.operation_type == 'delete_sthi':
req.singleTenantHsmInstanceProposal.deleteSingleTenantHsmInstance = (
messages.DeleteSingleTenantHsmInstance()
)
elif args.operation_type == 'add_quorum_member':
if args.member_public_key_pem is None:
raise exceptions.BadArgumentException(
'--member-public-key-pem',
'The member public key must be specified for'
' add_quorum_member operation type.',
)
pem = pems.GetPemPublicKey(args.member_public_key_pem)
req.singleTenantHsmInstanceProposal.addQuorumMember = (
messages.AddQuorumMember(twoFactorPublicKeyPem=pem)
)
elif args.operation_type == 'remove_quorum_member':
if args.member_public_key_pem is None:
raise exceptions.BadArgumentException(
'--member-public-key',
'The member public key must be specified for'
' remove_quorum_member operation type.',
)
pem = pems.GetPemPublicKey(args.member_public_key_pem)
req.singleTenantHsmInstanceProposal.removeQuorumMember = (
messages.RemoveQuorumMember(twoFactorPublicKeyPem=pem)
)
elif args.operation_type == 'refresh_sthi':
req.singleTenantHsmInstanceProposal.refreshSingleTenantHsmInstance = (
messages.RefreshSingleTenantHsmInstance()
)
else:
raise exceptions.BadArgumentException(
'--operation-type',
'The operation type must be one of register_2fa_keys,'
' disable_sthi,'
' enable_sthi,'
' delete_sthi,'
' add_quorum_member,'
' remove_quorum_member, or'
' refresh_sthi.',
)
return req
def Run(self, args):
"""Create a single tenant HSM instance."""
client = cloudkms_base.GetClientInstance()
operation = (
client.projects_locations_singleTenantHsmInstances_proposals.Create(
self.CreateRequest(args)
)
)
return operation

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Delete a single tenant HSM instance proposal."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import resource_args
from googlecloudsdk.core import exceptions as core_exceptions
from googlecloudsdk.core import log
@base.DefaultUniverseOnly
class Delete(base.DeleteCommand):
r"""Delete a single tenant HSM instance proposal.
## EXAMPLES
The following command deletes a single tenant HSM instance proposal:
$ {command} projects/my-project/locations/us-east1/singleTenantHsmInstances/
my_sthi/proposals/my_proposal
"""
@staticmethod
def Args(parser):
resource_args.AddKmsSingleTenantHsmInstanceProposalResourceArgForKMS(
parser, True, 'single_tenant_hsm_instance_proposal'
)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
single_tenant_hsm_instance_proposal_ref = (
args.CONCEPTS.single_tenant_hsm_instance_proposal.Parse()
)
if not single_tenant_hsm_instance_proposal_ref.Name():
raise exceptions.InvalidArgumentException(
'singletenanthsminstanceproposal',
'singletenanthsminstanceproposal id must be non-empty.',
)
try:
result = client.projects_locations_singleTenantHsmInstances_proposals.Delete(
messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesProposalsDeleteRequest(
name=single_tenant_hsm_instance_proposal_ref.RelativeName()
)
)
log.status.Print('Deleted proposal successfully.')
return result
except exceptions.HttpException as e:
raise core_exceptions.Error('Failed to delete proposal: {}'.format(e))

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Describe a single tenant HSM instance proposal."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.kms import pems
from googlecloudsdk.command_lib.kms import resource_args
@base.DefaultUniverseOnly
class Describe(base.DescribeCommand):
r"""Get metadata for a single tenant HSM instance proposal.
## EXAMPLES
The following command returns the metadata for the single tenant HSM instance
proposal with the name `my_proposal`, of the instance `my_sthi`, and in the
location `us-east1` using the fully specified resource name.
```
$ {command} projects/my-project/locations/us-east1/singleTenantHsmInstances/
my_sthi/proposals/my_proposal
```
The following command returns the metadata for the single tenant HSM instance
proposal with the name `my_proposal`, of the instance `my_sthi`, and in the
location `us-east1` using the location, instance id, and proposal id.
```
$ {command} my_proposal --single_tenant_hsm_instance=my_sthi
--location=us-east1
```
"""
@staticmethod
def Args(parser):
resource_args.AddKmsSingleTenantHsmInstanceProposalResourceArgForKMS(
parser, True, 'single_tenant_hsm_instance_proposal'
)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
single_tenant_hsm_instance_proposal_ref = (
args.CONCEPTS.single_tenant_hsm_instance_proposal.Parse()
)
if not single_tenant_hsm_instance_proposal_ref.Name():
raise exceptions.InvalidArgumentException(
'singletenanthsminstanceproposal',
'singletenanthsminstanceproposal id must be non-empty.',
)
return client.projects_locations_singleTenantHsmInstances_proposals.Get(
messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesProposalsGetRequest(
name=single_tenant_hsm_instance_proposal_ref.RelativeName()
)
)

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""Executes a single tenant HSM proposal."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
@base.DefaultUniverseOnly
class Execute(base.Command):
r"""Executes a single tenant HSM proposal.
Executes a single tenant HSM proposal. The proposal must be in an approved
state.
## EXAMPLES
The following command executes a single tenant HSM proposal named
`my_proposal` associated with the single tenant HSM instance `my_sthi`
within the location `us-central1` with the fully specified name.
$ {command}
projects/my-project/locations/us-central1/singleTenantHsmInstances/
my_sthi/proposals/my_proposal
The following command executes a single tenant HSM proposal named
`my_proposal` associated with the single tenant HSM instance `my_sthi`
within the location `us-central1` using the location,
single-tenant-hsm-instance, and proposal id.
$ {command} my_proposal --location=us-central1
--single_tenant_hsm_instance=my_sthi
proposal_id=my_proposal
"""
@staticmethod
def Args(parser):
resource_args.AddKmsSingleTenantHsmInstanceProposalResourceArgForKMS(
parser, True, 'single_tenant_hsm_instance_proposal'
)
def Run(self, args):
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
single_tenant_hsm_instance_proposal_ref = (
args.CONCEPTS.single_tenant_hsm_instance_proposal.Parse()
)
req = messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesProposalsExecuteRequest(
name=single_tenant_hsm_instance_proposal_ref.RelativeName()
)
return client.projects_locations_singleTenantHsmInstances_proposals.Execute(
req
)

View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 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.
"""List single tenant HSM instances proposals within a location."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import list_pager
from googlecloudsdk.api_lib.cloudkms import base as cloudkms_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.kms import resource_args
@base.DefaultUniverseOnly
class List(base.ListCommand):
r"""List single tenant HSM instance proposals within a single tenant HSM instance.
## EXAMPLES
To list all single tenant HSM instance proposals in a single tenant instance
using the single tenant HSM instance name `my_sthi` and the location
`us-east1`:
$ {command} my_sthi --location=us-east1
To list all single tenant HSM instance proposals in a single tenant instance
using the single tenant HSM instance name `my_sthi` and the location
`us-east1` with the full single tenant HSM instance resource name:
$ {command}
projects/my-project/locations/us-east1/singleTenantHsmInstances/my_sthi
"""
@staticmethod
def Args(parser):
resource_args.AddKmsSingleTenantHsmInstanceResourceArgForKMS(
parser, True, 'single_tenant_hsm_instance'
)
parser.display_info.AddFormat("""
table(
name,
state,
quorum_parameters.required_approver_count,
expire_time)
""")
def Run(self, args):
"""List single tenant HSM instance proposals."""
client = cloudkms_base.GetClientInstance()
messages = cloudkms_base.GetMessagesModule()
location_ref = args.CONCEPTS.single_tenant_hsm_instance.Parse()
request = messages.CloudkmsProjectsLocationsSingleTenantHsmInstancesProposalsListRequest(
parent=location_ref.RelativeName(),
)
return list_pager.YieldFromList(
client.projects_locations_singleTenantHsmInstances_proposals,
request,
field='singleTenantHsmInstanceProposals',
limit=args.limit,
batch_size=args.page_size,
batch_size_attribute='pageSize',
)