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,26 @@
# -*- 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.
"""The command group for certificates."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Certificates(base.Group):
"""Manage certificates."""

View File

@@ -0,0 +1,390 @@
# -*- 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.
"""Create a certificate."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.cloudkms import cryptokeyversions
from googlecloudsdk.api_lib.privateca import base as privateca_base
from googlecloudsdk.api_lib.privateca import certificate_utils
from googlecloudsdk.api_lib.privateca import request_utils
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.calliope.concepts import deps
from googlecloudsdk.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import key_generation
from googlecloudsdk.command_lib.privateca import pem_utils
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
import six
_KEY_OUTPUT_HELP = """The path where the generated private key file should be written (in PEM format).
Note: possession of this key file could allow anybody to act as this certificate's
subject. Please make sure that you store this key file in a secure location at all
times, and ensure that only authorized users have access to it."""
def _ReadCsr(csr_file):
try:
return files.ReadFileContents(csr_file)
except (files.Error, OSError, IOError):
raise exceptions.BadFileException(
"Could not read provided CSR file '{}'.".format(csr_file)
)
def _WritePemChain(pem_cert, issuing_chain, cert_file):
try:
pem_chain = [pem_cert] + issuing_chain
files.WriteFileContents(cert_file, pem_utils.PemChainForOutput(pem_chain))
except (files.Error, OSError, IOError):
raise exceptions.BadFileException(
"Could not write certificate to '{}'.".format(cert_file)
)
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class Create(base.CreateCommand):
r"""Create a new certificate.
## EXAMPLES
To create a certificate using a CSR:
$ {command} frontend-server-tls \
--issuer-pool=my-pool --issuer-location=us-west1 \
--csr=./csr.pem \
--cert-output-file=./cert.pem \
--validity=P30D
To create a certificate using a client-generated key:
$ {command} frontend-server-tls \
--issuer-pool=my-pool --issuer-location=us-west1 \
--generate-key \
--key-output-file=./key \
--cert-output-file=./cert.pem \
--dns-san=www.example.com \
--use-preset-profile=leaf_server_tls
"""
@staticmethod
def Args(parser):
persistence_group = parser.add_group(
mutex=True, required=True, help='Certificate persistence options.'
)
base.Argument(
'--cert-output-file',
help=(
'The path where the resulting PEM-encoded certificate chain file'
' should be written (ordered from leaf to root).'
),
required=False,
).AddToParser(persistence_group)
base.Argument(
'--validate-only',
help=(
'If this flag is set, the certificate resource will not be'
' persisted and the returned certificate will not contain the'
' pem_certificate field.'
),
action='store_true',
default=False,
required=False,
).AddToParser(persistence_group)
flags.AddValidityFlag(parser, 'certificate', 'P30D', '30 days')
labels_util.AddCreateLabelsFlags(parser)
cert_generation_group = parser.add_group(
mutex=True, required=True, help='Certificate generation method.'
)
csr_group = cert_generation_group.add_group(
help='To issue a certificate from a CSR use the following:',
)
base.Argument(
'--csr', help='A PEM-encoded certificate signing request file path.',
required=True,
).AddToParser(csr_group)
base.Argument(
'--rdn-sequence-subject',
help=(
'If this value is set then the issued certificate will use the '
'subject found in the CSR preserving the exact RDN sequence.'
),
hidden=True,
action='store_true',
).AddToParser(csr_group)
non_csr_group = cert_generation_group.add_group(
help='Alternatively, you may describe the certificate and key to use.'
)
key_group = non_csr_group.add_group(
mutex=True,
required=True,
help=(
'To describe the key that will be used for this certificate, use '
'one of the following options.'
),
)
key_generation_group = key_group.add_group(
help='To generate a new key pair, use the following:'
)
base.Argument(
'--generate-key',
help=(
'Use this flag to have a new RSA-2048 private key securely'
' generated on your machine.'
),
action='store_const',
const=True,
default=False,
required=True,
).AddToParser(key_generation_group)
base.Argument(
'--key-output-file', help=_KEY_OUTPUT_HELP, required=True
).AddToParser(key_generation_group)
base.Argument(
'--ca',
help=(
'The name of an existing certificate authority to use for issuing'
' the certificate. If omitted, a certificate authority will be will'
' be chosen from the CA pool by the service on your behalf.'
),
required=False,
).AddToParser(parser)
subject_group = non_csr_group.add_group(
help='The subject names for the certificate.', required=True
)
flags.AddSubjectFlags(subject_group)
x509_parameters_group = non_csr_group.add_group(
mutex=True, help='The x509 configuration used for this certificate.'
)
flags.AddInlineX509ParametersFlags(
x509_parameters_group, is_ca_command=False, default_max_chain_length=0
)
flags.AddUsePresetProfilesFlag(x509_parameters_group)
flags.AddSubjectKeyIdFlag(parser)
cert_arg = 'CERTIFICATE'
concept_parsers.ConceptParser(
[
presentation_specs.ResourcePresentationSpec(
cert_arg,
resource_args.CreateCertResourceSpec(
cert_arg, [Create._GenerateCertificateIdFallthrough()]
),
'The name of the certificate to issue. If the certificate ID '
'is omitted, a random identifier will be generated according '
'to the following format: {YYYYMMDD}-{3 random alphanumeric '
'characters}-{3 random alphanumeric characters}. The '
'certificate ID is not required when the issuing CA pool is in '
'the DevOps tier.',
required=True,
),
presentation_specs.ResourcePresentationSpec(
'--template',
resource_args.CreateCertificateTemplateResourceSpec(
'certificate_template'
),
'The name of a certificate template to use for issuing this '
'certificate, if desired. A template may overwrite parts of '
'the certificate request, and the use of certificate templates '
"may be required and/or regulated by the issuing CA Pool's CA "
'Manager. The specified template must be in the same location '
'as the issuing CA Pool.',
required=False,
prefixes=True,
),
presentation_specs.ResourcePresentationSpec(
'--kms-key-version',
resource_args.CreateKmsKeyVersionResourceSpec(),
'An existing KMS key version backing this certificate.',
group=key_group,
),
],
command_level_fallthroughs={
'--template.location': ['CERTIFICATE.issuer-location']
},
).AddToParser(parser)
# The only time a resource is returned is when args.validate_only is set.
parser.display_info.AddFormat('yaml(certificateDescription)')
@classmethod
def _GenerateCertificateIdFallthrough(cls):
cls.id_fallthrough_was_used = False
def FallthroughFn():
cls.id_fallthrough_was_used = True
return certificate_utils.GenerateCertId()
return deps.Fallthrough(
function=FallthroughFn,
hint='certificate id will default to an automatically generated id',
active=False,
plural=False,
)
def _ValidateArgs(self, args):
"""Validates the command-line args."""
if args.IsSpecified('use_preset_profile') and args.IsSpecified('template'):
raise exceptions.OneOfArgumentsRequiredException(
['--use-preset-profile', '--template'],
(
'To create a certificate, please specify either a preset profile '
'or a certificate template.'
),
)
resource_args.ValidateResourceIsCompleteIfSpecified(args, 'kms_key_version')
@classmethod
def _PrintWarningsForUnpersistedCert(cls, args):
"""Prints warnings if certain command-line args are used for an unpersisted cert."""
unused_args = []
if not cls.id_fallthrough_was_used:
unused_args.append('certificate ID')
if args.IsSpecified('labels'):
unused_args.append('labels')
if unused_args:
names = ', '.join(unused_args)
verb = 'was' if len(unused_args) == 1 else 'were'
log.warning(
'{names} {verb} specified but will not be used since the '
'issuing CA pool is in the DevOps tier, which does not expose '
'certificate lifecycle.'.format(names=names, verb=verb)
)
def _GetPublicKey(self, args):
"""Fetches the public key associated with a non-CSR certificate request, as UTF-8 encoded bytes."""
kms_key_version = args.CONCEPTS.kms_key_version.Parse()
if args.generate_key:
private_key, public_key = key_generation.RSAKeyGen(2048)
key_generation.ExportPrivateKey(args.key_output_file, private_key)
return public_key
elif kms_key_version:
public_key_response = cryptokeyversions.GetPublicKey(kms_key_version)
# bytes(..) requires an explicit encoding in PY3.
return (
bytes(public_key_response.pem)
if six.PY2
else bytes(public_key_response.pem, 'utf-8')
)
else:
# This should not happen because of the required arg group, but protects
# in case of future additions.
raise exceptions.OneOfArgumentsRequiredException(
['--csr', '--generate-key', '--kms-key-version'],
(
'To create a certificate, please specify either a CSR, the'
' --generate-key flag to create a new key, or the'
' --kms-key-version flag to use an existing KMS key.'
),
)
def _GenerateCertificateConfig(self, request, args):
public_key = self._GetPublicKey(args)
config = self.messages.CertificateConfig()
config.publicKey = self.messages.PublicKey()
config.publicKey.key = public_key
config.publicKey.format = self.messages.PublicKey.FormatValueValuesEnum.PEM
config.subjectConfig = flags.ParseSubjectFlags(args)
config.x509Config = flags.ParseX509Parameters(args, is_ca_command=False)
config.subjectKeyId = flags.ParseSubjectKeyId(args, self.messages)
return config
def Run(self, args):
self.client = privateca_base.GetClientInstance(api_version='v1')
self.messages = privateca_base.GetMessagesModule(api_version='v1')
self._ValidateArgs(args)
cert_ref = args.CONCEPTS.certificate.Parse()
labels = labels_util.ParseCreateArgs(
args, self.messages.Certificate.LabelsValue
)
request = (
self.messages.PrivatecaProjectsLocationsCaPoolsCertificatesCreateRequest()
)
request.certificate = self.messages.Certificate()
request.certificateId = cert_ref.Name()
request.certificate.lifetime = flags.ParseValidityFlag(args)
request.certificate.labels = labels
request.parent = cert_ref.Parent().RelativeName()
request.requestId = request_utils.GenerateRequestId()
request.validateOnly = args.validate_only
if args.IsSpecified('ca'):
request.issuingCertificateAuthorityId = args.ca
template_ref = args.CONCEPTS.template.Parse()
if template_ref:
if template_ref.locationsId != cert_ref.locationsId:
raise exceptions.InvalidArgumentException(
'--template',
'The certificate template must be in the same location as the '
'issuing CA Pool.',
)
request.certificate.certificateTemplate = template_ref.RelativeName()
if args.csr:
request.certificate.pemCsr = _ReadCsr(args.csr)
if args.rdn_sequence_subject:
request.certificate.subjectMode = (
self.messages.Certificate.SubjectModeValueValuesEnum.RDN_SEQUENCE
)
else:
request.certificate.config = self._GenerateCertificateConfig(
request, args
)
certificate = self.client.projects_locations_caPools_certificates.Create(
request
)
# Validate-only certs don't have a resource name or pem certificate.
if args.validate_only:
return certificate
status_message = 'Created Certificate'
if certificate.name:
status_message += ' [{}]'.format(certificate.name)
else:
Create._PrintWarningsForUnpersistedCert(args)
if certificate.pemCertificate:
status_message += ' and saved it to [{}]'.format(args.cert_output_file)
_WritePemChain(
certificate.pemCertificate,
certificate.pemCertificateChain,
args.cert_output_file,
)
status_message += '.'
log.status.Print(status_message)

View File

@@ -0,0 +1,32 @@
- release_tracks: [GA]
help_text:
brief: |
Get metadata for a certificate.
description: |
Returns metadata for the given certificate.
examples: |
To get metadata for the 'frontend-server-tls' certificate:
$ {command} frontend-server-tls \
--issuer-pool=my-pool --issuer-location=us-west1
To download the PEM-encoded certificate for the 'frontend-server-tls'
certificate to a file
called 'frontend-server-tls.crt':
$ {command} frontend-server-tls \
--issuer-pool=my-pool --issuer-location=us-west1 \
--format="value(pemCertificate)" > ./frontend-server-tls.crt
request:
collection: privateca.projects.locations.caPools.certificates
api_version: v1
arguments:
resource:
help_text: The certificate for which to obtain metadata.
spec: !REF googlecloudsdk.command_lib.privateca.resources:cert
response:
modify_response_hooks:
- googlecloudsdk.command_lib.privateca.hooks:ConvertCertificateLifetimeToIso8601

View File

@@ -0,0 +1,121 @@
# -*- 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.
"""Export a pem-encoded certificate to a file."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.privateca import base as privateca_base
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.privateca import pem_utils
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
_DETAILED_HELP = {
'EXAMPLES':
"""\
To export a single pem-encoded certificate to a file, run the following:
$ {command} my-cert --issuer=my-ca --issuer-location=us-west1 --output-file=cert.pem
To export a pem-encoded certificate along with its issuing chain in the
same file, run the following:
$ {command} my-cert --issuer=my-ca --issuer-location=us-west1 --include-chain --output-file=chain.pem
You can omit the --issuer-location flag in both of the above examples if
you've already set the privateca/location property. For example:
$ {top_command} config set privateca/location us-west1
# The following is equivalent to the first example above.
$ {command} my-cert --issuer=my-ca --output-file=cert.pem
# The following is equivalent to the second example above.
$ {command} my-cert --issuer=my-ca --include-chain --output-file=chain.pem
"""
}
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Export(base.SilentCommand):
r"""Export a pem-encoded certificate to a file.
## EXAMPLES
To export a single pem-encoded certificate to a file, run the following:
$ {command} my-cert --issuer-pool=my-pool --issuer-location=us-west1 \
--output-file=cert.pem
To export a pem-encoded certificate along with its issuing chain in the
same file, run the following:
$ {command} my-cert --issuer-pool=my-pool --issuer-location=us-west1 \
--include-chain \
--output-file=chain.pem
You can omit the --issuer-location flag in both of the above examples if
you've already set the privateca/location property. For example:
$ {top_command} config set privateca/location us-west1
# The following is equivalent to the first example above.
$ {command} my-cert --issuer-pool=my-pool --output-file=cert.pem
# The following is equivalent to the second example above.
$ {command} my-cert --issuer-pool=my-pool --include-chain \
--output-file=chain.pem
"""
@staticmethod
def Args(parser):
resource_args.AddCertPositionalResourceArg(parser, 'to export')
base.Argument(
'--output-file',
help='The path where the resulting PEM-encoded certificate will be '
'written.',
required=True).AddToParser(parser)
base.Argument(
'--include-chain',
help="Whether to include the certificate's issuer chain in the "
"exported file. If this is set, the resulting file will contain "
"the pem-encoded certificate and its issuing chain, ordered from "
"leaf to root.",
action='store_true',
default=False,
required=False).AddToParser(parser)
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
certificate_ref = args.CONCEPTS.certificate.Parse()
certificate = client.projects_locations_caPools_certificates.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificatesGetRequest(
name=certificate_ref.RelativeName()))
pem_chain = [certificate.pemCertificate]
if args.include_chain:
pem_chain += certificate.pemCertificateChain
files.WriteFileContents(args.output_file,
pem_utils.PemChainForOutput(pem_chain))
log.status.write('Exported certificate [{}] to [{}].'.format(
certificate_ref.RelativeName(), args.output_file))

View File

@@ -0,0 +1,161 @@
# -*- 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 certificates within a project."""
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.privateca import base as privateca_base
from googlecloudsdk.api_lib.privateca import resource_utils
from googlecloudsdk.api_lib.util import common_args
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope.concepts import deps
from googlecloudsdk.command_lib.privateca import filter_rewrite
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.privateca import response_utils
from googlecloudsdk.command_lib.privateca import text_utils
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core import log
from googlecloudsdk.core.resource import resource_projection_spec
_DETAILED_HELP = {
'EXAMPLES':
"""\
To list all Certificates issued by a given Certificate Authority, run:
$ {command} --issuer=my-ca --location=us-west1
To list all Certificates issued by all Certificate Authorities in a
location, run:
$ {command} --location=us-west1
You can omit the `--location` flag in both of the above examples if
you've already set the ``privateca/location'' property. For example:
$ {top_command} config set privateca/location us-west1
+
# The following is equivalent to the first example above.
$ {command} --issuer=my-ca
+
# The following is equivalent to the second example above.
$ {command}
"""
}
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
r"""List certificates within a project.
List certificates within a project. Note that listing certificates accross
locations is not supported.
## EXAMPLES
To list all Certificates issued by a given CA pool, run:
$ {command} --issuer-pool=my-pool --location=us-west1
To list all Certificates issued by all CA pools in a location, run:
$ {command} --location=us-west1
To list all Certificates issued directly under a CA, run:
$ {command} --issuer-pool=my-pool --location=us-west1 \
--filter="issuer_certificate_authority='projects/1234567890/locations/us-west1/caPools/my-pool/certificateAuthorities/my-ca'"
You can omit the `--location` flag in both of the above examples if you've
already set the ``privateca/location'' property. For example:
$ {top_command} config set privateca/location us-west1
+
# The following is equivalent to the first example above.
$ {command} --issuer-pool=my-pool
+
# The following is equivalent to the second example above.
$ {command}
"""
@staticmethod
def Args(parser):
concept_parsers.ConceptParser([
presentation_specs.ResourcePresentationSpec(
'--issuer-pool',
resource_args.CreateCaPoolResourceSpec(
'CA_POOL',
pool_id_fallthroughs=[
deps.Fallthrough(
function=lambda: '-',
hint=('defaults to all CA pools in the '
'given location'),
active=False,
plural=False)
]), 'The issuing CA pool. If this is omitted, '
'Certificates issued by all CA pools in the given '
'location will be listed.',
required=True),
]).AddToParser(parser)
base.PAGE_SIZE_FLAG.SetDefault(parser, 100)
parser.display_info.AddFormat("""
table(
name.basename(),
name.scope().segment(-3):label=CA_POOL,
name.scope().segment(-5):label=LOCATION,
revocation_details.yesno(yes="REVOKED", no="ACTIVE"):label=REVOCATION_STATUS,
certificate_description.subject_description.not_before_time():label=NOT_BEFORE,
certificate_description.subject_description.not_after_time():label=NOT_AFTER)
""")
parser.display_info.AddTransforms({
'not_before_time': text_utils.TransformNotBeforeTime,
'not_after_time': text_utils.TransformNotAfterTime
})
parser.display_info.AddUriFunc(
resource_utils.MakeGetUriFunc(
'privateca.projects.locations.caPools.certificates'))
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
display_info = args.GetDisplayInfo()
defaults = resource_projection_spec.ProjectionSpec(
symbols=display_info.transforms, aliases=display_info.aliases)
client_filter, server_filter = filter_rewrite.BackendFilterRewrite(
).Rewrite(
args.filter, defaults=defaults)
log.info('original_filter=%r, client_filter=%r, server_filter=%r',
args.filter, client_filter, server_filter)
# Overwrite client filter used by gcloud.
args.filter = client_filter
parent = args.CONCEPTS.issuer_pool.Parse()
request = messages.PrivatecaProjectsLocationsCaPoolsCertificatesListRequest(
parent=parent.RelativeName(),
orderBy=common_args.ParseSortByArg(args.sort_by),
filter=server_filter)
return list_pager.YieldFromList(
client.projects_locations_caPools_certificates,
request,
field='certificates',
limit=args.limit,
batch_size_attribute='pageSize',
batch_size=args.page_size,
get_field_func=response_utils.GetFieldAndLogUnreachable)

View File

@@ -0,0 +1,184 @@
# -*- 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.
"""Revoke a certificate."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.privateca import base as privateca_base
from googlecloudsdk.api_lib.privateca import certificate_utils
from googlecloudsdk.api_lib.privateca import request_utils
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import times
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Revoke(base.SilentCommand):
r"""Revoke a certificate.
Revokes the given certificate for the given reason.
## EXAMPLES
To revoke the 'frontend-server-tls' certificate due to key compromise:
$ {command} \
--certificate=frontend-server-tls \
--issuer-pool=my-pool --issuer-location=us-west1 \
--reason=key_compromise
To revoke the a certificate with the serial number
'7dc1d9186372de2e1f4824abb1c4c9e5e43cbb40' due to a newer one being issued:
$ {command} \
--serial-number=7dc1d9186372de2e1f4824abb1c4c9e5e43cbb40 \
--issuer-pool=my-pool --issuer-location=us-west1 \
--reason=superseded
"""
@staticmethod
def Args(parser):
id_group = parser.add_group(
mutex=True, required=True, help='The certificate identifier.'
)
base.Argument(
'--serial-number', help='The serial number of the certificate.'
).AddToParser(id_group)
concept_parsers.ConceptParser([
presentation_specs.ResourcePresentationSpec(
'--certificate',
resource_args.CreateCertResourceSpec('certificate'),
'The certificate to revoke.',
flag_name_overrides={
'issuer-pool': '',
'issuer-location': '',
'project': '',
},
group=id_group,
),
presentation_specs.ResourcePresentationSpec(
'--issuer-pool',
resource_args.CreateCaPoolResourceSpec(
'Issuing CA pool', 'issuer-location'
),
'The issuing CA pool of the certificate to revoke.',
required=False,
),
]).AddToParser(parser)
flags.AddRevocationReasonFlag(parser)
@staticmethod
def ParseCertificateResource(args):
"""Gets the certificate resource to be revoked based on the specified args."""
# Option 1: user specified full resource name for the certificate.
cert_ref = args.CONCEPTS.certificate.Parse()
if cert_ref:
return cert_ref
if not args.IsSpecified('issuer_pool'):
raise exceptions.RequiredArgumentException(
'--issuer-pool',
(
'The issuing CA pool is required if a full resource name is not'
' provided for --certificate.'
),
)
issuer_ref = args.CONCEPTS.issuer_pool.Parse()
if not issuer_ref:
raise exceptions.RequiredArgumentException(
'--issuer-pool',
(
'The issuer flag is not fully specified. Please add the'
" --issuer-location flag or specify the issuer's full resource"
' name.'
),
)
cert_collection_name = 'privateca.projects.locations.caPools.certificates'
# Option 2: user specified certificate ID + issuer.
if args.IsSpecified('certificate'):
return resources.REGISTRY.Parse(
args.certificate,
collection=cert_collection_name,
params={
'projectsId': issuer_ref.projectsId,
'locationsId': issuer_ref.locationsId,
'caPoolsId': issuer_ref.caPoolsId,
},
)
# Option 3: user specified serial number + issuer.
if args.IsSpecified('serial_number'):
certificate = certificate_utils.GetCertificateBySerialNum(
issuer_ref, args.serial_number
)
return resources.REGISTRY.Parse(
certificate.name, collection=cert_collection_name
)
raise exceptions.OneOfArgumentsRequiredException(
['--certificate', '--serial-number'],
(
'To revoke a Certificate, please provide either its resource ID or '
'serial number.'
),
)
def Run(self, args):
cert_ref = Revoke.ParseCertificateResource(args)
if not console_io.PromptContinue(
message='You are about to revoke Certificate [{}]'.format(
cert_ref.RelativeName()
),
default=True,
):
log.status.Print('Aborted by user.')
return
reason = flags.ParseRevocationChoiceToEnum(args.reason)
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
certificate = client.projects_locations_caPools_certificates.Revoke(
messages.PrivatecaProjectsLocationsCaPoolsCertificatesRevokeRequest(
name=cert_ref.RelativeName(),
revokeCertificateRequest=messages.RevokeCertificateRequest(
reason=reason, requestId=request_utils.GenerateRequestId()
),
)
)
revoke_time = times.ParseDateTime(
certificate.revocationDetails.revocationTime
)
log.status.Print(
'Revoked certificate [{}] at {}.'.format(
certificate.name,
times.FormatDateTime(revoke_time, tzinfo=times.LOCAL),
)
)

View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Update an existing certificate."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.privateca import base as privateca_base
from googlecloudsdk.api_lib.privateca import request_utils
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.util.args import labels_util
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Update(base.UpdateCommand):
r"""Update an existing certificate.
## EXAMPLES
To update labels on a certificate:
$ {command} frontend-server-tls \
--issuer-pool=my-pool --issuer-location=us-west1 \
--update-labels=in_use=true
"""
NO_CHANGES_MESSAGE = (
'There are no changes to the certificate [{certificate}].')
@staticmethod
def Args(parser):
resource_args.AddCertPositionalResourceArg(parser, 'to update')
labels_util.AddUpdateLabelsFlags(parser)
def _RunUpdate(self, client, messages, original_cert, args):
# Collect the list of update masks
labels_diff = labels_util.GetAndValidateOpsFromArgs(args)
labels_update = labels_diff.Apply(messages.Certificate.LabelsValue,
original_cert.labels)
if not labels_update.needs_update:
raise exceptions.InvalidArgumentException(
'labels',
self.NO_CHANGES_MESSAGE.format(certificate=original_cert.name))
original_cert.labels = labels_update.labels
return client.projects_locations_caPools_certificates.Patch(
messages.
PrivatecaProjectsLocationsCaPoolsCertificatesPatchRequest(
name=original_cert.name,
certificate=original_cert,
updateMask='labels',
requestId=request_utils.GenerateRequestId()))
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
certificate_ref = args.CONCEPTS.certificate.Parse()
# Attempt to get the certificate
certificate = client.projects_locations_caPools_certificates.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificatesGetRequest(
name=certificate_ref.RelativeName()))
# The certificate exists, update it
return self._RunUpdate(client, messages, certificate, args)