naroSEC
article thumbnail

들어가기 앞서

소프트웨어 개발에서 보안은 더 이상 선택 사항이 아닌 필수 요소이다. 특히 사이버 위협으로 인한 침해 사고는 기업의 재정적 손실과 평판 손상으로 직결되므로, 완벽하지 않더라도 최소한의 보안 대책을 마련해야 한다. 또한, GDPR, HIPAA 등의 컴플라이언스 요구사항을 충족하기 위해서도 기업은 소프트웨어의 일정 수준의 보안성을 유지해야 한다.

 

이에 따라 업계 관계자들은 비용 효율적이면서도 보안을 강화할 수 있는 방법을 모색하였고, 그 결과 개발 초기 단계에서 보안을 고려하는 접근 방식으로 CI(Continuous Integration) 파이프라인에 SAST(Static Application Security Testing)를 적용하고, 실제 환경에서의 취약점을 식별하기 위해 CD(Continuous Delivery/Continuous Deployment) 파이프라인에서 DAST(Dynamic Application Security Testing)를 도입하는 방안을 도출하게 되었다. 그리고 이러한 DevOps(개발과 운영의 통합)에 보안을 통합하는 접근 방식을 DevSecOps라고 한다.

 

이번 포스팅에서는 DevOps의 핵심 개념 중 하나인 CI(Continuous Integration, 지속적인 통합)와 CD(Continuous Delivery /Continuous Deployment  , 지속적인 제공/배포) 중, CI 파이프라인에서의 SAST(Static Application Security Testing) 적용 방안을 실습을 통해 알아보고자 한다.


개요

CI(Continuous Integration)CD(Continuous Deployment/Continuous Delivery) 란?

[그림 1] CI/CD 프로세스

CI/CD는 Continuous Integration(지속적 통합), Continuous Delivery(지속적 제공), 및 Continuous Deployment(지속적 배포)의 약자이다. 이는 현대 소프트웨어 개발 및 운영 방식인 DevOps의 핵심 개념 중 하나로, 소프트웨어 개발 및 배포 과정을 자동화하여, 코드 변경 사항이 개발 환경에서부터 프로덕션 환경까지 자동으로 반영되는 일련의 프로세스를 의미한다.

 

그리고 CI/CD를 각 관점에서  살펴보면, CI(Continuous Integration, 이하 CI) 단계에서는 개발자가 변경한 코드를 버전 관리 리포지토리(Repository)에 병합하고 자동화된 빌드(Build)와 테스트(Unit Tests/Integration Tests)를 통해 코드 문제를 검사한다. 이 과정은 서로 다른 팀 구성원의 코드 변경으로 인한 충돌을 방지하고, 버그를 조기 발견 및 해결함으로써, 코드의 품질이 유지되도록 보장한다.

 

CD(Continuous Deployment/Continuous Delivery, 이하 CD) 단계는 운영 방식에 따라  Continuous Delivery(지속적인 제공) Continuous Deployment(지속적인 배포)두 가지 관점으로 나눠진다.

 

Continuous Delivery는 CI의 연장선으로, 자동화된 테스트를 통과한 코드 변경 사항이 사용자 환경으로 배포되기 전에 수동으로 검토할 수 있는 환경(ex 코드 리뷰 환경)에 먼저 제공된다. 이 후 최종 승인 단계를 거쳐 사용자에게 배포된다. 반면, Continuous Deployment는 자동화된 테스트를 통과한 코드 변경 사항을 수동 개입 없이 즉시 사용자 환경에 배포한다.

[그림 2] CI/CD 프로세스

[그림 2]는 CI/CD의 전체 작업 과정을 간결하게 도식화하고 있다. 개발자가 코드를 커밋하는 시점부터 테스트 단계까지를 CI(Continuous Integration) 파이프라인으로, 리뷰부터 배포 및 모니터링 단계까지를 CD(Continuous Delivery/Continuous Deployment) 파이프라인으로 구분할 수 있다.

"파이프라인(Pipeline)"이라는 용어는 소프트웨어 개발의 빌드, 테스트, 배포 과정을 자동화하고 관리하는 데 사용되는 일련의 단계들을 의미한다.

SAST(Static Application Security Testing)란?

SAST(Static Application Security Testing, 이하 SAST)는 코드가 실행되기 전의 정적 분석을 통해 코드 내부의 보안 취약점을 식별하는 소스코드 분석 기술 및 솔루션이다. 주로 코드가 사용자 환경으로 배포되기 전인 소프트웨어 개발 초기 단계에서 수행되며, 잠재적인 보안 문제를 조기에 발견하고 해결할 수 있게 해준다. 결과적으로 유지 보수의 효율성이 향상되고, 장기적으로 비용 절감의 이점을 가져온다.

 

이러한 맥락에서, CI 파이프라인에 SAST를 통합하는 것은 보안 측면에서 매우 효과적인 접근 방식이다. SAST를 CI 파이프라인에 포함함으로써, 개발팀은 코드가 최종 사용자 환경에 배포되기 전에 잠재적인 보안 취약점을 조기에 식별하고 해결할 수 있는 기회를 얻게 된다.

CI 파이프라인은 코드 변경 사항을 통합하고, 자동화된 빌드와 테스트를 수행하는 과정이다. 즉, 코드가 프로덕션 환경에 배포되기 전의 워크플로우를 의미한다.

 

정적 분석 유형

일반적으로  정적 코드 분석(소스코드 분석)은 아래와 같이 두 가지 유형으로 나뉘며, 이에 따른 장단점이 존재한다.

[ 사람이 직접 분석하는 방법 ]

  • 장점
    1. 코드의 의도, 설계 패턴, 비즈니스 로직 등 맥락을 이해할 수 있어 복잡한 코드를 해석하는데 유리하다.
    2. 자동화 도구가 놓칠 수 있는 비정형적인 취약점을 식별할 수 있어 설계상의 문제를 파악할 수 있다.
    3. 특정 도메인에 대한 깊이 있는 지식을 바탕으로 보안 취약점을 식별하고, 해당 도메인에 맞는 적절한 대응 방안을 제시할 수 있다.
  • 단점
    1. 사람이 직접 분석하는 과정은 시간이 많이 소요되며, 대규모 코드베이스에 대해 효과적으로 분석하기 어렵다.
    2. 분석자의 개인적인 경험과 지식에 따라 분석 결과가 달라질 수 있으며, 주관적 판단이 개입될 수 있다.
    3. 충분한 경험과 전문 지식을 갖춘 인력을 확보하기 어려울 수 있으며, 인건비가 증가할 수 있다.

[ 자동화 도구를 사용하여 분석하는 방법 ]

  • 장점
    1. 대규모 코드베이스를 빠르게 분석할 수 있으며, 반복적인 작접을 신속하게 수행할 수 있다.
    2. 일관된 기준으로 분석을 수행하므로, 분석 결과의 일관성을 유지할 수 있다.
    3. 사람이 직접 분석하는 방법에 비해 인건비 요소를 절감할 수 있어 장기적으로 볼 때, 경제적이다.
    4. CI/CD 파이플인에 통합하여 개발 과정 중 자동으로 분석을 수행할 수 있으며, 신속한 피드백을 제공받을 수 있다.
  • 단점
    1. 자동화 도구는 패턴 기반 탐지로, 동적인 실행 환경에서 발생할 수 있는 문제나 복잡한 논리적 오류를 놓칠 수 있다.
    2. 자동화 도구는 모든 보안 취약점을 정확히 식별하지 못할 수 있으며, 오탐(False Positive)과 미탐(False Negative)이 발생할 수 있다.
    3. 코드의 의도나 비즈니스 로직을 이해하지 못하므로, 복잡한 비즈니스 규칙이나 특정 설계상의 문제를 인식하는데 한계가 있다.

 

[그림 3] 정적 코드 분석 프로세스

위에서 살펴본 바와 같이, 사람의 직접 분석과 자동화 도구 사용은 각각의 장단점이 존재한다. 사람이 직접 분석하는 방법은 분석자의 경험과 지식에 따라 정확도가 달라질 수 있으며, 주관적인 판단으로 인해 오탐률이 높아질 수 있다. 반면, 자동화 도구를 이용한 분석 방법은 정해진 패턴에 기반하기 때문에 복잡한 비즈니스 로직을 완전히 이해하지 못해 오탐률이 높아질 수 있다.

 

따라서, 두 가지 유형을 적절히 결합하여 서로의 단점을 보완하는 방식이 오탐 및 미탐률을 낮추는데 가장 효과적이다. 이러한 분석 방법은 하이브리드 방식이라고 하며, 실제 현업에서 가장 많이 사용되는 방법이기도 하다.

 

예를 들어, [그림 3]과 같이 자동화 도구를 사용하여 빠르고 일관된 기본 분석을 수행한 후, 사람이 직접 분석하여 발견된 취약점을 검토하고, 문제를 해결하는 접근 방식이다. 이를 통해 결과물의 전반적인 품질을 향상시킬 수 있다. 이러한 접근 방식은 CI 파이프라인에서의 SAST에도 그대로 적용된다.

 

SAST 를 통해 발견할 수 있는 보안 취약점 유형

SAST 검사에서 사용되는 대표적인 정적 코드 분석 자동화 도구로는 SonarQube, FindBugs, Sparrow, PMD, Checkstyle 등이 있다. 이러한 도구들은 소스코드에서 취약점을 찾을 때 벤더사의 자체적인 점검 기준을 사용하기도 하지만, 보편적으로 OWASP TOP 10 및 CWE(Common Weakness Enumeration)와 같은 표준을 기반으로 취약점을 식별한다. 예로, 크로스사이트 스크립트(XSS), SQL 삽입, 운영체제 명령어 삽입, 적절하지 않은 난수 값 사용, 메모리 버퍼 오버플로우 취약점 등이 있다.

 

SAST 검사는 실행 중인 애플리케이션이 아니라 소스코드를 분석하기 때문에, 알려진 취약점 외에 구성 오류와 런타임에서 발생할 수 있는 문제를 발견하지 못한다. 또한, 대규모 코드베이스에서는 도구가 제공하는 결과만으로는 잠재적인 취약성이 실제 위협인지 또는 오탐인지 구별하기 어렵다.

 

그런 점에서, SAST 검사에서 도출된 결과를 무조건 신뢰하고 그대로 소스코드에 반영하기보다는, 경험이 풍부한 분석자가 이를 철저히 검토하는 과정이 이루어져야 한다. 국내에서는 소프트웨어의 소스코드에서 보안 약점을 진단하고, 조치 방안을 수립하며, 조치 결과를 확인하는 전문가를 양성하기 위해 행정안전부와 한국인터넷진흥원(KISA)이 SW 보안 약점 진단원 양성 과정을 운영하고 있다.

 

https://academy.kisa.or.kr/edu/planning03.kisa

 

academy.kisa.or.kr

 

추가로, 행정안전부와 한국인터넷진흥원(KISA)에서는 소프트웨어 개발 생명주기에 고려되어야 하는 보안 위협을 최소화하기 위해 각 단계별 수행해야 하는 보안 활동들이 개발자들에 의해 적절하게 수행되었는지를 점검하기 위한 기준 가이드를 배포하고 있다.

 

KISA 한국인터넷진흥원

 

www.kisa.or.kr


실습

전체 도식도

[그림 4] 실습에서 구축할 CI/CD 파이프라인 도식도

이번 실습에서 구축할 CI/CD 파이프라인 환경은 [그림 4]와 같다. 다만, 본 포스팅의 주제가 CI 파이프라인에서 SAST 적용 방안에 중점을 맞추고 있으므로, CD 파이프라인 환경은 제외하고 CI 파이프라인에 초점을 맞출 예정이다. CD 파이프라인과 관련된 내용은 추후 포스팅에서 DAST(Dynamic Application Security Testing) 구축을 주제로 다룰 생각이다.

 

실습 환경 소개

도구 설명
Code Repository GitHub
 Source WebGoat
Build Tools Maven
Building Service Jenkins
SAST Tools SonarCube
Monitering Tools Prometheus, Grafana

[표 1] 실습에서 사용될 도구

CI 파이프라인에서 SAST 통합 환경을 구축하기 위해 사용될 애플리케이션 및 서비스 도구는 [표 1]에 나열된 것과 같다. 접근성을 고려하여 오픈 소스 도구를 중심으로 환경을 구성하였으며, 실행 환경은 Dcoker를 사용할 예정이다.

 

실습 환경 구축하기

1. WebGoat Fork

[그림 5] WebGoat GitHub Repository

빌드에 사용할 소스 코드는 OWASP(Open Web Application Security Project)에서 개발하고 유지보수 중인 WebGoat 프로젝트이다. CI/CD 파이프라인을 이해하기 위한 단순한 환경 구축이라면, 개인 소스 코드나 테스트용 빌드 소스를 사용해도 무방하다. 그러나 이 포스팅에서는 SAST를 구현하여 소스 코드의 취약점을 점검할 예정이므로, 다양한 취약점이 존재하는 WebGoat 환경을 사용하는 것이 적합하다.

WebGoat는 웹 애플리케이션 보안 교육을 위한 도구로, 다양한 보안 취약점과 그에 따른 공격 기법을 실습할 수 있는 환경을 제공하는 오픈 소스 프로젝트이다.

 

WebGoat GitHub Repository는 아래의 링크를 참고하면 된다.

 

GitHub - WebGoat/WebGoat: WebGoat is a deliberately insecure application

WebGoat is a deliberately insecure application. Contribute to WebGoat/WebGoat development by creating an account on GitHub.

github.com

 

[그림 6] WebGoat Repository Fork

WebGoat 소스코드를 개인 저장소로 복제하기 위해 해당 프로젝트를 대상으로 Fork를 수행한다.

 

[그림 7] Fork된 WebGoat프로젝드

Fork가 완료되면 [그림 7]과 같이 개인 저장소에 프로젝트가 생성되고, 해당 프로젝트의 Fork 출처가 표시된다.

 

2. Jenkins 설치

[그림 8] Jenkins Master Node와 Slave Node 구성

Jenkins에서 "SCM(Source Code Management) → Build Triggers → Build Steps → Post-build Actions"와 같은 일련의 파이프라인 작업을 구성하여 실행할 때, 이는 하나의 작업(Task)으로 관리되고 수행된다. 그러다 보니 코드의 규모가 커지거나 SAST, DAST와 같은 추가 작업이 포함되면, 빌드 시간이 기하급수적으로 증가하게 된다. 특히 대규모 코드베이스 환경에서는 SAST 작업이 추가되는 것만으로도 전체 작업 시간이 1시간 이상 소요될 수 있다. 더욱이, 실제 기업의 개발 및 운영 팀은 빌드 및 작업 소요 시간에 매우 민감하기 때문에, 이러한 작업을 무턱대고 적용하기 어렵다.

 

그런 점에서, 빌드와 배포, 그리고 추가 작업에 대한 부하를 효율적으로 분산시키기 위한 적절한 구성 설정이 필요한데 Jenkins에서는 이를 위해 "Node" 기능을 제공한다.

 

Master Node는 Jenkins의 중앙 관리 역할을 맡아 빌드 파이프라인을 정의하고 관리한다. 또한, 대기 중인 작업을 적절한 Slave Node에 배포하여 전체 빌드 프로세스를 조정한다. 반면, Slave Node는 Master Node의 지시에 따라 실제 빌드와 테스트 작업을 수행한다. 이때, Slave Node에서 지정된 작업을 병렬로 처리하기 때문에 단일 Node에서 작업을 수행할 때보다 전체 빌드 시간이 단축된다. 

 

따라서, 본 실습에서도 빌드 작업을 수행하는 Node와 SAST 작업을 위한 Node를 별도로 나누어서 진행한다.

 

먼저, Jenkins를 구축하기 위한 환경이 필요하다. VMware와 같은 가상 머신을 사용할 수도 있지만, 본 실습에서는 설치와 설정이 간편한 Docker Desktop을 이용하여 컨테이너를 실행할 예정이다.

 

아래의 링크에서 파일을 다운로드 받아  Docker Desktop을 설치한다.

 

Docker Desktop: The #1 Containerization Tool for Developers | Docker

Docker Desktop is collaborative containerization software for developers. Get started and download Docker Desktop today on Mac, Windows, or Linux.

www.docker.com

 

[그림 9] Jenkins 설치 과정

설치가 되었다면, Docker Desktop을 실행하여 [그림 9]와 같이 "jenkins/jenkins", "jenkins/ssh-agent" 이미지를 다운로드 받는다. 여기서 "jenkins/ssh-agent"는 Slave Node 구성을 위해 사용된다.

 

[그림 10] Jenkins 실행 과정

"Images" 항목으로 이동하면 다운로드한 이미지 목록을 확인할 수 있다. 이전에 다운로드한 "jenkins/jenkins" 이미지를 선택한 후, [그림 10]과 같이 실행 버튼을 클릭하고 구성 설정을 완료한 뒤, "Run" 버튼을 클릭한다.

 

[그림 11] Jenkins 초기 설정 페이지

컨테이너가 실행되고 나면, 앞서 설정한 포트를 통해 접속할 수 있다. 그러면 [그림 11]과 같이 Jenkins의 초기 설정 페이지가 나타난다.

 

[그림 12] Administrator password 확인 과정

"Administrator password"는 실행된 컨테이너의 "Logs" 항목에서 확인할 수 있으며, 최초 한 번만 입력하면 된다.

 

[그림 13] Jenkins 권장 플러그인 설치

패스워드를 입력하면 플러그인 설정 페이지로 이동한다. "Install suggested plugins"를 클릭하여 Jenkins의 기본 플러그인들을 설치해준다.

 

[그림 14] 관리자 계정 생성

플러그인 설치가 완료되면, [그림 14]과 같이 관리자 계정 생성 페이지로 이동한다. 입력 폼에 필요한 정보를 기입한 후, "Save and Continue" 버튼을 클릭하여 계정을 생성한다.

 

[그림 15] Jenkins Root URL 설정

마지막으로 Jenkins Root URL을 설정하면, Jenkins 설치 및 구성 과정이 완료된다.

 

3. SonarQube 설치

[그림 16] SonarQube 설치 과정

Dokcer Desktop으로 돌아가서 "sonarqube" 이미지를 다운로드 받는다.

SonarQube는 소프트웨어 개발에서 코드 품질을 분석하고 개선하기 위해 사용되는 대표적인 정적 코드 분석 도구이다. 국내에서는 정적 코드 분석 도구로 Sparrow가 널리 알려져 사용되고 있지만, 해외에서는 SonarQube가 주로 사용된다. 아울러, SonarQube는 무료 오픈 소스 도구로, 유료 상용 도구인 Sparrow에 비해 비용 부담이 없으며, 현재도 활발히 개발 및 개선되고 있어 다양한 기능을 제공한다.

[그림 17] SonarQube 실행

다운로드한 "sonarqube" 이미지를 선택한 후, [그림 17]과 같이 실행 버튼을 클릭하고 구성 설정을 완료환 뒤, "Run" 버튼을 클릭한다.

 

[그림 18] SonarQube 로그인 페이지

컨테이너가 실행되고 나면, 앞서 설정한 포트를 통해 로그인 페이지로 접속할 수 있다. SonarQube 설치 시 제공되는 디폴트 계정은 "admin:admin"으로, 최초 로그인 시 패스워드 변경이 필요하다.

 

4. Jenkins 플러그인 설치

[그림 19] Jenkins 플러그인 설치 과정(1/3)

Jenkins에서 소스코드 빌드 및 SonarQube 연동을 위해서는 별도의 플러그인 설치가 필요하다. [그림 19]와 같이 "Jenkins 관리" 페이지로 이동한 뒤, "플러그인 관리" 버튼을 클릭한다.

 

[그림 20] Jenkins 플러그인 설치 과정(2/3)

앞서 실습을 위해 Fork 했던 WebGoat 프로젝트는 Maven 빌드 도구를 사용한다. Jenkins에서도 이를 사용하기 위해 "Maven Invoker"와 "Maven Integration" 플러그인을 설치해준다.

 

[그림 21] Jenkins 플러그인 설치 과정(3/3)

다음으로 Jenkins와 SonarQube 연동을 위해 "SonarQube Scanner" 플러그인도 설치해준다.

Jekins 파이프라인 구성하기

파이프라인(Pipeline)은 소프트웨어 개발에서 빌드, 테스트, 배포 과정을 자동화하고 관리하는 일련의 단계를 총칭한다. 반면, Jenkins에서 파이프라인은 코드 변경이 발생한 순간부터 최종 배포까지의 모든 과정을 정의하고 실행하는 스크립트를 의미한다.

 

즉, 이는 하나의 오케스트레이션이자 CI/CD 파이프라인 구성의 핵심이기도 하다. 그리고 이를 구성하기 위해서는 Jenkins에게 각 단계별로 무엇을 실행해야 하는지 명확하게 알려줘야 한다.

 

[그림 22] Jenkins 파이프라인 구성 과정

Jenkins 페이지에 접속한 후, 새로운 빌드 작업을 구성하기 위해 홈 화면에서 "새로운 Item" 버튼을 클릭한다. 작업 타입은 [그림 22]와 같이 "Pipeline"으로 지정해준다.

 

[그림 23] Jenkins 파이프라인 구성 과정(1/5)

앞서 Jenkins에서의 파이프라인은 코드 변경이 발생한 순간부터 최종 배포까지의 모든 과정을 정의하고 실행하는 스크립트를 의미한다고 설명했었다. [그림 23]의 "Pipeline" 항목은 이러한 설정을 지정하는 부분이다.

 

Jenkins에서는 파이프라인 스크립트를 작성하는 두 가지 주요 방법을 제공한다. 첫 번째는, Jenkins 내에서 직접 스크립트를 작성하는 방법이 있고, 두 번째는 GitHub, SVN 등과 같은 소스 코드 관리 시스템(SCM)을 활용하는 방법이다. 후자의 방법은 스크립트 배포 방식으로 불리며, 소스 코드를 Git에 커밋할 때 빌드 및 배포 구성에 관한 방법도 함께 배포할 수 있어 유지 보수가 용이하다. 더불어, 이 방법은 Jenkins에서도 권장하고 있는 방식이기도 하다.

 

그림 23]과 같이 "Definition" 항목 값을 "Pipeline script from SCM"으로 설정한 후, Fork한 WebGoat 프로젝트의 Clone 주소를 입력해준다.

 

[그림 24] Jenkins 파이프라인 구성 과정(2/5)

Jenkins가 설정된 GitHub Repository로 접근하여 코드를 클론(Clone)하거나 풀(Pull)할 수 있도록 필요한 권한을 부여하기 위해 GitHub Access Token 정보를 추가해야 한다.  "Credentials" 항목에서 "Add" 버튼을 클릭한 뒤, 출력되는 팝업 창의 "Kind" 항목을 "Secret text"로 변경한다.

 

[그림 25]

GitHub Access Token 값 생성을 위해 [그림 25]와 같이 GitHub 설정 페이지로 이동한다.

 

[그림 26] Jenkins 파이프라인 구성 과정(3/5)

"Select scopes" 항목에서 "repo", "admin:org" 작업에 필요한 권한을 지정해주고 토큰을 생성한다.

 

[그림 27] Jenkins 파이프라인 구성 과정(4/5)

생성한 Token 값을 기입하고 "Add" 버튼을 클릭하여 "Credentials"를 생성해준다.

 

[그림 28] Jenkins 파이프라인 구성 과정(5/5)

다음으로, ① Branch 값을 Fork한 WebGoat Repository의 Branch와 일치시켜준다. ② Script Path에는 SCM에서 읽어 올 Pipeline Script가 위치한 경로를 명시해줘야 한다. 기준은 GitHub Repository의 Root 경로이다. 별다른 설정을 하지 않고 디폴트 값인 "Jenkinsfile"로 두고 저장 버튼을 클릭한다.

 

[그림 29] Jenkinsfile 생성

WebGoat Repository 루트 경로에 [그림 28]에서  "Script Path"로 지정한 "Jenkinsfile"이라는 이름으로 스크립트를 생성한다. 코드는 아래 "더보기" 블록을 참고하면 된다.

더보기
#!groovy
// 공유 라이브러리를 설정한다.
@Library('jenkins-lib') _

pipeline {
	agent any

	environment {
    	// 공유 라이브러리의 getGitRepoName() 함수를 호출한다. Repository 이름 파싱
		gitRepoName = getGitRepoName()
	}

	stages{
		stage('Checkout SCM') {
			steps {
				echo 'Checkout SCM!'
				// Jenkins 프로젝트에 설정된 Repository로 부터 현재 브랜치의 소스코드를 가져오는데 사용한다.
				checkout scm
			}
		}

		stage('Build') {
			steps {
				script {
                	// Maven Build Tool에 실행 권한을 부여한다.
					sh "chmod u+x ./mvnw"
                    sh "./mvnw clean package
				}
			}
		}

		stage('Deploy') {
			steps {
				echo 'Deploy is not yet implemented!'
			}
		}

		stage('SAST') {
			steps{
				script{
                	// SonarQube 작업을 호출한다.
					build job: 'SAST-SonarQube', parameters:[
						string(name: 'GIT_REPONAME', value: gitRepoName)
					], wait: false
				}
			}
		}
	}
}

 

코드 상단의 "@Library('jenkins-lib') _" 구문은 공유 라이브러리를 사용하겠다는 의미이다. 쉽게 말해, 외부 모듈을 불러오는 것이라고 생각하면 된다. 다만, 타 프로그래밍 플랫폼과의 차이점은 Jenkins에서는 공유 라이브러리를 GitHub Repository를 통해 불러온다는 점이다.

 

Jenkins 공유 라이브러리에 대한 자세한 사용법과 활용은 아래의 링크에서 확인 가능하다

 

Extending with Shared Libraries

Jenkins – an open source automation server which enables developers around the world to reliably build, test, and deploy their software

www.jenkins.io

Jenkins 공유 라이브러리는 반복되는 코드를 줄이고 유지보수를 용이하게 하는데 사용된다. 공통된 CI 로직을 모듈로 분리하여 관리한다.

 

공유 라이브러리 설정하기

(root)
+- src                     # Groovy source files
|   +- org
|       +- foo
|           +- Bar.groovy  # for org.foo.Bar class
+- vars
|   +- foo.groovy          # for global 'foo' variable
|   +- foo.txt             # help for 'foo' variable
+- resources               # resource files (external libraries only)
|   +- org
|       +- foo
|           +- bar.json    # static helper data for org.foo.Bar

Jenkins 문서를 살펴보면 공유 라이브러리를 사용하기 위해서는 위와 같은 트리 구조를 가져야 한다고 나와있다. 이를 참고하여 파일을 만들어 주겠다.

[그림 30] Jenkins 공유 라이브러리 생성

개인 GitHub에 새로운 공유 라이브러리를 위한 Repository를 생성한다. 이때, Repository의 이름은 "@Library('jenkins-lib') _" 구문에서 지정한 "jenkins-lib"으로 설정해준다. 이후, 아래 "더보기"를 참조하여 /vars 디렉토리 하위에 스크립트를 추가해준다.

더보기
def call() {
    def gitRepoUrl = scm.getUserRemoteConfigs()[0].getUrl()
    def gitRepoName = gitRepoUrl.tokenize('/').last().replaceAll(/\.git$/, '')
	
    return gitRepoName
}

 

[그림 31] Jenkins 공유 라이브러리 설정 과정(1/3)

생성한 공유 라이브러리를 Jenkins에서 불러올 수 있도록 설정을 해주겠다. 해당 설정은 "Jenkins" 관리의 "System" 항목에서 할 수 있다.

 

[그림 32] Jenkins 공유 라이브러리 설정 과정(1/3)

"Global Trusted Pipeline Libraries" 항목에서 [그림 32]와 같이 설정한다. "Name" 필드에는 [그림 30]에서 생성한 Repository의 이름을 입력하고, "Default version"에는 사용할 Branch를 명시해준다.

 

[그림 33] Jenkins 공유 라이브러리 설정 과정(1/3)

다음으로, ① "Retrieval method"에는 "Modern SCM"을 선택하고, ② SCM 플랫폼으로는 Git을 지정합니다. ③ "Project Repository" 필드에는 공유 라이브러리의 Repository URL을 입력한다.

 

SAST 적용

SonarQube 연동하기

[그림 34] SonarQube 연동 과정(1/9)

Jenkins에서 SonarQube로 프로젝트를 생성하고 소스코드를 분석하려면 SonarQube 서버에 접근할 수 있는 권한이 필요하다. 이를 위해 SonarQube Access Token을 등록해야 한다. "Jenkins 관리"에서 "Credentials" 항목으로 이동한다.

 

[그림 35] SonarQube 연동 과정(2/9)

"global" 도메인을 클릭한 뒤, 새로운 크리덴셜 등록을 위해 "Add Credentials" 버튼을 클릭한다.

 

[그림 36] SonarQube 연동 과정(3/9)

"Kind" 항목을 "Secret text"로 설정하고, "Secret" 입력란에 SonarQube Access Token 값을 입력해주면 된다.

 

[그림 37] SonarQube 연동 과정(4/9)

SonarQube Access Token 값은 SonarQube 사용자 관리 페이지에서 확인할 수 있다. SonarQube 페이지로 접속한 뒤, [그림 37]과 같이 "Generate Tokens" 항목에서 Token을 발급받을 수 있다.

 

[그림 38] SonarQube 연동 과정(5/9)

Jenkins 페이지로 돌아와서 앞서 발급한 Token 값과 "ID"를 입력해준다.

 

[그림 39] SonarQube 연동 과정(6/9)

Jenkins 서버와 SonarQube 서버 간의 연동을 위해 환경 변수를 설정해줘야 한다. [그림 39]와 같이 "jenkins 관리" 페이지에서 "System"으로 이동한다. 이후, "SonarQube server" 항목에서 "Add SonarQube" 버튼을 클릭한다.

 

[그림 40] SonarQube 연동 과정(7/9)

설정에서 SonarQube 서버 주소를 입력할 때, Docker 컨테이너의 내부 IP 주소를 사용해야 한다. 이 주소는 [그림 40]에서 SonarQube 컨테이너의 정보를 통해 확인할 수 있다. "Server authentication token" 필드에는 [그림 38]에서 생성한 크리덴셜을 선택하면 된다.

 

[그림 41] SonarQube 연동 과정(8/9)

마지막으로, SonarQube Scanner를 설정하기 위해 "Jenkins 관리" 페이지에서 "Tools" 항목으로 이동한다.

 

[그림 42] SonarQube 연동 과정(9/9)

"SonarQube Scanner installations" 항목에서 "Add SonarQube Scanner" 버튼을 클릭하고 가장 최신 버전의 SonarQube Scanner를 선택한다.

 

SonarQube 작업 등록

[그림 43] SonarQube 빌드 작업 등록(1/3)

Jenkins 서버와 SonarQube 서버 간의 연동 작업이 완료되었다면, SonarQube 서버에 실제 SAST 작업을 요청하기 위한 새로운 작업을 생성한다. 이때, "Item Name"은 [그림 29]에서 생성한 Pipeline Script의 "build job" 이름과 동일하게 설정해준다.

 

[그림 44] SonarQube 빌드 작업 등록(1/3)

[그림 44]와 같이 "이 빌드는 매개변수가 있습니다."를 체크한 후, String 타입의 매개변수를 추가한다. 이 매개변수는 [그림 29]의 Pipeline Sciprt에서 값을 전달받는데 사용되며, SonarQube 프로젝트 이름으로 사용할 예정이다.

 

[그림 45] SonarQube 빌드 작업 등록(1/3)

"Build Steps" 항목에서 "Add build step" 버튼을 클릭한 후, "Execute SonarQube Scanner"를 선택한다. 이후 "Analysis properties" 입력란에 "더보기"에 제공된 환경 변수 코드를 입력해준다.

더보기
sonar.projectKey=${GIT_REPONAME} // SonarQube Project 이름을 지정해준다.
sonar.java.binaries=. // 컴파일된 클래스 파일의 경로를 지정해준다.

여기서 사용되는 "sonar.projectKey"는 SonarQube 프로젝트의 이름을 설정하는 변수이다. 이 변수의 값을 [그림 44]에서 정의한 "GIT_REPONAME" 매개변수 값으로 지정해줌으로써, SonarQube 프로젝트 이름을 설정하게 된다. "sonar.java.binaries"는 Java 코드의 바이너리 파일(컴파일된 클래스 파일)을 경로를 지정하는데 사용된다.

 

Node 설정하기

신규 Node 등록

[그림 46] 신규 Node 등록 과정(1/11)

[ 3.3.2 Jenkins 설치 ] 단락에서 Jenkins Node의 필요성에 대해 설명했었다. 여기서는 SSH Agent를 Jenkins Slave로 활용하여 SonarQube Scanner를 실행함으로써 파이프라인 구조를 분리하고, 이를 통해 Master 서버의 부하를 줄이는 방법을 다루겠다.

 

Docker Desktop에서 이전에 다운로드한 jenkins/ssh-agent 이미지를 사용하여 [그림 46]과 같이 컨테이너를 실행한다.

 

[그림 47] 신규 Node 등록 과정(2/11)

SSH Agent를 사용하여 Jenkins 서버에 원격으로 접속하기 위해서는 Jenkins 서버의 SSH 공개 키를 발급받아야 한다. 우선, [그림 47]과 같이 "Docker Desktop"에서 Jenkins 서버 컨테이너를 선택해준다. 이후 "Exec" 항목을 클릭하여 해당 컨테이너의 쉘을 활성화한 후, ssh-keygen 명령어를 입력한다. 이 명령어는 SSH 공개 키와 개인 키를 생성하는데 사용된다.

 

[그림 48] 신규 Node 등록 과정(3/11)

Jenkins 서버에서 생성한 SSH 공개 키를 복사한 후, SSH Agent 서버에서 cat 명령어를 사용하여 해당 키를 authorized_keys 파일에 추가한다. 이제, Jenkins 서버와 SSH Agent 서버 간에 SSH 키를 통한 원격 접속이 가능해진다.

 

[그림 49] 신규 Node 등록 과정(4/11)

새로운 Node를 등록하기 위해 [그림 49]와 같이 "Jenkins 관리" 페이지에서 "Node" 항목으로 이동한 뒤, "New Node" 버튼을 클릭한다.

 

[그림 50] 신규 Node 등록 과정(5/11)

"노드명"을 입력하고 "Type"을 "Permanent Agent"로 선택한 후, "Create"  버튼을 클릭한다.

 

[그림 51] 신규 Node 등록 과정(6/11)

① "Number of executors"는 Jenkins가 동시에 수행할 수 있는 최대 빌드 수를 지정하는 항목으로, 개인 PC의 사양에 따라 값을 설정하면 된다. ② "Remote root directory" 항목에는 에이전트의 홈 디렉토리를 지정해준다. 이 디렉토리를 기준으로 SSH 접근 키 등의 파일을 탐색하게 된다. ③ "Labels"는 해당 노드를 식별하는 데 사용되는 명칭으로, SonarQube에서 이 노드를 호출할 때 사용된다.

 

[그림 52] 신규 Node 등록 과정(7/11)

다음으로, "Launch method" 항목에서 "Launch agents via SSH"를 선택한다. "Host" 값에는 SSH Agent 서버의 컨터이네 내부 IP 주소를 기입한다.

 

[그림 53] 신규 Node 등록 과정(8/11)

SSH 인증을 수행할 크리덴셜 정보를 등록하기 위해, [그림 53]과 같이 "Add" 버튼을 클릭한다.

 

[그림 54] 신규 Node 등록 과정(9/11)

① "Kind" 항목에서는 SSH 공개키 기반 인증을 위해 "SSH Username with private key"를 선택한다. ② "ID"는 임의의 값을 지정한다. ③ "Username"에는 [그림 48]에서 생성했던 SSH 공개키의 소유자 정보를 입력한다.

 

[그림 55] 신규 Node 등록 과정(10/11)

"Private Key" 항목에서 "Enter directly"를 선택한다. 이 항목은 SSH 공개키 인증을 위한 개인키를 등록하는 곳으로 Jenkins 서버에서 생성한 SSH 개인키 파일인 id_rsa의 내용을 입력해주면 된다.

 

[그림 56] 신규 Node 등록 과정(11/11)

다시 페이지로 돌아와서, "Credentials" 항목에서 앞서 생성한 인증 정보를 선택하고 "Save" 버튼을 클릭하여 Node 등록을 마친다.

페이지로 돌아가서 "Credentials" 항목에서 앞서 생성한 인증 정보를 선택한 후, "Save" 버튼을 클릭하여 Node 등록을 완료한다.

 

Node 연동

[그림 57] SonarQube 빌드 작업과 신규 Node 연동

Node 등록이 완료된 후, Node에서 SonarQube 작업을 실행하기 위해 추가적인 연동 작업이 필요하다. 먼저, SonarQube 작업 페이지로 이동한 뒤, [그림 57]과 같이 "Restrict where this project can be run" 항목을 체크한다. 이 설정은 특정 에이전트에서만 작업이 실행되도록 제한하는 기능이다. [그림 51]에서 설정한 "Labels" 값을 입력해주면 된다.

 

이로써, Jenkins 파이프라인을 수행하기 위한 모든 작업 구성이 끝났다.

 

오류

Maven Build 오류

[그림 58] WebGoat 프로젝트 pom.xml

WebGoat 프로젝트의 pom.xml 파일을 확인해보면, Java 버전이 21로 설정되어 있다. 빌드 환경에서 Java 21 버전을 사용하지 않는 경우, 빌드 작업 시 Java 컴파일러 오류가 발생한다. 따라서, 정상 빌드 작업을 위해 컨테이너 빌드 환경에서 지원하는 버전으로 수정해줘야 한다.

 

Node known_hosts 관련 SSH Connection 오류

[그림 59] known_hosts 관련 SSH Connection 오류

"known_hosts" 파일은 SSH 연결을 통해 인증된 서버의 공개 키를 저장하는 파일이다. 일반적으로 이 파일은 SSH 연결 시 자동으로 생성되는데 서버 오류로 인해 이 파일이 생성되지 못하는 경우, [그림 59]와 같이 SSH 연결 오류가 발생하고 연결이 거부될 수 있다.

 

ssh-keyscan -H 신규노드IP >> /var/jenkins_home/.ssh/known_hosts

오류 해결 방안은 간단하다. 사용자가 직접 위 명령어를 통해 수동으로 생성해주면 된다. 

 

Jenkins Pipeline 실행

결과 확인

[그림 60] Jenkins Pipeline 실행 결과

모든 구성 작업이 완료되었다면, Jenkins WebGoat Pipeline 빌드 작업을 실행한다. 이후 [그림 60]에서 확인할 수 있듯이, 각 Pipeline Stage가 순서대로 "SCM → Build → Deploy → SAST"로 실행되어 작업이 완료된 것을 확인할 수 있다.

 

[그림 61] SAST SonarQube 실행 결과

SonarQube 페이지를 확인해보면 SAST 산출물인 정적 코드 분석 결과도 확인할 수 있다.

profile

naroSEC

@naroSEC

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...