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,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)