Skip to content

security(ci): Actions SHA pinning 정책을 조직 수준으로 넓힌다 — BE 포함 전 저장소 감사 선행 #1591

Open
1 of 1 issue completed

Description

@OnlyReadOneBook

작업 유형

security — 보안 취약점·권한·자격·공급망 대응

주 담당 모듈

platform — Android 앱·CI·빌드·릴리스·저장소 운영

개요

#1538 에서 Afternote-FE 저장소 한 곳의 Actions 정책을 좁혔다. 조직 수준은 손대지 않았고, 여전히 열려 있다.

실측 (2026-08-30)

// GET /orgs/Afternote/actions/permissions
{"enabled_repositories":"all","allowed_actions":"all","sha_pinning_required":false}

// GET /repos/Afternote/Afternote-FE/actions/permissions  ← #1538 에서 좁힘
{"enabled":true,"allowed_actions":"selected","sha_pinning_required":true}

조직이 all 이라 Afternote-FE 를 뺀 나머지 저장소(Afternote-BE 포함)는 지금도 uses: someone/action@main 이 그대로 통과한다. FE 한 곳만 막아 둔 상태라 조직 전체의 공급망 경계는 아직 없는 셈이다.

조직 쪽을 그냥 켜면 안 되는 이유가 이 이슈의 본체다 — 조직 설정은 하위 저장소 전부에 즉시 적용되고, BE 워크플로의 pinning 상태를 감사하지 않은 채 켜면 그쪽 CI 가 startup_failure 로 죽는다. job 이 만들어지지 않아 로그에 사유도 남지 않는다.

완료 조건

  • 조직 하위 전 저장소의 워크플로 uses 를 전수 스캔해 floating tag·branch 참조를 열거한다 (Afternote-BE 포함, 열린 PR 브랜치까지)
  • floating 참조가 남아 있으면 해당 저장소에서 SHA 고정을 먼저 끝낸다
  • 조직 전체가 호환되는 것을 확인한 뒤 orgs/Afternote/actions/permissions 의 sha_pinning_required 를 켠다
  • 조직 allowed_actions 를 좁힐지 판단한다 — 저장소마다 쓰는 액션이 달라 조직 목록은 합집합이 된다. 좁히는 값이 실제로 있는지, 아니면 저장소별 목록으로 두는 게 맞는지 근거와 함께 결정한다
  • 켠 뒤 차단이 실제로 도는 것을 안전한 fixture 로 확인한다 (chore(ci): Actions SHA pinning required·허용 정책을 저장소 설정에 적용한다 #1538 에서 쓴 절차: 정책 적용 전 대조군 run → 적용 후 같은 내용 재실행 → 결과 뒤집힘 확인 → fixture 브랜치 삭제)

참고

#1538 에서 이미 끝난 것(이 이슈 범위 아님) — Afternote-FE 저장소의 sha_pinning_required 활성화, allowed_actions: selected 적용(github_owned + verified + 명시 패턴 5종), 허용 목록을 supply-chain-policy.test.mjs 에 미러링(#1590), fixture 로 차단·허용 양쪽 실측.

#1538 에서 얻은 실측 사실 두 가지가 조직 쪽 판단에도 그대로 쓰인다.

  • github_owned_allowed 는 actions 조직뿐 아니라 github 조직도 덮는다 — github/codeql-action 은 patterns 없이 통과한다.
  • patterns_allowed 의 * 는 / 를 넘지 않는다. gradle/actions/* 로 서브패스 액션을 한 번에 덮을 수 없고 액션마다 한 줄씩 필요하다.

2026-09-13 착수 결과

3개 저장소의 브랜치 160개와 열린 PR head 34개를 조회해 workflow/action 고유 blob 195개를 파싱했다. 조회·파싱 오류는 0건이다. 감사 시각은 03:12 UTC이며 모든 결과는 ref별 commit SHA에 고정했다.

  • 기본 브랜치: BE 미고정 32건, demo 2건. 열린 PR head의 미고정은 0건이다.
  • 과거 브랜치까지 포함한 미고정은 1014건이며 동일 코드의 브랜치 중복을 포함한다. FE 13개·BE 75개·demo 2개 브랜치에 남아 있다.
  • 조직 정책은 sha_pinning_required=false / allowed_actions=all 유지. BE/demo 및 재사용할 기존 브랜치 고정과 담당 저장소 검증 전 활성화하지 않는다.

조직 허용 목록 판단

조직 allowed_actions를 selected로 좁힐 실익이 있다. 기본 브랜치와 열린 PR에서 사용하는 원격 액션은 20종이며 GitHub 소유를 제외하면 아래 10종이다. GitHub 소유 액션을 허용하고 verified 공급자 전체 허용을 끄면, 목록에 없는 새 공급자의 액션까지 자동 허용되는 범위를 줄인다. 오래된 브랜치까지는 원격 24종이므로 유지할 브랜치를 정한 뒤 실제 목록을 확정한다.

후보: github_owned_allowed=true, verified_allowed=false. patterns_allowed:

  • anishathalye/proof-html@*
  • appleboy/scp-action@*
  • appleboy/ssh-action@*
  • docker/build-push-action@*
  • docker/login-action@*
  • docker/setup-buildx-action@*
  • google-github-actions/auth@*
  • gradle/actions/dependency-submission@*
  • gradle/actions/setup-gradle@*
  • pozil/auto-assign-issue@*

API에 이 값을 적용하지 않았다. BE 기본 브랜치 32건·demo 2건 SHA 고정과 검증, 유지할 옛 브랜치 정리, 외부 composite action의 추가 의존 확인이 선행이다. BE CI·배포 담당자가 BE 수정과 검증을 맡고, 1hyok의 조직 운영 범위에서는 demo 담당·브랜치 유지 범위를 확정하고 정책 전후 fixture를 준비한다. 저장소별 더 좁은 목록도 유지할 수 있다.

GitHub 공식 문서상 SHA 강제는 액션에 적용되며 reusable workflow는 tag 참조가 허용된다. 정책 fixture에서 두 대상을 구분한다. 정책 문서, API 정의.

Activity

  1. added
    security보안 취약점·권한·자격·공급망 대응
    area:platformCI·빌드·릴리스·저장소 운영 영역
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Aug 30, 2026
  2. OnlyReadOneBook commented on Aug 30, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    감사 완료 — 조직 설정은 아직 켜지 않았다

    전수 감사를 끝냈습니다. 결과와 판정은 #1607 에서 docs/security/actions-supply-chain.md 로 들어갑니다. orgs/Afternote/actions/permissions 는 조회(GET)만 했고 그대로입니다 — {"enabled_repositories":"all","allowed_actions":"all","sha_pinning_required":false}.

    1. 전수 스캔 (2026-08-30T13:25:28Z)

    조직 저장소는 3개가 전부입니다(아카이브·포크 0건, public_repos: 2 + total_private_repos: 1 과 일치). 기본 브랜치 3개 + Afternote-FE 열린 PR 35건의 head, 38개 리비전 · uses 4,699건을 훑었습니다. 워크플로뿐 아니라 composite action 매니페스트(action.yml)까지 포함합니다.

    저장소 스캔 리비전 uses SHA 고정 floating 로컬(./)
    Afternote-FE (develop fc8f32cd) 36 4,665 3,739 0 926
    Afternote-BE (main 0a4b870c) 1 (열린 PR 0건) 32 0 32 0
    demo-repository (main 57ea595f, private) 1 (열린 PR 0건) 2 0 2 0
    floating 34건 전량
    저장소 참조 건수 워크플로
    Afternote-BE actions/checkout@v7 12 ci.yml, codeql.yml, dependency-submission.yml, deploy.yml, quality.yml, tls-expiry.yml
    Afternote-BE actions/dependency-review-action@v5 1 ci.yml
    Afternote-BE actions/setup-java@v5 5 ci.yml, dependency-submission.yml, deploy.yml
    Afternote-BE actions/setup-python@v5 1 deploy.yml
    Afternote-BE actions/upload-artifact@v7 3 ci.yml
    Afternote-BE appleboy/scp-action@v1.0.0 2 deploy.yml
    Afternote-BE appleboy/ssh-action@v1.2.5 1 deploy.yml
    Afternote-BE docker/login-action@v4 1 deploy.yml
    Afternote-BE github/codeql-action/analyze@v4 1 codeql.yml
    Afternote-BE github/codeql-action/init@v4 1 codeql.yml
    Afternote-BE gradle/actions/dependency-submission@v6 2 ci.yml, dependency-submission.yml
    Afternote-BE gradle/actions/setup-gradle@v6 2 ci.yml
    demo-repository anishathalye/proof-html@v1.1.0 1 proof-html.yml
    demo-repository pozil/auto-assign-issue@v1 1 auto-assign.yml
    • Afternote-FE 는 이미 호환됩니다. chore(ci): Actions SHA pinning required·허용 정책을 저장소 설정에 적용한다 #1538 이 켜 둔 상태가 유지되고 있고, 열린 PR 35건 어디에도 floating 이 없습니다. 조직 정책을 켜도 이 저장소에서 깨지는 곳은 없습니다.
    • Afternote-BE 는 32건 전량이 floating 입니다. 지금 조직 설정을 켜면 BE 의 CI·배포·CodeQL·TLS 만료 감시가 통째로 startup_failure 로 죽습니다. 이슈 본문의 우려가 실측으로 확인됐습니다.
    • actions/dependency-review-action@v5 는 태그가 아니라 브랜치입니다 — refs/tags/v5 는 없고 refs/heads/v5 만 있습니다(현재 a1d282b3…, 태그 v5.0.0 과 같은 커밋). 업스트림이 force-push 하면 그 시점부터 다른 코드가 BE CI 에서 돕니다.
    • demo-repository 는 2026-08-27 에 만들어진 GitHub 데모 저장소로 run 이 1건뿐입니다. 워크플로를 고치는 것보다 Actions 를 끄는 편이 쌉니다.

    감사는 .github/scripts/audit-org-action-pinning.mjs 로 재현합니다. floating 이 남아 있으면 exit 1 이라 정책을 켜기 직전 게이트로 그대로 씁니다.

    2. allowed_actions 판정 — 조직 목록은 만들지 않습니다

    완료 조건의 «좁힐지 판단한다» 에 대한 답입니다. 조직은 all 로 두고, 허용 목록은 저장소별로 좁힙니다. 근거 셋:

    (1) 조직 목록은 구조적으로 가장 느슨한 목록이 됩니다. 저장소별 외부 액션 집합이 FE 14종 / BE 12종 / demo 2종이라 합집합이 20종입니다. github_owned_allowed 가 덮는 actions/*·github/* 를 빼도 조직 patterns 는 10줄이 되는데, FE 가 #1538 에서 정한 5줄에 FE 가 쓰지도 않는 appleboy/scp-action·appleboy/ssh-action·pozil/auto-assign-issue·anishathalye/proof-html·docker/login-action 이 얹힙니다.

    (2) 조직 목록이 저장소 목록을 덮어쓰는지가 문서에 없습니다. REST 스펙(api.github.com.json, 2026-08-30 시점)의 조직·저장소 selected-actions 엔드포인트 설명 어디에도 상하위 관계가 없고, 저장소 설정 문서는 "You might not be able to manage these settings if your organization has an overriding policy" 라고만 합니다. 대체하는 쪽이 맞다면 (1)의 10줄이 곧 FE 의 새 경계가 되고 #1538 이 좁혀 둔 것이 도로 넓어집니다. 실측으로 가리려면 조직 정책을 실제로 켜야 해서, 확인되지 않은 채로는 손대지 않았습니다.

    (3) patterns_allowed 는 공개 저장소에만 적용됩니다. REST 스펙의 selected-actions 스키마 주석입니다 — "The patterns_allowed setting only applies to public repositories." demo-repository 는 private 이라 조직 목록을 만들어도 그 저장소는 patterns 로 안 막힙니다.

    → 조직 수준에서 켤 값은 boolean 인 sha_pinning_required 하나입니다. 합집합 문제가 없고 어떤 저장소도 느슨해지지 않습니다.

    3. 켜기까지 남은 선행 조건

    1. Afternote-BE 의 floating 32건 → 0. 이 저장소에서 고칠 수 없는 몫이라 PR 을 열지 않았습니다. 문서에 워크플로별 참조 표와 감사 시점 해석된 커밋 SHA 를 남겼습니다.
    2. demo-repository 의 2건 처리. SHA 고정 / Actions 끄기(gh api --method PUT repos/Afternote/demo-repository/actions/permissions -F enabled=false) / 저장소 삭제 중 택일.
    3. 켜기 직전 재감사에서 floating 0건 확인. 열린 PR 은 계속 생깁니다 — 이 감사 중에도 fix(afternote): 빈 곡 목록을 명시적으로 실어 곡 전부 삭제를 말할 수 있게 한다 (#1599) #1602 가 시작 13초 뒤에 열렸습니다. 스냅샷은 금방 낡습니다.

    4. 켜는 명령과 fixture 검증 (승인 대기)

    gh api --method PUT orgs/Afternote/actions/permissions \
      -f enabled_repositories=all \
      -f allowed_actions=all \
      -F sha_pinning_required=true

    fixture 검증은 #1538 과 절차가 하나 달라집니다. Afternote-FE 는 이미 저장소 수준에서 pinning 이 켜져 있어, 그대로는 조직 정책과 저장소 정책을 구별할 수 없습니다. 대조군을 잡으려면 FE 의 저장소 수준 pinning 을 잠시 끄고(그동안 FE 에 floating 이 머지될 수 있는 창이 열립니다) floating fixture 가 통과하는 것을 본 뒤, 조직 정책을 켜고 같은 fixture 로 결과가 뒤집히는지 확인하고, FE 저장소 설정을 되돌립니다. 전 절차와 되돌리기 명령은 문서에 있습니다.

    또 하나 — 조직 설정 화면 문서에 따르면 재사용 워크플로는 태그 참조가 계속 허용됩니다("Reusable workflows can still be referenced by tag"). FE 의 재사용 워크플로 참조는 develop 기준 6건(고유 파일 4개) 전부 로컬(./.github/workflows/*.yml)이라 어느 쪽이든 영향이 없습니다.

  3. removed
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Aug 31, 2026
  4. OnlyReadOneBook commented on Sep 23, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    2026-09-23 BE 고정 진행

    BE 기본 브랜치의 floating 32건을 담당별로 나눴다. 담당은 BE 역할 지도(CI/CD 는 1hyok)와 파일별 커밋 이력으로 정했다.

    BE 이슈 담당 대상 건수 상태
    Afternote-BE#297 1hyok ci.yml·codeql.yml·dependency-submission.yml·quality.yml 22 PR Afternote-BE#299 리뷰 대기
    Afternote-BE#298 Hwanggyuun deploy.yml·tls-expiry.yml 10 인계

    두 이슈를 이 이슈의 blocked_by 로 걸었다. Afternote-BE#299 의 SHA 는 오늘 태그를 다시 해석한 값이다. docs/security/actions-supply-chain.md 의 0830 값과 비교하면 이 PR 이 쓰는 액션 중 github/codeql-action v4 하나만 다르다(0830 에는 v4.37.9 cdf488f5, 지금은 v4.38.1 1c5b6756). 고친 뒤 4개 파일은 감사 스크립트의 분류 함수 기준 floating 0건이다.

    남은 것:

    • demo-repository 의 Actions 끄기와 조직 sha_pinning_required 켜기는 BE 고정 두 건이 모두 머지된 뒤에 한다. 조직·저장소 설정은 이번에 바꾸지 않았다.
    • BE dependabot.yml 의 github-actions 가 open-pull-requests-limit: 0 이라 고정한 SHA 가 자동으로 올라오지 않는다. Afternote-BE#299 본문에 남은 위험으로 적었고 바꾸지는 않았다.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:platformCI·빌드·릴리스·저장소 운영 영역security보안 취약점·권한·자격·공급망 대응

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions