Krong Dev.
아카이브 목록

Github Actions란?


  • github actions는 github 저장소 안에서 빌드, 테스트, 배포, 검사 같은 작업을 하는 자동화 도구다. 쉽게 말해서 ‘github에서 어떤 일이 발생하면 미리 정해둔 작업을 자동 실행하는 시스템’ 이라고 보면 된다.
  • 예를 들어
    • 코드가 push 되면 테스트 실행 (테스트)
    • PR이 열리면 lint 실행 (빌드)
    • main에 머지되면 배포 실행 (배포)

이런 작업을 사람이 매번 수동으로 하지 않도록 해주는 게 핵심이다.

왜 쓰는가?

  • 가장 큰 이유는 반복 작업 자동화다. 개발자가 직접 매번 빌드하고, 테스트하고, 배포하는 대신 Github가 정해진 규칙에 따라 대신 실행해준다.
  • 또 다른 이유는 실수를 줄이는 것이다. 항상 같은 순서와 같은 조건으로 실행되니까, 사람마다 다르게 처리하는 문제를 줄일 수 있다.
  • 기본 구조
    • Event : 언제 실행할지
    • Workflow : 무엇을 할지
    • Job : 큰 작업 단위
    • Step : Job 안의 세부 단계
    • Action : 재사용 가능한 작업 조각
    • Runner : 실제 실행되는 머신
  • 전체 그림
    • GIthub Actions는 Github 저장소에서 일어나는 이벤트를 계기로 자동으로 작업을 실행하는 시스템이다. 예를 들어서 push, pull_request, schedule같은 이벤트가 발생하면, Github가 미리 정해둔 워크플로우를 실행한다.
    • 이 워크플로우 안에는 보통 다음이 들어간다.
      • workflow : 전체 자동화 규칙
      • job : 큰 작업 단위
      • step : job 안에서 순서대로 실행되는 세부 단계
      • action : 재사용 가능한 작업 조각
      • runner : 실제 명령이 실행되는 머신

즉, 이벤트가 발생 → 워크플로우 시작 → Job이 runner에서 실행 → 여러 step과 action이 차례대로 동작

헷갈리는 ‘checkout’이 정확이 무엇일까?

  • actions/checkout은 git/checkout 명령어와 비슷하지만, Github Actions용 액션이다.
  • actions/checkout : 워크플로우가 실행되는 runner 위에 저장소의 코드를 내려받는 역할을 한다. 즉, CI가 실제로 코드를 검사하려면 파일이 runner안에 있어야 하니까 그걸 가져오는 단계라고 이해하면 된다.

그러면 ‘runner’는 정확이 무엇일까?

  • runner는 워크플로우 job이 실제로 실행되는 환경이다.
  • 쉽게 말하면 ci를 돌리는 컴퓨터라고 생각하면 된다. 대표적으로 github가 제공하는 ‘ubuntu-latest’같은 환경도 runner다.
    • 여기서 중요한 점은 같은 job 안의 step들은 모두 같은 runner에서 순차적으로 실행된다는 점인데, 이 때문에 앞 step에서 남긴 ‘파일, 환경 변수, 설정값’이 다음 step에서도 그대로 보인다. 이게 왜 중요하냐면, 만약 어떤 step이 악성 행동을 하면 그 runner 안의 상태를 이용할 수 있기 때문이다.

인증 정보를 담을 수 있는게 왜 문제일까?

  • checkout 단계가 남겨둔 인증 정보가 있으면, 뒤에서 실행되는 스크립트나 액션이 그 정보를 이용해 원치 않는 쓰기 작업을 할 수 있다.
  • 이 말을 쉽게 풀면
      1. actions/checkout이 저장소를 가져오면서 토큰을 git 설정에 남길 수 있다.
      1. 그 뒤에 실행되는 step이 그 토큰을 읽을 수 있다.
      1. 그러면 그 step은 인증된 상태로 Git push 같은 작업을 할 수 있다.
      1. 만약 그 step이 악성이라면, 저장소에 원치 않는 커밋이나 브랜치를 만들 수 있다.

즉, 문제는 ‘코드를 가져오는 것’ 자체가 아니라, 그 과정에서 남은 자격증명이 뒤 step까지 노출될 수 있다는 것이다.

  • 예시로
    • checkout이 저장소를 받아오고
    • 토큰이 runner의 git 설정에 남아있고
    • 그 뒤 step에서 누군가 악성 스크립트를 실행함
    • 그 스크립트가 git push origin main을 실행함

이 경우 원래는 push 권한이 없는 상황이어야 하는데, checkout이 남긴 자격증명 때문에 인증이 통과할 수 있다. 그러면 ci가 단순 검사를 넘어서 저장소에 쓰기 작업을 하는 통로가 되어버리는 셈이다.

  • 그래서 persist-credentials: false 설정이 필요하다.
  • 이 옵션은 checkout이 인증 정보를 git 설정에 남기지 못하게 해서, 뒤 step이 그 자격증명을 재사용하는 것을 막는다.

permissions: contents: read는 무엇일까?

  • 이건 github actions가 워크플로우에 주는 권한을 읽기 전용으로 제한하는 설정이다. 즉, ‘파일 읽기 가능’, ‘저장소 쓰기 불가’ 기능을 하는 것이다.
  • 만약 ci가 lint, typecheck, format만 하는 경우에는 저장소를 수정할 필요가 없으니까, 굳이 쓰기 권한을 줄 이유가 없다.
  • 따라서 permissions: contents: read를 넣는 게 안전하다.
  • 이 설정과 persist-credentials: false는 역할이 다르다.
    • permissions: contents: read 는 토큰 자체의 권한을 줄이지만,
    • persist-credentials: false 는 그 토큰을 git 설정에 남기지 않는 것이다.

둘을 같이 사용하면 더 안전해진다.

  • 보통 ci.yml과 security.yml을 나누는데, 이는 어떤 조건을 check 하냐. 즉, 관심에 따라 나눈 것이다.
    • ci.yml에는 lint, typecheck, format check, 테스트 같은 코드 품질 검사용이다.
    • security.yml은 npm audit, CodeQL, 주기적 취약 점검과 같은 보안 검사용이다.

이렇게 나누면 관심사에 따라 역할이 분명해진다. ci는 ‘코드가 잘 작성됐는지’를 보고, security는 ‘의존성이나 코드에 위험이 없는지’를 본다.

  • npm audit과 CodeQL의 차이
    • 둘 다 보안과 관련 있지만 보는 대상이 다르다.
    • npm audit : 패키지 의존성의 알려진 취약점을 찾는다. 즉, 내가 설치한 라이브러리 쪽 문제를 본다.
    • CodeQL: 소스 코드 자체를 분석해서 취약한 패턴을 찾는다. 예를 들면 SQL 인젝션, 잘못된 입력 처리, 위험한 코드 구조 같은 것을 볼 수 있다.
    • 쉽게 말하면
      • npm audit : 외부 패키지 위험 검사
      • CodeQL : 내 코드 위험 검사

그래서 둘은 서로 대체 관계라기보다는 보완 관계다.

SHA로 고정하라는 말은 무엇일까?

  • 서드파티 액션은 보통 @v4, @v5 처럼 버전 태그로 쓰는데, 이 태그는 바뀔 수 있다. 즉, 같은 @v4라도 나중에 그 태그가 다른 커밋을 가리키게 되면 문제가 생길 수 있다.
  • 그래서 보안을 더 강하게 하려면 커밋 SHA로 고정하는 게 좋다. 그렇게 하면 내가 의도한 정확한 코드만 실행된다.
  • 다만 공식 액션만 쓰는 단순 CI에서는 필수는 아니고, 외부 액션이 많아질수록 중요해진다.

포크 PR을 왜 조심해야 하나?

  • 포크에서 온 PR은 외부 사용자가 만든 코드가 워크플로우에서 실행될 수 있다는 뜻이다. 이때 시크릿이나 쓰기 권한이 있으면 위험하다.
  • 그래서 보통은
    • 포크 PR에서는 시크릿을 주지 않거나
    • 검증용 워크플로우만 돌리거나
    • 권한을 최소화한다.
  • #TIL
  • #GitHub Actions