192 lines
6.9 KiB
Python
192 lines
6.9 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2023 Google LLC. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""Implements the command to upload an SBOM file."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
from googlecloudsdk.calliope import arg_parsers
|
|
from googlecloudsdk.calliope import base
|
|
from googlecloudsdk.calliope import exceptions
|
|
from googlecloudsdk.command_lib.artifacts import endpoint_util
|
|
from googlecloudsdk.command_lib.artifacts import flags
|
|
from googlecloudsdk.command_lib.artifacts import sbom_util
|
|
from googlecloudsdk.command_lib.artifacts import util
|
|
from googlecloudsdk.core import log
|
|
from googlecloudsdk.core import properties
|
|
|
|
|
|
@base.DefaultUniverseOnly
|
|
@base.ReleaseTracks(base.ReleaseTrack.GA)
|
|
class Load(base.Command):
|
|
"""Upload an SBOM file and create a reference occurrence."""
|
|
|
|
detailed_help = {
|
|
'DESCRIPTION': '{description}',
|
|
'EXAMPLES': """\
|
|
To upload an SBOM file at /path/to/sbom.json for a Docker image in Artifact Registry:
|
|
|
|
$ {command} --source=/path/to/sbom.json \
|
|
--uri=us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz
|
|
|
|
To upload an SBOM file at /path/to/sbom.json for a Docker image with a KMS key version to sign the created SBOM reference:
|
|
|
|
$ {command} --source=/path/to/sbom.json \
|
|
--uri=us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz \
|
|
--kms-key-version=projects/my-project/locations/us-west1/keyRings/my-key-ring/cryptoKeys/my-key/cryptoKeyVersions/1
|
|
|
|
To upload an SBOM file at /path/to/sbom.json for a Docker image from a Docker registry:
|
|
|
|
$ {command} --source=/path/to/sbom.json \
|
|
--uri=my-docker-registry/my-image@sha256:abcxyz \
|
|
--destination=gs://my-cloud-storage-bucket
|
|
""",
|
|
}
|
|
|
|
@staticmethod
|
|
def Args(parser):
|
|
"""Set up arguments for this command.
|
|
|
|
Args:
|
|
parser: An argparse.ArgumentPaser.
|
|
"""
|
|
parser.add_argument(
|
|
'--source',
|
|
metavar='SOURCE',
|
|
required=True,
|
|
default='.',
|
|
help='The SBOM file for uploading.',
|
|
)
|
|
parser.add_argument(
|
|
'--uri',
|
|
metavar='ARTIFACT_URI',
|
|
required=True,
|
|
help="""\
|
|
The URI of the artifact the SBOM is generated from.
|
|
The URI can be a Docker image from any Docker registries. A URI provided with a tag (e.g. `[IMAGE]:[TAG]`) will be resolved into a URI with a digest (`[IMAGE]@sha256:[DIGEST]`).
|
|
When passing an image which is not from Artifact Registry or Container Registry with a tag, only public images can be resolved.
|
|
Also, when passing an image which is not from Artifact Registry or Container Registry, the `--destination` flag is required.
|
|
""",
|
|
)
|
|
parser.add_argument(
|
|
'--kms-key-version',
|
|
default=None,
|
|
help="""\
|
|
Cloud KMS key version to sign the SBOM reference.
|
|
The key version provided should be the resource ID in the format of
|
|
`projects/[KEY_PROJECT_ID]/locations/[LOCATION]/keyRings/[RING_NAME]/cryptoKeys/[KEY_NAME]/cryptoKeyVersions/[KEY_VERSION]`.
|
|
""",
|
|
required=False,
|
|
type=arg_parsers.RegexpValidator(
|
|
r'^projects/[^/]+/locations/[^/]+/keyRings/[^/]+/cryptoKeys/[^/]+/cryptoKeyVersions/[^/]+$',
|
|
(
|
|
'Must be in format of'
|
|
" 'projects/[KEY_PROJECT_ID]/locations/[LOCATION]/keyRings/[RING_NAME]/cryptoKeys/[KEY_NAME]/cryptoKeyVersions/[KEY_VERSION]'"
|
|
),
|
|
),
|
|
)
|
|
|
|
parser.add_argument(
|
|
'--destination',
|
|
metavar='DESTINATION',
|
|
default=None,
|
|
required=False,
|
|
help="""\
|
|
The storage path will be used to store the SBOM file.
|
|
Currently only supports Cloud Storage paths start with 'gs://'.
|
|
""",
|
|
type=arg_parsers.RegexpValidator(
|
|
r'^gs://.*$', 'Must be in format of gs://[STORAGE_PATH]'
|
|
),
|
|
)
|
|
|
|
flags.GetOptionalAALocationFlag().AddToParser(parser)
|
|
|
|
def Run(self, args):
|
|
"""Run the load command."""
|
|
# Parse file and get the version.
|
|
s = sbom_util.ParseJsonSbom(args.source)
|
|
log.info(
|
|
'Successfully loaded the SBOM file. Format: {0}-{1}.'.format(
|
|
s.sbom_format, s.version
|
|
)
|
|
)
|
|
|
|
if not args.uri:
|
|
raise exceptions.InvalidArgumentException(
|
|
'ARTIFACT_URI',
|
|
'--uri is required.',
|
|
)
|
|
# Get information from the artifact.
|
|
a = sbom_util.ProcessArtifact(args.uri)
|
|
log.info(
|
|
(
|
|
'Processed artifact. '
|
|
+ 'Project: {0}, Location: {1}, URI: {2}, Digest {3}.'
|
|
).format(
|
|
a.project, a.location, a.resource_uri, a.digests.get('sha256', '')
|
|
)
|
|
)
|
|
if (
|
|
sbom_util.ARTIFACT_TYPE_OTHER == a.artifact_type
|
|
and not args.destination
|
|
):
|
|
raise exceptions.InvalidArgumentException(
|
|
'DESTINATION',
|
|
(
|
|
'--destination is required for images not in Artifact Registry or'
|
|
' Container Registry.'
|
|
),
|
|
)
|
|
|
|
# Upload SBOM to a GCS bucket.
|
|
remote_path = sbom_util.UploadSbomToGCS(
|
|
source=args.source,
|
|
artifact=a,
|
|
sbom=s,
|
|
gcs_path=args.destination,
|
|
)
|
|
log.info('Uploaded the SBOM file at {0}'.format(remote_path))
|
|
|
|
# Create occurrence in the same project as the artifact's project.
|
|
# If artifact doesn't have a project (e.g. non AR/GCR images), we will use
|
|
# the current working project.
|
|
project_id = a.project
|
|
if not project_id:
|
|
project_id = args.project or properties.VALUES.core.project.GetOrFail()
|
|
log.info(
|
|
(
|
|
'Failed to get project_id from the artifact URI {0}. Using'
|
|
' project {1} for occurrence.'
|
|
).format(args.uri, project_id)
|
|
)
|
|
parent = util.GetParent(project_id, args.location)
|
|
# Write reference occurrence.
|
|
with endpoint_util.WithRegion(args.location):
|
|
occurrence_id = sbom_util.WriteReferenceOccurrence(
|
|
artifact=a,
|
|
project_id=parent,
|
|
storage=remote_path,
|
|
sbom=s,
|
|
kms_key_version=args.kms_key_version,
|
|
)
|
|
log.info('Wrote reference occurrence {0}.'.format(occurrence_id))
|
|
log.status.Print(
|
|
'Uploaded the SBOM file under the resource URI {}.'.format(
|
|
a.GetOccurrenceResourceUri()
|
|
)
|
|
)
|