feat: redis, fileserver, httpclient 런타임 시점 구현 추가

This commit is contained in:
donghyeon-ka
2026-07-28 14:26:54 +09:00
parent 7363b2aa1e
commit b3add0162d
257 changed files with 30430 additions and 1357 deletions
+113
View File
@@ -0,0 +1,113 @@
name: ci-quality-gates
on:
pull_request:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
TESTCONTAINERS_REUSE_ENABLE: "false"
jobs:
quality-gates:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Require the committed public-path security baseline
run: |
set -euo pipefail
readonly snapshot='docs/security/public-paths-snapshot.txt'
if [[ ! -s "${snapshot}" ]]; then
echo "::error::${snapshot} is missing or empty. CI must not let verifyPublicPathSnapshot create its own first-run baseline."
exit 1
fi
if ! git ls-files --error-unmatch "${snapshot}" >/dev/null 2>&1; then
echo "::error::${snapshot} exists locally but is not committed."
exit 1
fi
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Check quality, public paths, and dependency locks
working-directory: src
run: ./gradlew check verifyPublicPathSnapshot verifyDependencyLocks --no-daemon --stacktrace
sample-off:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Verify the application without the sample fixture
working-directory: src
run: ./gradlew :app-bootstrap:sampleOffTest verifyCleanArchitectureDependencies --no-daemon --stacktrace
gate-matrix-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Verify the gate matrix against the repository
run: bash .github/scripts/verify-gate-matrix.sh
# Advisory only. Quarantine expiry/drift remains blocking through verifyQuarantineSunset in check.
quarantine:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Run quarantined tests as an advisory signal
working-directory: src
run: ./gradlew quarantineTest --no-daemon
release-gate:
needs:
- quality-gates
- sample-off
- gate-matrix-lint
if: always()
runs-on: ubuntu-latest
steps:
- name: Require every current blocking job to succeed
env:
QUALITY_RESULT: ${{ needs.quality-gates.result }}
SAMPLE_OFF_RESULT: ${{ needs.sample-off.result }}
MATRIX_RESULT: ${{ needs.gate-matrix-lint.result }}
run: |
set -euo pipefail
for result in "${QUALITY_RESULT}" "${SAMPLE_OFF_RESULT}" "${MATRIX_RESULT}"; do
if [[ "${result}" != "success" ]]; then
echo "::error::release-gate: required job result was ${result}"
exit 1
fi
done
echo "release-gate: all current blocking quality jobs succeeded."
@@ -0,0 +1,185 @@
name: dependency-vulnerability
on:
pull_request:
push:
branches: ["main"]
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# The compare API exists only on GitHub.com. Trivy remains the full-snapshot backstop elsewhere.
dependency-review:
if: github.event_name == 'pull_request' && github.server_url == 'https://github.com'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Review newly introduced dependencies
uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # actions/dependency-review-action@v4.8.0
with:
config-file: ./.github/dependency-review-config.yml
# The submission API is also GitHub.com-only and is not required for the platform-neutral scan.
dependency-submission:
if: github.event_name == 'push' && github.server_url == 'https://github.com'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # actions/setup-java@v4.7.1
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
- name: Submit the resolved Gradle dependency graph
uses: gradle/actions/dependency-submission@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # gradle/actions@v4.4.4
with:
build-root-directory: src
trivy-fs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Install pinned Trivy under RUNNER_TEMP
env:
TRIVY_DOWNLOAD_BASE_URL: ${{ vars.TRIVY_DOWNLOAD_BASE_URL }}
run: |
set -euo pipefail
readonly TRIVY_VERSION='0.71.2'
readonly TRIVY_SHA256_AMD64='0510e71e2fd39bf863856d499c8dc19feb4e7336546394c502a8f5cc7ab27460'
readonly TRIVY_SHA256_ARM64='fe1c7106e15a5365d485b098a8c338f91e3b7ba71cb0e4963b98a3a098763cfc'
readonly DOWNLOAD_BASE_URL="${TRIVY_DOWNLOAD_BASE_URL:-https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}}"
case "${RUNNER_ARCH:-X64}" in
X64)
asset_arch='64bit'
expected_sha256="${TRIVY_SHA256_AMD64}"
;;
ARM64)
asset_arch='ARM64'
expected_sha256="${TRIVY_SHA256_ARM64}"
;;
*)
echo "::error::unsupported runner architecture: ${RUNNER_ARCH:-unknown}"
exit 1
;;
esac
install_dir="${RUNNER_TEMP}/trivy-${TRIVY_VERSION}"
archive="${RUNNER_TEMP}/trivy-${TRIVY_VERSION}.tar.gz"
mkdir -p "${install_dir}"
curl --fail --show-error --silent --location --retry 3 \
--proto '=https' --tlsv1.2 \
"${DOWNLOAD_BASE_URL}/trivy_${TRIVY_VERSION}_Linux-${asset_arch}.tar.gz" \
--output "${archive}"
printf '%s %s\n' "${expected_sha256}" "${archive}" | sha256sum -c -
tar -xzf "${archive}" -C "${install_dir}" trivy
chmod 0755 "${install_dir}/trivy"
printf '%s\n' "${install_dir}" >> "${GITHUB_PATH}"
- name: Install checksum-pinned jq
env:
JQ_DOWNLOAD_BASE_URL: ${{ vars.JQ_DOWNLOAD_BASE_URL }}
run: bash .github/scripts/install-jq.sh
- name: Block High and Critical vulnerabilities
run: |
trivy fs \
--scanners vuln,license \
--severity CRITICAL,HIGH \
--exit-code 1 \
--ignorefile .trivyignore.yaml \
.
- name: Report Medium and Low vulnerabilities
run: |
trivy fs \
--scanners vuln,license \
--severity MEDIUM,LOW \
--exit-code 0 \
--ignorefile .trivyignore.yaml \
.
- name: Produce the governed all-severity KEV input
run: |
trivy fs \
--scanners vuln \
--severity CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN \
--exit-code 0 \
--ignorefile .trivyignore.yaml \
--format json \
--output trivy-kev.json \
.
- name: Fail closed on any CISA KEV match
env:
CONFIGURED_KEV_FEED_URL: ${{ vars.KEV_FEED_URL }}
run: |
set -euo pipefail
readonly DEFAULT_KEV_FEED_URL='https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json'
readonly KEV_FEED_URL="${CONFIGURED_KEV_FEED_URL:-${DEFAULT_KEV_FEED_URL}}"
if ! curl --fail --show-error --silent --location --retry 3 \
--proto '=https' --tlsv1.2 "${KEV_FEED_URL}" --output kev.json; then
echo "::error::KEV feed unavailable; configure KEV_FEED_URL to an approved internal mirror"
exit 1
fi
if ! jq -e '
(.catalogVersion | type == "string" and test("\\S"))
and (.dateReleased | type == "string" and test("\\S"))
and (.count | type == "number")
and (.count > 0)
and (.count == (.count | floor))
and (.vulnerabilities | type == "array")
and ((.vulnerabilities | length) > 0)
and (.count == (.vulnerabilities | length))
and (all(
.vulnerabilities[];
(.cveID | type == "string" and test("^CVE-[0-9]{4}-[0-9]{4,}$"))
))
and (([.vulnerabilities[].cveID] | unique | length) == .count)
' kev.json >/dev/null; then
echo "::error::KEV feed is malformed, empty, count-inconsistent, or contains invalid/duplicate cveID values"
exit 1
fi
if ! jq -e '
(type == "object")
and (.Results | type == "array")
and ((.Results | length) > 0)
and (all(.Results[]; type == "object"))
and (all(
.Results[];
(.Vulnerabilities == null) or (.Vulnerabilities | type == "array")
))
and (all(
.Results[];
all(
.Vulnerabilities[]?;
(type == "object")
and (.VulnerabilityID | type == "string" and test("\\S"))
)
))
' trivy-kev.json >/dev/null; then
echo "::error::Trivy KEV input is malformed, empty, or contains an invalid VulnerabilityID"
exit 1
fi
jq -r '[.Results[]?.Vulnerabilities[]?.VulnerabilityID | select(type == "string")] | unique[]?' \
trivy-kev.json | sort -u > found-cves.txt
jq -r '.vulnerabilities[]?.cveID | select(type == "string")' \
kev.json | sort -u > kev-cves.txt
hits="$(comm -12 found-cves.txt kev-cves.txt || true)"
if [[ -n "${hits}" ]]; then
echo "::error::CISA KEV-listed vulnerability found regardless of CVSS:"
printf '%s\n' "${hits}"
exit 1
fi
echo "KEV cross-check: no catalog match."
+43
View File
@@ -0,0 +1,43 @@
name: link-check
on:
pull_request:
paths:
- "README.md"
- "src/README.md"
- "docs/**/*.md"
- ".github/**/*.md"
- ".github/workflows/link-check.yml"
push:
branches: ["main"]
paths:
- "README.md"
- "src/README.md"
- "docs/**/*.md"
- ".github/**/*.md"
- ".github/workflows/link-check.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lychee:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Check committed documentation links
uses: lycheeverse/lychee-action@7cd0af4c74a61395d455af97419279d86aafaede # lycheeverse/lychee-action@v2.0.2
with:
args: >-
--no-progress
--root-dir .
README.md
src/README.md
'docs/**/*.md'
'.github/**/*.md'
fail: true