feat: add Jenkins thin artifact CI milestone for security distribution phase

This commit is contained in:
toki 2026-06-15 14:37:53 +09:00
parent b4400065a2
commit 7d276bac1e
3 changed files with 97 additions and 8 deletions

View file

@ -14,6 +14,9 @@ AppSok을 개인용 도구에서 사내 표준 도구로 확장하기 위한 배
완료, 검토중, 진행중, 계획, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다.
스케치 Milestone은 아직 구현 가능한 계획이 아니므로 계획 Milestone보다 아래에 둔다.
- [계획] Jenkins 얇은 빌드와 artifact 다운로드
- 경로: `agent-roadmap/phase/security-distribution/milestones/jenkins-thin-artifact-ci.md`
- 요약: OTO 없이 Jenkins job만 얇게 구성해 AppSok 인증 ZIP을 빌드하고 Jenkins artifact 링크로 바로 다운로드할 수 있게 한다.
- [계획] macOS 배포와 sandbox 검증
- 경로: `agent-roadmap/phase/security-distribution/milestones/macos-distribution.md`
- 요약: signing/notarization/MDM 후보와 sandbox 상태에서 Jenkins/Keychain/ADB가 동작하는지 검증한다.

View file

@ -0,0 +1,83 @@
# Milestone: Jenkins 얇은 빌드와 artifact 다운로드
## 위치
- Roadmap: `agent-roadmap/ROADMAP.md`
- Phase: `agent-roadmap/phase/security-distribution/PHASE.md`
## 목표
OTO 없이 Jenkins job만 얇게 구성해 AppSok 인증 ZIP을 반복 빌드하고, Jenkins artifact 링크로 바로 다운로드할 수 있게 한다. Jenkins에는 빌드 지식을 최소한으로 두고, 현재 프로젝트의 기존 인증 빌드 스크립트를 실행하는 thin launcher 역할만 부여한다.
## 상태
[계획]
## 승격 조건
- 없음
## 구현 잠금
- 상태: 잠금
- 결정 필요: 아래 체크리스트
- [x] Jenkins job 생성/갱신은 사용자 제공 Jenkins API credential을 사용한다. 원격 SOPS 저장과 Jenkins API 인증 확인을 완료했다.
- [ ] artifact 다운로드를 Jenkins 로그인 사용자에게만 허용할지, 별도 공개 다운로드 경로를 마련할지 결정한다.
- [ ] build trigger를 수동 실행만으로 시작할지, Git push webhook 또는 polling까지 포함할지 결정한다.
- [ ] job 이름과 artifact 보관 개수 기본값을 확정한다. 기본 후보는 `appsok-macos-certified`, 최근 20개 build/artifact 보관이다.
## 범위
- AppSok 전용 Jenkins job 생성 또는 갱신
- `macbook-ultra` Jenkins node에서 기존 인증 빌드 스크립트 실행
- 최종 ZIP과 checksum을 Jenkins archived artifact로 노출
- 최신 성공 빌드 artifact 다운로드 URL 확인
- Jenkins credential/API token을 tracked 파일에 남기지 않는 운영 절차
## 기능
### Epic: [job] Jenkins thin job
Jenkins job은 AppSok 빌드 스크립트를 호출하고 artifact를 보관하는 얇은 launcher로 유지한다.
- [ ] [job-config] `macbook-ultra` node, AppSok SCM, `main` branch, shell build step을 포함한 Jenkins job config를 만든다.
- [ ] [script-call] job build step이 `./scripts/build-certified-macos.sh`를 호출하고 secret 원문을 job config나 repository에 남기지 않는다.
- [ ] [artifact-copy] `AppSok-certified.zip``sha256` 값을 Jenkins workspace의 archive 대상 경로로 모은다.
- [ ] [archive] Jenkins archived artifact 설정으로 ZIP과 checksum을 보관한다.
- [ ] [retention] build log와 artifact 보관 정책을 AppSok 1차 운영 기준에 맞춘다.
### Epic: [access] 빌드 실행과 다운로드 확인
사용자가 Jenkins에서 빌드를 실행하고 최신 성공 artifact를 확인할 수 있게 한다.
- [ ] [trigger] 1차 trigger 방식을 적용한다.
- [ ] [download-url] `lastSuccessfulBuild/artifact/.../AppSok-certified.zip` 다운로드 URL을 확인하고 문서화한다.
- [ ] [auth-policy] Jenkins artifact 다운로드 권한 정책을 결정 사항에 맞게 반영한다.
- [ ] [smoke] Jenkins build를 1회 실행하고 archived ZIP과 checksum을 다운로드 가능한 상태로 검증한다. 검증: Jenkins 최신 성공 빌드의 artifact URL이 인증된 사용자 기준으로 ZIP 응답을 반환한다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 모든 기능 Task와 Task 안에 명시된 검증 충족 후 기록한다.
- 리뷰 필요:
- [ ] 사용자가 완료 결과를 확인했다
- [ ] archive 이동을 승인했다
- 리뷰 코멘트: 없음
## 범위 제외
- OTO runner 또는 OTO Server를 Jenkins 대체 실행 주체로 도입하는 작업
- workspace 공통 Jenkins 가이드 또는 별도 agent skill 작성
- Jenkins plugin, Auth Broker, 중앙 감사 서버 구현
- 공용 Jenkins API token을 앱이나 repository에 내장하는 방식
- AppSok 앱 코드에서 Jenkins artifact 소비 흐름을 변경하는 작업
## 작업 컨텍스트
- 관련 경로: `scripts/build-certified-macos.sh`, `docs/macos-certified-build.md`, `agent-roadmap/phase/security-distribution/`
- 표준선(선택): Jenkins는 1차로 thin launcher와 artifact archive만 담당하고, 빌드 절차의 source of truth는 AppSok repository의 스크립트와 문서에 둔다.
- 표준선(선택): Jenkins API token, SSH credential, Apple credential, Keychain password 원문은 tracked 파일, task log, 최종 응답에 기록하지 않는다.
- 선행 작업: macOS 빌드 CI와 인증 빌드
- 후속 작업: macOS 배포와 sandbox 검증, 향후 OTO 기반 CI/CD 마이그레이션 후보
- 확인 필요: Jenkins API token 또는 reload 권한, artifact 다운로드 권한 정책, trigger 방식, job 이름과 보관 정책

View file

@ -1,18 +1,21 @@
{
"keychain_password": "ENC[AES256_GCM,data:dbdUnU08,iv:qiMCxcUuB5cU5dF+If44SlnyV7F8z9QH99xBbAYujUc=,tag:5vJ2wPtgZV1vymi5nlwAsQ==,type:str]",
"apple_id": "ENC[AES256_GCM,data:zQFNWgCLUAcOQLMWCKyNiabOM0rOVcEN,iv:pIAwYUz11v5I3iR3ooP4B2s0ZQO4tbpaC/RDPfZa12A=,tag:X7DK3DGB2EwbSO4bchmWxw==,type:str]",
"app_specific_password": "ENC[AES256_GCM,data:kF14kJqVBb7/R6CdnGWEUgEd1w==,iv:rUsHkTp7ou+xgHRfBT8CvYJ8wQfOCdGa+JdmCtPNjIw=,tag:nT06ha1S6toRommwIbBgxg==,type:str]",
"notary_profile": "ENC[AES256_GCM,data:8GuRGFIMKEGMq+wHgg==,iv:N3Cg0i138Brc3RdUSzX6ZFnUGzxZEcBf8gmUPdayxqU=,tag:Q7cFKyTgWvT29bkW9czabA==,type:str]",
"team_id": "ENC[AES256_GCM,data:vdQ+EGiKNzBfPw==,iv:6/X2mZEv0oFlTUAnXKJpSZuNAELe0Op+zJm9ZITfIVA=,tag:LdBWE9y0sMO5jB/RehNKUA==,type:str]",
"keychain_password": "ENC[AES256_GCM,data:ulrv+g/E,iv:vqS4g+GSZu8E38XvSFuzRzMcuQoTluD7CCdwnRGiHLY=,tag:TWNkSagck75+4FanQQrHTA==,type:str]",
"apple_id": "ENC[AES256_GCM,data:1bDyzRIfG79q5OAo70sRoTf9sOsSW0G8,iv:++cKhT1X5yh7XiZGnOm2Q6iITmdLCV25xT7BTMd5s20=,tag:OLfRMO77wxNddMSWtRmpzA==,type:str]",
"app_specific_password": "ENC[AES256_GCM,data:z4Kv7Bi+2R/USTSZbk6ESkDjUg==,iv:HauauFN7y3z9CeLpvpNr/Id0L0h4SqPhknEtgeDVwVE=,tag:qJgj4OrrAO3zstUMKC86oQ==,type:str]",
"notary_profile": "ENC[AES256_GCM,data:xq5iHMRjxj7yFKsStA==,iv:zsDcxv5cI+hEjoydIsgb1Ud8OFBZ5VJf8n1RrYDFBp4=,tag:DDiv9p58l2N7pXHl/GS9/w==,type:str]",
"team_id": "ENC[AES256_GCM,data:mx4yG0XYctO4HQ==,iv:4UR73U+mOpt+r63NHAjZ59bJxBnL+Bam0br0KEo5wTc=,tag:AdFx8eloqfR1kscwOs3okw==,type:str]",
"jenkins_url": "ENC[AES256_GCM,data:VlqZN3Ot0HbTe5YINvOTiH+i5IFl,iv:3bvQDu+ZhsI4ni3k7cpaZTH3141lwgj0Jobf91yo3NA=,tag:+wXbONTkX7CsCo8+f9XlVQ==,type:str]",
"jenkins_username": "ENC[AES256_GCM,data:OlfHPQ==,iv:E26Sph5O6frAW4s2L5h/zfmidABxew3jNcqkT+wNpoE=,tag:XAwNsbTRPxuacLTbJBLTjA==,type:str]",
"jenkins_api_token": "ENC[AES256_GCM,data:4vX4p3O501E4ew7rzK0YcV3KQAN44ifQ6ImVhqXjma7sGQ==,iv:NmmarZOiKUJynmP7EveClLl1XTbv5bvKa9kiiqdmWGc=,tag:BfCc5pbSXQr25ynTHX35vA==,type:str]",
"sops": {
"age": [
{
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmOFBGQlcyMlZaS2ltVzNr\nQ0ZqZU43UW11cHQrRGtmT2Y2WS9kLyt2ZUFBCkVaZW5QZTdWeGJob2U0WVR0emJO\ndzY1ZUZTNFRzUFBRcmhvdCttYWp2MUkKLS0tIE9ncGprRXZRQTBzUzIwcjlwR2cx\nNUpQNGZYVk14bm9zMkdnNEg3N3FXcUkKD8r/akXXQ6ZVWiKBA62RlB9kB8mXUgBd\nCreJtxzJG4A+9Xla6UdzJPZYSP3IY5JPQoWUtzc0xXbBHfCSmpWoww==\n-----END AGE ENCRYPTED FILE-----\n",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHckdoaWc5Zkh4eDU3aU42\nUXBqU3BEMzcyT0JiTlJUV0FWdnZZUEZkYmpnCnZHNmFpTEFUOEtOTFFibVdFU2Zi\ncnFRSjRZZms4UXptWXFSa1BzTGVZMkkKLS0tIFhHRGV3aXNDdWF3V0R2MkFTajgr\nSXQzZDFGeDZ3dy9MT0NoMTdMYXJLWUEKahLHUzDvofFCzBY+UOyx1IVb4zBRlwZW\nwEAf2nHNNx4vtEK5m2mlS/8xZrw2LIaXQgazpRwlNVZe/k3mplgEWQ==\n-----END AGE ENCRYPTED FILE-----\n",
"recipient": "age1l4fylw9cvw49urz84pa02gjupnpnpz8qp6k0t7225wt8lukkg3cs6qflph"
}
],
"lastmodified": "2026-06-14T07:00:35Z",
"mac": "ENC[AES256_GCM,data:UjOl2IcvEm9P+EldqwW5kj3vkUFmW5zylLXgSjZYDKvAZIHHW5JwgerKDqw+90AdV9dl5vIIReXRWiHepQQugkofJmsAddHRhK5DI8Xam9X1cC87S28phAp5iQm1xMF55jF2gsuQ5FVtDZ2W5ZX3epGxhLLUXk2xK+mtyUj9i9w=,iv:d+Gc6HA8VRUjFoNsMjYI3ckqujiMeYjAkhIyEWoMvWk=,tag:jo0sglvMW1P3asBP3giIpA==,type:str]",
"lastmodified": "2026-06-15T04:46:38Z",
"mac": "ENC[AES256_GCM,data:NSNEwOc1vKpG2o0fH35758+2HWPxBcuetOYNWJ3wdXLLzhIYhHjuiDUiacIKX1GO8IHXottrY0wXnKbgaPvYZpFsmCPnweXOTLqbeLRfxymB8Pbt5Hfb3CnNefHdnSuje/b/lW5b2YXHPgAftzfky6IOlQVziDk/pSOAxKvFoY8=,iv:gP8n8M89hc3lP1a61SgoRyb/uMFdJSOMa8bEK0goPrk=,tag:7u3h15fDW+2VNFQ43KltPw==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.13.1"
}