OWASP Dependency-Check 통합 추가
- Jenkinsfile.security: 라이브러리 취약점 검사용 파이프라인 작성 (SAST/SCA 병행) - dependency-check-classpath.gradle: 스캔 대상 의존성 jar 분리 로직 작성 - dependency-check-suppressions.xml: 오탐 억제 규칙 파일 추가
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
// OWASP Dependency-Check 가 스캔할 서드파티 jar 를 한 디렉터리로 모은다.
|
||||
//
|
||||
// build.gradle 을 건드리지 않기 위해 init script(-I) 로만 주입한다.
|
||||
// gradle -I ci/dependency-check-classpath.gradle exportDependencyJars --no-daemon
|
||||
//
|
||||
// 출력:
|
||||
// build/dependency-check/libs/*.jar runtimeClasspath 의 외부 의존 jar (원본 파일명 유지)
|
||||
// build/dependency-check/jars.txt 수집 목록(경로 1줄씩) — 진단용
|
||||
//
|
||||
// 왜 디렉터리로 모으는가
|
||||
// Dependency-Check CLI 는 --scan <경로> 로 파일/디렉터리를 받는다. Gradle 캐시를 통째로 스캔하면
|
||||
// 이 프로젝트가 쓰지 않는 버전까지 잡히고, 프로젝트 디렉터리만 스캔하면 의존 jar 가 아예 안 잡힌다.
|
||||
// 실제 배포본에 실리는 것과 같은 목록(runtimeClasspath)만 모아야 결과가 배포 산출물과 일치한다.
|
||||
//
|
||||
// 파일명을 유지해야 하는 이유
|
||||
// Dependency-Check 는 jar 안의 POM/MANIFEST 외에 파일명에서도 CPE(제품/버전)를 추론한다.
|
||||
// 이름을 바꾸면 탐지 정확도가 떨어진다.
|
||||
|
||||
rootProject { project ->
|
||||
project.plugins.withId('java') {
|
||||
project.tasks.register('exportDependencyJars') {
|
||||
group = 'verification'
|
||||
description = 'Dependency-Check 스캔용 런타임 의존 jar 를 build/dependency-check/libs 에 모은다'
|
||||
|
||||
doLast {
|
||||
def outRoot = new File(project.layout.buildDirectory.get().asFile, 'dependency-check')
|
||||
def outDir = new File(outRoot, 'libs')
|
||||
project.delete(outDir)
|
||||
outDir.mkdirs()
|
||||
|
||||
// 컴포지트/서브프로젝트의 build 디렉터리 산출물(우리가 만든 jar)은 제외한다.
|
||||
// 자체 코드는 SonarQube 가 보는 영역이고, SCA 대상은 외부 라이브러리다.
|
||||
def buildDirs = project.allprojects.collect {
|
||||
it.layout.buildDirectory.get().asFile.absolutePath
|
||||
}
|
||||
def isOwnArtifact = { File f ->
|
||||
buildDirs.any { f.absolutePath.startsWith(it + File.separator) }
|
||||
}
|
||||
|
||||
def jars = project.configurations.runtimeClasspath.files
|
||||
.findAll { it.isFile() && it.name.endsWith('.jar') && !isOwnArtifact(it) }
|
||||
.unique()
|
||||
.sort { it.name }
|
||||
|
||||
project.copy {
|
||||
from jars
|
||||
into outDir
|
||||
}
|
||||
|
||||
new File(outRoot, 'jars.txt').text =
|
||||
jars.collect { it.absolutePath }.join(System.lineSeparator()) + System.lineSeparator()
|
||||
|
||||
def totalMb = (jars.sum { it.length() } ?: 0L) / (1024 * 1024)
|
||||
logger.lifecycle("dependency-check scan target: ${jars.size()} jars, ${totalMb as int} MB -> ${outDir}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
OWASP Dependency-Check 오탐(false positive) 억제 목록.
|
||||
|
||||
규칙
|
||||
1. 억제는 "오탐"에만 쓴다. 진짜 취약점을 조용히 숨기는 용도로 쓰지 않는다.
|
||||
실제 취약하지만 당장 못 올리는 경우는 억제 대신 만료일(until)을 넣어 재검토를 강제한다.
|
||||
2. 항목마다 <notes> 에 판단 근거와 판단자/일자를 남긴다. 근거 없는 억제는 리뷰에서 거절한다.
|
||||
3. 범위를 좁게 잡는다. cve 단건 + 특정 파일(sha1/packageUrl)이 기본이고,
|
||||
cpe 나 정규식 filePath 로 넓게 억제하지 않는다.
|
||||
|
||||
작성법
|
||||
Dependency-Check HTML 리포트의 각 취약점 옆 "Suppress" 버튼을 누르면 해당 항목의
|
||||
<suppress> 블록이 그대로 생성된다. 그것을 이 파일에 붙여넣고 <notes> 만 채우면 된다.
|
||||
|
||||
참고: https://dependency-check.github.io/DependencyCheck/general/suppression.html
|
||||
-->
|
||||
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
|
||||
|
||||
<!-- 예시 (실제 억제 시 주석을 풀고 값 교체)
|
||||
<suppress until="2026-12-31Z">
|
||||
<notes><![CDATA[
|
||||
오탐 근거: 해당 CVE 는 X 기능을 사용할 때만 성립하는데 이 앱은 해당 API 를 호출하지 않음.
|
||||
확인: 홍길동 / 2026-08-13 / 호출부 grep 결과 0건.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.example/example-lib@.*$</packageUrl>
|
||||
<cve>CVE-2026-00000</cve>
|
||||
</suppress>
|
||||
-->
|
||||
|
||||
</suppressions>
|
||||
Reference in New Issue
Block a user