blob: 7b4bfeb4c9dfc14e9a771968f1c9dd99e04aa614 [file] [log] [blame]
# Copyright 2023 The Dart Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from recipe_engine.post_process import (MustRun, DoesNotRunRE, DropExpectation)
from PB.recipes.dart.release.sign import Sign
import datetime
import json
DEPS = [
'dart',
'depot_tools/osx_sdk',
'depot_tools/gsutil',
'recipe_engine/archive',
'recipe_engine/bcid_reporter',
'recipe_engine/buildbucket',
'recipe_engine/context',
'recipe_engine/cipd',
'recipe_engine/file',
'recipe_engine/path',
'recipe_engine/properties',
'recipe_engine/raw_io',
'recipe_engine/runtime',
'recipe_engine/service_account',
'recipe_engine/step',
'recipe_engine/time',
]
ARCHS = ['x64', 'arm64']
IDENTITY = 'Developer ID Application: FLUTTER.IO LLC (S8QB4VV633)'
BINARIES = [
'bin/dart', 'bin/dartaotruntime', 'bin/utils/gen_snapshot',
'bin/utils/wasm-opt'
]
PROPERTIES = Sign
PYTHON_VERSION_COMPATIBILITY = 'PY3'
def _arch_to_cipd_arch(arch):
return {
'ia32': '386',
'x64': 'amd64',
'arm': 'armv6l',
}.get(arch, arch)
def _is_already_signed(api, version, channel, zip_name):
return api.gsutil.stat(
f'gs://dart-archive/channels/{channel}/signed/{version}/sdk/{zip_name}',
name=f'check if {zip_name} {version} is already signed',
ok_ret=[0, 1]).exc_result.retcode == 0
def _get_secret(api, name, object_path=None, extension='.key'):
return api.dart.get_secret(
name,
bucket='flutter_configs',
keyring='projects/flutter-infra-staging/locations/global'
'/keyRings/luci/cryptoKeys/flutter-infra',
object_path=object_path,
extension=extension)
def _get_commit_of_version(api, version, channel):
return json.loads(
api.gsutil.cat(
f'gs://dart-archive/channels/{channel}/raw/{version}/VERSION',
name=f'read {version} VERSION',
stdout=api.raw_io.output_text()).stdout)['revision']
def _download(api, version, channel, directory, zip_name):
zip_file = directory.join(zip_name)
api.dart.download_and_verify(
f'raw {zip_name} {version}', 'dart-archive',
f'channels/{channel}/raw/{version}/sdk/{zip_name}', zip_file,
'misc_software://dart/sdk/macos')
def _codesign(api, raw_dir, signed_dir, notary_dir, arch):
zip_basename = f'dartsdk-macos-{arch}-release'
zip_name = f'{zip_basename}.zip'
zip_file = raw_dir.join(zip_name)
notary_subdir = notary_dir.join(zip_basename)
api.file.ensure_directory(f'mkdir notary/{zip_basename}', notary_subdir)
api.archive.extract(f'extract dart-sdk from raw {zip_name}', zip_file,
api.path['cleanup'])
for binary in BINARIES:
program = api.path['cleanup'].join(f'dart-sdk/{binary}')
api.path.mock_add_paths(program)
if api.path.exists(program):
basename = api.path.basename(program)
entitlements = api.resource(f'Entitlements_{basename}.plist')
command = [
'/usr/bin/codesign', '-f', '-s', IDENTITY, '--timestamp',
'--options=runtime', '--verbose'
]
api.path.mock_add_paths(entitlements)
if api.path.exists(entitlements):
command.append(f'--entitlements={entitlements}')
command.append(program)
program_id = f'{zip_basename}/{basename}'
api.step(f'codesign {program_id}', command)
api.step(f'verify {program_id}', ['codesign', '-v', '--verbose', program])
api.file.copy(f'save {program_id} for notarization', program,
notary_subdir.join(basename))
dart_sdk_path = api.path['cleanup'].join('dart-sdk')
(api.archive.package(api.path['cleanup']).with_dir(dart_sdk_path).archive(
f'zip signed {zip_name}', signed_dir.join(zip_name)))
cipd_platform = 'mac-' + _arch_to_cipd_arch(arch)
cipd_file = signed_dir.join(f'{cipd_platform}.zip')
package_name = f'dart/dart-sdk/{cipd_platform}'
pkg = api.cipd.PackageDefinition(
package_name, dart_sdk_path, 'copy', preserve_writable=True)
pkg.add_dir(dart_sdk_path)
api.cipd.build_from_pkg(pkg, cipd_file)
api.file.rmtree(f'delete dart-sdk from raw {zip_name}',
api.path['cleanup'].join('dart-sdk'))
def _notarize(api, notary_dir, password_file, apple_id_file, team_id_file):
notary_zip = f'{notary_dir}.zip'
(api.archive.package(notary_dir).with_dir(notary_dir).archive(
f'zip for notarization', notary_zip))
with api.osx_sdk('mac'):
api.step('notarize', [
'sh',
api.resource('notarize.sh'), password_file, apple_id_file, team_id_file,
'xcrun', 'notarytool', 'submit', notary_zip, '--wait'
])
def _sign(api, archs, raw_dir, signed_dir, notary_dir, password_file,
apple_id_file, team_id_file, codesign_key):
try:
api.step(
'delete previous keychain',
['security', 'delete-keychain', 'build.keychain'],
ok_ret='any')
api.step('create keychain',
['security', 'create-keychain', '-p', '', 'build.keychain'])
api.step('keychain settings',
['security', 'set-keychain-settings', 'build.keychain'])
api.step('default keychain',
['security', 'default-keychain', '-s', 'build.keychain'])
api.step('unlock keychain',
['security', 'unlock-keychain', '-p', '', 'build.keychain'])
api.step('import certificate keychain', [
'security', 'import', codesign_key, '-k', 'build.keychain', '-P',
'password', '-T', '/usr/bin/codesign'
])
api.step('set key partition list', [
'security', 'set-key-partition-list', '-S',
'apple-tool:,apple:,codesign:', '-s', '-k', '', 'build.keychain'
])
api.step('list-keychains', ['security', 'list-keychains', '-d', 'user'])
identities = api.step(
'find-identity', ['security', 'find-identity', '-v'],
stdout=api.raw_io.output_text(add_output_log=True)).stdout
if not IDENTITY in identities:
raise api.step.InfraFailure(f'Failed to import {IDENTITY}')
for arch in archs:
_codesign(api, raw_dir, signed_dir, notary_dir, arch)
_notarize(api, notary_dir, password_file, apple_id_file, team_id_file)
finally:
api.step(
'lock keychain', ['security', 'lock-keychain', 'build.keychain'],
ok_ret='any')
api.step(
'delete keychain', ['security', 'delete-keychain', 'build.keychain'],
ok_ret='any')
api.step('restore default keychain',
['security', 'default-keychain', '-s', 'login.keychain'])
def _upload(api, version, commit, channel, directory, arch):
zip_name = f'dartsdk-macos-{arch}-release.zip'
zip_file = directory.join(zip_name)
sha256 = api.file.file_hash(zip_file)
sha256_name = zip_name + '.sha256sum'
sha256_path = directory.join(sha256_name)
api.file.write_text(f'write {sha256_name}', sha256_path,
f'{sha256} *{zip_name}\n')
bucket = 'dart-archive'
object_path = f'channels/{channel}/signed/{version}/sdk/{zip_name}'
api.gsutil.upload(
zip_file, bucket, object_path, name=f'upload signed {zip_name} {version}')
api.gsutil.upload(
sha256_path,
bucket,
object_path + '.sha256sum',
name=f'upload signed {sha256_name} {version}')
api.bcid_reporter.report_gcs(sha256, f'gs://{bucket}/{object_path}')
cipd_platform = 'mac-' + _arch_to_cipd_arch(arch)
cipd_file = directory.join(f'{cipd_platform}.zip')
package_name = f'dart/dart-sdk/{cipd_platform}'
pin = api.cipd.register(
package_name, cipd_file, tags={'git_revision': commit})
sha256 = api.file.file_hash(cipd_file)
api.bcid_reporter.report_cipd(sha256, pin.package, pin.instance_id)
def RunSteps(api, properties):
assert not api.buildbucket.builder_name.endswith(
'-try'), 'tryjob is not supported'
api.bcid_reporter.report_stage('start')
version = properties.version
commit = None
if version:
channel = api.dart.Version(version=version).fields['CHANNEL']
else:
commit = api.buildbucket.gitiles_commit.id
ref = api.buildbucket.gitiles_commit.ref
assert ref.startswith(
'refs/heads/'), f'build must be triggered on a branch: {ref}'
channel = ref[len('refs/heads/'):]
version = f'hash/{commit}'
all_archs = properties.archs
assert all_archs, 'the recipe requires a list of archs'
api.bcid_reporter.report_stage('fetch')
with api.context(cwd=api.path['cleanup']):
all_signed = True
archs = []
for arch in all_archs:
if api.runtime.is_experimental or \
not _is_already_signed(api, version, channel,
f'dartsdk-macos-{arch}-release.zip'):
archs.append(arch)
if not archs:
api.step.empty(f'dart sdk {version} is already signed')
return
raw_dir = api.path['cleanup'].join('raw')
signed_dir = api.path['cleanup'].join('signed')
notary_dir = api.path['cleanup'].join('notary')
api.file.ensure_directory('mkdir raw', raw_dir)
api.file.ensure_directory('mkdir signed', signed_dir)
api.file.ensure_directory('mkdir notary', notary_dir)
if not commit:
commit = _get_commit_of_version(api, version, channel)
for arch in archs:
_download(api, version, channel, raw_dir,
f'dartsdk-macos-{arch}-release.zip')
password_file = _get_secret(api, 'codesign_app_specific_password')
apple_id_file = _get_secret(api, 'codesign_app_store_id')
team_id_file = _get_secret(api, 'codesign_team_id')
codesign_key = _get_secret(
api, 'codesign_key', object_path='dart_encrypted.p12', extension='.p12')
api.bcid_reporter.report_stage('compile')
_sign(api, archs, raw_dir, signed_dir, notary_dir, password_file,
apple_id_file, team_id_file, codesign_key)
api.bcid_reporter.report_stage('upload')
if not api.runtime.is_experimental:
for arch in archs:
_upload(api, version, commit, channel, signed_dir, arch)
api.bcid_reporter.report_stage('upload-complete')
def GenTests(api):
find_identity_bad = api.step_data(
'find-identity',
stdout=api.raw_io.output_text('''
1) 72D8CF20531F3ABD251E5A15335E85F677A709E9 "MDM Client Identity Certificate"
1 valid identities found
'''))
find_identity = api.step_data(
'find-identity',
stdout=api.raw_io.output_text(f'''
1) E4CE0EFC4C211873BDBF547104E1F7FFEE0B70CF "{IDENTITY}"
2) 72D8CF20531F3ABD251E5A15335E85F677A709E9 "MDM Client Identity Certificate"
2 valid identities found
'''))
ci_build = api.buildbucket.ci_build(
project='dart-internal',
builder='sign-mac',
git_repo='https://dart.googlesource.com/sdk',
git_ref='refs/heads/stable',
revision='abfdc3d50f6cf66165767da8df4f681a68467178')
version_data = api.step_data(
'gsutil read 2.19.5 VERSION',
stdout=api.raw_io.output_text(
json.dumps({
'date': '2023-03-20',
'version': '2.19.5',
'revision': 'a318303f460e3c9d91e9aa2bdc73040ccbb885c6',
})))
yield api.test(
'sign',
api.properties(Sign(archs=ARCHS)),
ci_build,
api.step_data(
'gsutil check if dartsdk-macos-x64-release.zip '
'hash/abfdc3d50f6cf66165767da8df4f681a68467178 is already signed',
retcode=1),
api.step_data(
'gsutil check if dartsdk-macos-arm64-release.zip '
'hash/abfdc3d50f6cf66165767da8df4f681a68467178 is already signed',
retcode=1),
api.step_data(
'verify raw dartsdk-macos-x64-release.zip hash/abfdc3d50f6cf66165767da8df4f681a68467178 provenance',
stdout=api.raw_io.output_text('{"allowed": true}')),
api.step_data(
'verify raw dartsdk-macos-arm64-release.zip hash/abfdc3d50f6cf66165767da8df4f681a68467178 provenance',
stdout=api.raw_io.output_text('{"allowed": true}')),
api.post_process(MustRun, 'cloudkms get key codesign_key'),
api.step_data('delete previous keychain', retcode=1),
find_identity,
api.post_process(MustRun, 'codesign dartsdk-macos-x64-release/dart'),
api.post_process(MustRun, 'notarize'),
api.post_process(MustRun, 'delete keychain'),
api.post_process(
MustRun,
'gsutil upload signed dartsdk-macos-x64-release.zip hash/abfdc3d50f6cf66165767da8df4f681a68467178'
),
api.post_process(
MustRun,
'gsutil upload signed dartsdk-macos-arm64-release.zip hash/abfdc3d50f6cf66165767da8df4f681a68467178'
),
api.post_process(MustRun, 'register dart/dart-sdk/mac-amd64'),
api.post_process(MustRun, 'register dart/dart-sdk/mac-arm64'),
api.post_process(MustRun, 'snoop: report_stage (5)'),
)
yield api.test(
'sign-failed',
api.properties(Sign(version='2.19.5', archs=ARCHS)),
api.step_data(
'gsutil check if dartsdk-macos-x64-release.zip '
'2.19.5 is already signed',
retcode=1),
api.step_data(
'gsutil check if dartsdk-macos-arm64-release.zip '
'2.19.5 is already signed',
retcode=1),
version_data,
api.step_data(
'verify raw dartsdk-macos-x64-release.zip 2.19.5 provenance',
stdout=api.raw_io.output_text('{"allowed": true}')),
api.step_data(
'verify raw dartsdk-macos-arm64-release.zip 2.19.5 provenance',
stdout=api.raw_io.output_text('{"allowed": true}')),
api.post_process(MustRun, 'cloudkms get key codesign_key'),
api.step_data('delete previous keychain', retcode=1),
find_identity_bad,
api.post_process(DropExpectation),
status='INFRA_FAILURE',
)
yield api.test(
'already',
api.properties(Sign(version='2.19.5', archs=ARCHS)),
api.post_process(DoesNotRunRE, 'cloudkms get key codesign_key'),
api.post_process(DoesNotRunRE, 'notarize'),
api.post_process(DoesNotRunRE, 'gsutil upload signed.*'),
api.post_process(DoesNotRunRE, 'snoop: report_stage \(5\)'),
api.post_process(DoesNotRunRE, 'register dart/dart-sdk/.*'),
)
yield api.test(
'dry',
api.runtime(is_experimental=True),
api.properties(Sign(version='2.19.5', archs=ARCHS)),
version_data,
api.step_data(
'verify raw dartsdk-macos-x64-release.zip 2.19.5 provenance',
stdout=api.raw_io.output_text('{"allowed": true}')),
api.step_data(
'verify raw dartsdk-macos-arm64-release.zip 2.19.5 provenance',
stdout=api.raw_io.output_text('{"allowed": true}')),
api.post_process(MustRun, 'cloudkms get key codesign_key'),
api.step_data('delete previous keychain', retcode=1),
find_identity,
api.post_process(MustRun, 'codesign dartsdk-macos-x64-release/dart'),
api.post_process(MustRun, 'notarize'),
api.post_process(MustRun, 'delete keychain'),
api.post_process(DoesNotRunRE, 'gsutil upload signed.*'),
api.post_process(DoesNotRunRE, 'register dart/dart-sdk/.*'),
api.post_process(MustRun, 'snoop: report_stage (5)'),
)
yield api.test(
'provenance-rejected',
api.properties(Sign(version='2.19.5', archs=ARCHS)),
api.step_data(
'gsutil check if dartsdk-macos-x64-release.zip '
'2.19.5 is already signed',
retcode=1),
api.step_data(
'gsutil check if dartsdk-macos-arm64-release.zip '
'2.19.5 is already signed',
retcode=1),
version_data,
api.step_data(
'verify raw dartsdk-macos-x64-release.zip 2.19.5 provenance',
stdout=api.raw_io.output_text(
'{"rejectionMessage": "it\'s not me it\'s you"}')),
api.post_process(
MustRun,
'raw dartsdk-macos-x64-release.zip 2.19.5 provenance rejected: '
'it\'s not me it\'s you'),
api.post_process(DoesNotRunRE, 'cloudkms get key codesign_key'),
api.post_process(DoesNotRunRE, 'notarize'),
api.post_process(DoesNotRunRE, 'gsutil upload signed.*'),
api.post_process(DoesNotRunRE, 'register dart/dart-sdk/.*'),
api.post_process(DoesNotRunRE, 'snoop: report_stage \(5\)'),
status='FAILURE',
)
yield api.test(
'provenance-outage',
api.properties(Sign(version='2.19.5', archs=ARCHS)),
api.step_data(
'gsutil check if dartsdk-macos-x64-release.zip '
'2.19.5 is already signed',
retcode=1),
api.step_data(
'gsutil check if dartsdk-macos-arm64-release.zip '
'2.19.5 is already signed',
retcode=1),
version_data,
api.step_data(
'verify raw dartsdk-macos-x64-release.zip 2.19.5 provenance',
stdout=api.raw_io.output_text('internal server error')),
api.post_process(MustRun, 'Provenance verification API failed'),
api.post_process(DoesNotRunRE, 'cloudkms get key codesign_key'),
api.post_process(DoesNotRunRE, 'notarize'),
api.post_process(DoesNotRunRE, 'gsutil upload signed.*'),
api.post_process(DoesNotRunRE, 'register dart/dart-sdk/.*'),
api.post_process(DoesNotRunRE, 'snoop: report_stage \(5\)'),
status='INFRA_FAILURE',
)