Skip to content

Commit bb660f3

Browse files
committed
WIP
1 parent 14daceb commit bb660f3

6 files changed

Lines changed: 102 additions & 78 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/build.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,26 @@ struct Def {
4646
#[cfg(target_os = "macos")]
4747
fn write_plist() {
4848
let plist = format!(
49-
r#"
50-
<?xml version="1.0" encoding="UTF-8"?>
49+
r#"<?xml version="1.0" encoding="UTF-8"?>
5150
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
5251
<plist version="1.0">
5352
<dict>
54-
<key>CFBundlePackageType</key>
55-
<string>APPL</string>
56-
<key>CFBundleIdentifier</key>
57-
<string>com.amazon.codewhisperer</string>
58-
<key>CFBundleName</key>
59-
<string>{}</string>
60-
<key>CFBundleVersion</key>
61-
<string>{}</string>
62-
<key>CFBundleShortVersionString</key>
63-
<string>{}/string>
64-
<key>CFBundleInfoDictionaryVersion</key>
65-
<string>6.0</string>
66-
<key>NSHumanReadableCopyright</key>
67-
<string>Copyright © 2022 Amazon Q CLI Team (q-cli@amazon.com):Chay Nabors (nabochay@amazon.com):Brandon Kiser (bskiser@amazon.com) All rights reserved.</string>
53+
<key>CFBundlePackageType</key>
54+
<string>APPL</string>
55+
<key>CFBundleIdentifier</key>
56+
<string>com.amazon.codewhisperer</string>
57+
<key>CFBundleName</key>
58+
<string>{}</string>
59+
<key>CFBundleVersion</key>
60+
<string>{}</string>
61+
<key>CFBundleShortVersionString</key>
62+
<string>{}</string>
63+
<key>CFBundleInfoDictionaryVersion</key>
64+
<string>6.0</string>
65+
<key>NSHumanReadableCopyright</key>
66+
<string>Copyright © 2022 Amazon Q CLI Team (q-cli@amazon.com):Chay Nabors (nabochay@amazon.com):Brandon Kiser (bskiser@amazon.com) All rights reserved.</string>
6867
</dict>
68+
</plist>
6969
"#,
7070
option_env!("AMAZON_Q_BUILD_HASH").unwrap_or("unknown"),
7171
option_env!("AMAZON_Q_BUILD_DATETIME").unwrap_or("unknown"),

scripts/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
# cargo packages
2323
CLI_PACKAGE_NAME = "q_cli"
24-
CHAT_PACKAGE_NAME = "chat_cli"
24+
CHAT_PACKAGE_NAME = "cli"
2525
PTY_PACKAGE_NAME = "figterm"
2626
DESKTOP_PACKAGE_NAME = "fig_desktop"
2727
DESKTOP_FUZZ_PACKAGE_NAME = "fig_desktop-fuzz"

scripts/qchatbuild.py

Lines changed: 76 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from dataclasses import dataclass
12
import json
23
import pathlib
34
from functools import cache
@@ -8,14 +9,9 @@
89
from build import generate_sha
910
from const import APPLE_TEAM_ID, CHAT_BINARY_NAME, CHAT_PACKAGE_NAME, LINUX_ARCHIVE_NAME
1011
from signing import (
11-
CdSigningData,
12-
CdSigningType,
1312
load_gpg_signer,
14-
rebundle_dmg,
15-
cd_sign_file,
16-
apple_notarize_file,
1713
)
18-
from util import debug, info, isDarwin, run_cmd, warn
14+
from util import debug, info, isDarwin, run_cmd, run_cmd_output, warn
1915
from rust import cargo_cmd_name, rust_env, rust_targets
2016
from importlib import import_module
2117

@@ -24,6 +20,19 @@
2420

2521
REGION = "us-west-2"
2622
SIGNING_API_BASE_URL = "https://api.signer.builder-tools.aws.dev"
23+
MACOS_BUNDLE_ID = "com.amazon.codewhisperer"
24+
25+
26+
@dataclass
27+
class CdSigningData:
28+
bucket_name: str
29+
'''The bucket hosting signing artifacts accessible by CD Signer.'''
30+
aws_account_id: str
31+
'''The id of the account hosting the build'''
32+
aws_region: str
33+
'''The AWS region where the app is built'''
34+
notarizing_secret_id: str
35+
signing_role_name: str
2736

2837

2938
def run_cargo_tests():
@@ -179,73 +188,45 @@ def cd_signer_status_request(request_id: str):
179188
return response_json["signingRequest"]["status"]
180189

181190

182-
def cd_build_signed_package(file_path: pathlib.Path):
191+
def cd_build_signed_package(exe_path: pathlib.Path):
183192
"""
184193
Creates a tarball `package.tar.gz` with the following structure:
185194
```
186195
package
187-
├─ manifest.yaml
188-
├─ artifact
189-
| ├─ EXECUTABLES_TO_SIGN
190-
| | ├─ qchat
191-
```
192-
193-
Try #2:
194-
```
195-
package
196196
├─ EXECUTABLES_TO_SIGN
197197
| ├─ qchat
198198
```
199199
"""
200-
# working_dir = BUILD_DIR / "package"
201-
# shutil.rmtree(working_dir, ignore_errors=True)
202-
# (BUILD_DIR / "package" / "artifact" / "EXECUTABLES_TO_SIGN").mkdir(parents=True)
203-
#
204-
# name = file_path.name
205-
#
206-
# # Write the manifest.yaml
207-
# manifest_template_path = pathlib.Path.cwd() / "build-config" / "signing" / "qchat" / "manifest.yaml.template"
208-
# (working_dir / "manifest.yaml").write_text(manifest_template_path.read_text().replace("__NAME__", name))
209-
#
210-
# shutil.copy2(file_path, working_dir / "artifact" / "EXECUTABLES_TO_SIGN" / file_path.name)
211-
# file_path.unlink()
212-
#
213-
# run_cmd(
214-
# ["gtar", "-czf", BUILD_DIR / "package.tar.gz", "manifest.yaml", "artifact"],
215-
# cwd=working_dir,
216-
# )
217-
218200
# Trying a different format without manifest.yaml and placing EXECUTABLES_TO_SIGN
219201
# at the root.
220202
# The docs contain conflicting information, idk what to even do here
221203
working_dir = BUILD_DIR / "package"
222204
shutil.rmtree(working_dir, ignore_errors=True)
223205
(BUILD_DIR / "package" / "EXECUTABLES_TO_SIGN").mkdir(parents=True)
224206

225-
shutil.copy2(file_path, working_dir / "EXECUTABLES_TO_SIGN" / file_path.name)
226-
file_path.unlink()
207+
shutil.copy2(exe_path, working_dir / "EXECUTABLES_TO_SIGN" / exe_path.name)
208+
exe_path.unlink()
227209

210+
run_cmd(["gtar", "-czf", "artifact.gz", "EXECUTABLES_TO_SIGN"], cwd=working_dir)
228211
run_cmd(
229-
["gtar", "-czf", BUILD_DIR / "package.tar.gz", "."],
212+
["gtar", "-czf", BUILD_DIR / "package.tar.gz", "artifact.gz"],
230213
cwd=working_dir,
231214
)
232215

233216
return BUILD_DIR / "package.tar.gz"
234217

235218

236219
def manifest(
237-
name: str,
238220
identifier: str,
239221
):
240222
"""
241-
Creates the required manifest argument when submitting the signing task. This has the same
242-
structure as the manifest.yaml.template under `build-config/signing/qchat/manifest.yaml.template`
223+
Returns the manifest arguments required when creating a new CD Signer request.
243224
"""
244225
return {
245226
"type": "app",
246227
"os": "osx",
247-
"name": name,
248-
"outputs": [{"label": "macos", "path": name}],
228+
"name": "EXECUTABLES_TO_SIGN",
229+
"outputs": [{"label": "macos", "path": "EXECUTABLES_TO_SIGN"}],
249230
"app": {
250231
"identifier": identifier,
251232
"signing_requirements": {
@@ -256,7 +237,7 @@ def manifest(
256237
}
257238

258239

259-
def sign_executable(signing_data: CdSigningData, chat_path: pathlib.Path):
240+
def sign_executable(signing_data: CdSigningData, chat_path: pathlib.Path) -> pathlib.Path:
260241
name = chat_path.name
261242
info(f"Signing {name}")
262243

@@ -269,7 +250,7 @@ def sign_executable(signing_data: CdSigningData, chat_path: pathlib.Path):
269250
run_cmd(["aws", "s3", "cp", package_path, f"s3://{signing_data.bucket_name}/pre-signed/package.tar.gz"])
270251

271252
info("Sending request...")
272-
request_id = cd_signer_create_request(manifest(name, "com.amazon.codewhisperer"))
253+
request_id = cd_signer_create_request(manifest("com.amazon.codewhisperer"))
273254
cd_signer_start_request(
274255
request_id=request_id,
275256
source_key="pre-signed/package.tar.gz",
@@ -303,18 +284,60 @@ def sign_executable(signing_data: CdSigningData, chat_path: pathlib.Path):
303284
info("Signed!")
304285

305286
info("Downloading...")
306-
run_cmd(["aws", "s3", "cp", f"s3://{signing_data.bucket_name}/signed/signed.zip", "signed.zip"])
307-
run_cmd(["unzip", "signed.zip"])
287+
# CD Signer should return the signed executable under "Payload/EXECUTABLES_TO_SIGN/{executable name}"
288+
run_cmd(["aws", "s3", "cp", f"s3://{signing_data.bucket_name}/signed/signed.zip", "signed.zip"], cwd=BUILD_DIR)
289+
290+
# Create a new directory for unzipping the signed executable.
291+
payload_path = BUILD_DIR / "signed"
292+
shutil.rmtree(payload_path, ignore_errors=True)
293+
run_cmd(["unzip", "signed.zip", "-d", payload_path], cwd=BUILD_DIR)
294+
signed_exe_path = BUILD_DIR / "signed" / "Payload" / "EXECUTABLES_TO_SIGN" / name
295+
# Verify that the exe is signed
296+
run_cmd(["codesign", "--verify", "--verbose=4", signed_exe_path])
297+
return signed_exe_path
298+
299+
300+
def notarize_executable(signing_data: CdSigningData, executable_path: pathlib.Path):
301+
# Load the Apple id and password from secrets manager.
302+
secret_id = signing_data.notarizing_secret_id
303+
info(f"Loading secretmanager value: {secret_id}")
304+
secret_value = run_cmd_output(["aws", "--region", signing_data.aws_region, "secretsmanager", "get-secret-value", "--secret-id", secret_id])
305+
secret_string = json.loads(secret_value)["SecretString"]
306+
secrets = json.loads(secret_string)
307+
308+
# Submit the exe to Apple notary service. It must be zipped first.
309+
info(f"Submitting {executable_path} to Apple notary service")
310+
zip_path = BUILD_DIR / f"{executable_path.name}.zip"
311+
zip_path.unlink(missing_ok=True)
312+
run_cmd(["zip", "-j", zip_path, executable_path], cwd=BUILD_DIR)
313+
submit_res = run_cmd_output(
314+
[
315+
"xcrun",
316+
"notarytool",
317+
"submit",
318+
zip_path,
319+
"--team-id",
320+
APPLE_TEAM_ID,
321+
"--apple-id",
322+
secrets["appleId"],
323+
"--password",
324+
secrets["appleIdPassword"],
325+
"--wait",
326+
"-f", "json"
327+
]
328+
)
329+
debug(f"Notary service response: {submit_res}")
330+
331+
# Confirm notarization succeeded.
332+
assert(json.loads(submit_res)["status"] == "Accepted")
308333

309334

310335
def sign_and_notarize(signing_data: CdSigningData, chat_path: pathlib.Path):
311336
# First, sign the application
312-
sign_executable(signing_data, chat_path)
337+
executable_path = sign_executable(signing_data, chat_path)
313338

314339
# Next, notarize the application
315-
316-
# Last, staple the notarization to the application
317-
pass
340+
notarize_executable(signing_data, executable_path)
318341

319342

320343
def build_macos(chat_path: pathlib.Path, signing_data: CdSigningData | None):
@@ -382,16 +405,18 @@ def build(
382405
output_bucket: str | None = None,
383406
signing_bucket: str | None = None,
384407
aws_account_id: str | None = None,
408+
aws_region: str | None = None,
385409
apple_id_secret: str | None = None,
386410
signing_role_name: str | None = None,
387411
stage_name: str | None = None,
388412
run_lints: bool = True,
389413
run_test: bool = True,
390414
):
391-
if signing_bucket and aws_account_id and apple_id_secret and signing_role_name:
415+
if signing_bucket and aws_account_id and aws_region and apple_id_secret and signing_role_name:
392416
signing_data = CdSigningData(
393417
bucket_name=signing_bucket,
394418
aws_account_id=aws_account_id,
419+
aws_region=aws_region,
395420
notarizing_secret_id=apple_id_secret,
396421
signing_role_name=signing_role_name,
397422
)
@@ -428,16 +453,6 @@ def build(
428453
)
429454

430455
if isDarwin():
431-
if signing_bucket and aws_account_id and apple_id_secret and signing_role_name:
432-
signing_data = CdSigningData(
433-
bucket_name=signing_bucket,
434-
aws_account_id=aws_account_id,
435-
notarizing_secret_id=apple_id_secret,
436-
signing_role_name=signing_role_name,
437-
)
438-
else:
439-
signing_data = None
440-
441456
chat_path = build_macos(chat_path, signing_data)
442457
sha_path = generate_sha(chat_path)
443458

scripts/qchatmain.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ def __call__(self, parser, namespace, values, option_string=None):
3939
action=StoreIfNotEmptyAction,
4040
help="The AWS account ID",
4141
)
42+
build_subparser.add_argument(
43+
"--aws-region",
44+
action=StoreIfNotEmptyAction,
45+
help="The AWS region",
46+
)
4247
build_subparser.add_argument(
4348
"--apple-id-secret",
4449
action=StoreIfNotEmptyAction,
@@ -79,6 +84,7 @@ def __call__(self, parser, namespace, values, option_string=None):
7984
output_bucket=args.output_bucket,
8085
signing_bucket=args.signing_bucket,
8186
aws_account_id=args.aws_account_id,
87+
aws_region=args.aws_region,
8288
apple_id_secret=args.apple_id_secret,
8389
signing_role_name=args.signing_role_name,
8490
stage_name=args.stage_name,

scripts/util.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ def run_cmd_output(
105105
env: Env | None = None,
106106
cwd: Cwd | None = None,
107107
) -> str:
108+
args_str = [str(arg) for arg in args]
109+
print(f"+ {shlex.join(args_str)}")
108110
res = subprocess.run(args, env=env, cwd=cwd, check=True, stdout=subprocess.PIPE)
109111
return res.stdout.decode("utf-8")
110112

0 commit comments

Comments
 (0)