From 49cdc2bea37bd327e8a95c4410b11fa7019feeb4 Mon Sep 17 00:00:00 2001 From: Ruslan Kuprieiev Date: Fri, 26 Mar 2021 02:31:32 +0200 Subject: [PATCH] build: add osx signing/notarization (#5695) * build: add osx signing/notarization * gha: temporarily enable pkg builds --- .github/workflows/packages.yaml | 3 ++ scripts/build.py | 54 ++++++++++++++++++++++ scripts/fpm/notarize.py | 64 ++++++++++++++++++++++++++ scripts/fpm/sign.py | 54 ++++++++++++++++++++++ scripts/pyinstaller/entitlements.plist | 11 +++++ scripts/pyinstaller/sign.py | 37 +++++++++++++++ 6 files changed, 223 insertions(+) create mode 100644 scripts/fpm/notarize.py create mode 100644 scripts/fpm/sign.py create mode 100644 scripts/pyinstaller/entitlements.plist create mode 100644 scripts/pyinstaller/sign.py diff --git a/.github/workflows/packages.yaml b/.github/workflows/packages.yaml index 747297225..1ee05f6a7 100644 --- a/.github/workflows/packages.yaml +++ b/.github/workflows/packages.yaml @@ -1,5 +1,8 @@ name: Build packages on: + pull_request: + branches: + - master push: branches: - master diff --git a/scripts/build.py b/scripts/build.py index 2250d0f13..219ea27d3 100644 --- a/scripts/build.py +++ b/scripts/build.py @@ -14,6 +14,13 @@ parser = argparse.ArgumentParser() parser.add_argument( "pkg", choices=["deb", "rpm", "osxpkg", "exe"], help="package type" ) +parser.add_argument("--sign-application", default=False, action="store_true") +parser.add_argument("--application-id") +parser.add_argument("--sign-installer", default=False, action="store_true") +parser.add_argument("--installer-id") +parser.add_argument("--notarize") +parser.add_argument("--apple-id-username") +parser.add_argument("--apple-id-password") args = parser.parse_args() (dvc / "utils" / "build.py").write_text(f'PKG = "{args.pkg}"') @@ -39,6 +46,18 @@ check_call( ["python", "build.py"], cwd=pyinstaller, stderr=STDOUT, ) +if args.sign_application: + if args.pkg != "osxpkg": + raise NotImplementedError + if not args.application_id: + print("--sign-application requires --application-id") + exit(1) + check_call( + ["python", "sign.py", "--application-id", args.application_id], + cwd=pyinstaller, + stderr=STDOUT, + ) + if args.pkg == "exe": check_call( ["python", "build.py"], cwd=innosetup, stderr=STDOUT, @@ -47,3 +66,38 @@ else: check_call( ["python", "build.py", args.pkg], cwd=fpm, stderr=STDOUT, ) + +if args.sign_installer: + if args.pkg != "osxpkg": + raise NotImplementedError + if not all( + [args.installer_id, args.apple_id_username, args.apple_id_password] + ): + print("--sign-installer requires --installer-id") + exit(1) + check_call( + ["python", "sign.py", "--installer-id", args.installer_id], + cwd=fpm, + stderr=STDOUT, + ) + +if args.notarize: + if args.pkg != "osxpkg": + raise NotImplementedError + if not all([args.apple_id_username, args.apple_id_password]): + print( + "--notarize requires --apple-id-username and --apple-id-password" + ) + exit(1) + check_call( + [ + "python", + "notarize.py", + "--apple-id-username", + args.apple_id_username, + "--apple-id-password", + args.apple_id_password, + ], + cwd=fpm, + stderr=STDOUT, + ) diff --git a/scripts/fpm/notarize.py b/scripts/fpm/notarize.py new file mode 100644 index 000000000..3b3f2c54a --- /dev/null +++ b/scripts/fpm/notarize.py @@ -0,0 +1,64 @@ +import argparse +import json +import os +import pathlib +import sys +from subprocess import STDOUT, check_call + +if sys.platform != "Darwin": + raise NotImplementedError + +parser = argparse.ArgumentParser() +parser.add_argument( + "path", + nargs="?", + help="Path to the osxpkg to notarize. If not specified - try to find one.", +) +parser.add_argument( + "--apple-id-username", required=True, help="Apple ID username." +) +parser.add_argument( + "--apple-id-password", + required=True, + help=( + "Apple ID app-specific password. Note that this is not a regular " + "Apple ID password, so you need to generate one at " + "https://appleid.apple.com/account/manage" + ), +) +args = parser.parse_args() + +path = pathlib.Path(__file__).parent.absolute() + +if args.path: + pkg = pathlib.Path(args.path) +else: + pkgs = list(path.glob("*.pkg")) + if not pkgs: + print("No pkgs found") + exit(1) + + if len(pkgs) > 1: + print("Too many packages") + exit(1) + + (pkg,) = pkgs + + +config = { + "notarize": { + "path": os.fspath(pkg), + "bundle_id": "com.iterative.dvc", + "staple": True, + }, + "apple_id": { + "username": args.apple_id_username, + "password": args.apple_id_password, + }, +} + +(path / "config.json").write_text(json.dumps(config)) + +check_call( + ["gon", "config.json"], cwd=path, stderr=STDOUT, +) diff --git a/scripts/fpm/sign.py b/scripts/fpm/sign.py new file mode 100644 index 000000000..17bfdf9a8 --- /dev/null +++ b/scripts/fpm/sign.py @@ -0,0 +1,54 @@ +import argparse +import os +import pathlib +import sys +from subprocess import STDOUT, check_call + +if sys.platform != "darwin": + raise NotImplementedError + +parser = argparse.ArgumentParser() +parser.add_argument( + "path", + nargs="?", + help="Path to the osxpkg to sign. If not specified - try to find one.", +) +parser.add_argument( + "--installer-id", + required=True, + help="Certificate ID (should be added to the keychain).", +) +args = parser.parse_args() + +path = pathlib.Path(__file__).parent.absolute() + +if args.path: + pkg = pathlib.Path(args.path) +else: + pkgs = list(path.glob("*.pkg")) + if not pkgs: + print("No pkgs found") + exit(1) + + if len(pkgs) > 1: + print("Too many packages") + exit(1) + + (pkg,) = pkgs + +unsigned = pkg.with_suffix(".unsigned") +os.rename(pkg, unsigned) +check_call( + [ + "productsign", + "--sign", + args.installer_id, + os.fspath(unsigned), + os.fspath(pkg), + ], + stderr=STDOUT, +) + +check_call( + ["pkgutil", "--check-signature", os.fspath(pkg)], stderr=STDOUT, +) diff --git a/scripts/pyinstaller/entitlements.plist b/scripts/pyinstaller/entitlements.plist new file mode 100644 index 000000000..5f4ee2ade --- /dev/null +++ b/scripts/pyinstaller/entitlements.plist @@ -0,0 +1,11 @@ + + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + + diff --git a/scripts/pyinstaller/sign.py b/scripts/pyinstaller/sign.py new file mode 100644 index 000000000..072c4f2f4 --- /dev/null +++ b/scripts/pyinstaller/sign.py @@ -0,0 +1,37 @@ +import argparse +import os +import pathlib +import sys +from subprocess import STDOUT, check_call + +if sys.platform != "darwin": + raise NotImplementedError + +parser = argparse.ArgumentParser() +parser.add_argument( + "--application-id", + required=True, + help="Certificate ID (should be added to the keychain).", +) +args = parser.parse_args() + +path = pathlib.Path(__file__).parent.absolute() +dvc = path / "dist" / "dvc" +for root, _, fnames in os.walk(dvc): + for fname in fnames: + fpath = os.path.join(root, fname) + print(f"signing {fpath}") + check_call( + [ + "codesign", + "--force", + "-s", + args.application_id, + "-o", + "runtime", + "--entitlements", + "entitlements.plist", + fpath, + ], + stderr=STDOUT, + )