From 464fd96f45b666ba8a4daed6c6e9b3dde0470b85 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 21 Jul 2026 08:13:57 +0200 Subject: [PATCH 1/5] chore(ci): pilot docker-image-ci gold-standard reusable Replace the hand-rolled lint/build/security-scan jobs in ci.yml with a single call to the org meta-reusable netresearch/.github/.github/workflows/docker-image-ci.yml, composing Dockerfile+shell lint, YAML/compose lint, a validation-only image build with Trivy, secret scanning and dependency review. - Grants the full caller permission union the meta requires (contents/packages/security-events/id-token/attestations/actions/ pull-requests). - shell-scandirs=setup, lint-compose over docker-compose.yml, gitleaks with GITLEAKS_LICENSE forwarded. - Adds .hadolint.yaml to carry forward the previous DL3018 ignore. - Keeps the bespoke 'Test FTP Server' runtime job (not covered by the meta's container-structure-test smoke test). References @feat/docker-gold-standard; flip to @main once netresearch/.github#251 merges. Signed-off-by: Sebastian Mendel --- .github/workflows/ci.yml | 105 ++++++++++++--------------------------- .hadolint.yaml | 6 +++ 2 files changed, 37 insertions(+), 74 deletions(-) create mode 100644 .hadolint.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b27f04d..a870cfb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,88 +11,45 @@ on: - master workflow_dispatch: -permissions: - contents: read - security-events: write +permissions: {} jobs: - lint: - name: Lint Dockerfile - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Run hadolint - uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 - with: - dockerfile: Dockerfile - ignore: DL3018 - - build: - name: Build Docker Image - runs-on: ubuntu-latest - needs: lint - permissions: - contents: read - steps: - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - - - name: Build Docker image - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 - with: - context: . - push: false - tags: ghcr.io/netresearch/docker-ftp-server:test - cache-from: type=gha - cache-to: type=gha,mode=max - - security-scan: - name: Security Scan - runs-on: ubuntu-latest - needs: build + # Gold-standard container CI via the org meta-reusable: Dockerfile lint + # (+ shellcheck), YAML/compose lint, validation-only image build with + # Trivy, secret scanning and dependency review — one call site. + # References @feat/docker-gold-standard until netresearch/.github#251 + # merges; flip to @main afterwards. + docker-ci: + uses: netresearch/.github/.github/workflows/docker-image-ci.yml@feat/docker-gold-standard permissions: contents: read + packages: write security-events: write - steps: - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 - - - name: Build Docker image - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 - with: - context: . - load: true - tags: ghcr.io/netresearch/docker-ftp-server:test - cache-from: type=gha - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - with: - image-ref: ghcr.io/netresearch/docker-ftp-server:test - format: 'sarif' - output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH' - - - name: Upload Trivy results to GitHub Security tab - uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 - if: always() - with: - sarif_file: 'trivy-results.sarif' - + id-token: write + attestations: write + actions: read + pull-requests: write + with: + image-name: docker-ftp-server + dockerfile: ./Dockerfile + lint-container: true + shell-scandirs: setup + lint-yaml: true + lint-markdown: false + lint-compose: true + compose-files: docker-compose.yml + enable-gitleaks: true + enable-codeql: false + enable-smoke-test: false + secrets: + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} + + # Bespoke runtime test — boots the image and asserts vsftpd listens on + # port 21. Not covered by the meta (its smoke-test uses + # container-structure-test), so it stays here. test: name: Test FTP Server runs-on: ubuntu-latest - needs: build permissions: contents: read steps: diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 0000000..99f5e53 --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,6 @@ +--- +# Carries forward the DL3018 ignore the previous CI set inline +# (`ignore: DL3018`). Alpine `apk add` version pinning is impractical for +# this image, so DL3018 (pin package versions) stays suppressed. +ignored: + - DL3018 From 00001cf315d27e2c3d7ee9953fe06e9eef6da125 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 21 Jul 2026 08:17:26 +0200 Subject: [PATCH 2/5] chore(ci): disable lint-compose until #251 lands lint-compose.yml on main The meta pins its leaf reusables @main; lint-compose.yml is introduced by netresearch/.github#251 and is not yet on main. GitHub resolves reusable uses: refs at startup, so an active lint-compose job makes the whole run startup-fail. Keep it off until the leaf is on main. Signed-off-by: Sebastian Mendel --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a870cfb..03c1ec3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,8 +36,11 @@ jobs: shell-scandirs: setup lint-yaml: true lint-markdown: false - lint-compose: true - compose-files: docker-compose.yml + # lint-compose stays false until netresearch/.github#251 merges: the + # meta pins its leaves @main, but lint-compose.yml is new in #251 and + # not yet on main, so enabling it startup-fails the whole run. Re-enable + # (with compose-files: docker-compose.yml) after flipping to @main. + lint-compose: false enable-gitleaks: true enable-codeql: false enable-smoke-test: false From 7c88ec611d1e1c373e9311b02eafc5302334b706 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 21 Jul 2026 08:18:28 +0200 Subject: [PATCH 3/5] chore(ci): restore lint-compose: true (faithful gold-standard config) Reverts the diagnostic toggle. The run startup-fails either way until netresearch/.github#251 merges (the meta references lint-compose.yml@main, a leaf that only exists on the feature branch, and GitHub resolves reusable uses: refs at startup regardless of the job's if:). Keep the intended config; flip @feat/docker-gold-standard -> @main once #251 merges. Signed-off-by: Sebastian Mendel --- .github/workflows/ci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03c1ec3..a870cfb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,11 +36,8 @@ jobs: shell-scandirs: setup lint-yaml: true lint-markdown: false - # lint-compose stays false until netresearch/.github#251 merges: the - # meta pins its leaves @main, but lint-compose.yml is new in #251 and - # not yet on main, so enabling it startup-fails the whole run. Re-enable - # (with compose-files: docker-compose.yml) after flipping to @main. - lint-compose: false + lint-compose: true + compose-files: docker-compose.yml enable-gitleaks: true enable-codeql: false enable-smoke-test: false From 54871b14d7a39a2b0499e23ee1a114e682354c87 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 21 Jul 2026 08:30:54 +0200 Subject: [PATCH 4/5] chore(ci): pin docker-image-ci to @main (gold standard merged) Signed-off-by: Sebastian Mendel --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a870cfb..e4eb74e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: # References @feat/docker-gold-standard until netresearch/.github#251 # merges; flip to @main afterwards. docker-ci: - uses: netresearch/.github/.github/workflows/docker-image-ci.yml@feat/docker-gold-standard + uses: netresearch/.github/.github/workflows/docker-image-ci.yml@main permissions: contents: read packages: write From 9eadd141fadbe19568c0a009c8a30f402e3ddd10 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 21 Jul 2026 08:34:52 +0200 Subject: [PATCH 5/5] fix(entrypoint): quote expansions for shellcheck (SC2086/SC2206/SC2129) The docker-ci gold-standard lint-container job surfaced unquoted expansions in setup/docker-entrypoint.sh. Quote them; mark the intentional ;-split array (SC2206) and the sequential vsftpd.conf appends (SC2129) as deliberate. Signed-off-by: Sebastian Mendel --- setup/docker-entrypoint.sh | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/setup/docker-entrypoint.sh b/setup/docker-entrypoint.sh index f11f41c..5a879d5 100755 --- a/setup/docker-entrypoint.sh +++ b/setup/docker-entrypoint.sh @@ -3,25 +3,26 @@ set -e # Add Users and Home directories +# shellcheck disable=SC2206 # intentional split of ;-separated USERS into an array arrUSERS=(${USERS//;/ }) for userAndPass in "${arrUSERS[@]}" do username=${userAndPass%%:*} # Check if user exists and create if not - userInfo="$(id ${username} || echo 'no such user')" + userInfo="$(id "${username}" || echo 'no such user')" if [ "${userInfo}" == "no such user" ]; then - adduser -h /home/./${username} -s /bin/false -D ${username} + adduser -h "/home/./${username}" -s /bin/false -D "${username}" echo "Create user ${username}" fi echo "${userAndPass}" | /usr/sbin/chpasswd - if [ -z ${GROUP} ]; then + if [ -z "${GROUP}" ]; then GROUP=${username} fi - chown ${username}:${GROUP} /home/${username}/ -R - chmod 2700 /home/${username} - find /home/${username}/* -type d -exec chmod 2775 {} \; || : - find /home/${username}/* -type f -exec chmod 0664 {} \; || : + chown "${username}:${GROUP}" "/home/${username}/" -R + chmod 2700 "/home/${username}" + find "/home/${username}"/* -type d -exec chmod 2775 {} \; || : + find "/home/${username}"/* -type f -exec chmod 0664 {} \; || : done # Fix vsftpd.conf permissions @@ -30,6 +31,7 @@ chmod 664 /etc/vsftpd/vsftpd.conf if [ "${ENABLE_LOGGING}" == "yes" ]; then +# shellcheck disable=SC2129 # sequential appends to vsftpd.conf are clearer than a grouped block echo "xferlog_enable=YES" >> /etc/vsftpd/vsftpd.conf echo "xferlog_std_format=NO" >> /etc/vsftpd/vsftpd.conf echo "log_ftp_protocol=YES" >> /etc/vsftpd/vsftpd.conf @@ -40,15 +42,15 @@ if [ "${ENABLE_PASSIVE_MODE}" == "yes" ]; then echo "port_enable=YES" >> /etc/vsftpd/vsftpd.conf fi -if [ ! -z ${PASSIVE_MODE_MIN_PORT} ]; then +if [ -n "${PASSIVE_MODE_MIN_PORT}" ]; then echo "pasv_min_port=${PASSIVE_MODE_MIN_PORT}" >> /etc/vsftpd/vsftpd.conf fi -if [ ! -z ${PASSIVE_MODE_MAX_PORT} ]; then +if [ -n "${PASSIVE_MODE_MAX_PORT}" ]; then echo "pasv_max_port=${PASSIVE_MODE_MAX_PORT}" >> /etc/vsftpd/vsftpd.conf fi -if [ ! -z ${PASSIVE_MODE_ADDRESS} ]; then +if [ -n "${PASSIVE_MODE_ADDRESS}" ]; then echo "pasv_address=${PASSIVE_MODE_ADDRESS}" >> /etc/vsftpd/vsftpd.conf fi