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 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 all of the Private CA 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.DefaultUniverseOnly
class PrivateCaGa(base.Group):
"""Manage private Certificate Authorities on Google Cloud.
The privateca command group lets you create and manage private certificate
authorities and certificates.
"""
category = base.IDENTITY_AND_SECURITY_CATEGORY
def Filter(self, context, args):
del context, args
base.DisableUserProjectQuota()

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)

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 locations."""
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 Locations(base.Group):
"""Manage locations."""

View File

@@ -0,0 +1,54 @@
# -*- 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.
"""List supported locations for the Private CA API."""
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 locations
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List supported locations for the Private CA GA API."""
detailed_help = {
'DESCRIPTION':
"""\
Returns supported locations where resources can be managed through
the Private CA GA API.""",
'EXAMPLES':
"""\
To list the locations available for the Private CA GA API:
$ {command}
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat('table(locationId:label=LOCATION_ID)')
def Run(self, args):
"""Runs the command."""
messages = privateca_base.GetMessagesModule('v1')
return [
messages.Location(locationId=location)
for location in locations.GetSupportedLocations('v1')
]

View File

@@ -0,0 +1,28 @@
# -*- 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.
"""The command group for operations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Hidden
class Operations(base.Group):
"""Manage operations."""

View File

@@ -0,0 +1,70 @@
# -*- 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 an operation for the Private CA API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.privateca import operations
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope.concepts import concepts
from googlecloudsdk.command_lib.util.concepts import concept_parsers
def _GetOperationResourceSpec():
return concepts.ResourceSpec(
'privateca.projects.locations.operations',
resource_name='operation',
operationsId=concepts.ResourceParameterAttributeConfig(
name='operation', help_text='The operation to describe.'
),
locationsId=concepts.ResourceParameterAttributeConfig(
name='location',
help_text='The location of the operation to describe.',
),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
)
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Hidden
class Describe(base.DescribeCommand):
"""Describe an operation ran on the Private CA API."""
detailed_help = {
'DESCRIPTION': """\
Get details about a Long Running Operation.
""",
'EXAMPLES': """\
To describe an operation:
$ {command} operation-12345 --location=us-west1
""",
}
@staticmethod
def Args(parser):
concept_parsers.ConceptParser.ForResource(
'operation',
_GetOperationResourceSpec(),
'The operation to describe.',
required=True,
).AddToParser(parser)
def Run(self, args):
operation_ref = args.CONCEPTS.operation.Parse()
return operations.GetOperation(operation_ref)

View File

@@ -0,0 +1,106 @@
# -*- 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 operations for the Private CA API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.privateca import operations
from googlecloudsdk.api_lib.privateca import resource_utils
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope.concepts import concepts
from googlecloudsdk.command_lib.util.concepts import concept_parsers
def _GetOperationStatus(op):
if op.get('error'):
return 'FAILURE'
if op.get('done'):
return 'SUCCESS'
return 'RUNNING'
def _GetLocationResourceSpec():
return concepts.ResourceSpec(
'privateca.projects.locations',
resource_name='location',
locationsId=concepts.ResourceParameterAttributeConfig(
name='location',
help_text='The location to list operations in.',
),
projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG,
)
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Hidden
class List(base.ListCommand):
"""List operations for the Private CA API."""
detailed_help = {
'DESCRIPTION': """\
Returns completed, failed, and pending operations on the Private CA
API in a given location or across all locations.""",
'EXAMPLES': """\
To list all operations:
$ {command}
To list all operations in a given location:
$ {command} --location=us-west1
To filter for a specific end time in a given location:
$ {command} --location=us-west1 --filter="metadata.endTime>=2025-09-25T16:00:00Z"
""",
}
@staticmethod
def Args(parser):
concept_parsers.ConceptParser.ForResource(
'--location',
_GetLocationResourceSpec(),
'The location to list operations in. If not specified, operations '
'from all locations will be listed.',
required=False,
).AddToParser(parser)
parser.display_info.AddFormat(
'table(name.segment(-1):label=ID, name.segment(-3):label=LOCATION,'
' metadata.createTime:label=START_TIME, status():label=STATUS)'
)
parser.display_info.AddTransforms({'status': _GetOperationStatus})
parser.display_info.AddUriFunc(
resource_utils.MakeGetUriFunc(
'privateca.projects.locations.operations'
)
)
base.PAGE_SIZE_FLAG.SetDefault(parser, 100)
def Run(self, args):
"""Runs the command."""
location_id = '-'
if args.IsSpecified('location'):
location_id = args.CONCEPTS.location.Parse().locationsId
# Note: there is no orderBy on the list operations request so args.sort_by
# is handled client-side.
return operations.ListOperations(
location=location_id,
list_filter=args.filter,
limit=args.limit,
page_size=args.page_size)

View File

@@ -0,0 +1,27 @@
# -*- 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 pools."""
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 Pools(base.Group):
"""Manage CA pools.
"""

View File

@@ -0,0 +1,33 @@
- release_tracks: [GA]
help_text:
brief: |
Add IAM policy binding for a CA pool.
description: |
Adds a policy binding to the IAM policy of a CA pool. One binding
consists of a member and a role.
See https://cloud.google.com/iam/docs/managing-policies for details of
the policy file format and contents.
examples: |
To add an IAM policy binding for the role of 'roles/privateca.certificateManager' for the user
'test-user@gmail.com' on the CA pool 'my-pool' with the location 'us-west1', run:
$ {command} my-pool \
--location='us-west1' \
--member='user:test-user@gmail.com' \
--role='roles/privateca.certificateManager'
request:
collection: privateca.projects.locations.caPools
api_version: v1
arguments:
resource:
help_text: The CA pool for which to add the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.privateca.resources:ca_pool
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,93 @@
# -*- 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 new CA pool."""
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.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class Create(base.CreateCommand):
r"""Create a new CA Pool.
## EXAMPLES
To create a CA pool in the dev ops tier:
$ {command} my-pool --location=us-west1 \
--tier=devops
To create a CA pool and restrict what it can issue:
$ {command} my-pool --location=us-west1 \
--issuance-policy=policy.yaml
To create a CA pool that doesn't publicly publish CA certificates and CRLs:
$ {command} my-pool --location=us-west1 \
--issuance-policy=policy.yaml \
--no-publish-ca-cert \
--no-publish-crl
"""
@staticmethod
def Args(parser):
resource_args.AddCaPoolPositionalResourceArg(parser, 'to create')
flags.AddTierFlag(parser)
flags.AddPublishingOptionsFlags(parser, use_update_help_text=False)
flags.AddCaPoolIssuancePolicyFlag(parser)
flags.AddEncryptionKeyFlag(parser)
labels_util.AddCreateLabelsFlags(parser)
def Run(self, args):
client = privateca_base.GetClientInstance('v1')
messages = privateca_base.GetMessagesModule('v1')
ca_pool_ref = args.CONCEPTS.ca_pool.Parse()
issuance_policy = flags.ParseIssuancePolicy(args)
publishing_options = flags.ParsePublishingOptions(args)
tier = flags.ParseTierFlag(args)
labels = labels_util.ParseCreateArgs(args, messages.CaPool.LabelsValue)
encryption_spec = flags.ParseEncryptionSpec(args)
new_ca_pool = messages.CaPool(
issuancePolicy=issuance_policy,
publishingOptions=publishing_options,
tier=tier,
labels=labels,
encryptionSpec=encryption_spec)
operation = client.projects_locations_caPools.Create(
messages.PrivatecaProjectsLocationsCaPoolsCreateRequest(
caPool=new_ca_pool,
caPoolId=ca_pool_ref.Name(),
parent=ca_pool_ref.Parent().RelativeName(),
requestId=request_utils.GenerateRequestId()))
ca_pool_response = operations.Await(
operation, 'Creating CA Pool.', api_version='v1')
ca_pool = operations.GetMessageFromResponse(ca_pool_response,
messages.CaPool)
log.status.Print('Created CA Pool [{}].'.format(ca_pool.name))

View File

@@ -0,0 +1,88 @@
# -*- 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.
"""Delete a CA pool."""
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.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Delete(base.DeleteCommand):
r"""Delete a CA pool.
Note that all certificate authorities must be removed from the CA Pool
before the CA pool can be deleted.
## EXAMPLES
To delete a CA pool:
$ {command} my-pool --location=us-west1
To delete a CA pool while skipping the confirmation input:
$ {command} my-pool --location=us-west1 --quiet
"""
@staticmethod
def Args(parser):
resource_args.AddCaPoolPositionalResourceArg(parser, 'to delete')
flags.AddIgnoreDependentResourcesFlag(parser)
def Run(self, args):
client = privateca_base.GetClientInstance('v1')
messages = privateca_base.GetMessagesModule('v1')
ca_pool_ref = args.CONCEPTS.ca_pool.Parse()
if args.ignore_dependent_resources:
prompt_message = (
'You are about to delete the CA Pool [{}] without '
'checking if it is being used by another cloud '
'resource. If you proceed, there may be unintended and '
'unrecoverable effects on any dependent resource(s) since the '
'CA Pool would not be able to issue certificates.'
).format(ca_pool_ref.RelativeName())
else:
prompt_message = ('You are about to delete the CA pool [{}]').format(
ca_pool_ref.RelativeName())
if not console_io.PromptContinue(
message=prompt_message,
default=True):
log.status.Print('Aborted by user.')
return
operation = client.projects_locations_caPools.Delete(
messages.PrivatecaProjectsLocationsCaPoolsDeleteRequest(
name=ca_pool_ref.RelativeName(),
ignoreDependentResources=args.ignore_dependent_resources,
requestId=request_utils.GenerateRequestId()))
operations.Await(
operation, 'Deleting the CA pool', api_version='v1')
log.status.Print('Deleted the CA pool [{}].'.format(
ca_pool_ref.RelativeName()))

View File

@@ -0,0 +1,20 @@
- release_tracks: [GA]
help_text:
brief: |
Get metadata for a CA pool.
description: |
Returns metadata for the given CA pool.
examples: |
To get metadata for the CA pool 'my-pool' in location 'us-west1':
$ {command} my-pool \
--location=us-west1
request:
collection: privateca.projects.locations.caPools
api_version: v1
arguments:
resource:
help_text: The CA pool for which to obtain metadata.
spec: !REF googlecloudsdk.command_lib.privateca.resources:ca_pool

View File

@@ -0,0 +1,73 @@
# -*- 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.
"""Get the root CA certs for all active CAs in a CA Pool."""
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.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
@base.ReleaseTracks(base.ReleaseTrack.GA)
class GetCaCerts(base.Command):
r"""Get the root CA certs for all active CAs in the CA pool.
## EXAMPLES
To get the root CA certs for all active CAs in the CA pool:
$ {command} my-pool --output-file=ca-certificates.pem \
--location=us-west1
"""
@staticmethod
def Args(parser):
resource_args.AddCaPoolPositionalResourceArg(
parser, 'whose CA certificates should be fetched')
base.Argument(
'--output-file',
help='The path where the concatenated PEM certificates will be '
'written. This will include the root CA certificate for each '
'active CA in the CA pool. ',
required=True).AddToParser(parser)
def _GetRootCerts(self, ca_pool_ref):
"""Returns the root CA certs for all active CAs in the CA pool."""
client = privateca_base.GetClientInstance('v1')
messages = privateca_base.GetMessagesModule('v1')
fetch_ca_certs_response = client.projects_locations_caPools.FetchCaCerts(
messages.PrivatecaProjectsLocationsCaPoolsFetchCaCertsRequest(
caPool=ca_pool_ref.RelativeName(),
fetchCaCertsRequest=messages.FetchCaCertsRequest(
requestId=request_utils.GenerateRequestId())))
root_certs = [
chain.certificates[-1] for chain in fetch_ca_certs_response.caCerts
]
return ''.join(pem_utils.PemChainForOutput(root_certs))
def Run(self, args):
ca_pool_ref = args.CONCEPTS.ca_pool.Parse()
pem_bag = self._GetRootCerts(ca_pool_ref)
files.WriteFileContents(args.output_file, pem_bag)
log.status.write('Exported the CA certificates to [{}].'.format(
args.output_file))

View File

@@ -0,0 +1,27 @@
- release_tracks: [GA]
help_text:
brief: Get the IAM policy for a CA pool.
description: |
Gets the IAM policy for the given CA pool.
Returns an empty policy if the resource does not have a policy
set.
examples: |
To get the IAM policy for the CA pool 'my-pool' with the location 'us-west1', run:
$ {command} my-pool --location=us-west1
request:
collection: privateca.projects.locations.caPools
api_version: v1
arguments:
resource:
help_text: The CA pool for which to display the IAM policy.
spec: !REF googlecloudsdk.command_lib.privateca.resources:ca_pool
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,85 @@
# -*- 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.
"""List CA pools 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.command_lib.privateca import response_utils
from googlecloudsdk.core import properties
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List CA pools within a project.
## EXAMPLES
To list the CA Pools within a project:
$ {command}
To list the CA Pools within a project and region 'us-west1':
$ {command} --location=us-west1
"""
@staticmethod
def Args(parser):
base.Argument(
'--location',
help='Location of the CA pools. If this is not specified, CA pools '
'across all locations will be listed.').AddToParser(parser)
base.PAGE_SIZE_FLAG.SetDefault(parser, 100)
parser.display_info.AddFormat("""
table(
name.basename(),
name.scope().segment(-3):label=LOCATION,
tier)
""")
parser.display_info.AddUriFunc(
resource_utils.MakeGetUriFunc('privateca.projects.locations.caPools'))
def Run(self, args):
client = privateca_base.GetClientInstance('v1')
messages = privateca_base.GetMessagesModule('v1')
location = args.location if args.IsSpecified('location') else '-'
parent_resource = 'projects/{}/locations/{}'.format(
properties.VALUES.core.project.GetOrFail(), location)
request = messages.PrivatecaProjectsLocationsCaPoolsListRequest(
parent=parent_resource,
filter=args.filter,
orderBy=common_args.ParseSortByArg(args.sort_by))
return list_pager.YieldFromList(
client.projects_locations_caPools,
request,
field='caPools',
limit=args.limit,
batch_size_attribute='pageSize',
batch_size=args.page_size,
get_field_func=response_utils.GetFieldAndLogUnreachable)

View File

@@ -0,0 +1,33 @@
- release_tracks: [GA]
help_text:
brief: |
Remove IAM policy binding for a CA pool.
description: |
Removes a policy binding to the IAM policy of a CA pool. One binding
consists of a member and a role.
See https://cloud.google.com/iam/docs/managing-policies for details of
the policy file format and contents.
examples: |
To remove an IAM policy binding for the role of 'roles/privateca.certificateManager' for the user
'test-user@gmail.com' on the CA pool 'my-pool' with the location 'us-west1', run:
$ {command} my-pool \
--location=us-west1 \
--member='user:test-user@gmail.com' \
--role='roles/privateca.certificateManager'
request:
collection: privateca.projects.locations.caPools
api_version: v1
arguments:
resource:
help_text: The CA pool for which to remove the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.privateca.resources:ca_pool
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,28 @@
- release_tracks: [GA]
help_text:
brief: |
Set the IAM policy for a CA pool.
description: |
Sets the IAM policy for the given CA pool 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 CA pool 'my-pool' with the location 'us-west1':
$ {command} my-pool policy.json --location=us-west1
request:
collection: privateca.projects.locations.caPools
api_version: v1
arguments:
resource:
help_text: The CA pool for which to update the IAM policy.
spec: !REF googlecloudsdk.command_lib.privateca.resources:ca_pool
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,79 @@
# -*- 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 CA pool."""
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.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.privateca import update_utils
from googlecloudsdk.command_lib.util.args import labels_util
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class Update(base.UpdateCommand):
r"""Update an existing CA Pool.
## EXAMPLES
To update labels on a CA pool:
$ {command} my-pool \
--location=us-west1 \
--update-labels=foo=bar
To disable publishing CRLs on a CA pool:
$ {command} my-pool \
--location=us-west1 \
--no-publish-crl
"""
@staticmethod
def Args(parser):
resource_args.AddCaPoolPositionalResourceArg(
parser, 'to update')
flags.AddPublishingOptionsFlags(parser, use_update_help_text=True)
flags.AddCaPoolIssuancePolicyFlag(parser)
flags.AddEncryptionKeyFlag(parser)
labels_util.AddUpdateLabelsFlags(parser)
def Run(self, args):
client = privateca_base.GetClientInstance('v1')
messages = privateca_base.GetMessagesModule('v1')
ca_pool_ref = args.CONCEPTS.ca_pool.Parse()
current_ca_pool = client.projects_locations_caPools.Get(
messages.PrivatecaProjectsLocationsCaPoolsGetRequest(
name=ca_pool_ref.RelativeName()))
pool_to_update, update_mask = update_utils.UpdateCaPoolFromArgs(
args, current_ca_pool.labels)
operation = client.projects_locations_caPools.Patch(
messages.PrivatecaProjectsLocationsCaPoolsPatchRequest(
name=ca_pool_ref.RelativeName(),
caPool=pool_to_update,
updateMask=','.join(update_mask),
requestId=request_utils.GenerateRequestId()))
return operations.Await(operation, 'Updating CA pool.', api_version='v1')

View File

@@ -0,0 +1,27 @@
# -*- 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 roots."""
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 Roots(base.Group):
"""Manage root certificate authorities.
"""

View File

@@ -0,0 +1,205 @@
# -*- 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 new root certificate authority."""
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.concepts import deps
from googlecloudsdk.command_lib.privateca import create_utils
from googlecloudsdk.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import iam
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import p4sa
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.privateca import storage
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.console import console_io
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Create(base.CreateCommand):
# pylint: disable=line-too-long
r"""Create a new root certificate authority.
TIP: Consider setting a [project lien](https://cloud.google.com/resource-manager/docs/project-liens) on the project to prevent it from accidental deletion.
## EXAMPLES
To create a root CA that supports one layer of subordinates:
$ {command} prod-root \
--location=us-west1 --pool=my-pool \
--kms-key-version="projects/my-project-pki/locations/us-west1/keyRings/kr1/cryptoKeys/k1/cryptoKeyVersions/1" \
--subject="CN=Example Production Root CA, O=Google" \
--max-chain-length=1
To create a root CA that is based on an existing CA:
$ {command} prod-root \
--location=us-west1 --pool=my-pool \
--kms-key-version="projects/my-project-pki/locations/us-west1/keyRings/kr1/cryptoKeys/k1/cryptoKeyVersions/1" \
--from-ca=source-root
"""
def __init__(self, *args, **kwargs):
super(Create, self).__init__(*args, **kwargs)
self.client = privateca_base.GetClientInstance(api_version='v1')
self.messages = privateca_base.GetMessagesModule(api_version='v1')
@staticmethod
def Args(parser):
key_spec_group = parser.add_group(
mutex=True,
help='The key configuration used for the CA certificate. Defaults to a '
'managed key if not specified.')
x509_config_group = parser.add_group(
mutex=True,
required=False,
help='The X.509 configuration used for the CA certificate.')
concept_parsers.ConceptParser([
presentation_specs.ResourcePresentationSpec(
'CERTIFICATE_AUTHORITY',
resource_args.CreateCertAuthorityResourceSpec(
'Certificate Authority'),
'The name of the root CA to create.',
required=True),
presentation_specs.ResourcePresentationSpec(
'--kms-key-version',
resource_args.CreateKmsKeyVersionResourceSpec(),
'An existing KMS key version to back this CA.',
group=key_spec_group),
presentation_specs.ResourcePresentationSpec(
'--from-ca',
resource_args.CreateCertAuthorityResourceSpec(
'source CA',
location_fallthroughs=[
deps.ArgFallthrough('--location'),
resource_args.LOCATION_PROPERTY_FALLTHROUGH
],
pool_id_fallthroughs=[deps.ArgFallthrough('--pool')]),
'An existing CA from which to copy configuration values for the new CA. '
'You can still override any of those values by explicitly providing '
'the appropriate flags. The specified existing CA must be part of '
'the same pool as the one being created.',
flag_name_overrides={
'project': '',
'location': '',
'pool': '',
},
prefixes=True)
]).AddToParser(parser)
flags.AddSubjectFlags(parser, subject_required=False)
flags.AddKeyAlgorithmFlag(
key_spec_group, default='rsa-pkcs1-4096-sha256')
flags.AddValidityFlag(
parser,
resource_name='CA',
default_value='P10Y',
default_value_text='10 years')
labels_util.AddCreateLabelsFlags(parser)
flags.AddBucketFlag(parser)
flags.AddUsePresetProfilesFlag(x509_config_group)
# If max_chain_len is unspecified, no max length will be provided to the
# server on create, this allowing any number of subordinates.
flags.AddInlineX509ParametersFlags(
x509_config_group, is_ca_command=True, default_max_chain_length=None)
flags.AddAutoEnableFlag(parser)
flags.AddSubjectKeyIdFlag(parser)
flags.AddUserDefinedAccessUrlsFlags(parser)
def _EnableCertificateAuthority(self, ca_name):
"""Enables the given CA."""
enable_request = self.messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesEnableRequest(
name=ca_name,
enableCertificateAuthorityRequest=self.messages
.EnableCertificateAuthorityRequest(
requestId=request_utils.GenerateRequestId()))
operation = self.client.projects_locations_caPools_certificateAuthorities.Enable(
enable_request)
return operations.Await(operation, 'Enabling CA.', api_version='v1')
def _ShouldEnableCa(self, args, ca_ref):
"""Determines whether the CA should be enabled or not."""
if args.auto_enable:
return True
# Return false if there already is an enabled CA in the pool.
ca_pool_name = ca_ref.Parent().RelativeName()
list_response = self.client.projects_locations_caPools_certificateAuthorities.List(
self.messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesListRequest(
parent=ca_pool_name))
if create_utils.HasEnabledCa(
list_response.certificateAuthorities, self.messages):
return False
# Prompt the user if they would like to enable a CA in the pool.
return console_io.PromptContinue(
message='The CaPool [{}] has no enabled CAs and cannot issue any '
'certificates until at least one CA is enabled. Would you like to '
'also enable this CA?'.format(ca_ref.Parent().Name()), default=False)
def Run(self, args):
new_ca, ca_ref, _ = create_utils.CreateCAFromArgs(
args, is_subordinate=False)
pool_ref = ca_ref.Parent()
project_ref = pool_ref.Parent().Parent()
key_version_ref = args.CONCEPTS.kms_key_version.Parse()
kms_key_ref = key_version_ref.Parent() if key_version_ref else None
iam.CheckCreateCertificateAuthorityPermissions(project_ref, kms_key_ref)
bucket_ref = None
if args.IsSpecified('bucket'):
bucket_ref = storage.ValidateBucketForCertificateAuthority(args.bucket)
new_ca.gcsBucket = bucket_ref.bucket
# P4SA is needed only if user specifies any resource.
if bucket_ref or kms_key_ref:
p4sa.AddResourceRoleBindings(
p4sa.GetOrCreate(project_ref), kms_key_ref, bucket_ref)
operation = self.client.projects_locations_caPools_certificateAuthorities.Create(
self.messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesCreateRequest(
certificateAuthority=new_ca,
certificateAuthorityId=ca_ref.Name(),
parent=pool_ref.RelativeName(),
requestId=request_utils.GenerateRequestId()))
ca_response = operations.Await(operation, 'Creating Certificate Authority.', api_version='v1')
ca = operations.GetMessageFromResponse(ca_response,
self.messages.CertificateAuthority)
log.status.Print('Created Certificate Authority [{}].'.format(ca.name))
log.status.Print(
'TIP: To avoid accidental deletion, '
'please consider adding a project lien on this project. To find out '
'more, see the following doc: '
'https://cloud.google.com/resource-manager/docs/project-liens.'
)
if self._ShouldEnableCa(args, ca_ref):
self._EnableCertificateAuthority(ca_ref.RelativeName())

View File

@@ -0,0 +1,150 @@
# -*- 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.
"""Delete a root certificate authority."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from dateutil import tz
from googlecloudsdk.api_lib.privateca import base as privateca_base
from googlecloudsdk.api_lib.privateca import request_utils
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import times
import six
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Delete(base.DeleteCommand):
r"""Delete a Root Certificate Authority.
Delete a Root Certificate Authority. Deleted Root Certificate Authorities
may be recovered with the `{parent_command} undelete` command within a grace
period of 30 days.
Use the --skip-grace-period flag to delete as soon as possible without the
30-day grace period to undelete.
Note that any user-managed KMS keys or Google Cloud Storage buckets
will not be affected by this operation. You will need to delete the user-
managed resources separately once the CA is deleted. Any Google-managed
resources will be cleaned up.
The CA specified in this command MUST:
1) be in the DISABLED or STAGED state.
2) have no un-revoked or un-expired certificates. Use the revoke command
to revoke any active certificates.
Use the `--ignore-active-certificates` flag to remove 2) as a requirement.
## EXAMPLES
To delete a root CA:
$ {command} prod-root --pool=my-pool --location=us-west1
To delete a CA while skipping the confirmation
input:
$ {command} prod-root --pool=my-pool --location=us-west1 --quiet
To undo the deletion for a root CA:
$ {parent_command} undelete prod-root --pool=my-pool --location=us-west1
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to delete')
flags.AddIgnoreActiveCertificatesFlag(parser)
flags.AddSkipGracePeriodFlag(parser)
flags.AddIgnoreDependentResourcesFlag(parser)
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
prompt_message = (
'You are about to delete Certificate Authority [{}].').format(
ca_ref.RelativeName())
if args.ignore_dependent_resources:
prompt_message += (
'\nThis deletion will happen without '
'checking if the CA\'s CA Pool is being used by another '
'resource, which may cause unintended and effects on any '
'dependent resource(s) since the CA Pool would not be '
'able to issue certificates.')
if args.skip_grace_period:
prompt_message += (
'\nThis deletion will happen as '
'soon as possible without a 30-day grace period where '
'undeletion would have been allowed. If you proceed, there '
'will be no way to recover this CA.')
if not console_io.PromptContinue(message=prompt_message, default=True):
log.status.Print('Aborted by user.')
return
current_ca = client.projects_locations_caPools_certificateAuthorities.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesGetRequest(
name=ca_ref.RelativeName()))
resource_args.CheckExpectedCAType(
messages.CertificateAuthority.TypeValueValuesEnum.SELF_SIGNED,
current_ca,
version='v1')
operation = client.projects_locations_caPools_certificateAuthorities.Delete(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesDeleteRequest(
name=ca_ref.RelativeName(),
ignoreActiveCertificates=args.ignore_active_certificates,
skipGracePeriod=args.skip_grace_period,
ignoreDependentResources=args.ignore_dependent_resources,
requestId=request_utils.GenerateRequestId()))
try:
ca_response = operations.Await(
operation, 'Deleting Root CA', api_version='v1')
except waiter.OperationError as e:
# API error message refers to the proto field name which is slightly
# different from the gcloud flag name.
raise operations.OperationError(
six.text_type(e).replace('`ignore_active_certificates` parameter',
'`--ignore-active-certificates` flag'))
ca = operations.GetMessageFromResponse(ca_response,
messages.CertificateAuthority)
formatted_expire_time = times.ParseDateTime(ca.expireTime).astimezone(
tz.tzutc()).strftime('%Y-%m-%dT%H:%MZ')
if args.skip_grace_period:
log.status.Print('Deleted Root CA [{}]. CA can not be undeleted.'.format(
ca_ref.RelativeName()))
else:
log.status.Print(
'Deleted Root CA [{}]. CA can be undeleted until {}.'.format(
ca_ref.RelativeName(), formatted_expire_time))

View File

@@ -0,0 +1,32 @@
- release_tracks: [GA]
help_text:
brief: |
Get metadata for a root Certificate Authority.
description: |
Returns metadata for the given Certificate Authority.
examples: |
To get metadata for the root CA 'prod-root' in location 'us-west1' and CA Pool 'my-pool':
$ {command} server-tls-1 \
--location=us-west1 --pool=my-pool
To download the PEM-encoded CA certificate chain for the 'prod-root'
CA in location 'us-west1' and CA Pool 'my-pool' to a file
called 'prod-root.crt':
$ {command} prod-root \
--location=us-west1 --pool=my-pool \
--format="value(pemCaCertificates)" > ./prod-root.crt
request:
collection: privateca.projects.locations.caPools.certificateAuthorities
api_version: v1
arguments:
resource:
help_text: The certificate authority for which to obtain metadata.
spec: !REF googlecloudsdk.command_lib.privateca.resources:cert_authority
response:
modify_response_hooks:
- googlecloudsdk.command_lib.privateca.hooks:CheckResponseRootTypeHook:version=v1

View File

@@ -0,0 +1,92 @@
# -*- 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.
"""Disable a root certificate authority."""
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.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Disable(base.SilentCommand):
r"""Disable a root certificate authority.
Disables a root certificate authority. The root certificate authority
will not be allowed to issue certificates once disabled. It may still revoke
certificates and/or generate CRLs. The CA certfificate will still be
included in the FetchCaCertificates response for the parent CA Pool.
## EXAMPLES
To disable a root CA:
$ {command} prod-root --pool=prod-root-pool --location=us-west1
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to disable')
flags.AddIgnoreDependentResourcesFlag(parser)
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
if args.ignore_dependent_resources:
prompt_message = (
'You are about to disable Certificate Authority [{}] without '
'checking if the CA\'s CA Pool is being used by another '
'resource. If you proceed and this is the last enabled CA in '
'the CA Pool, there may be unintended and '
'unrecoverable effects on any dependent resource(s) since the '
'CA Pool would not be able to issue certificates'
).format(ca_ref.RelativeName())
if not console_io.PromptContinue(message=prompt_message, default=True):
log.status.Print('Aborted by user.')
return
current_ca = client.projects_locations_caPools_certificateAuthorities.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesGetRequest(
name=ca_ref.RelativeName()))
resource_args.CheckExpectedCAType(
messages.CertificateAuthority.TypeValueValuesEnum.SELF_SIGNED,
current_ca,
version='v1')
operation = client.projects_locations_caPools_certificateAuthorities.Disable(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesDisableRequest(
name=ca_ref.RelativeName(),
disableCertificateAuthorityRequest=messages
.DisableCertificateAuthorityRequest(
ignoreDependentResources=args.ignore_dependent_resources,
requestId=request_utils.GenerateRequestId())))
operations.Await(operation, 'Disabling Root CA', api_version='v1')
log.status.Print('Disabled Root CA [{}].'.format(ca_ref.RelativeName()))

View File

@@ -0,0 +1,73 @@
# -*- 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.
"""Enable a root certificate authority."""
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.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Enable(base.SilentCommand):
r"""Enable a root certificate authority.
Enables a root certificate authority. The root certificate authority will be
allowed to issue certificates once enabled.
## EXAMPLES
To enable a root CA:
$ {command} prod-root --location=us-west1 --pool=my-pool
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to enable')
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
current_ca = client.projects_locations_caPools_certificateAuthorities.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesGetRequest(
name=ca_ref.RelativeName()))
resource_args.CheckExpectedCAType(
messages.CertificateAuthority.TypeValueValuesEnum.SELF_SIGNED,
current_ca,
version='v1')
operation = client.projects_locations_caPools_certificateAuthorities.Enable(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesEnableRequest(
name=ca_ref.RelativeName(),
enableCertificateAuthorityRequest=messages
.EnableCertificateAuthorityRequest(
requestId=request_utils.GenerateRequestId())))
operations.Await(operation, 'Enabling Root CA', api_version='v1')
log.status.Print('Enabled Root CA [{}].'.format(ca_ref.RelativeName()))

View File

@@ -0,0 +1,115 @@
# -*- 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.
"""List the root certificate authorities 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 import exceptions
from googlecloudsdk.command_lib.privateca import response_utils
from googlecloudsdk.command_lib.privateca import text_utils
from googlecloudsdk.core import properties
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List root certificate authorities.
List the root certificate authorities within a project.
## EXAMPLES
To list all root certificate authorities in a projects:
$ {command}
To list all root certificate authorities within a project and location
'us-central1':
$ {command} --location=us-central1
To list all root certificate authorities within a CA Pool in location
'us-central1':
$ {command} --pool=my-pool --location=us-central1
"""
@staticmethod
def Args(parser):
base.Argument(
'--location',
help='Location of the certificate authorities. If ommitted, root CAs across all regions will be listed.'
).AddToParser(parser)
base.Argument(
'--pool',
help='ID of the CA Pool where the certificate authorities reside. If ommitted, root CAs across all CA pools will be listed.'
).AddToParser(parser)
base.PAGE_SIZE_FLAG.SetDefault(parser, 100)
base.FILTER_FLAG.RemoveFromParser(parser)
parser.display_info.AddFormat("""
table(
name.basename(),
name.scope().segment(-5):label=LOCATION,
name.scope().segment(-3):label=POOL,
state,
state.regex("ENABLED","YES","NO"):label=INCLUDED_IN_POOL_ISSUANCE,
ca_certificate_descriptions[0].subject_description.not_before_time():label=NOT_BEFORE,
ca_certificate_descriptions[0].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.certificateAuthorities'))
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
location = args.location if args.IsSpecified('location') else '-'
ca_pool_id = args.pool if args.IsSpecified('pool') else '-'
if location == '-' and ca_pool_id != '-':
raise exceptions.InvalidArgumentException(
'--location',
'If a pool id is specified, you must also specify the location of that pool.'
)
parent_resource = 'projects/{}/locations/{}/caPools/{}'.format(
properties.VALUES.core.project.GetOrFail(), location, ca_pool_id)
request = messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesListRequest(
parent=parent_resource,
filter='type:SELF_SIGNED',
orderBy=common_args.ParseSortByArg(args.sort_by))
return list_pager.YieldFromList(
client.projects_locations_caPools_certificateAuthorities,
request,
field='certificateAuthorities',
limit=args.limit,
batch_size_attribute='pageSize',
batch_size=args.page_size,
get_field_func=response_utils.GetFieldAndLogUnreachable)

View File

@@ -0,0 +1,74 @@
# -*- 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.
"""Undelete a root Certificate Authority."""
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.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Undelete(base.SilentCommand):
r"""Undelete a root Certificate Authority.
Restores a root Certificate Authority that has been deleted. A Certificate
Authority can be undeleted within 30 days of being deleted. Use this command
to halt the deletion process. An undeleted CA will move to DISABLED state.
## EXAMPLES
To undelete a root CA:
$ {command} prod-root --location=us-west1 --pool=my-pool
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to undelete')
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
current_ca = client.projects_locations_caPools_certificateAuthorities.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesGetRequest(
name=ca_ref.RelativeName()))
resource_args.CheckExpectedCAType(
messages.CertificateAuthority.TypeValueValuesEnum.SELF_SIGNED,
current_ca,
version='v1')
operation = client.projects_locations_caPools_certificateAuthorities.Undelete(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesUndeleteRequest(
name=ca_ref.RelativeName(),
undeleteCertificateAuthorityRequest=messages
.UndeleteCertificateAuthorityRequest(
requestId=request_utils.GenerateRequestId())))
operations.Await(operation, 'Undeleting Root CA', api_version='v1')
log.status.Print('Undeleted Root CA [{}].'.format(ca_ref.RelativeName()))

View File

@@ -0,0 +1,77 @@
# -*- 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 root certificate authority."""
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.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.privateca import update_utils
from googlecloudsdk.command_lib.util.args import labels_util
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Update(base.UpdateCommand):
r"""Update an existing root certificate authority.
## EXAMPLES
To update labels on a root CA:
$ {command} prod-root \
--location=us-west1 \
--pool=my-pool \
--update-labels=foo=bar
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to update')
labels_util.AddUpdateLabelsFlags(parser)
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
ca_name = ca_ref.RelativeName()
current_ca = client.projects_locations_caPools_certificateAuthorities.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesGetRequest(
name=ca_name))
resource_args.CheckExpectedCAType(
messages.CertificateAuthority.TypeValueValuesEnum.SELF_SIGNED,
current_ca,
version='v1')
ca_to_update, update_mask = update_utils.UpdateCAFromArgs(
args, current_ca.labels)
# Patch is the REST client method associated with updating a CA.
operation = client.projects_locations_caPools_certificateAuthorities.Patch(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesPatchRequest(
name=ca_name,
certificateAuthority=ca_to_update,
updateMask=','.join(update_mask),
requestId=request_utils.GenerateRequestId()))
return operations.Await(operation, 'Updating Root CA.', api_version='v1')

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 subordinates."""
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 Subordinates(base.Group):
"""Manage subordinate certificate authorities."""

View File

@@ -0,0 +1,170 @@
# -*- 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.
"""Activate a pending certificate authority."""
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 create_utils
from googlecloudsdk.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import pem_utils
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import files
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Activate(base.SilentCommand):
r"""Activate a subordinate certificate authority awaiting user activation.
## EXAMPLES
To activate a subordinate CA named 'server-tls-1' in the location 'us-west1'
and CA Pool 'server-tls-pool' using a PEM certificate chain in 'chain.crt':
$ {command} server-tls-1 \
--location=us-west1 \
--pool=server-tls-pool \
--pem-chain=./chain.crt
"""
def __init__(self, *args, **kwargs):
super(Activate, self).__init__(*args, **kwargs)
self.client = privateca_base.GetClientInstance(api_version='v1')
self.messages = privateca_base.GetMessagesModule(api_version='v1')
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to activate')
base.Argument(
'--pem-chain',
required=True,
help=(
'A file containing a list of PEM-encoded certificates, starting '
'with the current CA certificate and ending with the root CA '
'certificate.'
),
).AddToParser(parser)
flags.AddAutoEnableFlag(parser)
def _ParsePemChainFromFile(self, pem_chain_file):
"""Parses a pem chain from a file, splitting the leaf cert and chain.
Args:
pem_chain_file: file containing the pem_chain.
Raises:
exceptions.InvalidArgumentException if not enough certificates are
included.
Returns:
A tuple with (leaf_cert, rest_of_chain)
"""
try:
pem_chain_input = files.ReadFileContents(pem_chain_file)
except (files.Error, OSError, IOError):
raise exceptions.BadFileException(
"Could not read provided PEM chain file '{}'.".format(pem_chain_file)
)
certs = pem_utils.ValidateAndParsePemChain(pem_chain_input)
if len(certs) < 2:
raise exceptions.InvalidArgumentException(
'pem-chain',
'The pem_chain must include at least two certificates - the'
' subordinate CA certificate and an issuer certificate.',
)
return certs[0], certs[1:]
def _EnableCertificateAuthority(self, ca_name):
"""Enables the given CA."""
enable_request = self.messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesEnableRequest(
name=ca_name,
enableCertificateAuthorityRequest=self.messages.EnableCertificateAuthorityRequest(
requestId=request_utils.GenerateRequestId()
),
)
operation = (
self.client.projects_locations_caPools_certificateAuthorities.Enable(
enable_request
)
)
return operations.Await(operation, 'Enabling CA.', api_version='v1')
def _ShouldEnableCa(self, args, ca_ref):
"""Determines whether the CA should be enabled or not."""
if args.auto_enable:
return True
# Return false if there already is an enabled CA in the pool.
ca_pool_name = ca_ref.Parent().RelativeName()
list_response = self.client.projects_locations_caPools_certificateAuthorities.List(
self.messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesListRequest(
parent=ca_pool_name
)
)
if create_utils.HasEnabledCa(
list_response.certificateAuthorities, self.messages
):
return False
# Prompt the user if they would like to enable a CA in the pool.
return console_io.PromptContinue(
message=(
'The CaPool [{}] has no enabled CAs and cannot issue any '
'certificates until at least one CA is enabled. Would you like to '
'also enable this CA?'.format(ca_ref.Parent().Name())
),
default=False,
)
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
pem_cert, pem_chain = self._ParsePemChainFromFile(args.pem_chain)
operation = client.projects_locations_caPools_certificateAuthorities.Activate(
messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesActivateRequest(
name=ca_ref.RelativeName(),
activateCertificateAuthorityRequest=messages.ActivateCertificateAuthorityRequest(
pemCaCertificate=pem_cert,
subordinateConfig=messages.SubordinateConfig(
pemIssuerChain=messages.SubordinateConfigChain(
pemCertificates=pem_chain
)
),
),
)
)
operations.Await(
operation, 'Activating Certificate Authority.', api_version='v1'
)
log.status.Print(
'Activated Certificate Authority [{}].'.format(ca_ref.Name())
)
if self._ShouldEnableCa(args, ca_ref):
self._EnableCertificateAuthority(ca_ref.RelativeName())

View File

@@ -0,0 +1,358 @@
# -*- 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 new subordinate certificate authority."""
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.calliope.concepts import deps
from googlecloudsdk.command_lib.privateca import create_utils
from googlecloudsdk.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import iam
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import p4sa
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.privateca import storage
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.console import console_io
from googlecloudsdk.core.util import files
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Create(base.CreateCommand):
r"""Create a new subordinate certificate authority.
## EXAMPLES
To create a subordinate CA named 'server-tls-1' whose issuer is on Private CA:
$ {command} server-tls-1 \
--location=us-west1 --pool=my-pool \
--subject="CN=Example TLS CA, O=Google" \
--issuer-pool=other-pool --issuer-location=us-west1 \
--kms-key-version="projects/my-project-pki/locations/us-west1/keyRings/kr1/cryptoKeys/key2/cryptoKeyVersions/1"
To create a subordinate CA named 'server-tls-1' whose issuer is located
elsewhere:
$ {command} server-tls-1 \
--location=us-west1 --pool=my-pool \
--subject="CN=Example TLS CA, O=Google" \
--create-csr \
--csr-output-file=./csr.pem \
--kms-key-version="projects/my-project-pki/locations/us-west1/keyRings/kr1/cryptoKeys/key2/cryptoKeyVersions/1"
To create a subordinate CA named 'server-tls-1' chaining up to a root CA
named 'prod-root' based on an existing CA:
$ {command} server-tls-1 \
--location=us-west1 --pool=my-pool \
--issuer-pool=other-pool --issuer-location=us-west1 \
--from-ca=source-ca \
--kms-key-version="projects/my-project-pki/locations/us-west1/keyRings/kr1/cryptoKeys/key2/cryptoKeyVersions/1"
"""
def __init__(self, *args, **kwargs):
super(Create, self).__init__(*args, **kwargs)
self.client = privateca_base.GetClientInstance(api_version='v1')
self.messages = privateca_base.GetMessagesModule(api_version='v1')
@staticmethod
def Args(parser):
key_spec_group = parser.add_group(
mutex=True,
help=(
'The key configuration used for the CA certificate. Defaults to a '
'managed key if not specified.'
),
)
x509_config_group = parser.add_group(
mutex=True,
required=False,
help='The X.509 configuration used for the CA certificate.',
)
issuer_configuration_group = parser.add_group(
mutex=True,
required=True,
help='The issuer configuration used for this CA certificate.',
)
issuing_resource_group = issuer_configuration_group.add_group(
mutex=False,
required=False,
help='The issuing resource used for this CA certificate.',
)
base.Argument(
'--issuer-ca',
help=(
'The Certificate Authority ID of the CA to issue the subordinate '
'CA certificate from. This ID is optional. If ommitted, '
'any available ENABLED CA in the issuing CA pool will be chosen.'
),
required=False,
).AddToParser(parser)
concept_parsers.ConceptParser([
presentation_specs.ResourcePresentationSpec(
'CERTIFICATE_AUTHORITY',
resource_args.CreateCertAuthorityResourceSpec(
'Certificate Authority'
),
'The name of the subordinate CA to create.',
required=True,
),
presentation_specs.ResourcePresentationSpec(
'--issuer-pool',
resource_args.CreateCaPoolResourceSpec('Issuer'),
'The issuing CA Pool to use, if it is on Private CA.',
prefixes=True,
required=False,
flag_name_overrides={
'location': '--issuer-location',
},
group=issuing_resource_group,
),
presentation_specs.ResourcePresentationSpec(
'--kms-key-version',
resource_args.CreateKmsKeyVersionResourceSpec(),
'The KMS key version backing this CA.',
group=key_spec_group,
),
presentation_specs.ResourcePresentationSpec(
'--from-ca',
resource_args.CreateCertAuthorityResourceSpec(
'source CA',
location_fallthroughs=[
deps.ArgFallthrough('--location'),
resource_args.LOCATION_PROPERTY_FALLTHROUGH,
],
pool_id_fallthroughs=[deps.ArgFallthrough('--pool')],
),
'An existing CA from which to copy configuration values for the '
'new CA. You can still override any of those values by explicitly '
'providing the appropriate flags. The specified existing CA must '
'be part of the same pool as the one being created.',
flag_name_overrides={
'project': '',
'location': '',
'pool': '',
},
prefixes=True,
),
]).AddToParser(parser)
flags.AddSubjectFlags(parser, subject_required=False)
flags.AddKeyAlgorithmFlag(key_spec_group, default='rsa-pkcs1-2048-sha256')
flags.AddUsePresetProfilesFlag(x509_config_group)
# Subordinates should have no children by default.
flags.AddInlineX509ParametersFlags(
x509_config_group, is_ca_command=True, default_max_chain_length=0
)
flags.AddValidityFlag(
parser,
resource_name='CA',
default_value='P3Y',
default_value_text='3 years',
)
labels_util.AddCreateLabelsFlags(parser)
flags.AddBucketFlag(parser)
flags.AddSubjectKeyIdFlag(parser)
offline_issuer_group = issuer_configuration_group.add_group(
help=(
'If the issuing CA is not hosted on Private CA, you must provide '
'these settings:'
)
)
base.Argument(
'--create-csr',
help=(
'Indicates that a CSR should be generated which can be signed by '
'the issuing CA. This must be set if --issuer is not provided.'
),
action='store_const',
const=True,
default=False,
required=True,
).AddToParser(offline_issuer_group)
base.Argument(
'--csr-output-file',
help=(
'The path where the resulting PEM-encoded CSR file should be '
'written.'
),
required=True,
).AddToParser(offline_issuer_group)
flags.AddAutoEnableFlag(parser)
flags.AddUserDefinedAccessUrlsFlags(parser)
def _EnableCertificateAuthority(self, ca_name):
"""Enable the given CA."""
enable_request = self.messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesEnableRequest(
name=ca_name,
enableCertificateAuthorityRequest=self.messages.EnableCertificateAuthorityRequest(
requestId=request_utils.GenerateRequestId()
),
)
operation = (
self.client.projects_locations_caPools_certificateAuthorities.Enable(
enable_request
)
)
return operations.Await(operation, 'Enabling CA.', api_version='v1')
def _ShouldEnableCa(self, args, ca_ref):
"""Determines whether the CA should be enabled or not."""
if args.auto_enable:
return True
# Return false if there already is an enabled CA in the pool.
ca_pool_name = ca_ref.Parent().RelativeName()
list_response = self.client.projects_locations_caPools_certificateAuthorities.List(
self.messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesListRequest(
parent=ca_pool_name
)
)
if create_utils.HasEnabledCa(
list_response.certificateAuthorities, self.messages
):
return False
# Prompt the user if they would like to enable a CA in the pool.
return console_io.PromptContinue(
message=(
'The CaPool [{}] has no enabled CAs and cannot issue any '
'certificates until at least one CA is enabled. Would you like to '
'also enable this CA?'.format(ca_ref.Parent().Name())
),
default=False,
)
def _ActivateCertificateAuthority(self, ca_name, pem_cert, issuer_chain):
"""Activates the given CA using the given certificate and issuing CA chain."""
activate_request = self.messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesActivateRequest(
name=ca_name,
activateCertificateAuthorityRequest=self.messages.ActivateCertificateAuthorityRequest(
pemCaCertificate=pem_cert,
subordinateConfig=self.messages.SubordinateConfig(
pemIssuerChain=self.messages.SubordinateConfigChain(
pemCertificates=issuer_chain
)
),
),
)
operation = (
self.client.projects_locations_caPools_certificateAuthorities.Activate(
activate_request
)
)
return operations.Await(operation, 'Activating CA.', api_version='v1')
def Run(self, args):
new_ca, ca_ref, issuer_ref = create_utils.CreateCAFromArgs(
args, is_subordinate=True
)
# Retrive the Project reference from the Parent -> Location -> Pool -> CA
# resource structure.
project_ref = ca_ref.Parent().Parent().Parent()
key_version_ref = args.CONCEPTS.kms_key_version.Parse()
kms_key_ref = key_version_ref.Parent() if key_version_ref else None
if not args.IsSpecified('issuer_pool') and args.IsSpecified('auto_enable'):
raise exceptions.InvalidArgumentException(
['--auto-enable'],
(
"The '--auto-enable' is only supported in the create command if"
' an issuer resource is specified. You can use the'
" '--auto-enable' command in the subordinate CA activate command."
),
)
if args.issuer_pool == args.pool:
if not console_io.PromptContinue(
message=(
'The new CA will be in the same CA pool as the issuer CA. All'
' certificate authorities within a CA pool should be'
' interchangeable. Do you want to continue?'
),
default=True,
):
log.status.Print('Aborted by user.')
return
iam.CheckCreateCertificateAuthorityPermissions(project_ref, kms_key_ref)
if issuer_ref:
iam.CheckCreateCertificatePermissions(issuer_ref)
# Proactively look for issuing CA Pool problems to avoid downstream
# issues.
issuer_ca = args.issuer_ca if args.IsSpecified('issuer_ca') else None
create_utils.ValidateIssuingPool(issuer_ref.RelativeName(), issuer_ca)
bucket_ref = None
if args.IsSpecified('bucket'):
bucket_ref = storage.ValidateBucketForCertificateAuthority(args.bucket)
new_ca.gcsBucket = bucket_ref.bucket
p4sa_email = p4sa.GetOrCreate(project_ref)
p4sa.AddResourceRoleBindings(p4sa_email, kms_key_ref, bucket_ref)
operations.Await(
self.client.projects_locations_caPools_certificateAuthorities.Create(
self.messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesCreateRequest(
certificateAuthority=new_ca,
certificateAuthorityId=ca_ref.Name(),
parent=ca_ref.Parent().RelativeName(),
requestId=request_utils.GenerateRequestId(),
)
),
'Creating Certificate Authority.',
api_version='v1',
)
csr_response = self.client.projects_locations_caPools_certificateAuthorities.Fetch(
self.messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesFetchRequest(
name=ca_ref.RelativeName()
)
)
csr = csr_response.pemCsr
if args.create_csr:
files.WriteFileContents(args.csr_output_file, csr)
log.status.Print(
"Created Certificate Authority [{}] and saved CSR to '{}'.".format(
ca_ref.RelativeName(), args.csr_output_file
)
)
return
if issuer_ref:
issuer_ca = args.issuer_ca if args.IsSpecified('issuer_ca') else None
ca_certificate = create_utils.SignCsr(issuer_ref, csr, issuer_ca, new_ca)
self._ActivateCertificateAuthority(
ca_ref.RelativeName(),
ca_certificate.pemCertificate,
ca_certificate.pemCertificateChain,
)
log.status.Print(
'Created Certificate Authority [{}].'.format(ca_ref.RelativeName())
)
if self._ShouldEnableCa(args, ca_ref):
self._EnableCertificateAuthority(ca_ref.RelativeName())
return

View File

@@ -0,0 +1,163 @@
# -*- 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.
"""Delete a subordinate certificate authority."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from dateutil import tz
from googlecloudsdk.api_lib.privateca import base as privateca_base
from googlecloudsdk.api_lib.privateca import request_utils
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import times
import six
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Delete(base.DeleteCommand):
r"""Delete a subordinate certificate authority.
Delete a Subordinate Certificate Authority. Deleted Subordinate Certificate
Authorities may be recovered with the `{parent_command} undelete` command
within a grace period of 30 days.
Use the --skip-grace-period flag to delete as soon as possible without the
30-day grace period to undelete.
Note that any user-managed KMS keys or Google Cloud Storage buckets
will not be affected by this operation. You will need to delete the user-
managed resources separately once the CA is deleted. Any Google-managed
resources will be cleaned up.
The CA specified in this command MUST:
1) be in the DISABLED or STAGED state.
2) have no un-revoked or un-expired certificates. Use the revoke command
to revoke any active certificates.
Use the `--ignore-active-certificates` flag to remove 2) as a requirement.
## EXAMPLES
To delete a subordinate CA:
$ {command} server-tls-1 --pool=my-pool --location=us-west1
To delete a CA while skipping the confirmation input:
$ {command} server-tls-1s --pool=my-pool --location=us-west1 --quiet
To undo the deletion for a subordinate CA:
$ {parent_command} undelete server-tls-1 --pool=my-pool
--location=us-west1
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to delete')
flags.AddIgnoreActiveCertificatesFlag(parser)
flags.AddSkipGracePeriodFlag(parser)
flags.AddIgnoreDependentResourcesFlag(parser)
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
ca_name = ca_ref.RelativeName()
prompt_message = (
'You are about to delete Certificate Authority [{}].').format(
ca_ref.RelativeName())
if args.ignore_dependent_resources:
prompt_message += (
'\nThis deletion will happen without '
'checking if the CA\'s CA Pool is being used by another '
'resource, which may cause unintended and effects on any '
'dependent resource(s) since the CA Pool would not be '
'able to issue certificates.')
if args.skip_grace_period:
prompt_message += (
'\nThis deletion will happen as '
'soon as possible without a 30-day grace period where '
'undeletion would have been allowed. If you proceed, there '
'will be no way to recover this CA.')
if not console_io.PromptContinue(message=prompt_message, default=True):
log.status.Print('Aborted by user.')
return
current_ca = client.projects_locations_caPools_certificateAuthorities.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesGetRequest(
name=ca_name))
resource_args.CheckExpectedCAType(
messages.CertificateAuthority.TypeValueValuesEnum.SUBORDINATE,
current_ca,
version='v1')
operation = client.projects_locations_caPools_certificateAuthorities.Delete(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesDeleteRequest(
name=ca_name,
ignoreActiveCertificates=args.ignore_active_certificates,
skipGracePeriod=args.skip_grace_period,
ignoreDependentResources=args.ignore_dependent_resources,
requestId=request_utils.GenerateRequestId()))
try:
ca_response = operations.Await(
operation, 'Deleting Subordinate CA', api_version='v1')
except waiter.OperationError as e:
# API error message refers to the proto field name which is slightly
# different from the gcloud flag name.
raise operations.OperationError(
six.text_type(e).replace('`ignore_active_certificates` parameter',
'`--ignore-active-certificates` flag'))
ca = operations.GetMessageFromResponse(ca_response,
messages.CertificateAuthority)
formatted_expire_time = times.ParseDateTime(ca.expireTime).astimezone(
tz.tzutc()).strftime('%Y-%m-%dT%H:%MZ')
if (
current_ca.state
== messages.CertificateAuthority.StateValueValuesEnum.AWAITING_USER_ACTIVATION
):
log.status.Print(
'Deleted Subordinate CA [{}]. This CA was never activated and cannot'
' be recovered using `subordinates undelete`.'.format(ca_name)
)
elif args.skip_grace_period:
log.status.Print(
'Deleted Subordinate CA [{}]. CA can not be undeleted.'.format(
ca_name
)
)
else:
log.status.Print(
'Deleted Subordinate CA [{}]. CA can be undeleted until {}.'.format(
ca_name, formatted_expire_time
)
)

View File

@@ -0,0 +1,35 @@
- release_tracks: [GA]
help_text:
brief: |
Get metadata for a subordinate certificate authority.
description: |
Returns metadata for the given certificate authority.
examples: |
To get metadata for the subordinate CA 'server-tls-1' in CA Pool 'my-pool' and location
'us-west1':
$ {command} server-tls-1 \
--location=us-west1 \
--pool=my-pool
To download the PEM-encoded CA certificate chain for the 'server-tls-1'
CA in location 'us-west1' to a file
called 'server-tls-1.crt':
$ {command} server-tls-1 \
--location=us-west1 \
--pool=my-pool \
--format="value(pemCaCertificates)" > ./server-tls-1.crt
request:
collection: privateca.projects.locations.caPools.certificateAuthorities
api_version: v1
arguments:
resource:
help_text: The certificate authority for which to obtain metadata.
spec: !REF googlecloudsdk.command_lib.privateca.resources:cert_authority
response:
modify_response_hooks:
- googlecloudsdk.command_lib.privateca.hooks:CheckResponseSubordinateTypeHook:version=v1

View File

@@ -0,0 +1,92 @@
# -*- 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.
"""Disable a subordinate certificate authority."""
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.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Disable(base.SilentCommand):
r"""Disable a subordinate certificate authority.
Disables a subordinate certificate authority. The subordinate certificate
authority will not be allowed to issue certificates once disabled. It may
still revoke certificates and/or generate CRLs.
## EXAMPLES
To disable a subordinate CA:
$ {command} server-tls1 --location=us-west1 --pool=my-pool
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to disable')
flags.AddIgnoreDependentResourcesFlag(parser)
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
ca_name = ca_ref.RelativeName()
if args.ignore_dependent_resources:
prompt_message = (
'You are about to disable Certificate Authority [{}] without '
'checking if the CA\'s CA Pool is being used by another '
'resource. If you proceed and this is the last enabled CA in '
'the CA Pool, there may be unintended and '
'unrecoverable effects on any dependent resource(s) since the '
'CA Pool would not be able to issue certificates.'
).format(ca_ref.RelativeName())
if not console_io.PromptContinue(message=prompt_message, default=True):
log.status.Print('Aborted by user.')
return
current_ca = client.projects_locations_caPools_certificateAuthorities.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesGetRequest(
name=ca_name))
resource_args.CheckExpectedCAType(
messages.CertificateAuthority.TypeValueValuesEnum.SUBORDINATE,
current_ca,
version='v1')
operation = client.projects_locations_caPools_certificateAuthorities.Disable(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesDisableRequest(
name=ca_name,
disableCertificateAuthorityRequest=messages
.DisableCertificateAuthorityRequest(
ignoreDependentResources=args.ignore_dependent_resources,
requestId=request_utils.GenerateRequestId())))
operations.Await(operation, 'Disabling Subordinate CA', api_version='v1')
log.status.Print('Disabled Subordinate CA [{}].'.format(ca_name))

View File

@@ -0,0 +1,74 @@
# -*- 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.
"""Enable a subordinate certificate authority."""
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.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.GA)
class EnableGA(base.SilentCommand):
r"""Enable a subordinate certificate authority.
Enables a subordinate certificate authority. The
subordinate certificate authority will be allowed to issue certificates once
enabled.
## EXAMPLES
To enable a subordinate CA:
$ {command} server-tls1 --pool=my-pool --location=us-west1
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to enable')
def Run(self, args):
client = privateca_base.GetClientInstance('v1')
messages = privateca_base.GetMessagesModule('v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
current_ca = client.projects_locations_caPools_certificateAuthorities.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesGetRequest(
name=ca_ref.RelativeName()))
resource_args.CheckExpectedCAType(
messages.CertificateAuthority.TypeValueValuesEnum.SUBORDINATE,
current_ca, 'v1')
operation = client.projects_locations_caPools_certificateAuthorities.Enable(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesEnableRequest(
name=ca_ref.RelativeName(),
enableCertificateAuthorityRequest=messages
.EnableCertificateAuthorityRequest(
requestId=request_utils.GenerateRequestId())))
operations.Await(operation, 'Enabling Subordinate CA', api_version='v1')
log.status.Print('Enabled Subordinate CA [{}].'.format(
ca_ref.RelativeName()))

View File

@@ -0,0 +1,57 @@
# -*- 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 the csr of a pending Certificate Authority."""
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 resource_args
@base.ReleaseTracks(base.ReleaseTrack.GA)
class GetCsr(base.Command):
# pylint: disable=line-too-long
r"""Get the CSR for a subordinate certificate authority that has not yet been activated.
Gets the PEM-encoded CSR for a subordinate certificate authority that is
awaiting user activation. The CSR should be signed by the issuing Certificate
Authority and uploaded back using the `subordinates activate` command.
## EXAMPLES
To download the CSR for the 'server-tls-1' CA into a file called
'server-tls-1.csr':
$ {command} server-tls-1 --location=us-west1 --pool=my-pool > server-tls-1.csr
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(
parser, 'for which to get the CSR')
parser.display_info.AddFormat("""value(pemCsr)""")
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
return client.projects_locations_caPools_certificateAuthorities.Fetch(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesFetchRequest(
name=ca_ref.RelativeName()))

View File

@@ -0,0 +1,127 @@
# -*- 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.
"""List the subordinate certificate authorities 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 import exceptions
from googlecloudsdk.command_lib.privateca import response_utils
from googlecloudsdk.command_lib.privateca import text_utils
from googlecloudsdk.core import properties
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List subordinate certificate authorities.
List the subordinate certificate authorities within a project.
## EXAMPLES
To list all subordinate certificate authorities in a project:
$ {command}
To list all subordinate certificate authorities within a project and location
'us-central1':
$ {command} --location=us-central1
To list all subordinate certificate authorities within a CA Pool in location
'us-central1':
$ {command} --pool=my-pool --location=us-central1
"""
@staticmethod
def Args(parser):
base.Argument(
'--location',
help=(
'Location of the certificate authorities. If omitted, subordinate '
'CAs across all regions will be listed. Note that, if it is '
'populated, the privateca/location property will be used if this '
'flag is not specified. To ignore this property, specify "-" as '
'the location.')).AddToParser(parser)
base.Argument(
'--pool',
help=('ID of the CA Pool where the certificate authorities reside. If '
'omitted, subordinate CAs across all CA pools will be listed.'
)).AddToParser(parser)
base.PAGE_SIZE_FLAG.SetDefault(parser, 100)
base.FILTER_FLAG.RemoveFromParser(parser)
parser.display_info.AddFormat("""
table(
name.basename(),
name.scope().segment(-5):label=LOCATION,
name.scope().segment(-3):label=POOL,
state,
state.regex("ENABLED","YES","NO"):label=INCLUDED_IN_POOL_ISSUANCE,
ca_certificate_descriptions[0].subject_description.not_before_time():label=NOT_BEFORE,
ca_certificate_descriptions[0].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.certificateAuthorities'))
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
location_property_fallback = properties.VALUES.privateca.location.Get()
if args.IsSpecified('location'):
location = args.location
elif location_property_fallback and args.IsSpecified('pool'):
location = location_property_fallback
else:
location = '-'
ca_pool_id = args.pool if args.IsSpecified('pool') else '-'
if location == '-' and args.IsSpecified('pool'):
raise exceptions.InvalidArgumentException(
'--location',
'If a pool id is specified, you must also specify the location of that pool.'
)
parent_resource = 'projects/{}/locations/{}/caPools/{}'.format(
properties.VALUES.core.project.GetOrFail(), location, ca_pool_id)
request = messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesListRequest(
parent=parent_resource,
filter='type:SUBORDINATE',
orderBy=common_args.ParseSortByArg(args.sort_by))
return list_pager.YieldFromList(
client.projects_locations_caPools_certificateAuthorities,
request,
field='certificateAuthorities',
limit=args.limit,
batch_size_attribute='pageSize',
batch_size=args.page_size,
get_field_func=response_utils.GetFieldAndLogUnreachable)

View File

@@ -0,0 +1,76 @@
# -*- 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.
"""Undelete a subordinate certificate authority."""
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.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Undelete(base.SilentCommand):
r"""Undelete a subordinate certificate authority.
Restores a subordinate Certificate Authority that has been deleted. A
Certificate Authority can be undeleted within 30 days of being deleted. Use
this command to halt the deletion process. An undeleted CA will move to
DISABLED state.
## EXAMPLES
To undelete a subordinate CA:
$ {command} server-tls-1 --location=us-west1 --pool=my-pool
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to undelete')
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
current_ca = client.projects_locations_caPools_certificateAuthorities.Get(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesGetRequest(
name=ca_ref.RelativeName()))
resource_args.CheckExpectedCAType(
messages.CertificateAuthority.TypeValueValuesEnum.SUBORDINATE,
current_ca,
version='v1')
operation = client.projects_locations_caPools_certificateAuthorities.Undelete(
messages
.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesUndeleteRequest(
name=ca_ref.RelativeName(),
undeleteCertificateAuthorityRequest=messages
.UndeleteCertificateAuthorityRequest(
requestId=request_utils.GenerateRequestId())))
operations.Await(operation, 'Undeleting Subordinate CA', api_version='v1')
log.status.Print('Undeleted Subordinate CA [{}].'.format(
ca_ref.RelativeName()))

View File

@@ -0,0 +1,101 @@
# -*- 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 subordinate certificate authority."""
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.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.privateca import update_utils
from googlecloudsdk.command_lib.util.args import labels_util
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Update(base.UpdateCommand):
r"""Update an existing subordinate certificate authority.
## EXAMPLES
To update labels on a subordinate CA:
$ {command} server-tls-1 \
--pool=my-pool \
--location=us-west1 \
--update-labels=foo=bar
To update the CA certificate chain for a subordinate CA:
$ {command} server-tls-1 \
--pool=my-pool \
--location=us-west1 \
--pem-chain=pem_chain.txt
"""
@staticmethod
def Args(parser):
resource_args.AddCertAuthorityPositionalResourceArg(parser, 'to update')
base.Argument(
'--pem-chain',
required=False,
help=(
'A file containing a list of PEM-encoded certificates that'
' represent the issuing chain of this CA. Please note'
' that the certificate corresponding to this specific CA should be'
' excluded.'
),
).AddToParser(parser)
labels_util.AddUpdateLabelsFlags(parser)
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
ca_ref = args.CONCEPTS.certificate_authority.Parse()
ca_name = ca_ref.RelativeName()
current_ca = client.projects_locations_caPools_certificateAuthorities.Get(
messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesGetRequest(
name=ca_name
)
)
resource_args.CheckExpectedCAType(
messages.CertificateAuthority.TypeValueValuesEnum.SUBORDINATE,
current_ca,
version='v1',
)
ca_to_update, update_mask = update_utils.UpdateCAFromArgs(
args, current_ca.labels
)
# Patch is the gcloud client lib method to update a CA.
operation = client.projects_locations_caPools_certificateAuthorities.Patch(
messages.PrivatecaProjectsLocationsCaPoolsCertificateAuthoritiesPatchRequest(
name=ca_name,
certificateAuthority=ca_to_update,
updateMask=','.join(update_mask),
requestId=request_utils.GenerateRequestId(),
)
)
return operations.Await(
operation, 'Updating Subordinate CA.', api_version='v1'
)

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 templates."""
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 Templates(base.Group):
"""Manage certificate templates."""

View File

@@ -0,0 +1,33 @@
- release_tracks: [GA]
help_text:
brief: |
Add IAM policy binding for a certificate template.
description: |
Adds a policy binding to the IAM policy of a certificate template. One binding
consists of a member and a role.
See https://cloud.google.com/iam/docs/managing-policies for details of
the policy file format and contents.
examples: |
To add an IAM policy binding for the role of 'roles/privateca.templateUser' for the user
'test-user@gmail.com' on the certificate template 'mtls-template' with the location 'us-west1', run:
$ {command} mtls-template \
--location='us-west1' \
--member='user:test-user@gmail.com' \
--role='roles/privateca.templateUser'
request:
collection: privateca.projects.locations.certificateTemplates
api_version: v1
arguments:
resource:
help_text: The certificate template for which to add the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.privateca.resources:certificate_template
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,114 @@
# -*- 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 new certificate template."""
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.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Create(base.CreateCommand):
r"""Create a new certificate template."""
detailed_help = {
'DESCRIPTION':
"""\
Create a certificate template that enforces policy restrictions on
certificate requestors. Using a certificate template, you can define
restrictions on the kinds of Subjects/SANs and x509 extensions allowed
from certificate requestors as well as a default set of x509
extensions that should be applied to all certificates using that
template. These templates can be binded to IAM identities such that
certain groups of requestors must use particular templates, allowing
for fine-grained policy enforcements based on identity.
For more information and examples, see https://cloud.google.com/certificate-authority-service/docs/creating-certificate-template.
""",
'EXAMPLES':
"""\
To create a template that prohibits any x509 extension from a requester,
but permits custom subjects/SANs and defines the default x509
extensions, run:
$ {command} restricted-template --location=us-west1 --copy-subject --copy-sans --predefined-values-file=x509_parameters.yaml
To create a template that allows requesters to specify only DNS names
from requesters, use a custom CEL expression with a SAN only restriction:
$ {command} dns-only-template --location=us-west1 --description="Restricts certificates to DNS SANs." --no-copy-subject --copy-sans --identity-cel-expression="subject_alt_names.all(san, san.type == DNS)"
To create a template that permits a requestor to specify extensions by
OIDs, and subjects (but not SANs), with default x509 exensions:
$ {command} mtls-only-extensions --location=us-west1 --copy-subject --no-copy-sans --predefined-values-file=mtls_cert_exts.yaml --copy-extensions-by-oid=1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1
""",
}
@staticmethod
def Args(parser):
resource_args.AddCertificateTemplatePositionalResourceArg(parser,
'to create')
base.Argument(
'--description',
help='A text description for the Certificate Template.').AddToParser(
parser)
flags.AddPredefinedValuesFileFlag(parser)
flags.AddIdentityConstraintsFlags(parser)
flags.AddExtensionConstraintsFlags(parser)
flags.AddMaximumLifetimeFlag(parser)
labels_util.AddCreateLabelsFlags(parser)
def Run(self, args):
client = privateca_base.GetClientInstance('v1')
messages = privateca_base.GetMessagesModule('v1')
cert_template_ref = args.CONCEPTS.certificate_template.Parse()
flags.ValidateIdentityConstraints(args)
new_cert_template = messages.CertificateTemplate(
predefinedValues=flags.ParsePredefinedValues(args),
identityConstraints=flags.ParseIdentityConstraints(args),
passthroughExtensions=flags.ParseExtensionConstraints(args),
description=args.description
if args.IsSpecified('description')
else None,
maximumLifetime=flags.ParseMaximumLifetime(args),
)
operation = client.projects_locations_certificateTemplates.Create(
messages.PrivatecaProjectsLocationsCertificateTemplatesCreateRequest(
parent=cert_template_ref.Parent().RelativeName(),
certificateTemplateId=cert_template_ref.Name(),
certificateTemplate=new_cert_template,
requestId=request_utils.GenerateRequestId()))
cert_template_response = operations.Await(
operation, 'Creating Certificate Template.', api_version='v1')
cert_template = operations.GetMessageFromResponse(
cert_template_response, messages.CertificateTemplate)
log.status.Print('Created Certificate Template [{}].'.format(
cert_template.name))

View File

@@ -0,0 +1,74 @@
# -*- 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.
"""Delete a certificate template."""
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.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Delete(base.DeleteCommand):
r"""Delete a certificate template.
## EXAMPLES
To delete a certificate template:
$ {command} my-template --location=us-west1
To delete a certificate template while skipping the confirmation input:
$ {command} my-template --location=us-west1 --quiet
"""
@staticmethod
def Args(parser):
resource_args.AddCertificateTemplatePositionalResourceArg(
parser, 'to delete')
def Run(self, args):
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
template_ref = args.CONCEPTS.certificate_template.Parse()
template_name = template_ref.RelativeName()
if not console_io.PromptContinue(
message='You are about to delete the certificate template [{}]'.format(
template_ref.RelativeName()),
default=True):
log.status.Print('Aborted by user.')
return
operation = client.projects_locations_certificateTemplates.Delete(
messages
.PrivatecaProjectsLocationsCertificateTemplatesDeleteRequest(
name=template_name,
requestId=request_utils.GenerateRequestId()))
operations.Await(
operation, 'Deleting Certificate Template', api_version='v1')
log.status.Print(
'Deleted Certificate Template [{}].'.format(template_name))

View File

@@ -0,0 +1,18 @@
- release_tracks: [GA]
help_text:
brief: Show details about a certificate template.
description: Show details about a certificate template.
examples: |
To show details about a certificate template, run:
$ {command} my-template --location=us-central1
request:
collection: privateca.projects.locations.certificateTemplates
api_version: v1
arguments:
resource:
help_text: The certificate template you want to describe.
spec: !REF googlecloudsdk.command_lib.privateca.resources:certificate_template

View File

@@ -0,0 +1,27 @@
- release_tracks: [GA]
help_text:
brief: Get the IAM policy for a certificate template.
description: |
Gets the IAM policy for the given certificate template.
Returns an empty policy if the resource does not have a policy
set.
examples: |
To get the IAM policy for the certificate template 'mtls-template' with the location 'us-west1', run:
$ {command} mtls-template --location=us-west1
request:
collection: privateca.projects.locations.certificateTemplates
api_version: v1
arguments:
resource:
help_text: The certificate template for which to display the IAM policy.
spec: !REF googlecloudsdk.command_lib.privateca.resources:certificate_template
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,89 @@
# -*- 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 certificate templates 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.command_lib.privateca import response_utils
from googlecloudsdk.core import properties
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List certificate templates within a project."""
detailed_help = {
'DESCRIPTION':
'List certificate templates.',
'EXAMPLES':
"""\
To list all certificate templates in a project across all locations, run:
$ {command}
To list all certificate templates in a project and location 'us-central1',
run:
$ {command} --location=us-central1""",
}
@staticmethod
def Args(parser):
base.Argument(
'--location',
help=('The location you want to list the certificate templates for. '
'Set this to "-" to list certificate templates across all '
'locations.'),
default='-').AddToParser(parser)
base.PAGE_SIZE_FLAG.SetDefault(parser, 100)
base.SORT_BY_FLAG.SetDefault(parser, 'name')
parser.display_info.AddFormat("""
table(
name.scope("certificateTemplates"):label=NAME,
name.scope("locations").segment(0):label=LOCATION,
description
)""")
parser.display_info.AddUriFunc(
resource_utils.MakeGetUriFunc(
'privateca.projects.locations.certificateTemplates'))
def Run(self, args):
"""Runs the command."""
client = privateca_base.GetClientInstance(api_version='v1')
messages = privateca_base.GetMessagesModule(api_version='v1')
parent = 'projects/{}/locations/{}'.format(
properties.VALUES.core.project.GetOrFail(), args.location)
request = messages.PrivatecaProjectsLocationsCertificateTemplatesListRequest(
parent=parent,
orderBy=common_args.ParseSortByArg(args.sort_by),
filter=args.filter)
return list_pager.YieldFromList(
client.projects_locations_certificateTemplates,
request,
field='certificateTemplates',
limit=args.limit,
batch_size_attribute='pageSize',
batch_size=args.page_size,
get_field_func=response_utils.GetFieldAndLogUnreachable)

View File

@@ -0,0 +1,33 @@
- release_tracks: [GA]
help_text:
brief: |
Remove IAM policy binding for a certificate template.
description: |
Removes a policy binding to the IAM policy of a certificate template. One binding
consists of a member and a role.
See https://cloud.google.com/iam/docs/managing-policies for details of
the policy file format and contents.
examples: |
To remove an IAM policy binding for the role of 'roles/privateca.templateUser' for the user
'test-user@gmail.com' on the certificate template 'my-template' with the location 'us-west1', run:
$ {command} my-template \
--location=us-west1 \
--member='user:test-user@gmail.com' \
--role='roles/privateca.templateUser'
request:
collection: privateca.projects.locations.certificateTemplates
api_version: v1
arguments:
resource:
help_text: The certificate template for which to remove the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.privateca.resources:certificate_template
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,203 @@
# -*- 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.
"""Replicate a certificate template to multiple regions."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as api_exceptions
from googlecloudsdk.api_lib.privateca import base as privateca_base
from googlecloudsdk.api_lib.privateca import locations
from googlecloudsdk.api_lib.privateca import request_utils
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.core import log
import six
class ReplicationError(Exception):
"""Represents an error that occurred while replicating a resource to a given location."""
def __init__(self, location, message):
self._message = 'Failed to replicate to location [{}]: {}'.format(
location, message)
super(ReplicationError, self).__init__(self._message)
def __str__(self):
return self._message
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Replicate(base.SilentCommand):
"""Replicate a certificate template to multiple locations."""
detailed_help = {
'DESCRIPTION':
'Replicate a certificate template to multiple locations.',
'EXAMPLES':
"""\
To replicate a certificate templates to all supported locations, run:
$ {command} my-template --location=us-west1 --all-locations
To replicate a certificate template to 'us-west2' and 'us-east1', run:
$ {command} my-template --location=us-west1 --target-locations=us-west2,us-east1
To overwrite existing templates with the same resource ID in the target
locations, use the --overwrite flag:
$ {command} my-template --location=us-west1 --target-locations=us-west2,us-east1 --overwrite
To continue replicating templates in other locations in the event of a
failure in one or more locations, use the --continue-on-error flag:
$ {command} my-template --location=us-west1 --all-locations --continue-on-error""",
}
@staticmethod
def Args(parser):
resource_args.AddCertificateTemplatePositionalResourceArg(
parser, 'to replicate')
target_locations_group = base.ArgumentGroup(
mutex=True,
required=True,
help='Specify where the certificate template should be replicated.'
).AddToParser(parser)
base.Argument(
'--all-locations',
action='store_const',
const=True,
help='Replicate this template to all supported locations.').AddToParser(
target_locations_group)
base.Argument(
'--target-locations',
help='Replicate this template to the given locations.',
type=arg_parsers.ArgList(
element_type=lambda x: six.text_type(x).strip()),
metavar='LOCATION').AddToParser(target_locations_group)
base.Argument(
'--overwrite',
help=('Overwrite any existing templates with the same name, '
'if they exist.'),
action='store_const',
const=True,
default=False).AddToParser(parser)
base.Argument(
'--continue-on-error',
help=('Continue replicating the template to other locations '
'even if an error is encountered. If this is set, an '
'error in one location will be logged but will not '
'prevent replication to other locations.'),
action='store_const',
const=True,
default=False).AddToParser(parser)
def _CreateOrUpdateTemplate(self, project, location, template_id, template,
overwrite):
"""Returns an LRO for a Create or Update operation for the given template.
Args:
project: str, the project ID or number for the new template.
location: str, the location for the new template.
template_id: str, the resource ID for the new template.
template: object, the body of the new template.
overwrite: bool, whether to overwrite existing templates with the same ID.
Raises:
ReplicationError, if the template could not be replicated to this
location.
"""
parent = 'projects/{}/locations/{}'.format(project, location)
resource_name = '{}/certificateTemplates/{}'.format(parent, template_id)
try:
return self.client.projects_locations_certificateTemplates.Create(
self.messages
.PrivatecaProjectsLocationsCertificateTemplatesCreateRequest(
parent=parent,
certificateTemplateId=template_id,
certificateTemplate=template,
requestId=request_utils.GenerateRequestId()))
except api_exceptions.HttpConflictError as e:
if not overwrite:
raise ReplicationError(
location,
'Certificate template [{}] already exists and the --overwrite flag '
'was not set.'.format(resource_name))
return self.client.projects_locations_certificateTemplates.Patch(
self.messages
.PrivatecaProjectsLocationsCertificateTemplatesPatchRequest(
name=resource_name,
certificateTemplate=template,
# Always copy all fields. Mask value of '*' doesn't seem to be
# currently supported by CCFE.
updateMask='predefined_values,identity_constraints,passthrough_extensions,description,labels',
requestId=request_utils.GenerateRequestId()))
except api_exceptions.HttpError as e:
raise ReplicationError(location, six.text_type(e))
def Run(self, args):
"""Runs the command."""
self.client = privateca_base.GetClientInstance(api_version='v1')
self.messages = privateca_base.GetMessagesModule(api_version='v1')
template_ref = args.CONCEPTS.certificate_template.Parse()
template = self.client.projects_locations_certificateTemplates.Get(
self.messages.PrivatecaProjectsLocationsCertificateTemplatesGetRequest(
name=template_ref.RelativeName()))
# Name is output-only and will be different for each location.
template.name = ''
success_count = 0
target_locations = args.target_locations
if args.all_locations:
target_locations = [
location for location in locations.GetSupportedLocations('v1')
if location != template_ref.locationsId
]
for location in target_locations:
location = location.strip()
if location == template_ref.locationsId:
log.warning(
'Skipping location [{}] since it is the source location.'.format(
location))
continue
try:
operation = self._CreateOrUpdateTemplate(template_ref.projectsId,
location, template_ref.Name(),
template, args.overwrite)
operations.Await(
operation,
'Replicating template to [{}]'.format(location),
api_version='v1')
success_count += 1
except ReplicationError as e:
if args.continue_on_error:
log.warning(six.text_type(e))
continue
raise e
log.status.Print(
'Replicated template [{}] to {} out of {} locations.'.format(
template_ref.RelativeName(), success_count, len(target_locations)))

View File

@@ -0,0 +1,29 @@
- release_tracks: [GA]
help_text:
brief: |
Set the IAM policy for a certificate template.
description: |
Sets the IAM policy for the given certificate template 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 certificate template 'my-template' with the location
'us-west1':
$ {command} my-template --location=us-west1 policy.json
request:
collection: privateca.projects.locations.certificateTemplates
api_version: v1
arguments:
resource:
help_text: The certificate template for which to update the IAM policy.
spec: !REF googlecloudsdk.command_lib.privateca.resources:certificate_template
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,198 @@
# -*- 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 new certificate template."""
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.command_lib.privateca import exceptions as privateca_exceptions
from googlecloudsdk.command_lib.privateca import flags
from googlecloudsdk.command_lib.privateca import operations
from googlecloudsdk.command_lib.privateca import resource_args
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Create(base.CreateCommand):
r"""Update a certificate template."""
detailed_help = {
'DESCRIPTION': """Update a certificate template.""",
'EXAMPLES': """\
To update a template named "dns-restricted" with new default x509 extensions:
$ {command} dns-restricted --location=us-west1 --predefined-values-file=x509_parameters.yaml
To update a template named "dns-restricted" to allow requestors to specify subject:
$ {command} dns-restricted --location=us-west1 --copy-subject
To update a template named "dns-restricted" with allowed extension
'base-key-usage' to allow requestors to specify additional x509 extension 'extended-key-usage':
$ {command} dns-restricted --location=us-west1 --copy-known-extensions=base-key-usage,extended-key-usage
To update a template named "mtls-restricted" with allowed OID
'1.1' to allow requestors to specify alternative OIDS '2.2,3.3':
$ {command} mtls-restricted --location=us-west1 --copy-extensions-by-oid=2.2,3.3
""",
}
def _UpdateCertificateTemplateFromArgs(self, args, current_labels):
"""Creates a Certificate template object and update mask from Certificate template update flags.
Requires that args has 'description', 'copy-sans', 'copy-subject',
'predefined-values-file', 'copy-known-extensions', 'copy-extensions-by-oid',
and update labels flags registered.
Args:
args: The parser that contains the flag values.
current_labels: The current set of labels for the Certificate Template.
Returns:
A tuple with the Certificate template object to update with and the list
of
strings representing the update mask, respectively.
"""
messages = privateca_base.GetMessagesModule('v1')
template_to_update = messages.CertificateTemplate()
update_mask = []
# We'll parse the identity constraints if any of the flags are specified,
# but only include the paths in the update masks of the flags that were
# explicitly specified.
if (
args.IsSpecified('copy_sans')
or args.IsSpecified('copy_subject')
or args.IsSpecified('identity_cel_expression')
):
template_to_update.identityConstraints = flags.ParseIdentityConstraints(
args
)
if args.IsSpecified('copy_sans'):
update_mask.append(
'identity_constraints.allow_subject_alt_names_passthrough'
)
if args.IsSpecified('copy_subject'):
update_mask.append('identity_constraints.allow_subject_passthrough')
if args.IsSpecified('identity_cel_expression'):
update_mask.append('identity_constraints.cel_expression')
if args.IsSpecified('predefined_values_file'):
template_to_update.predefinedValues = flags.ParsePredefinedValues(args)
update_mask.append('predefined_values')
if args.IsSpecified('description'):
template_to_update.description = args.description
update_mask.append('description')
known_exts_flags = args.IsSpecified(
'copy_known_extensions'
) or args.IsSpecified('drop_known_extensions')
oid_exts_flags = args.IsSpecified(
'copy_extensions_by_oid'
) or args.IsSpecified('drop_oid_extensions')
if known_exts_flags or oid_exts_flags:
# Parse all extension flags into a CertificateExtensionConstraints
# message.
template_to_update.passthroughExtensions = (
flags.ParseExtensionConstraints(args)
)
if known_exts_flags:
update_mask.append('passthrough_extensions.known_extensions')
if oid_exts_flags:
update_mask.append('passthrough_extensions.additional_extensions')
labels_diff = labels_util.Diff.FromUpdateArgs(args)
labels_update = labels_diff.Apply(
messages.CaPool.LabelsValue, current_labels
)
if labels_update.needs_update:
template_to_update.labels = labels_update.labels
update_mask.append('labels')
if not update_mask:
raise privateca_exceptions.NoUpdateException(
'No updates found for the requested certificate template.'
)
return template_to_update, update_mask
@staticmethod
def Args(parser):
resource_args.AddCertificateTemplatePositionalResourceArg(
parser, 'to update'
)
base.Argument(
'--description', help='A text description for the Certificate Template.'
).AddToParser(parser)
flags.AddPredefinedValuesFileFlag(parser)
flags.AddIdentityConstraintsFlags(parser, require_passthrough_flags=False)
flags.AddExtensionConstraintsFlagsForUpdate(parser)
labels_util.AddUpdateLabelsFlags(parser)
def Run(self, args):
client = privateca_base.GetClientInstance('v1')
messages = privateca_base.GetMessagesModule('v1')
cert_template_ref = args.CONCEPTS.certificate_template.Parse()
template_name = cert_template_ref.RelativeName()
current_cert_template = client.projects_locations_certificateTemplates.Get(
messages.PrivatecaProjectsLocationsCertificateTemplatesGetRequest(
name=template_name
)
)
cert_template_to_update, update_mask = (
self._UpdateCertificateTemplateFromArgs(
args, current_cert_template.labels
)
)
# Confirm that the result of this update is intended to be identity
# reflection, if applicable.
flags.ValidateIdentityConstraints(
args,
existing_copy_subj=current_cert_template.identityConstraints.allowSubjectPassthrough,
existing_copy_sans=current_cert_template.identityConstraints.allowSubjectAltNamesPassthrough,
for_update=True,
)
operation = client.projects_locations_certificateTemplates.Patch(
messages.PrivatecaProjectsLocationsCertificateTemplatesPatchRequest(
name=template_name,
certificateTemplate=cert_template_to_update,
updateMask=','.join(update_mask),
requestId=request_utils.GenerateRequestId(),
)
)
cert_template_response = operations.Await(
operation, 'Updating Certificate Template.', api_version='v1'
)
cert_template = operations.GetMessageFromResponse(
cert_template_response, messages.CertificateTemplate
)
log.status.Print(
'Updated Certificate Template [{}].'.format(cert_template.name)
)