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보다 아래에 둔다. 스케치 Milestone은 아직 구현 가능한 계획이 아니므로 계획 Milestone보다 아래에 둔다.
- [계획] Jenkins 얇은 빌드와 artifact 다운로드
- 경로: `agent-roadmap/phase/security-distribution/milestones/jenkins-thin-artifact-ci.md`
- 요약: OTO 없이 Jenkins job만 얇게 구성해 AppSok 인증 ZIP을 빌드하고 Jenkins artifact 링크로 바로 다운로드할 수 있게 한다.
- [계획] macOS 배포와 sandbox 검증 - [계획] macOS 배포와 sandbox 검증
- 경로: `agent-roadmap/phase/security-distribution/milestones/macos-distribution.md` - 경로: `agent-roadmap/phase/security-distribution/milestones/macos-distribution.md`
- 요약: signing/notarization/MDM 후보와 sandbox 상태에서 Jenkins/Keychain/ADB가 동작하는지 검증한다. - 요약: 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]", "keychain_password": "ENC[AES256_GCM,data:ulrv+g/E,iv:vqS4g+GSZu8E38XvSFuzRzMcuQoTluD7CCdwnRGiHLY=,tag:TWNkSagck75+4FanQQrHTA==,type:str]",
"apple_id": "ENC[AES256_GCM,data:zQFNWgCLUAcOQLMWCKyNiabOM0rOVcEN,iv:pIAwYUz11v5I3iR3ooP4B2s0ZQO4tbpaC/RDPfZa12A=,tag:X7DK3DGB2EwbSO4bchmWxw==,type:str]", "apple_id": "ENC[AES256_GCM,data:1bDyzRIfG79q5OAo70sRoTf9sOsSW0G8,iv:++cKhT1X5yh7XiZGnOm2Q6iITmdLCV25xT7BTMd5s20=,tag:OLfRMO77wxNddMSWtRmpzA==,type:str]",
"app_specific_password": "ENC[AES256_GCM,data:kF14kJqVBb7/R6CdnGWEUgEd1w==,iv:rUsHkTp7ou+xgHRfBT8CvYJ8wQfOCdGa+JdmCtPNjIw=,tag:nT06ha1S6toRommwIbBgxg==,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:8GuRGFIMKEGMq+wHgg==,iv:N3Cg0i138Brc3RdUSzX6ZFnUGzxZEcBf8gmUPdayxqU=,tag:Q7cFKyTgWvT29bkW9czabA==,type:str]", "notary_profile": "ENC[AES256_GCM,data:xq5iHMRjxj7yFKsStA==,iv:zsDcxv5cI+hEjoydIsgb1Ud8OFBZ5VJf8n1RrYDFBp4=,tag:DDiv9p58l2N7pXHl/GS9/w==,type:str]",
"team_id": "ENC[AES256_GCM,data:vdQ+EGiKNzBfPw==,iv:6/X2mZEv0oFlTUAnXKJpSZuNAELe0Op+zJm9ZITfIVA=,tag:LdBWE9y0sMO5jB/RehNKUA==,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": { "sops": {
"age": [ "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" "recipient": "age1l4fylw9cvw49urz84pa02gjupnpnpz8qp6k0t7225wt8lukkg3cs6qflph"
} }
], ],
"lastmodified": "2026-06-14T07:00:35Z", "lastmodified": "2026-06-15T04:46:38Z",
"mac": "ENC[AES256_GCM,data:UjOl2IcvEm9P+EldqwW5kj3vkUFmW5zylLXgSjZYDKvAZIHHW5JwgerKDqw+90AdV9dl5vIIReXRWiHepQQugkofJmsAddHRhK5DI8Xam9X1cC87S28phAp5iQm1xMF55jF2gsuQ5FVtDZ2W5ZX3epGxhLLUXk2xK+mtyUj9i9w=,iv:d+Gc6HA8VRUjFoNsMjYI3ckqujiMeYjAkhIyEWoMvWk=,tag:jo0sglvMW1P3asBP3giIpA==,type:str]", "mac": "ENC[AES256_GCM,data:NSNEwOc1vKpG2o0fH35758+2HWPxBcuetOYNWJ3wdXLLzhIYhHjuiDUiacIKX1GO8IHXottrY0wXnKbgaPvYZpFsmCPnweXOTLqbeLRfxymB8Pbt5Hfb3CnNefHdnSuje/b/lW5b2YXHPgAftzfky6IOlQVziDk/pSOAxKvFoY8=,iv:gP8n8M89hc3lP1a61SgoRyb/uMFdJSOMa8bEK0goPrk=,tag:7u3h15fDW+2VNFQ43KltPw==,type:str]",
"unencrypted_suffix": "_unencrypted", "unencrypted_suffix": "_unencrypted",
"version": "3.13.1" "version": "3.13.1"
} }