Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
},
{
"customType": "regex",
"description": "Track the pinned Coroot CE component images (server, node-agent, cluster-agent) in the Coroot CR. The coroot-operator's `Coroot` CRD nests each image reference under `image.name` (an ImageSpec object), NOT a bare `image:` string, so Renovate's built-in `kubernetes` manager does not detect it — and when the field is left unset the operator auto-resolves the highest semver tag from ghcr on every reconcile (floating, untrackable; the source of the stale base-image CVEs in platform#2149). This regex manager captures each `name: ghcr.io/coroot/<component>:<tag>` and tracks it via the docker datasource so the pins get the normal Renovate bump PRs (minor/patch automerge per the rules below).",
"description": "Track upstream Coroot component images when the Coroot CR consumes them directly. The operator nests each reference under image.name, so the kubernetes manager does not detect it; an unset reference floats on registry discovery. Signed compatibility builds remain pinned to their reviewed digests instead. Their upstream runtime bases are tracked by Renovate's built-in Dockerfile manager in CorootCheckpoint.Dockerfile and CorootClusterAgentCheckpoint.Dockerfile. Upstream refresh and compatibility retirement require coordinated source, patch, image and native API/browser verification; the undated-dependency guard follows these actual base-image inputs.",
"managerFilePatterns": [
"/^k8s/bases/infrastructure/coroot/coroot\\.ya?ml$/"
],
Expand Down Expand Up @@ -253,7 +253,7 @@
"automerge": false
},
{
"description": "Six more ghcr.io dependencies get no release date from Renovate, the same cause as the flux-operator chart above: the Homepage and origin-ca-issuer charts (flux manager, read from their OCIRepository), the PostgreSQL operand image (the CloudNativePG custom manager), the Coroot server and cluster-agent images (the Coroot custom manager) and the Umami image (kubernetes manager, in its CronJob). Every one had newer releases, yet none was proposed after the repository-wide wait began on 2026-05-04 (#5008). minimumReleaseAge is therefore 0 days. Raising the PR adopts nothing: automerge is off for every ordinary update, whatever its type, because with no wait nothing soaks an update before it merges, and several of these are minor or major versions behind, so each update is read against its release notes before merge. A vulnerability-fix PR is the one exception and keeps the repository-wide vulnerabilityAlerts policy above (no wait, automerge): Renovate applies that block after package rules, and this rule deliberately leaves the security fast path unchanged. Placed after the broad automerge rules so none of them can turn automerge back on; scripts/tests/oci-chart-sources checks each dependency's manager and that every update of it resolves to both settings.",
"description": "Six more ghcr.io dependencies get no release date from Renovate, the same cause as the flux-operator chart above: the Homepage and origin-ca-issuer charts (flux manager, read from their OCIRepository), the PostgreSQL operand image (the CloudNativePG custom manager), the Coroot server and cluster-agent upstream runtime bases (Dockerfile manager while compatibility builds are used) and the Umami image (kubernetes manager, in its CronJob). Every one had newer releases, yet none was proposed after the repository-wide wait began on 2026-05-04 (#5008). minimumReleaseAge is therefore 0 days. Raising the PR adopts nothing: automerge is off for every ordinary update, whatever its type, because with no wait nothing soaks an update before it merges, and several of these are minor or major versions behind, so each update is read against its release notes before merge. A vulnerability-fix PR is the one exception and keeps the repository-wide vulnerabilityAlerts policy above (no wait, automerge): Renovate applies that block after package rules, and this rule deliberately leaves the security fast path unchanged. Placed after the broad automerge rules so none of them can turn automerge back on; scripts/tests/oci-chart-sources checks each dependency's manager and that every update of it resolves to both settings.",
"matchPackageNames": [
"ghcr.io/m0nsterrr/helm-charts/homepage",
"ghcr.io/cloudflare/origin-ca-issuer-charts/origin-ca-issuer",
Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/validate-coroot-security-progress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ jobs:
permissions:
contents: read
env:
COROOT_IMAGE: ghcr.io/coroot/coroot:1.28.1
COROOT_IMAGE: ghcr.io/devantler-tech/platform-coroot:v1.28.1-checkpoints.1-27c6c840e2006076c0ff5ed7dabe15daa1c38477@sha256:9a2782f405f9ee673301912b3b2cf85a4a154fbfeb1542df2d4937d6600396f1
PROMETHEUS_IMAGE: ghcr.io/coroot/prometheus:2.55.1-ubi9-0@sha256:c2a4f92f728411b834b7abd07764cf587e28749c377b157443569dea4f81f833
SERVER_IMAGE: docker.io/library/busybox:1.38.0-musl@sha256:ea2b9914a16a4ac1981994af97b318f7c7d4db76b580c56177f08bf76f4a0be8
PLAYWRIGHT_VERSION: 1.56.1
Expand All @@ -163,9 +163,10 @@ jobs:
- name: Verify the native API contract matches the versioned Coroot image
shell: bash
run: |
# Updating either pin requires reviewing the v1.28.1 API and browser
# source contract in scripts/tests/coroot-security-native/README.md.
if [ "$COROOT_IMAGE" != ghcr.io/coroot/coroot:1.28.1 ] ||
# The checkpoint-only patch retains the reviewed v1.28.1 API/browser
# contract; this fixture exercises the exact proposed production image.
# Update the source audit and real startup-version assertion together.
if [ "$COROOT_IMAGE" != ghcr.io/devantler-tech/platform-coroot:v1.28.1-checkpoints.1-27c6c840e2006076c0ff5ed7dabe15daa1c38477@sha256:9a2782f405f9ee673301912b3b2cf85a4a154fbfeb1542df2d4937d6600396f1 ] ||
[ "$(grep -Fxc " name: $COROOT_IMAGE" k8s/bases/infrastructure/coroot/coroot.yaml)" != 1 ]; then
echo 'Native Coroot contract/image differs from communityEdition.image.name; review compatibility before updating the fixture.' >&2
exit 1
Expand Down
10 changes: 7 additions & 3 deletions k8s/bases/infrastructure/coroot/coroot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,19 @@ spec:
# container tags as BARE semver (`1.22.2`), NOT the `v`-prefixed GitHub
# *release* tag (`v1.22.2`); a `v`-prefixed ref 404s on ghcr → ErrImagePull →
# the operator's StatefulSet/DaemonSet rollout wedges and stalls the
# infrastructure Kustomization health check. Always pin the bare tag. The
# infrastructure Kustomization health check. Use bare tags for upstream
# releases; platform compatibility builds use their publisher's exact tag
# and immutable digest. The
# three pins below are kept
# Renovate-trackable by a dedicated regex customManager in
# `.github/renovate.json` (the built-in kubernetes manager only reads a bare
# `image:` string, not this `image.name` mapping). There is no spec.version
# field; the operator HelmRelease itself also stays Flux/Renovate-pinned.
communityEdition:
# Recognize actual checkpoint-record progress on idle archived databases;
# retain the existing warning when that evidence is missing or invalid.
image:
name: ghcr.io/coroot/coroot:1.28.1
name: ghcr.io/devantler-tech/platform-coroot:v1.28.1-checkpoints.1-27c6c840e2006076c0ff5ed7dabe15daa1c38477@sha256:9a2782f405f9ee673301912b3b2cf85a4a154fbfeb1542df2d4937d6600396f1

# CE has no native OIDC/SSO (Enterprise only), so the UI is fronted by the
# existing oauth2-proxy (Dex) via http-route.yaml — exactly how Prometheus /
Expand Down Expand Up @@ -244,4 +248,4 @@ spec:
# operator-managed components; the database has a separate DNS policy.
platform.devantler.tech/dns-policy-rollout: 2026-09-30-ndots-3
image:
name: ghcr.io/coroot/coroot-cluster-agent:1.11.7
name: ghcr.io/devantler-tech/platform-coroot-cluster-agent:v1.11.7-checkpoints.1-27c6c840e2006076c0ff5ed7dabe15daa1c38477@sha256:46b6abe52dbf5445277ef3e3bbcb469a89a88e2c610411c5d9a4270a8a8a88c3
18 changes: 15 additions & 3 deletions scripts/tests/coroot-security-native/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Native Coroot dashboard evidence

The hosted workflow runs `run.sh` against the released Coroot 1.28.1 image, an
The hosted workflow runs `run.sh` against the signed checkpoint compatibility
build of Coroot 1.28.1 pinned in the production resource, an
ephemeral Prometheus and a loopback-only synthetic metrics server. It provisions
the versioned dashboard definitions twice, checks persisted configurations and
an unrelated dashboard, queries every panel through Coroot using the same
Expand Down Expand Up @@ -54,8 +55,19 @@ no port is published or bound to a public interface. Cleanup removes only this
invocation's named containers and temporary files, preserving diagnostic logs
and screenshots as seven-day workflow artifacts.

The binary version is verified from its real startup log. Coroot 1.28.1 does
not expose a `--version` option.
The binary version is verified from its real startup log. The compatibility
publisher sets it to `v1.28.1-checkpoints.1-27c6c840e2006076c0ff5ed7dabe15daa1c38477`;
Coroot 1.28.1 does not expose a `--version` option.

The compatibility build uses upstream revision
`15137ef2f43e01d0a87245e44b937b25515e0afe` and the reviewed
[checkpoint patch](../../../k8s/bases/infrastructure/coroot/coroot-v1.28.1-checkpoint-record.patch).
That patch changes the PostgreSQL inspection, its metric construction and model,
and its regression tests. It changes none of the entrypoint, flags, bootstrap,
project selector, dashboard panel API, range-query alignment or browser routes
audited above. The final image retains the digest-pinned upstream runtime and
entrypoint. The fixture nevertheless runs the exact proposed image through all
persisted-dashboard, API, browser and negative controls before adoption.

The workflow also runs for changes to the base Coroot resource and production
Coroot patch. A preflight requires the native image to equal the versioned
Expand Down
2 changes: 1 addition & 1 deletion scripts/tests/coroot-security-native/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ wait_json "${base}/api/user" \
"${artifacts}/user.json"
# v1.28.1 has no --version option. main.go logs the compiled version before
# loading configuration; assert that real startup line after API readiness.
docker logs "${prefix}-coroot" 2>&1 | grep -E 'version: 1\.28\.1([[:space:]]|$)' \
docker logs "${prefix}-coroot" 2>&1 | grep -E 'version: v1\.28\.1-checkpoints\.1-27c6c840e2006076c0ff5ed7dabe15daa1c38477([[:space:]]|$)' \
>"${artifacts}/coroot-version.txt"
# db/project.go PrometheusConfig adds this selector when using the global
# backend. A successful unscoped Prometheus scrape alone cannot prove that the
Expand Down
7 changes: 6 additions & 1 deletion scripts/tests/oci-chart-sources/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,10 @@ func TestFluxOperatorUpdatesAreProposedAndReviewed(t *testing.T) {
var fluxOperatorUpdatePolicy = map[string]any{"automerge": false, "minimumReleaseAge": "0 days"}

type renovateConfig struct {
CustomManagers []struct {
EnabledManagers []string `json:"enabledManagers"`
IgnorePaths []string `json:"ignorePaths"`
IncludePaths []string `json:"includePaths"`
CustomManagers []struct {
CustomType string `json:"customType"`
ManagerFilePatterns []string `json:"managerFilePatterns"`
MatchStrings []string `json:"matchStrings"`
Expand All @@ -250,12 +253,14 @@ type renovateConfig struct {
} `json:"customManagers"`
Flux builtInManager `json:"flux"`
Kubernetes builtInManager `json:"kubernetes"`
Dockerfile builtInManager `json:"dockerfile"`
PackageRules []map[string]any `json:"packageRules"`
}

// builtInManager is the part of a built-in manager's settings that decides
// which files it reads.
type builtInManager struct {
Enabled *bool `json:"enabled"`
ManagerFilePatterns []string `json:"managerFilePatterns"`
}

Expand Down
72 changes: 68 additions & 4 deletions scripts/tests/oci-chart-sources/undated_ghcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ var undatedGHCRDependencies = []renovateDependency{
{"ghcr.io/cloudflare/origin-ca-issuer-charts/origin-ca-issuer", "flux", "docker", "k8s/providers/hetzner/infrastructure/controllers/origin-ca-issuer/oci-repository.yaml"},
{"ghcr.io/cloudnative-pg/postgresql", "custom.regex", "docker", "k8s/providers/hetzner/infrastructure/coroot/cluster.yaml"},
{"ghcr.io/cloudnative-pg/postgresql", "custom.regex", "docker", "k8s/bases/apps/umami/cluster.yaml"},
{"ghcr.io/coroot/coroot", "custom.regex", "docker", "k8s/bases/infrastructure/coroot/coroot.yaml"},
{"ghcr.io/coroot/coroot-cluster-agent", "custom.regex", "docker", "k8s/bases/infrastructure/coroot/coroot.yaml"},
{"ghcr.io/coroot/coroot", "dockerfile", "docker", "k8s/bases/infrastructure/coroot/CorootCheckpoint.Dockerfile"},
{"ghcr.io/coroot/coroot-cluster-agent", "dockerfile", "docker", "k8s/bases/infrastructure/coroot/CorootClusterAgentCheckpoint.Dockerfile"},
{"ghcr.io/umami-software/umami", "kubernetes", "docker", "k8s/bases/apps/umami/cron-job.yaml"},
}

Expand Down Expand Up @@ -81,11 +81,11 @@ func TestUndatedGHCRGuardRejectsMissingExemptionAndWrongManager(t *testing.T) {
{"image read by the flux manager",
renovateDependency{"ghcr.io/umami-software/umami", "flux", "docker", "k8s/bases/apps/umami/cron-job.yaml"}},
{"name that is only a prefix of the pinned image",
renovateDependency{"ghcr.io/coroot/coroot-cluster", "custom.regex", "docker", "k8s/bases/infrastructure/coroot/coroot.yaml"}},
renovateDependency{"ghcr.io/coroot/coroot-cluster", "dockerfile", "docker", "k8s/bases/infrastructure/coroot/CorootClusterAgentCheckpoint.Dockerfile"}},
{"custom manager that does not read the file",
renovateDependency{"ghcr.io/cloudnative-pg/postgresql", "custom.regex", "docker", "k8s/bases/apps/umami/cron-job.yaml"}},
{"wrong datasource",
renovateDependency{"ghcr.io/coroot/coroot", "custom.regex", "github-releases", "k8s/bases/infrastructure/coroot/coroot.yaml"}},
renovateDependency{"ghcr.io/coroot/coroot", "dockerfile", "github-releases", "k8s/bases/infrastructure/coroot/CorootCheckpoint.Dockerfile"}},
} {
t.Run(tc.name, func(t *testing.T) {
text, err := os.ReadFile(filepath.Join(root, tc.dep.packageFile))
Expand All @@ -108,6 +108,31 @@ func extractedAs(config renovateConfig, dep renovateDependency, text string) err
})
}
switch dep.manager {
case "dockerfile":
// Renovate's documented defaults include prefix.Dockerfile. Model the
// fixed, tagged FROM references used by these rows, not ARG expansion.
// https://docs.renovatebot.com/modules/manager/dockerfile/
patterns := []string{
`/(^|/|\.)([Dd]ocker|[Cc]ontainer)file$/`,
`/(^|/)([Dd]ocker|[Cc]ontainer)file[^/]*$/`,
}
patterns = append(patterns, config.Dockerfile.ManagerFilePatterns...)
if (config.Dockerfile.Enabled != nil && !*config.Dockerfile.Enabled) ||
(len(config.EnabledManagers) > 0 && !slices.Contains(config.EnabledManagers, dep.manager)) ||
!readsFile(patterns) || (len(config.IncludePaths) > 0 && !readsFile(config.IncludePaths)) {
return fmt.Errorf("the dockerfile manager does not read %s", dep.packageFile)
}
for _, pattern := range config.IgnorePaths {
if patternMatch(pattern, dep.packageFile) != noMatch {
return fmt.Errorf("the dockerfile source is ignored or undecidable: %s", dep.packageFile)
}
}
image := regexp.MustCompile(`(?im)^\s*FROM\s+(?:--platform=\S+\s+)?` + regexp.QuoteMeta(dep.packageName) +
`:[0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9.-]+)?(?:@sha256:[0-9a-f]{64})?(?:\s+AS\s+[A-Za-z0-9_-]+)?\s*$`)
if !image.MatchString(text) || dep.datasource != "docker" {
return fmt.Errorf("the dockerfile manager does not extract a fixed tagged FROM for %s from %s", dep.packageName, dep.packageFile)
}
return nil
case "flux":
if !readsFile(config.Flux.ManagerFilePatterns) {
return fmt.Errorf("the flux manager does not read %s", dep.packageFile)
Expand Down Expand Up @@ -162,3 +187,42 @@ func extractedAs(config renovateConfig, dep renovateDependency, text string) err
}
return fmt.Errorf("manager %q is not modelled", dep.manager)
}

// A base-image dependency must not disappear when its manager or file is
// excluded, or when its real FROM is replaced by an inert textual mention.
func TestUndatedGHCRDockerfileGuardRejectsLostDiscovery(t *testing.T) {
dep := undatedGHCRDependencies[4]
body, err := os.ReadFile(filepath.Join(repositoryRoot(t), dep.packageFile))
if err != nil {
t.Fatal(err)
}
for _, mutation := range []string{"disabled", "other-managers-only", "ignored", "excluded", "wrong-filename", "commented-from", "unresolved-version"} {
t.Run(mutation, func(t *testing.T) {
config, candidate, text := readRenovateConfig(t), dep, string(body)
if err := extractedAs(config, candidate, text); err != nil {
t.Fatalf("positive control: %v", err)
}
switch mutation {
case "disabled":
enabled := false
config.Dockerfile.Enabled = &enabled
case "other-managers-only":
config.EnabledManagers = []string{"custom.regex"}
case "ignored":
config.IgnorePaths = []string{dep.packageFile}
case "excluded":
config.IncludePaths = []string{"unrelated/**"}
case "wrong-filename":
candidate.packageFile += ".txt"
case "commented-from":
text = strings.Replace(text, "FROM "+dep.packageName+":", "# FROM "+dep.packageName+":", 1)
case "unresolved-version":
reference := regexp.MustCompile(regexp.QuoteMeta(dep.packageName) + `:[^\s@]+`)
text = reference.ReplaceAllLiteralString(text, dep.packageName+":${UNRESOLVED_VERSION}")
}
if err := extractedAs(config, candidate, text); err == nil {
t.Fatal("accepted lost dependency discovery")
}
})
}
}
10 changes: 6 additions & 4 deletions scripts/tests/test-coroot-api-key-contract.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ assert "${infrastructure}" \

# Component images stay pinned. The operator lists the registry only when an
# image is unset, so pins remain the primary path and GHCR egress the fallback.
# The node-agent may use either Coroot's upstream image or the platform's
# signed compatibility build; both repositories are explicit allow-list entries.
# The node-agent also accepts its platform compatibility repository. The server
# and collector additionally accept only these exact reviewed checkpoint builds.
assert "${infrastructure}" \
"${coroot} | .[0].spec |
(.communityEdition.image.name | test(\"^ghcr[.]io/coroot/coroot:[0-9]\")) and
((.communityEdition.image.name | test(\"^ghcr[.]io/coroot/coroot:[0-9]\")) or
.communityEdition.image.name == \"ghcr.io/devantler-tech/platform-coroot:v1.28.1-checkpoints.1-27c6c840e2006076c0ff5ed7dabe15daa1c38477@sha256:9a2782f405f9ee673301912b3b2cf85a4a154fbfeb1542df2d4937d6600396f1\") and
(.nodeAgent.image.name | test(\"^ghcr[.]io/(coroot/coroot-node-agent:[0-9]|devantler-tech/platform-coroot-node-agent:v[0-9])\")) and
(.clusterAgent.image.name | test(\"^ghcr[.]io/coroot/coroot-cluster-agent:[0-9]\"))" \
((.clusterAgent.image.name | test(\"^ghcr[.]io/coroot/coroot-cluster-agent:[0-9]\")) or
.clusterAgent.image.name == \"ghcr.io/devantler-tech/platform-coroot-cluster-agent:v1.11.7-checkpoints.1-27c6c840e2006076c0ff5ed7dabe15daa1c38477@sha256:46b6abe52dbf5445277ef3e3bbcb469a89a88e2c610411c5d9a4270a8a8a88c3\")" \
'the Coroot component images must remain pinned to explicit GHCR tags'

policy='map(select(.kind == "CiliumNetworkPolicy" and .metadata.namespace == "observability" and .metadata.name == "allow-coroot"))'
Expand Down
Loading