-
컨테이너 이미지 스캔 베스트 프랙티스DevOps/DevSecOps 2020. 7. 26. 22:46
이 글은 sysdig에서 작성한, _12 Container image scanning best practices to adopt in production _포스트를 번역했습니다. 번역 과정에서 sysdig 영업 성격의(...) 멘트들은 제하였으며, 이해를 돕기 위해 윤문과 문장 순서 변경 등의 작업을 거쳤습니다. 원본 포스트는 이 곳을 확인해주십시오. 아래 포스트에 포함된 모든 이미지는 sysdig 원본 블로그에서 가지고 왔음을 밝힙니다.
Secure DevOps, DevSecOps는 보안과 모니터링을, 개발에서 프로덕션까지 이르는 어플리케이션 라이프사이클에 접목시킵니다. 이를 통해 개발 속도를 해하지 않으면서도 더 안전하고 안정적이고 성능이 더 좋은 어플리케이션을 만들 수 있는 것이죠. 이런 워크플로우는 기존에 사용하고 있는 툴 체인을 유지하면서도 도입할 수 있고, DevOps 팀, 개발팀, 보안팀이 다 같이 바라보는 Single source of truth 로 동작할 수 있게 됩니다.이미지 스캐닝은 DevSecOps 워크플로우에 포함되어야 할 주요 기능 중 하나입니다. 가장 최전방의 방어 체계로서 보안 취약점을 감지하고 막아주는 역할을 하죠. 다행히 이미지 스캐닝은 구현과 자동화가 쉽습니다. 이 포스트에서는 이미지 스캐닝 베스트 프랙티스를 알아보도록 하겠습니다.
이미지 스캐닝이란?
이미지 스캐닝은 컨테이너 이미지의 내용과 빌드 프로세스를 분석해서 보안 이슈와 취약점, 혹은 지양해야 할 설정들이 들어가있지 않은지 확인하는 프로세스 입니다.
이미지 스캐닝 툴들은 보통 Common Vulnerabilities and Exposures (CVEs) 정보를 NVD, Alpine, Canonical 등의 여러 피드에서 수집하여 이 정보를 기반으로 스캔을 수행합니다. 몇몇 툴들은 이 외에도 범용적으로 자주 저지르는 실수 (인증 정보 하드 코딩 등..)를 걸러준다든지 하는 추가적인 스캐닝 룰을 갖기도 합니다.
BP1: CI/CD 파이프라인에 이미지 스캔 단계 추가
컨테이너 이미지를 만들고 퍼블리시 할 때 각별히 주의하고 스캐닝을 수행해야 합니다. 이는 이미 있는 CI/CD 파이프라인에 하나의 단계를 추가하는 것만으로도 달성할 수 있습니다.예를 들면 이렇게 하는거죠. 코드가 테스트를 통과하고 빌드가 완료되면 바로 프로덕션 레지스트리로 이미지를 푸시하는 대신, 일단 staging 단계의 레파지토리를 만들어서 여기에다가 푸시를 합니다. 그 후에 이미지 스캐닝 툴을 돌려서 취약점 검사 보고서를 제출하게 하고, 파이프라인 상에서 이를 확인하여 심각한 문제가 있으면 빌드를 fail 시켜버리는 방식으로 수행하는 겁니다.
BP2: Inline 스캐닝 더하기
위에서처럼 스테이징 레파지토리를 안 쓰고 싶다면 어떻게 해야 할까요? 정적인 이미지 스캐닝 외에, 컨테이너 이미지에 보안 credential이 포함되어 있다든지 하는 경우를 막으려면 어떻게 해야 할까요?이럴 때는 단순 이미지 스캔에 더해 inline 이미지 스캐닝을 추가로 수행하면 됩니다. inline 이미지 스캐닝은 스테이징 레파지토리에서 스캔을 수행하는 것이 아니라, 파이프라인 자체에서 스캐닝을 하는 겁니다. 이 경우 스캔된 메타데이터만 스캐닝 툴에 전달되기 때문에 인증 정보가 유출되는 것을 안전하게 막을 수 있다.
BP3: 레지스트리에서도 스캐닝하기
이미지 스캐닝을 시작할 때 레지스트리 자체 설정을 가장 첫번째로 해야 합니다. 배포되는 모든 이미지는 레지스트리에서 나오기 때문이죠. 레지스트리 스캔을 활성화해야 실행 전에 최소 한 번은 스캐닝이 됐다는 것을 확실히 할 수 있기 때문에 반드시 하는 것이 좋습니다.BP4: Kubernetes Admission Controllers 함께 사용하기
CI/CD 파이프라인에서 취약한 이미지를 원천 봉쇄하더라도, 여기를 지나면 실제 프로덕션에 배포를 막을 방법이 없습니다. 이미지 레지스트리에서 스캔한다고 해도 퍼블릭 이미지는 어떻게 할 수 없으니까요. 쿠버네티스 클러스터 차원에서 Pod 스케줄링 전에 스캔되지 않은 이미지나 취약하다고 판단되는 이미지는 배포하지 않게 하는 것이 필요합니다.Kubernetes admission controller가 이러한 역할을 해줄 수 있는데요, 이는 쿠버네티스 네이티브한 기능으로, 무엇을 클러스터에서 동작하도록 허용할 것인지 정의할 수 있게 합니다. admission controller는 요청이 인증/인가된 후 오브젝트로 배포되기 전에 확인 작업을 거치게 해주는 것이죠.
얘를 어떻게 활용하느냐? 스캐닝 툴들은 보통 이미지 스캔을 트리거하고 결과를 반환할 수 있는 webhook을 제공합니다. 그리고 admission controller가 이를 호출하게 하는거죠. 이 스캔 결과는 API server로 다시 돌아가 요청자에게 적당한 응답을 보낸 뒤, 스캔을 성공한 요청만 etcd DB에 저장하게 합니다.
그렇지만 이 경우 클러스터에 대한 어떠한 정보도 없이 이미지 스캐너에서 내린 결정을 무조건 신뢰하기는 어렵습니다. 이런 부분을 보완해줄 수 있는 것이 Open Policy Agent (OPA) 입니다. Open Policy Agent는 오픈소스 범용 정책 엔진으로, 쿠버네티스 클러스터가 자체적으로 스케줄링 여부를 결정하도록 해줍니다다. 예를 들어 같은 스캔 결과더라도 dev 네임스페이스에서는 통과시키고, production 네임스페이스에서는 통과시키지 않을 수 있는거죠.
BP 5: 이미지 버전 고정 (Immutable tagging)
코드 버전마다 고유한 태그를 사용하는 것을 Immutable tag 를 사용한다고 합니다. 그에 비해 latest 나 staging, dev 같이 태그 이름을 사용하는 경우, 코드의 버전이 올라가도 태그는 바뀌지 않죠. 그래서 태그 입장에서는 바라보는 코드가 달라진다는 관점에서 mutable tag라고 합니다.도커 베스트 프랙티스를 논할 때 Mutable tag는 절대 피해야 한다는 내용이 꼭 나오는데요, 그 이유는 의도와는 다르게 같은 태그 이름으로 다른 버전의 코드가 같이 배포될 수 있기 때문이죠. 이미지 스캐닝 관점에서도 Mutable tag는 위험합니다. 그 이유는 해당 태그를 갖는 이미지에 대해 스캔된 결과가 유효한지 여부를 확실히 할 수 없기 때문이죠.
그래서 예를 들면
ubuntu:focal
이런 방식으로 태그를 지정하는 것이 아니라 되도록이면ubuntu:focal-20200726
이렇게 태그를 써야 한다는 것입니다. 이를 방지하기 위해서 이미지 스캔 차원에서는 Kubernetes admission controller, OPA 엔진을 이미지 스캐너와 함께 사용해서 mutable tag를 사용하도록 강제할 수 있습니다.BP 6: Scan for OS vulnerability
이미지 스캔의 제1원칙은 이미지가 가벼울 수록 좋다는 것입니다. 이미지가 가볍다는 것은 빌드가 빠르다는 것을 의미하며, 스캔도 더 빨리 되고, 의존 패키지도 적기 때문에 덜 취약할 것으로 예상되기 때문입니다.새 도커 이미지는 보통 기존에 존재하는 베이스 이미지 위에 레이어를 더하는 방식으로 작성됩니다. 베이스 이미지는 Dockerfile 내에서
FROM
선언으로 지정됩니다. 이런 아키텍처를 레이어드 아키텍처 (Layered architecture)라고 하는데, 이 아키텍처에서는 여러 관리 업무가 훨씬 쉬워집니다. 이미지 스캐닝 차원에서는 베이스 이미지는 한 번만 스캔해도 된다는 점이 장점입니다. 예를 들어 부모 이미지가 취약하면 이를 베이스 이미지로 쓰는 다른 이미지들은 당연히 취약하겠죠. 이런 구조를 만들 수 있도록 컨테이너 이미지를 관리해야 합니다.BP 7: Distroless 이미지 사용하기
Distroless 이미지는 패키지 매니저나 쉘 등 보통 리눅스 배포판에서 사용하는 소프트웨어들이 포함되지 않은 베이스 이미지를 뜻합니다. Distroless 이미지를 사용하면 실제 어플리케이션과 의존성 패키지들을 조금 더 가볍게 패키징할 수 있습니다.
런타임 컨테이너에 꼭 필요한 것들만 남도록 하는 것이 공격 가능한 표면을 줄인다는 점에서 권장할 만한 일입니다. Distroless 이미지를 사용한다는 게 어떤 의미이고 어떤 효과가 있는지 예시를 통해 살펴보겠습니다.
코드 1: 일반 도커 이미지 사용
FROM ubuntu:focal COPY main / ENTRYPOINT ["/main"]
=> 이미지 사이즈 77.98MB
=> sysdig으로 스캔했을 때 24개의 OS 취약점 검출됨. 이 중 둘은 Medium 레벨 취약점.코드 2: Distroless 이미지 사용
FROM gcr.io/distroless/static-debian10 COPY main / ENTRYPOINT ["/main"]
=> 이미지 사이즈 6.93MB
=> Negligible 단계의 2개 취약점이 발견됨.BP 8: 써드파티 라이브러리 취약점스캔
어플리케이션은 많은 라이브러리들을 사용하고, 결국에는 실제 작성된 비즈니스 로직보다 많은 코드에 의존하게 되는 구조입니다. 결국 이 의존성 패키지 모두에 대한 면밀한 스캔이 필요하다는 이야기입니다. 보통 이 부분은 스캐너 솔루션에서 커버를 해주지만 모두가 그런 것은 아니니 실제로 의존성 패키지까지 다 스캔해주는지 확인할 필요가 있습니다.
BP 9: 레이어 순서 최적화
Dockerfile 내에서 RUN 커맨드 작성 부분을 최적화하는 것도 필요합니다. 변하지 않는 더 큰 레이어를 먼저 실행하고 자주 변하는 파일들(어플리케이션 코드)을 끝에 두면, 이미 존재하는 레이어를 재사용하게 되므로 이미지 빌드 속도와 간접적으로는 스캐닝 속도까지 개선할 수 있습니다.
BP 10: Dockerfile의 이상 설정 스캔
결국은 Dockerfile 자체를 잘 쓰는 것이 필요한데, 아래와 같은 부분은 보안 관점에서 주로 많이 실수가 일어나는 부분이라 한 번 살펴보시는 것이 좋겠습니다.
아래 내용은 베스트 프랙티스가 아니라, 자주 하는 실수입니다!
- 루트 권한으로 컨테이너 실행해서 필요한 것 이상으로 많은 권한을 갖게 함.
- 22번 SSH 포트 같이 보안상 위협이 될 수 있는 포트 불필요하게 EXPOSE 시키기.
COPY
,ADD
명령어로 노출되어서는 안되는 파일들 컨테이너에 포함시키기.- 인증 크레덴셜 등을 환경 변수나 마운트 통하지 않고 컨테이너에 바로 넣기.
- 이 외 보안 정책에 위배되는 소프트웨어를 사용한다든지, 허용된 이미지 외의 것을 베이스 이미지로 쓴다든지…
예를 들어, 다음과 같은 Dockerfile이 있다고 해보죠. 다음과 같은 점들이 이미지 스캔 과정에서 검출되어야 합니다.
- 루트로 컨테이너를 실행함
- SSH에 주요 사용하는 22번 포트를 EXPOSE 시킴. (80도 known port 이지만 서비스 목적이기 때문에 괜찮음)
- curl 할 때 크레덴셜 정보를 포함해서 호출함. 크레덴셜을 별도로 저장하는 곳에서 호출하도록 수정 필요.
이렇게 고려해야 할 점이 굉장히 많지만, 다행히 이런 것들은 NIST나 PCI 같은 보안 스탠다드에서 대부분 커버가 되고 많은 이미지 스캐닝 툴이 이런 정책에 기반한 스캐닝을 지원하고 있습니다.
BP 11: K8S Deployment에서 취약 여부 빠르게 Flag 처리하기
스캔을 통과한 이미지라고 모두 완전히 안전하다고는 할 수 없습니다. 이미지 스캔 이후 실제 배포한 뒤에 새로운 취약점이 발견되기도 하기 때문입니다. 혹은 특정 시점에 보안 기준을 사내에서 강화해서 취약하지 않다고 판단되었던 것들이 이제는 취약한 상태이게 될 수도 있겠죠. 그러면 이렇게 실행 중인 상태에서 취약하다고 판단되는 컨테이너들은 어떻게 해야 할까요?
일단 최소 아래 두 가지를 해야 합니다.
- 새로운 취약점을 감지하고 정책 변경을 반영하여 스캔해야 한다.
- 변경된 사실에 기반하여 파악된 취약점들을 관련 팀에 알려서 이미지를 최대한 빨리 픽스할 수 있도록 한다.
당연히 런타임 스캐닝을 구현하는 것이 취약점을 처리하는 데에 효과적일 것입니다. 그렇지만 런타임 스캐닝을 위해서는 새로운 취약점이 발견될 때마다 정책을 매번 업데이트해야 합니다. 이는 사람의 노동력이 많이 들어가야 하는 일입니다.
클러스터 내에서 실행 중인 이미지를 계속해서 스캐닝하는 것은 어려운 일이고, 스캐닝 툴 마다 이를 달성하기 위해 여러 전략을 택합니다. 가장 쉬운 것은 모든 이미지를 정기적으로 전수 조사하는 방법이지만, 보다 이상적으로 자원을 사용하기 위해서는 취약점 피드가 업데이트 되자 마자 관련된 이미지들을 스캔하는 것이 필요할 수도 있습니다. 다른 몇몇 툴은 이미지 메타데이터를 따로 저장한 뒤 이를 기반으로 풀스캔 할 필요 없이 새로운 취약점을 감지합니다.
실행 중인 컨테이너에서 취약점이 발견된 경우, 관건은 이 이미지를 최대한 빨리 고쳐서 재배포하는 것입니다. 이를 가능하게 하기 위해서는 취약점 보고가 효과적으로 이루어져야 합니다. 이를 달성하는 방법 하나는, 쿼리가 가능한 취약점 데이터베이스를 두어서 DevOps와 보안 팀이 광범위한 이미지, 패키지, CVE 카탈로그에서 우선순위를 지정할 수 있도록 하는 것입니다. 얼마나 오래된 CVE인지, 픽스가 있는지, 소프트웨어 버전은 몇인지 등을 확인할 수 있는 파라미터 검색이 가능하게 해야 합니다. (예: prod 네임스페이스에서 HIGH 등급 이상, 30일 이상 되고 픽스가 제공되는 취약점을 모두 보여줘) 그 조회 결과를 PDF, CSV 등의 형태로 취약점 관리 팀이나 CISO 등에게 공유할 수 있도록 하는 것도 필요합니다.
'DevOps > DevSecOps' 카테고리의 다른 글
어플리케이션 라이프사이클에 따른 OPA 정책 적용 (0) 2020.09.06 OPA Gatekeeper - Constraint, ConstraintTemplate (0) 2020.09.04 OPA 와 OPA Gatekeeper (1) 2020.08.07