Skip to content

Commit 06dc7b8

Browse files
committed
WIP failing signing request for some reason
1 parent 679f071 commit 06dc7b8

File tree

4 files changed

+255
-10
lines changed

4 files changed

+255
-10
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
type: :app # no need to change this for .pkg, .dmg and executables
2+
os: :osx
3+
name: qchat
4+
outputs:
5+
- label: macos
6+
path: qchat
7+
app:
8+
:identifier: com.amazon.codewhisperer # this must equal the Bundle Identifier value in the embedded Info.plist
9+
signing_requirements:
10+
certificate_type: :developerIDAppDistribution
11+
app_id_prefix: 94KV3E626L # AMZN Mobile LLC
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
type: :app # no need to change this for .pkg, .dmg and executables
2+
os: :osx
3+
name: __NAME__ # This should be the file name
4+
outputs:
5+
- label: macos
6+
path: __NAME__ # This should be the file name
7+
app:
8+
:identifier: com.amazon.codewhisperer # this must equal the Bundle Identifier value in the embedded Info.plist
9+
signing_requirements:
10+
certificate_type: :developerIDAppDistribution
11+
app_id_prefix: 94KV3E626L # AMZN Mobile LLC

build-scripts/qchatbuild.py

Lines changed: 229 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import json
12
import pathlib
3+
from functools import cache
24
import os
35
import shutil
4-
from typing import Mapping, Sequence
6+
import time
7+
from typing import Any, Mapping, Sequence
58
from build import generate_sha
6-
from const import CHAT_BINARY_NAME, CHAT_PACKAGE_NAME, LINUX_ARCHIVE_NAME
9+
from const import APPLE_TEAM_ID, CHAT_BINARY_NAME, CHAT_PACKAGE_NAME, LINUX_ARCHIVE_NAME
710
from signing import (
811
CdSigningData,
912
CdSigningType,
@@ -12,12 +15,16 @@
1215
cd_sign_file,
1316
apple_notarize_file,
1417
)
15-
from util import info, isDarwin, run_cmd
18+
from util import info, isDarwin, run_cmd, warn
1619
from rust import cargo_cmd_name, rust_env, rust_targets
20+
from importlib import import_module
1721

1822
BUILD_DIR_RELATIVE = pathlib.Path(os.environ.get("BUILD_DIR") or "build")
1923
BUILD_DIR = BUILD_DIR_RELATIVE.absolute()
2024

25+
REGION = "us-west-2"
26+
SIGNING_API_BASE_URL = "https://api.signer.builder-tools.aws.dev"
27+
2128

2229
def run_cargo_tests():
2330
args = [cargo_cmd_name()]
@@ -51,14 +58,23 @@ def build_chat_bin(
5158

5259
args = [cargo_cmd_name(), "build", "--locked", "--package", package]
5360

54-
if release:
55-
args.append("--release")
61+
for target in targets:
62+
args.extend(["--target", target])
5663

5764
if release:
65+
args.append("--release")
5866
target_subdir = "release"
5967
else:
6068
target_subdir = "debug"
6169

70+
run_cmd(
71+
args,
72+
env={
73+
**os.environ,
74+
**rust_env(release=release),
75+
},
76+
)
77+
6278
# create "universal" binary for macos
6379
if isDarwin():
6480
out_path = BUILD_DIR / f"{output_name or package}-universal-apple-darwin"
@@ -82,17 +98,201 @@ def build_chat_bin(
8298
return out_path
8399

84100

85-
def sign_and_notarize(chat_path: pathlib.Path):
101+
@cache
102+
def get_creds():
103+
boto3 = import_module("boto3")
104+
session = boto3.Session()
105+
credentials = session.get_credentials()
106+
creds = credentials.get_frozen_credentials()
107+
return creds
108+
109+
110+
def cd_signer_request(method: str, path: str, data: str | None = None):
111+
SigV4Auth = import_module("botocore.auth").SigV4Auth
112+
AWSRequest = import_module("botocore.awsrequest").AWSRequest
113+
requests = import_module("requests")
114+
115+
url = f"{SIGNING_API_BASE_URL}{path}"
116+
headers = {"Content-Type": "application/json"}
117+
request = AWSRequest(method=method, url=url, data=data, headers=headers)
118+
SigV4Auth(get_creds(), "signer-builder-tools", REGION).add_auth(request)
119+
120+
for i in range(1, 8):
121+
response = requests.request(method=method, url=url, headers=dict(request.headers), data=data)
122+
info(f"CDSigner Request ({url}): {response.status_code}")
123+
if response.status_code == 429:
124+
warn(f"Too many requests, backing off for {2**i} seconds")
125+
time.sleep(2**i)
126+
continue
127+
return response
128+
129+
raise Exception(f"Failed to request {url}")
130+
131+
132+
def cd_signer_create_request(manifest: Any) -> str:
133+
response = cd_signer_request(
134+
method="POST",
135+
path="/signing_requests",
136+
data=json.dumps({"manifest": manifest}),
137+
)
138+
response_json = response.json()
139+
info(f"Signing request create: {response_json}")
140+
request_id = response_json["signingRequestId"]
141+
return request_id
142+
143+
144+
def cd_signer_start_request(request_id: str, source_key: str, destination_key: str, signing_data: CdSigningData):
145+
response_text = cd_signer_request(
146+
method="POST",
147+
path=f"/signing_requests/{request_id}/start",
148+
data=json.dumps(
149+
{
150+
"iamRole": f"arn:aws:iam::{signing_data.aws_account_id}:role/{signing_data.signing_role_name}",
151+
"s3Location": {
152+
"bucket": signing_data.bucket_name,
153+
"sourceKey": source_key,
154+
"destinationKey": destination_key,
155+
},
156+
}
157+
),
158+
).text
159+
info(f"Signing request start: {response_text}")
160+
161+
162+
def cd_signer_status_request(request_id: str):
163+
response_json = cd_signer_request(
164+
method="GET",
165+
path=f"/signing_requests/{request_id}",
166+
).json()
167+
info(f"Signing request status: {response_json}")
168+
return response_json["signingRequest"]["status"]
169+
170+
171+
def cd_build_signed_package(file_path: pathlib.Path):
172+
"""
173+
Creates a tarball `package.tar.gz` with the following structure:
174+
```
175+
package
176+
├─ manifest.yaml
177+
├─ artifact
178+
| ├─ EXECUTABLES_TO_SIGN
179+
| | ├─ qchat
180+
```
181+
"""
182+
working_dir = BUILD_DIR / "package"
183+
shutil.rmtree(working_dir, ignore_errors=True)
184+
(BUILD_DIR / "package" / "artifact" / "EXECUTABLES_TO_SIGN").mkdir(parents=True)
185+
186+
name = file_path.name
187+
188+
# Write the manifest.yaml
189+
manifest_template_path = pathlib.Path.cwd() / "build-config" / "signing" / "qchat" / "manifest.yaml.template"
190+
(working_dir / "manifest.yaml").write_text(manifest_template_path.read_text().replace("__NAME__", name))
191+
192+
shutil.copy2(file_path, working_dir / "artifact" / "EXECUTABLES_TO_SIGN" / file_path.name)
193+
file_path.unlink()
194+
195+
run_cmd(
196+
["gtar", "-czf", BUILD_DIR / "package.tar.gz", "manifest.yaml", "artifact"],
197+
cwd=working_dir,
198+
)
199+
200+
return BUILD_DIR / "package.tar.gz"
201+
202+
203+
def manifest(
204+
name: str,
205+
identifier: str,
206+
):
207+
"""
208+
Creates the required manifest argument when submitting the signing task. This has the same
209+
structure as the manifest.yaml.template under `build-config/signing/qchat/manifest.yaml.template`
210+
"""
211+
return {
212+
"type": "app",
213+
"os": "osx",
214+
"name": name,
215+
"outputs": [{"label": "macos", "path": name}],
216+
"app": {
217+
"identifier": identifier,
218+
"signing_requirements": {
219+
"certificate_type": "developerIDAppDistribution",
220+
"app_id_prefix": APPLE_TEAM_ID,
221+
},
222+
},
223+
}
224+
225+
226+
def sign_executable(signing_data: CdSigningData, chat_path: pathlib.Path):
227+
name = chat_path.name
228+
info(f"Signing {name}")
229+
230+
info("Packaging...")
231+
package_path = cd_build_signed_package(chat_path)
232+
233+
info("Uploading...")
234+
run_cmd(["aws", "s3", "rm", "--recursive", f"s3://{signing_data.bucket_name}/signed"])
235+
run_cmd(["aws", "s3", "rm", "--recursive", f"s3://{signing_data.bucket_name}/pre-signed"])
236+
run_cmd(["aws", "s3", "cp", package_path, f"s3://{signing_data.bucket_name}/pre-signed/package.tar.gz"])
237+
238+
info("Sending request...")
239+
request_id = cd_signer_create_request(manifest(name, "com.amazon.codewhisperer"))
240+
cd_signer_start_request(
241+
request_id=request_id,
242+
source_key="pre-signed/package.tar.gz",
243+
destination_key="signed/signed.zip",
244+
signing_data=signing_data,
245+
)
246+
247+
max_duration = 180
248+
end_time = time.time() + max_duration
249+
i = 1
250+
while True:
251+
info(f"Checking for signed package attempt #{i}")
252+
status = cd_signer_status_request(request_id)
253+
info(f"Package has status: {status}")
254+
255+
match status:
256+
case "success":
257+
break
258+
case "created" | "processing" | "inProgress":
259+
pass
260+
case "failure":
261+
raise RuntimeError("Signing request failed")
262+
case _:
263+
warn(f"Unexpected status, ignoring: {status}")
264+
265+
if time.time() >= end_time:
266+
raise RuntimeError("Signed package did not appear, check signer logs")
267+
time.sleep(2)
268+
i += 1
269+
270+
info("Signed!")
271+
272+
info("Downloading...")
273+
run_cmd(["aws", "s3", "cp", f"s3://{signing_data.bucket_name}/signed/signed.zip", "signed.zip"])
274+
run_cmd(["unzip", "signed.zip"])
275+
276+
277+
def sign_and_notarize(signing_data: CdSigningData, chat_path: pathlib.Path):
86278
# First, sign the application
279+
sign_executable(signing_data, chat_path)
87280

88281
# Next, notarize the application
89282

90283
# Last, staple the notarization to the application
91284
pass
92285

93286

94-
def build_macos(chat_path: pathlib.Path):
95-
sign_and_notarize(chat_path)
287+
def build_macos(chat_path: pathlib.Path, signing_data: CdSigningData | None):
288+
chat_dst = BUILD_DIR / "qchat"
289+
chat_dst.unlink(missing_ok=True)
290+
shutil.copy2(chat_path, chat_dst)
291+
292+
if signing_data:
293+
sign_and_notarize(signing_data, chat_dst)
294+
295+
return chat_dst
96296

97297

98298
def build_linux(chat_path: pathlib.Path):
@@ -194,4 +394,24 @@ def build(
194394
targets=targets,
195395
)
196396

197-
pass
397+
if isDarwin():
398+
if signing_bucket and aws_account_id and apple_id_secret and signing_role_name:
399+
signing_data = CdSigningData(
400+
bucket_name=signing_bucket,
401+
aws_account_id=aws_account_id,
402+
notarizing_secret_id=apple_id_secret,
403+
signing_role_name=signing_role_name,
404+
)
405+
else:
406+
signing_data = None
407+
408+
chat_path = build_macos(chat_path, signing_data)
409+
sha_path = generate_sha(chat_path)
410+
411+
if output_bucket:
412+
staging_location = f"s3://{output_bucket}/staging/"
413+
info(f"Build complete, sending to {staging_location}")
414+
run_cmd(["aws", "s3", "cp", chat_path, staging_location])
415+
run_cmd(["aws", "s3", "cp", sha_path, staging_location])
416+
else:
417+
build_linux(chat_path)

crates/chat-cli/.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
build/
2-
spec.ts
2+
spec.ts
3+
4+
# Info.plist is generated by the build script for macOS
5+
src/Info.plist

0 commit comments

Comments
 (0)