Add recipe that automatically uploads the latest version of cosign to CIPD
* Uses the github api to get the latest release from sigstore/cosign, then uses it to find the specific artifacts needed based on the os
* Downloads the binary/certificates
* Uses the current version of cosign to verify the new version is valid
* If it is verified to be ok, it is uploaded to CIPD if the version is a new version
Exmaple build: https://ci.chromium.org/raw/build/logs.chromium.org/flutter/led/drewroen_google.com/6adad536aa2eede8631c572002a2cdb852b11ce571820acc133d3cb1793b9a48/+/build.proto?server=chromium-swarm.appspot.com
Buganizer: https://b.corp.google.com/issues/238423446
Change-Id: I3ac83a04cf13911d2c8628fadc5e12972b40f336
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/31780
Reviewed-by: Godofredo Contreras <godofredoc@google.com>
Reviewed-by: Jesse Seales <jseales@google.com>
Commit-Queue: Drew Roen <drewroen@google.com>
diff --git a/recipes/cipd/cosign.expected/cosign.json b/recipes/cipd/cosign.expected/cosign.json
new file mode 100644
index 0000000..aab3864
--- /dev/null
+++ b/recipes/cipd/cosign.expected/cosign.json
@@ -0,0 +1,323 @@
+[
+ {
+ "cmd": [
+ "curl",
+ "https://api.github.com/repos/sigstore/cosign/releases"
+ ],
+ "name": "Get cosign releases from github"
+ },
+ {
+ "cmd": [
+ "curl",
+ "https://api.github.com/releases/1"
+ ],
+ "name": "Get artifacts from sigstore/cosign for a specific release version"
+ },
+ {
+ "cmd": [
+ "curl",
+ "-L",
+ "cosign-darwin-amd64",
+ "-o",
+ "[START_DIR]/cosign/darwin/bin/cosign",
+ "--create-dirs"
+ ],
+ "infra_step": true,
+ "name": "Download darwin cosign binary"
+ },
+ {
+ "cmd": [
+ "curl",
+ "-L",
+ "cosign-darwin-amd64-keyless.pem",
+ "-o",
+ "[START_DIR]/cosign/darwin/certificate/cosign-cert.pem",
+ "--create-dirs"
+ ],
+ "infra_step": true,
+ "name": "Download darwin cosign certificate"
+ },
+ {
+ "cmd": [
+ "curl",
+ "-L",
+ "cosign-darwin-amd64-keyless.sig",
+ "-o",
+ "[START_DIR]/cosign/darwin/certificate/cosign-sig.sig",
+ "--create-dirs"
+ ],
+ "infra_step": true,
+ "name": "Download darwin cosign signature"
+ },
+ {
+ "cmd": [
+ "chmod",
+ "755",
+ "[START_DIR]/cosign/darwin/bin/cosign"
+ ],
+ "name": "Make darwin cosign binary executable"
+ },
+ {
+ "cmd": [
+ "cosign",
+ "verify-blob",
+ "--cert",
+ "[START_DIR]/cosign/darwin/certificate/cosign-cert.pem",
+ "--signature",
+ "[START_DIR]/cosign/darwin/certificate/cosign-sig.sig",
+ "[START_DIR]/cosign/darwin/bin/cosign"
+ ],
+ "name": "Verify darwin cosign binary is legitimate"
+ },
+ {
+ "cmd": [
+ "cipd",
+ "pkg-build",
+ "-in",
+ "[START_DIR]/cosign/darwin",
+ "-name",
+ "flutter/tools/cosign/mac-amd64",
+ "-out",
+ "cosign.zip",
+ "-hash-algo",
+ "sha256",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "build mac-amd64",
+ "~followup_annotations": [
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"flutter/tools/cosign/mac-amd64\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "pkg-register",
+ "cosign.zip",
+ "-ref",
+ "latest",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "register flutter/tools/cosign/mac-amd64",
+ "~followup_annotations": [
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"flutter/tools/cosign/mac-amd64\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@",
+ "@@@STEP_LINK@flutter/tools/cosign/mac-amd64@https://chrome-infra-packages.appspot.com/p/flutter/tools/cosign/mac-amd64/+/40-chars-fake-of-the-package-instance_id@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "curl",
+ "-L",
+ "cosign-linux-amd64",
+ "-o",
+ "[START_DIR]/cosign/linux/bin/cosign",
+ "--create-dirs"
+ ],
+ "infra_step": true,
+ "name": "Download linux cosign binary"
+ },
+ {
+ "cmd": [
+ "curl",
+ "-L",
+ "cosign-linux-amd64-keyless.pem",
+ "-o",
+ "[START_DIR]/cosign/linux/certificate/cosign-cert.pem",
+ "--create-dirs"
+ ],
+ "infra_step": true,
+ "name": "Download linux cosign certificate"
+ },
+ {
+ "cmd": [
+ "curl",
+ "-L",
+ "cosign-linux-amd64-keyless.sig",
+ "-o",
+ "[START_DIR]/cosign/linux/certificate/cosign-sig.sig",
+ "--create-dirs"
+ ],
+ "infra_step": true,
+ "name": "Download linux cosign signature"
+ },
+ {
+ "cmd": [
+ "chmod",
+ "755",
+ "[START_DIR]/cosign/linux/bin/cosign"
+ ],
+ "name": "Make linux cosign binary executable"
+ },
+ {
+ "cmd": [
+ "cosign",
+ "verify-blob",
+ "--cert",
+ "[START_DIR]/cosign/linux/certificate/cosign-cert.pem",
+ "--signature",
+ "[START_DIR]/cosign/linux/certificate/cosign-sig.sig",
+ "[START_DIR]/cosign/linux/bin/cosign"
+ ],
+ "name": "Verify linux cosign binary is legitimate"
+ },
+ {
+ "cmd": [
+ "cipd",
+ "pkg-build",
+ "-in",
+ "[START_DIR]/cosign/linux",
+ "-name",
+ "flutter/tools/cosign/linux-amd64",
+ "-out",
+ "cosign.zip",
+ "-hash-algo",
+ "sha256",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "build linux-amd64",
+ "~followup_annotations": [
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"flutter/tools/cosign/linux-amd64\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "pkg-register",
+ "cosign.zip",
+ "-ref",
+ "latest",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "register flutter/tools/cosign/linux-amd64",
+ "~followup_annotations": [
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"flutter/tools/cosign/linux-amd64\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@",
+ "@@@STEP_LINK@flutter/tools/cosign/linux-amd64@https://chrome-infra-packages.appspot.com/p/flutter/tools/cosign/linux-amd64/+/40-chars-fake-of-the-package-instance_id@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "curl",
+ "-L",
+ "cosign-windows-amd64.exe",
+ "-o",
+ "[START_DIR]/cosign/windows/bin/cosign.exe",
+ "--create-dirs"
+ ],
+ "infra_step": true,
+ "name": "Download windows cosign binary"
+ },
+ {
+ "cmd": [
+ "curl",
+ "-L",
+ "cosign-windows-amd64.exe-keyless.pem",
+ "-o",
+ "[START_DIR]/cosign/windows/certificate/cosign-cert.exe.pem",
+ "--create-dirs"
+ ],
+ "infra_step": true,
+ "name": "Download windows cosign certificate"
+ },
+ {
+ "cmd": [
+ "curl",
+ "-L",
+ "cosign-windows-amd64.exe-keyless.sig",
+ "-o",
+ "[START_DIR]/cosign/windows/certificate/cosign-sig.exe.sig",
+ "--create-dirs"
+ ],
+ "infra_step": true,
+ "name": "Download windows cosign signature"
+ },
+ {
+ "cmd": [
+ "cosign",
+ "verify-blob",
+ "--cert",
+ "[START_DIR]/cosign/windows/certificate/cosign-cert.exe.pem",
+ "--signature",
+ "[START_DIR]/cosign/windows/certificate/cosign-sig.exe.sig",
+ "[START_DIR]/cosign/windows/bin/cosign.exe"
+ ],
+ "name": "Verify windows cosign binary is legitimate"
+ },
+ {
+ "cmd": [
+ "cipd",
+ "pkg-build",
+ "-in",
+ "[START_DIR]/cosign/windows",
+ "-name",
+ "flutter/tools/cosign/windows-amd64",
+ "-out",
+ "cosign.zip",
+ "-hash-algo",
+ "sha256",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "build windows-amd64",
+ "~followup_annotations": [
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"flutter/tools/cosign/windows-amd64\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@"
+ ]
+ },
+ {
+ "cmd": [
+ "cipd",
+ "pkg-register",
+ "cosign.zip",
+ "-ref",
+ "latest",
+ "-json-output",
+ "/path/to/tmp/json"
+ ],
+ "name": "register flutter/tools/cosign/windows-amd64",
+ "~followup_annotations": [
+ "@@@STEP_LOG_LINE@json.output@{@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"result\": {@@@",
+ "@@@STEP_LOG_LINE@json.output@ \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+ "@@@STEP_LOG_LINE@json.output@ \"package\": \"flutter/tools/cosign/windows-amd64\"@@@",
+ "@@@STEP_LOG_LINE@json.output@ }@@@",
+ "@@@STEP_LOG_LINE@json.output@}@@@",
+ "@@@STEP_LOG_END@json.output@@@",
+ "@@@STEP_LINK@flutter/tools/cosign/windows-amd64@https://chrome-infra-packages.appspot.com/p/flutter/tools/cosign/windows-amd64/+/40-chars-fake-of-the-package-instance_id@@@"
+ ]
+ },
+ {
+ "name": "$result"
+ }
+]
\ No newline at end of file
diff --git a/recipes/cipd/cosign.py b/recipes/cipd/cosign.py
new file mode 100644
index 0000000..8ef69bd
--- /dev/null
+++ b/recipes/cipd/cosign.py
@@ -0,0 +1,275 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+from datetime import datetime
+
+PYTHON_VERSION_COMPATIBILITY = 'PY3'
+
+DEPS = [
+ 'flutter/repo_util',
+ 'flutter/flutter_deps',
+ 'recipe_engine/cipd',
+ 'recipe_engine/context',
+ 'recipe_engine/path',
+ 'recipe_engine/platform',
+ 'recipe_engine/properties',
+ 'recipe_engine/raw_io',
+ 'recipe_engine/step',
+]
+
+# This recipe builds the cosign CIPD package.
+def RunSteps(api):
+ env = {}
+ env_prefixes = {}
+ api.flutter_deps.required_deps(
+ env, env_prefixes, api.properties.get('dependencies', [])
+ )
+
+ cosign_default_dir = api.path['start_dir'].join('cosign')
+
+ cosign_download_uris = GetLatestCosignDownloadUris(api)
+
+ for platform in ['darwin', 'linux', 'windows']:
+ cosign_dir = cosign_default_dir.join(platform)
+
+ DownloadCosignArtifacts(api, cosign_dir, platform, cosign_download_uris)
+
+ with api.context(env=env, env_prefixes=env_prefixes):
+ VerifyCosignArtifactSignature(api, cosign_dir, platform)
+
+ UploadCosignToCipd(api, cosign_dir, platform)
+
+
+def GetLatestCosignDownloadUris(api):
+ """Gets the list of latest sigstore/cosign binary urls.
+
+ Queries Github for a list of cosign releases, then picks the latest release,
+ queries the artifacts for that release, and returns a list of cosign binary
+ urls for that release.
+
+ Args:
+ api: luci api object.
+ """
+ cosign_releases_raw_response = api.step('Get cosign releases from github',
+ cmd=['curl', 'https://api.github.com/repos/sigstore/cosign/releases'],
+ stdout=api.raw_io.output_text()
+ ).stdout
+ cosign_releases = json.loads(cosign_releases_raw_response)
+
+ latest_release = max(
+ cosign_releases,
+ key=lambda release: datetime.strptime(
+ release.get('published_at'), '%Y-%m-%dT%H:%M:%SZ')
+ ).get('url')
+
+ release_artifacts_raw_response = api.step(
+ 'Get artifacts from sigstore/cosign for a specific release version',
+ cmd=['curl', latest_release],
+ stdout=api.raw_io.output_text()
+ ).stdout
+ release_artifacts = json.loads(release_artifacts_raw_response)
+
+ release_artifacts_download_uris = list(
+ map(
+ lambda asset:
+ asset.get('browser_download_url'),
+ release_artifacts.get('assets')
+ )
+ )
+
+ return release_artifacts_download_uris
+
+def DownloadCosignArtifacts(api, cosign_dir, platform, cosign_download_uris):
+ """Downloads the latest cosign binary, certificate, and signature.
+
+ Takes a list of cosign download uris and finds the binary, certificate, and
+ signature urls based on the platform. Then, the three files are downloaded to
+ the cosign directory.
+
+ Args:
+ api: luci api object.
+ cosign_dir(str): the folder where the cosign binary/certificate/signature
+ will be downloaded.
+ platform(str): the platform of the binary that needs to be downloaded
+ (windows, linux, darwin)
+ cosign_download_uris(list(str)): a list of all the download uris for a
+ specific cosign release.
+ """
+ exe = '.exe' if platform == 'windows' else ''
+ cosign_base_name = 'cosign-%s-amd64%s' % (platform, exe)
+
+ cosign_binary_download_uri = next(
+ filter(
+ lambda uri:
+ uri.endswith(cosign_base_name),
+ cosign_download_uris
+ )
+ )
+
+ cosign_certificate_download_uri = next(
+ filter(
+ lambda uri:
+ uri.endswith('%s-keyless.pem' % cosign_base_name),
+ cosign_download_uris
+ )
+ )
+
+ cosign_signature_download_uri = next(
+ filter(
+ lambda uri:
+ uri.endswith('%s-keyless.sig' % cosign_base_name),
+ cosign_download_uris
+ )
+ )
+
+ api.step(
+ 'Download %s cosign binary' % platform,
+ [
+ 'curl', '-L', cosign_binary_download_uri,
+ '-o', cosign_dir.join('bin', 'cosign%s' % exe),
+ '--create-dirs'
+ ],
+ infra_step=True
+ )
+
+ api.step(
+ 'Download %s cosign certificate' % platform,
+ [
+ 'curl', '-L', cosign_certificate_download_uri,
+ '-o', cosign_dir.join("certificate", "cosign-cert%s.pem" % exe),
+ '--create-dirs'
+ ],
+ infra_step=True
+ )
+
+ api.step(
+ 'Download %s cosign signature' % platform,
+ [
+ 'curl', '-L', cosign_signature_download_uri,
+ '-o', cosign_dir.join("certificate", "cosign-sig%s.sig" % exe),
+ '--create-dirs'
+ ],
+ infra_step=True
+ )
+
+ if platform == 'linux' or platform == 'darwin':
+ api.step(
+ 'Make %s cosign binary executable' % platform,
+ [
+ 'chmod',
+ '755',
+ cosign_dir.join('bin', 'cosign%s' % exe)
+ ]
+ )
+
+
+def VerifyCosignArtifactSignature(api, cosign_dir, platform):
+ """Verifies the cosign artifact is legitimate
+
+ Uses the cosign release signature and certificate to ensure that the cosign
+ binary is legitimate. This is done using the current version of cosign in
+ CIPD.
+
+ Args:
+ api: luci api object.
+ cosign_dir(str): the folder where the cosign binary/certificate/signature
+ is located.
+ platform(str): the platform of the binary (windows, linux, darwin)
+ """
+ exe = '.exe' if platform == 'windows' else ''
+
+ api.step(
+ 'Verify %s cosign binary is legitimate' % platform,
+ [
+ 'cosign',
+ 'verify-blob',
+ '--cert',
+ cosign_dir.join("certificate", "cosign-cert%s.pem" % exe),
+ '--signature',
+ cosign_dir.join("certificate", "cosign-sig%s.sig" % exe),
+ cosign_dir.join("bin", "cosign%s" % exe)
+ ]
+ )
+
+
+def UploadCosignToCipd(api, cosign_dir, platform):
+ """Uploads cosign to CIPD.
+
+ Upload the cosign binary, certificate, and signature to CIPD and adds the
+ latest tag to this version.
+
+ Args:
+ api: luci api object.
+ cosign_dir(str): the folder where the cosign binary/certificate/signature
+ is located.
+ platform(str): the platform of the binary (windows, linux, darwin)
+ """
+ cipd_platform = 'mac' if platform == 'darwin' else platform
+ cipd_package_name = 'flutter/tools/cosign/%s-amd64' % cipd_platform
+ cipd_zip_path = 'cosign.zip'
+ api.cipd.build(cosign_dir, cipd_zip_path, cipd_package_name)
+ api.cipd.register(cipd_package_name, cipd_zip_path, refs=['latest'])
+
+
+def GenTests(api):
+ yield api.test(
+ 'cosign',
+ api.properties(cosign_version='v1.0'),
+ api.platform('linux', 64),
+ api.step_data(
+ 'Get cosign releases from github',
+ stdout=api.raw_io.output_text('''
+ [
+ {
+ "url": "https://api.github.com/releases/1",
+ "published_at": "2022-06-03T14:08:35Z"
+ },
+ {
+ "url": "https://api.github.com/releases/2",
+ "published_at": "2022-06-02T14:08:35Z"
+ }
+ ]
+ ''')
+ ) +
+ api.step_data(
+ 'Get artifacts from sigstore/cosign for a specific release version',
+ stdout=api.raw_io.output_text('''
+ {
+ "assets":[
+ {
+ "browser_download_url":"cosign-linux-amd64"
+ },
+ {
+ "browser_download_url":"cosign-linux-amd64-keyless.pem"
+ },
+ {
+ "browser_download_url":"cosign-linux-amd64-keyless.sig"
+ },
+ {
+ "browser_download_url":"cosign-darwin-amd64"
+ },
+ {
+ "browser_download_url":"cosign-darwin-amd64-keyless.pem"
+ },
+ {
+ "browser_download_url":"cosign-darwin-amd64-keyless.sig"
+ },
+ {
+ "browser_download_url":"cosign-windows-amd64.exe"
+ },
+ {
+ "browser_download_url":"cosign-windows-amd64.exe-keyless.pem"
+ },
+ {
+ "browser_download_url":"cosign-windows-amd64.exe-keyless.sig"
+ },
+ {
+ "browser_download_url":"some-other-artifact"
+ }
+ ]
+ }
+ ''')
+ )
+ )