From ef21375289b448cc611e4ba9d01be604974b3c0b Mon Sep 17 00:00:00 2001 From: joey-huckabee <138994589+joey-huckabee@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:11:28 -0500 Subject: [PATCH] ci: harden release and document security process --- .github/ISSUE_TEMPLATE/bug.yml | 66 +++++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++++ .github/pull_request_template.md | 16 ++++++++ .github/workflows/ci.yml | 9 +++++ .github/workflows/codeql.yml | 4 ++ .github/workflows/release.yml | 9 +++++ .github/workflows/scorecard.yml | 4 ++ CHANGELOG.md | 5 ++- README.md | 4 +- docs/CI.md | 59 +++++++++++++++++++++++++-- docs/ENDOR.md | 64 ++++++++++++++++++++++++++++++ docs/OPENSSF_SCORECARD.md | 23 +++++------ docs/ROADMAP.md | 4 +- docs/VERSION.md | 62 +++++++++++++++++++++++++++++ scripts/validate-release-tag.sh | 44 +++++++++++++++++++++ tests/release-tag.sh | 54 +++++++++++++++++++++++++ 16 files changed, 415 insertions(+), 20 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/pull_request_template.md create mode 100644 docs/ENDOR.md create mode 100644 docs/VERSION.md create mode 100644 scripts/validate-release-tag.sh create mode 100644 tests/release-tag.sh diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..252ab38 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,66 @@ +name: Bug report +description: Report reproducible image, runtime, or documentation behavior +title: "bug: " +labels: + - bug +body: + - type: markdown + attributes: + value: >- + Do not report vulnerabilities here. Use the private security-reporting link + shown before opening an issue. + - type: input + id: image + attributes: + label: Image tag and digest + description: Include both when available; do not report only `latest`. + placeholder: ghcr.io/datopsis/clickhouse-server-ubi9:v...@sha256:... + validations: + required: true + - type: input + id: runtime + attributes: + label: Runtime and version + placeholder: Docker 28.x, Podman 5.x, or OpenShift 4.x + validations: + required: true + - type: dropdown + id: architecture + attributes: + label: Architecture + options: + - linux/amd64 + - linux/arm64 + - Other or unknown + validations: + required: true + - type: textarea + id: deployment + attributes: + label: Minimal deployment configuration + description: Remove passwords, tokens, internal hostnames, and other secrets. + render: yaml + validations: + required: true + - type: textarea + id: behavior + attributes: + label: Reproduction and observed behavior + description: List exact steps, the result, and the result you expected. + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant logs + description: Redact all secrets and personal or internal information. + render: text + - type: checkboxes + id: checks + attributes: + label: Checks + options: + - label: I searched existing issues and tested a currently supported image. + required: true + - label: I removed secrets and sensitive data from this report. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..afc010b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Report a vulnerability privately + url: https://github.com/datopsis/clickhouse-server-ubi9/security/advisories/new + about: Never disclose a suspected vulnerability in a public issue. + - name: ClickHouse upstream support + url: https://github.com/ClickHouse/ClickHouse/issues + about: Report behavior that reproduces in an official ClickHouse distribution upstream. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..af8e50d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +## Summary + +Describe the user-facing or operational outcome and why the change is needed. + +## Validation + +- [ ] I ran the relevant local checks from `docs/CI.md`. +- [ ] I reviewed CI logs, warnings, annotations, skipped steps, and retained security evidence rather than relying only on green status checks. +- [ ] I added or updated tests for behavior changes. +- [ ] I updated user and operator documentation where needed. +- [ ] I added a `CHANGELOG.md` entry when the change is notable to users or security reviewers. +- [ ] I did not weaken an image, workflow, or release control without documenting the threat, rationale, compensating control, owner, and expiry. + +## Security and release impact + +State whether this changes image contents or runtime behavior. If it does, identify the required packaging-revision action under `docs/VERSION.md`. List accepted scanner findings or write `None`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a577a5a..7c4f87a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,9 +12,14 @@ on: permissions: contents: read +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: lint: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -35,6 +40,9 @@ jobs: - name: Run repository checks run: pre-commit run --all-files --show-diff-on-failure + - name: Test release tag validation + run: bash tests/release-tag.sh + - name: Audit GitHub Actions security uses: zizmorcore/zizmor-action@70fb788f84895a7701f5643d103d587e460b5c99 # v0.6.3 with: @@ -44,6 +52,7 @@ jobs: image: runs-on: ubuntu-latest + timeout-minutes: 30 permissions: contents: read security-events: write diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8514636..7800f76 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -15,6 +15,10 @@ on: permissions: read-all +concurrency: + group: codeql-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: analyze-actions: name: Analyze GitHub Actions diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dc02dda..f62e1d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,12 +7,17 @@ on: permissions: read-all +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + env: IMAGE: ghcr.io/datopsis/clickhouse-server-ubi9 jobs: release: runs-on: ubuntu-latest + timeout-minutes: 90 permissions: contents: write id-token: write @@ -22,8 +27,12 @@ jobs: - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: + fetch-depth: 0 persist-credentials: false + - name: Validate release tag and changelog + run: bash scripts/validate-release-tag.sh "${GITHUB_REF_NAME}" --require-annotated + - name: Set up QEMU uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index c557de7..af94fff 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -11,6 +11,10 @@ on: permissions: read-all +concurrency: + group: scorecard-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: analysis: name: Scorecard analysis diff --git a/CHANGELOG.md b/CHANGELOG.md index 1072da5..5c71857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases use container-version tags documented in `README.md`. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). GitHub Releases correspond only to published container images; repository-only changes remain under `Unreleased` until the next image release. See [docs/VERSION.md](docs/VERSION.md). ## [Unreleased] @@ -15,3 +15,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - OpenSSF Scorecard publishing, CodeQL analysis for GitHub Actions, hash-locked CI tooling, CODEOWNERS, and signed GitHub release evidence. - Syft SPDX SBOM generation and Grype vulnerability gates for CI and release images, with SARIF and release artifacts retained according to their useful lifecycle. - First-release roadmap, repository badge policy, and contributor-facing CI and artifact documentation. +- A documented container and repository versioning and release standard. +- CI security-layer, enforcement, result-review, and Endor Labs guidance. +- Structured bug reporting and pull-request review checklists. diff --git a/README.md b/README.md index 4790f4f..f337371 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ The smoke suite verifies startup with a read-only root filesystem and no capabil 1. Update and locally test the versions and digests in `Containerfile`. 2. Merge the change to `main` after CI passes. 3. Complete the release gates in [docs/ROADMAP.md](docs/ROADMAP.md). -4. Create a tag such as `v26.8.2.7-ubi9.8-1`. +4. Choose the next release version according to [docs/VERSION.md](docs/VERSION.md), validate it with `bash scripts/validate-release-tag.sh `, and create its tag, such as `v26.8.2.7-ubi9.8-1`. 5. Push the tag. GitHub Actions builds both architectures, scans the image with Trivy and Grype, publishes it to GHCR, attaches SBOM and provenance attestations, signs the resulting digest, and creates a GitHub release containing the SPDX SBOM, Sigstore bundle, and provenance evidence. Verify a release with GitHub as the keyless identity provider: @@ -135,7 +135,7 @@ ClickHouse commonly benefits from `nofile=262144:262144`. Optional capabilities Treat `/var/lib/clickhouse` as durable state, back it up according to your ClickHouse topology, and pin production deployments to an image digest rather than a mutable tag. -See [SECURITY.md](SECURITY.md) for vulnerability reporting and the support policy. Contributor references include the [first-release roadmap](docs/ROADMAP.md), [CI and artifact design](docs/CI.md), [badge policy](docs/BADGING.md), and [OpenSSF Scorecard controls](docs/OPENSSF_SCORECARD.md). +See [SECURITY.md](SECURITY.md) for vulnerability reporting and the support policy. Contributor references include the [versioning and release standard](docs/VERSION.md), [first-release roadmap](docs/ROADMAP.md), [CI and security process](docs/CI.md), [Endor Labs posture](docs/ENDOR.md), [badge policy](docs/BADGING.md), and [OpenSSF Scorecard controls](docs/OPENSSF_SCORECARD.md). ## License diff --git a/docs/CI.md b/docs/CI.md index 658af1a..8bba682 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -1,6 +1,6 @@ # Continuous integration and release automation -This repository treats the built image as the primary deliverable. CI therefore checks repository quality, builds and exercises the container, inventories its contents, and applies two independent vulnerability scanners before release. +This repository treats the built image as the primary deliverable. CI therefore checks repository quality, builds and exercises the container, inventories its contents, and applies two independent vulnerability scanners before release. Required checks are deliberately few; each one groups related tools so branch protection stays understandable and runner use remains modest. ## Workflow map @@ -9,19 +9,70 @@ This repository treats the built image as the primary deliverable. CI therefore | `CI` | Pull requests, pushes to `main`, weekly schedule, manual dispatch | Lint, workflow audit, configuration scan, image build, smoke tests, Trivy scan, Syft SBOM, and Grype scan. | | `CodeQL` | Workflow changes, weekly schedule, manual dispatch | Static analysis of GitHub Actions with the security-extended query suite. | | `OpenSSF Scorecard` | Pushes to `main`, ruleset changes, weekly schedule, manual dispatch | Supply-chain posture analysis, SARIF upload, and public Scorecard publication. | -| `Release image` | Tags matching `v*` | Multi-architecture publish, digest scans, evidence generation, keyless signing, and GitHub release creation. | +| `Release image` | Tags matching `v*` | Tag/input/changelog validation, multi-architecture publish, digest scans, evidence generation, keyless signing, and GitHub release creation. | Workflow-level permissions default to read-only. Write scopes are applied only to jobs that publish code-scanning results, packages, attestations, signatures, or releases. Third-party actions are pinned to full commit SHAs and tracked by Dependabot. +## Analysis layers and why each exists + +| Layer | Controls | What it can establish | Important limit | +| --- | --- | --- | --- | +| Repository hygiene | pre-commit built-ins and release-tag tests | Parseable YAML/JSON, normalized text, no obvious private keys, merge markers, unsafe symlinks, or oversized additions; release identifiers match image inputs and changelog state. | Pattern checks do not prove that no secret exists. GitHub secret scanning and push protection provide the native enforcement layer. | +| Source-specific lint | ShellCheck and Hadolint | Common shell defects and unsafe or wasteful container-build patterns. | These are static rules, not runtime evidence. | +| Workflow security | Actionlint, Zizmor, and CodeQL `actions` with `security-extended` | Workflow syntax, dangerous expressions, excessive permissions, untrusted checkout/data flows, artifact risks, and immutable references. | CodeQL runs on workflow changes and a schedule; Zizmor runs in every `lint` job. | +| Build configuration | Trivy configuration scan | High/critical Containerfile and infrastructure misconfigurations. | A clean configuration scan says nothing about packages in the built image. | +| Runtime behavior | Buildx plus `tests/smoke.sh` | The exact test image starts and stops correctly under production-oriented restrictions and supports documented initialization/authentication behavior. | Hosted-runner tests do not replace native-architecture and OpenShift release qualification. | +| Image vulnerabilities | Trivy image scan | No fixed high/critical findings according to Trivy's current databases and vendor severity selection. | `ignore-unfixed` intentionally leaves unfixed risk for human release review. | +| Independent inventory and scan | Syft plus Grype | SPDX inventory of the tested image and a second vulnerability matcher/database; fixed high/critical findings block. | Overlap is intentional, but scanner agreement is not proof of absence. | +| Supply-chain posture | OpenSSF Scorecard | Repository and build-pipeline practice signals published independently. | Historical and popularity signals improve only through genuine project operation. | +| Release integrity | BuildKit attestations, Cosign, GHCR, and GitHub Releases | Digest-bound multi-architecture artifact, SBOM/provenance evidence, keyless signature, and durable release assets. | The tag workflow publishes before post-build scans; a failed candidate must be quarantined or removed. | + +No additional general-purpose scanner is currently justified. Dependency Review has little useful input without a supported package manifest; another image CVE scanner would duplicate Trivy and Grype; another secret action would duplicate GitHub secret scanning and the private-key hook; and a generic SAST action would add little beyond ShellCheck, CodeQL Actions, and Zizmor for this shell/container repository. Reconsider when the repository gains a new language, manifest, deployment format, or credible fuzz target. See [ENDOR.md](ENDOR.md) for the conditional Endor Labs adoption decision. + +## Pull request and merge process + +1. A pull request starts the two required checks: `lint` and `image`. Workflow-file changes also start CodeQL. +2. Reviewers inspect the diff, check annotations, scanner summaries, smoke-test version, and the retained SBOM/SARIF. They confirm skipped steps are expected for the event and review warnings or ignored findings. +3. The active `main` ruleset requires a pull request, resolved review threads, and the latest `lint` and `image` results before merge; it also blocks deletion and force pushes. Required approving reviews remain deliberately disabled until the post-first-release review described in [ROADMAP.md](ROADMAP.md). +4. A merge starts `CI` on the exact `main` commit. Workflow changes start CodeQL, and every main push refreshes Scorecard. These post-merge runs are reviewed because merge-commit context, secrets, permissions, and SARIF publication differ from pull requests. +5. Weekly schedules refresh time-sensitive vulnerability and workflow analysis even when source has not changed. Manual dispatch supports investigation; it is not a substitute for the pull-request checks. +6. Only a validated annotated container-version tag starts a release. The workflow verifies that the tag's ClickHouse and UBI versions match `Containerfile` and that the changelog has a dated matching section before publishing. + +Concurrency cancels superseded CI, CodeQL, and Scorecard work for the same ref. Releases are never automatically cancelled. Job timeouts bound stuck or compromised work without hiding a failed control. + +## Enforced GitHub settings + +Repository configuration is part of the security boundary, even though it is not stored in Git: + +- Actions are enabled, the default `GITHUB_TOKEN` permission is read-only, and workflows cannot approve pull requests. +- GitHub requires third-party Actions to be referenced by a full commit SHA. Workflow files also keep the release tag in a comment for review and Dependabot updates. +- The `Protect main` ruleset requires pull requests, resolved review threads, and successful, up-to-date `lint` and `image` checks; it prevents branch deletion and non-fast-forward updates. The required approval count is intentionally zero for now. +- Secret scanning, push protection, Dependabot security updates, and private vulnerability reporting are enabled. +- Workflow permissions are narrowed per job; only code-scanning publication, OIDC signing, package publication, and release creation receive write scopes. + +Audit these settings before each release and after organization policy changes. A file review cannot detect a disabled ruleset or broadened repository-level token policy. + +## Reviewing a result rather than a color + +For every required run, verify the event and head SHA first. Then review the following evidence: + +- `lint`: every hook and the release-tag test ran, Zizmor audited every workflow, and there are no warnings or annotations hidden behind a successful wrapper. +- `image`: the configuration scan count, ClickHouse version printed by the smoke suite, Trivy target/OS/package count and result count, SBOM package count, and Grype found-versus-ignored counts. +- `CodeQL` and Scorecard: analysis covered the intended files, SARIF processing completed, and the Security tab has no new open alert. A successful upload is not the same as zero findings. +- skipped steps: PR SARIF publication is intentionally skipped to avoid permission failures from untrusted forks; it runs on `main`. A skipped build, smoke test, or scanner is not acceptable. +- warnings: Trivy may use another vendor's severity when Red Hat data is absent. Grype's `only-fixed` option can ignore real but currently unfixable findings. Review both against Red Hat and ClickHouse advisories before a release. + +Record accepted findings in the release pull request with the advisory, affected package, architecture, fix availability, rationale, owner, compensating control, and expiry. Do not rerun until a transient failure turns green or treat an empty SARIF file as proof that the scanner evaluated every risk category. + ## Image security pipeline The image job runs these controls in order: 1. **Trivy configuration scan** checks the `Containerfile`, Compose configuration, and repository infrastructure configuration for high and critical misconfigurations. 2. **Build and smoke tests** exercise startup, authentication, initialization, persistence, shutdown, read-only operation, dropped capabilities, and arbitrary UIDs. -3. **Trivy image scan** blocks fixed high and critical operating-system or application vulnerabilities. +3. **Trivy image scan** blocks fixed high and critical operating-system or application vulnerabilities and reports its detected OS and package count for review. 4. **Syft inventory** generates `clickhouse-server-ubi9.spdx.json` in SPDX JSON format from the tested image. -5. **Grype SBOM scan** scans that exact SPDX document and blocks fixed high and critical vulnerabilities. +5. **Grype SBOM scan** scans that exact SPDX document and blocks fixed high and critical vulnerabilities. Its log may also report ignored unfixed matches, which remain part of release review. 6. **Artifact and SARIF publication** retains the inventory and result for investigation and publishes non-PR Grype results to GitHub code scanning. Trivy and Grype deliberately overlap. They use different databases and matching logic, so a clean result from one does not replace the other. Both gates ignore vulnerabilities without an upstream fix; unfixed findings still require periodic review before release. Scanner disagreements should be investigated against the vendor advisory and documented if accepted. diff --git a/docs/ENDOR.md b/docs/ENDOR.md new file mode 100644 index 0000000..c99fb64 --- /dev/null +++ b/docs/ENDOR.md @@ -0,0 +1,64 @@ +# Endor Labs posture and adoption + +Endor Labs is useful here as an independent view of repository posture, GitHub Actions risk, secrets, and the published container. It is not useful to add an unauthenticated or permanently skipped workflow merely to display another check. This repository therefore prepares for Endor Labs and follows the score factors that reflect real quality, but does not add the Endor action until Datopsis has an approved Endor tenant, namespace, data-handling decision, and keyless authorization policy. + +## What an Endor score means + +Endor Scores evaluate open source packages and their source repositories across security, code quality, activity, and popularity signals. They are not a certification and they are not fully controlled by maintainers. Stars, forks, downloads, outside contributors, project age, and sustained activity must grow organically. + +Several legitimate project choices can also affect a score. In particular, Endor lists non-SemVer releases as a possible negative code-quality signal. This project intentionally uses an upstream-derived container version, `v-ubi-`, because it communicates the deployable ClickHouse and UBI inputs. Do not publish misleading SemVer tags, fake releases, empty commits, or artificial activity to optimize a score. + +Endor dependency scores primarily help consumers choose third-party packages. This repository has no application package-manager manifest; its main deliverable is a container assembled from ClickHouse archives and UBI packages. Container, GitHub Actions, secrets, and repository-posture analysis are therefore more relevant than a conventional language SCA scan. + +## Controls already present + +| Endor-relevant signal | Repository evidence | +| --- | --- | +| Tests and consistently passing CI | Runtime smoke suite plus required `lint` and `image` checks on pull requests. | +| Automated maintenance | Dependabot updates GitHub Actions, pre-commit hooks, and hash-locked CI Python tools. Renovate tracks ClickHouse and UBI image inputs. | +| Pinned dependencies | Container bases use digests, ClickHouse uses an exact version and upstream SHA-512 files, Actions and hooks use immutable commit SHAs, and CI Python wheels use hashes. | +| Documentation and operational files | README, contribution guide, security policy, changelog, version policy, CI design, release roadmap, and vulnerability-reporting path. | +| Security analysis | Secret scanning with push protection, CodeQL for Actions, Zizmor, Actionlint, Hadolint, ShellCheck, Trivy, Grype, Syft, and OpenSSF Scorecard. | +| Signed release evidence | The release workflow creates provenance and SBOM attestations, a keyless Cosign signature, and durable release assets. | +| Reviewable development | Protected `main`, pull requests, required CI, CODEOWNERS, and immutable history controls. Required approval remains a deliberate post-first-release decision. | + +The controls must exist for users and risk reduction first. A later score increase is supporting evidence, not the acceptance criterion. + +## Current integration decision + +As of 2026-09-06, no Endor namespace or authorization policy is configured in this repository and no Endor result is available to claim as a baseline. Adding the GitHub Action now would either fail CI, require a long-lived secret, or remain skipped forever. It would also duplicate existing scans without creating an Endor-monitored default-branch baseline. + +Do not add `ENDOR_API_CREDENTIALS_KEY` or `ENDOR_API_CREDENTIALS_SECRET` merely to get started. Endor recommends GitHub OIDC keyless authentication for CI. Installing the Endor GitHub App is also an organization-level trust decision because cloud scanning grants the app repository access and sends retained scan metadata to the Endor tenant. Datopsis must review the requested permissions and Endor's data-handling terms before installation. + +## Minimal adoption workflow + +1. Create or select the Datopsis Endor tenant and namespace. Record the service owner, billing owner, data-retention requirements, and incident contact outside this public repository. +2. Choose one baseline mechanism. Prefer the Endor GitHub App for scheduled RSPM and GitHub Actions monitoring; use a GitHub Actions integration only when CI policy enforcement or container reachability is required. Do not deploy both for identical scans. +3. For an Actions integration, create a keyless authorization policy restricted to this GitHub organization, repository, and intended refs. Pin `endorlabs/github-action`, `endorctl_version`, and `endorctl_checksum`; grant only `contents: read` and `id-token: write`, adding `security-events: write` only for SARIF upload and `pull-requests: write` only if reviewed PR comments are enabled. +4. Run a monitored scan on `main` with `pr: false`. Review every finding and establish policies before making the check required. +5. Add PR scans with `pr: true` and `pr_baseline: main`. Initially warn on new findings. Move a policy to blocking only after the team has confirmed severity, remediation, exceptions, and scanner availability behavior. +6. Enable only distinct coverage. `scan_github_actions` and RSPM complement the existing workflow analysis. Container OS reachability may complement Trivy and Grype after the test image is built. Conventional dependency reachability and SAST should remain off unless the repository gains a supported application language or package manifest. +7. Review Endor results alongside, not instead of, Trivy, Grype, CodeQL, Zizmor, and Scorecard. Record accepted risks with an owner, rationale, compensating control, and expiry. + +The initial Endor check should not be a required merge check until a successful `main` baseline and at least one successful PR scan prove the authentication and policy behavior. If the service is unavailable, the documented policy must say whether that is a hard failure; do not silently convert a security gate to `continue-on-error`. + +## Sustainable score improvement + +- Merge real changes through pull requests and keep CI reliable. +- Sign commits where practical and publish signed, verifiable container releases when the release gates are complete. +- Maintain accurate repository topics, issue/PR labels, issue templates, license metadata, and security documentation. +- Keep dependencies current and resolve or explicitly triage findings. +- Respond to and close issues with clear release linkage. +- Revisit the score after releases and normal project history accumulate; do not expect a new repository's activity or popularity categories to be high. + +For each review, capture the Endor project/version, scan time, policy version, finding counts by severity and category, new-versus-baseline findings, ignored findings and expiry, and the reviewer. A green check is insufficient if the scan skipped a category, used the wrong baseline, or ignored findings. + +Authoritative references: + +- [Endor Scores](https://docs.endorlabs.com/scan/sca/scores) +- [Repository code-quality score factors](https://docs.endorlabs.com/scan/sca/scores/repository-scores/code-quality-score-factors/) +- [Repository activity score factors](https://docs.endorlabs.com/scan/sca/scores/repository-scores/activity-score-factors) +- [Repository popularity score factors](https://docs.endorlabs.com/scan/sca/scores/repository-scores/popularity-score-factors) +- [Scanning with GitHub Actions](https://docs.endorlabs.com/setup-deployment/ci-cd/scan-with-github-actions) +- [GitHub App deployment](https://docs.endorlabs.com/setup-deployment/scm-integrations/github-app) +- [Endor trust and data handling](https://docs.endorlabs.com/trust-compliance) diff --git a/docs/OPENSSF_SCORECARD.md b/docs/OPENSSF_SCORECARD.md index 9707f2a..9290155 100644 --- a/docs/OPENSSF_SCORECARD.md +++ b/docs/OPENSSF_SCORECARD.md @@ -15,6 +15,12 @@ The workflow follows the restrictions required for published Scorecard results: The separate `CodeQL` workflow performs genuine static analysis of this repository's GitHub Actions workflows with the `security-extended` query suite. ShellCheck, Hadolint, Actionlint, Zizmor, Trivy, Syft, and Grype remain in CI because CodeQL does not replace their container-, inventory-, and shell-specific coverage. +## Where to view current results + +The current public score and per-check details are in the [OpenSSF Scorecard viewer](https://securityscorecards.dev/viewer/?uri=github.com/datopsis/clickhouse-server-ubi9). The [OpenSSF Scorecard workflow history](https://github.com/datopsis/clickhouse-server-ubi9/actions/workflows/scorecard.yml) shows each run and exposes its downloadable SARIF artifact for five days. Uploaded findings are also available under the repository's **Security > Code scanning** page to users with the required GitHub access. + +This file documents the repository's policy, controls, initial baseline, and expected score movement. It is not a copy of the live report; use the viewer or the latest workflow run for current results. + ## Baseline and expected movement The initial local measurement on 2026-09-06 used Scorecard 5.5.0 against commit `848ddb21e9b41a9adef09da015cc19fabd71bc66` and scored **5.2/10**. A new repository starts low because several checks depend on historical evidence rather than files. @@ -22,7 +28,7 @@ The initial local measurement on 2026-09-06 used Scorecard 5.5.0 against commit | Check | Baseline | Repository control or next action | | --- | ---: | --- | | Binary-Artifacts | 10 | Keep generated binaries and archives out of Git. | -| Branch-Protection | 0 | Configure the `main` ruleset described below after the required checks exist. | +| Branch-Protection | 0 | The active ruleset now requires pull requests, resolved threads, and up-to-date `lint` and `image` checks. A 2026-09-06 verification run scored 4; remaining points require the deliberately deferred review controls below. | | CI-Tests | N/A | Merge changes through pull requests with the `lint` and `image` jobs passing. | | CII-Best-Practices | 0 | Register the project at Best Practices when its public project details are ready. | | Code-Review | 0 | Use reviewed pull requests; direct commits do not create review evidence. | @@ -44,23 +50,18 @@ Scores can change when Scorecard changes its checks or when repository history c ## Required `main` ruleset -Repository rules are GitHub settings and cannot be represented by a committed file. The active `Protect main` ruleset currently targets the default branch, blocks force pushes and deletion, and has no bypass actors. This establishes the safe Tier 1 controls without preventing a solo maintainer from working. +Repository rules are GitHub settings and cannot be represented by a committed file. The active `Protect main` ruleset targets the default branch, requires a pull request and resolved review threads, requires successful and up-to-date `lint` and `image` checks, blocks force pushes and deletion, and has no bypass actors. Required approvals remain zero so either maintainer can contribute without needing two other available people. To reach the highest Scorecard branch-protection tier, extend the ruleset with: -- pull requests required before merging; - at least two approvals; - stale approvals dismissed when new commits are pushed; - approval required for the most recent reviewable push; -- Code Owner review required, using `.github/CODEOWNERS`; -- conversations resolved before merging; -- required status checks `lint`, `image`, and `Analyze GitHub Actions`; -- branches required to be up to date before merging; and -- administrator enforcement, retaining no bypass actors. +- Code Owner review required, using `.github/CODEOWNERS`. These settings match the highest Scorecard branch-protection tier. A second collaborator is now present, but required review enforcement is intentionally deferred for the first release at the maintainer's request. Two independent approvals would require at least three regularly available maintainers to avoid deadlocking an author's pull request. The current exception and revisit point are tracked in [ROADMAP.md](ROADMAP.md); never enable or weaken the review rules merely to change a score. -After changing the ruleset, manually run both `CI` and `CodeQL`, then trigger `OpenSSF Scorecard`. Confirm the required-check names against GitHub's ruleset UI because GitHub derives them from completed check runs. +After changing the ruleset, manually run the affected workflows, then trigger `OpenSSF Scorecard`. Confirm the required-check names against GitHub's ruleset UI because GitHub derives them from completed check runs. `Analyze GitHub Actions` is not required because its path-filtered workflow intentionally does not run on ordinary pull requests; making a conditional check required would block those changes. ## GitHub security settings @@ -76,9 +77,9 @@ Secret scanning, push protection, Dependabot security updates, and private vulne ## Pull request and release practice -All normal changes should enter `main` through reviewed pull requests. Reviewers should verify pinned dependency updates, security-sensitive workflow permissions, container provenance, and changelog entries. Emergency direct pushes should be exceptional and documented. +All changes to `main` must enter through pull requests with the required checks. Reviewers should verify pinned dependency updates, security-sensitive workflow permissions, container provenance, and changelog entries. An emergency that requires relaxing the ruleset must be time-bounded, approved by a maintainer, recorded in the pull request, and reverted immediately afterward. -Release tags use `v-ubi-`. The tag workflow publishes an immutable multi-architecture image, scans it with Trivy and Grype, generates a downloadable Syft SBOM, attaches OCI SBOM and provenance attestations, signs the digest with GitHub OIDC, and creates a GitHub release with: +Release identifiers, version increments, and the treatment of repository-only changes are defined in [VERSION.md](VERSION.md). The tag workflow publishes an immutable multi-architecture image, scans it with Trivy and Grype, generates a downloadable Syft SBOM, attaches OCI SBOM and provenance attestations, signs the digest with GitHub OIDC, and creates a GitHub release with: - `image.spdx.json`, the Syft-generated SPDX JSON inventory; - `image.sigstore.json`, the keyless signature verification bundle; and diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 268f2a5..d52d30f 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -26,7 +26,7 @@ This roadmap is the release gate for the first supported image. A checked item m ### Release mechanics and documentation -- [ ] Choose the first tag using `v-ubi-`. +- [ ] Choose and validate the first tag according to [VERSION.md](VERSION.md). - [ ] Rehearse the release workflow in a non-production package or with a disposable pre-release tag, then remove test artifacts through the GitHub UI. - [ ] Confirm the GHCR package is public and its description, source, documentation, and license metadata point to this repository. - [ ] Confirm release assets include `image.spdx.json`, `image.sigstore.json`, and `image.intoto.jsonl`. @@ -45,7 +45,7 @@ This roadmap is the release gate for the first supported image. A checked item m ## Deliberately deferred controls -- **Enforced pull-request reviews:** a second collaborator has been added, but required reviews, Code Owner approval, and two-approval enforcement remain disabled at the maintainer's request. The active ruleset must continue to prevent deletion and force pushes. Revisit this after the first release; do not enable it as part of unrelated automation. +- **Enforced pull-request approvals:** a second collaborator has been added, but required approvals and Code Owner approval remain disabled at the maintainer's request. The active ruleset requires a pull request, resolved threads, and the `lint` and `image` checks and prevents deletion and force pushes. Revisit approval enforcement after the first release; do not enable it as part of unrelated automation. - **Two independent approvals:** this requires at least three regularly available maintainers to avoid deadlocking a contributor's own pull request. Reassess when the contributor pool supports it. - **Fuzzing:** there is currently no credible parser or executable fuzz target owned by this packaging repository. diff --git a/docs/VERSION.md b/docs/VERSION.md new file mode 100644 index 0000000..2c8876b --- /dev/null +++ b/docs/VERSION.md @@ -0,0 +1,62 @@ +# Versioning and releases + +This project versions the published container image independently from the repository's source history. Git commits identify every repository change; release versions identify container artifacts that the project has intentionally published and supports. + +## Release version format + +Release tags use: + +```text +v-ubi- +``` + +For example, `v26.8.2.7-ubi9.8-1` means: + +- ClickHouse `26.8.2.7`; +- Red Hat UBI `9.8`; and +- Datopsis packaging revision `1` for that ClickHouse and UBI combination. + +This is an upstream-derived container version, not Semantic Versioning. ClickHouse's four-part version is preserved so operators can immediately identify the packaged server. The final packaging revision is a positive integer and starts at `1` the first time a ClickHouse and UBI version combination is released. + +Release tags are immutable. Never move or reuse a release tag, even when rebuilding nominally identical inputs. Production deployments should pin the published image digest; the human-readable release tag describes the release, while the digest identifies its exact content. + +## When the version changes + +| Change | Version action | Example | +| --- | --- | --- | +| Upgrade or downgrade ClickHouse | Set the new ClickHouse version. Use packaging revision `1` if that exact ClickHouse and UBI pair has never been released; otherwise use its next unused revision. | `v26.8.2.7-ubi9.8-3` to `v26.9.1.4-ubi9.8-1` | +| Change the named UBI release | Set the new UBI version. Use packaging revision `1` if that exact pair is new; otherwise use its next unused revision. | `v26.8.2.7-ubi9.8-2` to `v26.8.2.7-ubi9.9-1` | +| Refresh a UBI digest within the same named UBI release | Increment the packaging revision. | `v26.8.2.7-ubi9.8-1` to `v26.8.2.7-ubi9.8-2` | +| Change image contents, runtime behavior, configuration, entrypoint logic, build inputs, or release metadata without changing the ClickHouse or named UBI versions | Increment the packaging revision. | `v26.8.2.7-ubi9.8-2` to `v26.8.2.7-ubi9.8-3` | +| Change only documentation, tests, development tooling, issue templates, or analysis workflows without publishing a new image | Do not create or change a container release version. | Record the change in Git and, when notable, under `Unreleased` in the changelog. | + +Every newly published image must receive a new release tag. If maintainers decide that a repository-only change justifies republishing the image, increment the packaging revision even if the expected filesystem contents are unchanged. Normally, repository-only changes should wait for the next image-affecting release. + +When several version components change together, set the ClickHouse and UBI fields to the new values and use revision `1` only if that exact pair has never been released. The packaging revision is monotonic within one exact ClickHouse and UBI version pair, including after a temporary upgrade or downgrade away from that pair. + +## Repository-only changes and non-image releases + +A repository change that does not publish a container is tracked by its pull request and Git commit SHA. It is not part of a supported container release merely because it exists on `main`. + +This project does **not** create source-only GitHub Releases or tags for documentation, tests, development tooling, issue templates, or analysis workflows. GitHub Releases represent published container images, so creating another kind of release would make the release feed, version comparison, and support status ambiguous. Users who need an unreleased repository file should pin its full commit SHA. + +Use the following identifiers: + +- a full Git commit SHA for an exact source revision; +- `git describe --tags --always --dirty` for a convenient local description; and +- the `Unreleased` section of `CHANGELOG.md` for notable changes intended for the next container release. + +Commits after a release tag do not alter the already-published image or tag. A clean checkout of a tag identifies released source; a later commit identifies unreleased repository state until another release is tagged. + +Not every repository-only change needs a changelog entry. Add one when users, operators, contributors, or security reviewers would reasonably want to discover it. Keep the entry under `Unreleased` in the appropriate Keep a Changelog category (`Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, or `Security`). When the next image is released, move all accumulated entries into `## [] - YYYY-MM-DD`, including notable repository-only work. The section describes repository changes since the prior image release; it does not imply that every entry changed the image filesystem. + +## Release procedure + +1. Update the ClickHouse version, UBI references, and packaging code as needed. +2. Choose the release version using the rules above, convert the changelog's `Unreleased` entries into `## [] - YYYY-MM-DD`, and add a new empty `Unreleased` section. +3. Complete the release gates in [ROADMAP.md](ROADMAP.md), then merge the release change to `main` after CI and review pass. +4. Run `bash scripts/validate-release-tag.sh `, create an annotated tag using that version from the reviewed `main` commit, and push it. +5. Let the tag-triggered release workflow build, scan, sign, and publish the image and GitHub release. Do not create a source-only GitHub release manually. +6. Verify the published digest, signature, provenance, SBOM, image labels, and release assets before announcing support. + +The release workflow also creates a `sha-` image tag for traceability. It is supplementary and does not replace the release tag or immutable digest. diff --git a/scripts/validate-release-tag.sh b/scripts/validate-release-tag.sh new file mode 100644 index 0000000..bcd4ce2 --- /dev/null +++ b/scripts/validate-release-tag.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +tag="${1:-${GITHUB_REF_NAME:-}}" +require_annotated="${2:-}" +containerfile="${CONTAINERFILE:-Containerfile}" +changelog="${CHANGELOG_FILE:-CHANGELOG.md}" +if [[ ! "${tag}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)-ubi([0-9]+\.[0-9]+)-([1-9][0-9]*)$ ]]; then + echo "Release tag must match v-ubi-" >&2 + exit 1 +fi + +tag_clickhouse="${BASH_REMATCH[1]}" +tag_ubi="${BASH_REMATCH[2]}" + +mapfile -t clickhouse_versions < <( + sed -n 's/^ARG CLICKHOUSE_VERSION="\([^"]*\)"/\1/p' "${containerfile}" | sort -u +) +mapfile -t ubi_versions < <( + sed -n 's|^ARG UBI_.*_IMAGE="[^:"]*:\([^@"]*\)@sha256:[0-9a-f]*"|\1|p' "${containerfile}" | sort -u +) + +if (( ${#clickhouse_versions[@]} != 1 )) || [[ "${clickhouse_versions[0]}" != "${tag_clickhouse}" ]]; then + echo "Tag ClickHouse version ${tag_clickhouse} does not match Containerfile" >&2 + exit 1 +fi + +if (( ${#ubi_versions[@]} != 1 )) || [[ "${ubi_versions[0]}" != "${tag_ubi}" ]]; then + echo "Tag UBI version ${tag_ubi} does not match Containerfile" >&2 + exit 1 +fi + +if ! grep -Eq "^## \[${tag}\] - [0-9]{4}-[0-9]{2}-[0-9]{2}$" "${changelog}"; then + echo "CHANGELOG.md must contain a dated '## [${tag}] - YYYY-MM-DD' section" >&2 + exit 1 +fi + +if [[ "${require_annotated}" == "--require-annotated" ]] && \ + ! git cat-file -e "refs/tags/${tag}^{tag}" 2>/dev/null; then + echo "Release tag ${tag} must be an annotated Git tag" >&2 + exit 1 +fi + +echo "Validated release tag ${tag}" diff --git a/tests/release-tag.sh b/tests/release-tag.sh new file mode 100644 index 0000000..4e93f95 --- /dev/null +++ b/tests/release-tag.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +fixture_dir="$(mktemp -d)" +trap 'rm -rf "${fixture_dir}"' EXIT + +cp "${repo_root}/Containerfile" "${fixture_dir}/Containerfile" +printf '# Changelog\n\n## [v26.8.2.7-ubi9.8-1] - 2026-09-06\n' \ + > "${fixture_dir}/CHANGELOG.md" + +run_validator() { + CONTAINERFILE="${fixture_dir}/Containerfile" \ + CHANGELOG_FILE="${fixture_dir}/CHANGELOG.md" \ + bash "${repo_root}/scripts/validate-release-tag.sh" "$@" +} + +expect_failure() { + if run_validator "$@" >/dev/null 2>&1; then + echo "Release tag unexpectedly passed validation: $*" >&2 + exit 1 + fi +} + +run_validator v26.8.2.7-ubi9.8-1 +expect_failure v26.8.2.8-ubi9.8-1 +expect_failure v26.8.2.7-ubi9.9-1 +expect_failure v26.8.2.7-ubi9.8-0 +expect_failure release-26.8.2.7 + +git -C "${fixture_dir}" init --quiet +git -C "${fixture_dir}" config user.name "Release test" +git -C "${fixture_dir}" config user.email "release-test@example.invalid" +git -C "${fixture_dir}" add Containerfile CHANGELOG.md +git -C "${fixture_dir}" commit --quiet --message "test fixture" +git -C "${fixture_dir}" tag --annotate v26.8.2.7-ubi9.8-1 --message "test release" +( + cd "${fixture_dir}" + run_validator v26.8.2.7-ubi9.8-1 --require-annotated +) +git -C "${fixture_dir}" tag --delete v26.8.2.7-ubi9.8-1 >/dev/null +git -C "${fixture_dir}" tag v26.8.2.7-ubi9.8-1 +if ( + cd "${fixture_dir}" + run_validator v26.8.2.7-ubi9.8-1 --require-annotated >/dev/null 2>&1 +); then + echo "Lightweight release tag unexpectedly passed validation" >&2 + exit 1 +fi + +printf '# Changelog\n' > "${fixture_dir}/CHANGELOG.md" +expect_failure v26.8.2.7-ubi9.8-1 + +echo "Release tag validation tests passed"