Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ spec:
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: ^https://github\.com/devantler-tech/(actions/\.github/workflows/publish-app\.yaml@[0-9a-f]{40}|\.github/\.github/workflows/publish-app\.yaml@(0c7b6c3509b73adbc7537e030935bb01987e625b|467f07582436a26b62cf4aa756423512f85ba0c0))$
# #5377 approves this transition for KHoist alone. Preserve the shared app
# identity and Tenant constructor; lookalikes and child image repositories
# still use their original verification path.
- name: publishkhoist
cosign:
keyless:
identities:
- issuer: https://token.actions.githubusercontent.com
subjectRegExp: ^https://github\.com/devantler-tech/(actions/\.github/workflows/publish-app\.yaml@[0-9a-f]{40}|\.github/\.github/workflows/publish-app\.yaml@(2df24600b1113e291b87e300b91e59c160446134|467f07582436a26b62cf4aa756423512f85ba0c0))$
# The Kubescape storage compatibility image is produced inside this
# repository rather than through actions/publish-app.yaml: its publisher
# checks out one immutable upstream storage commit, applies the reviewed
Expand Down Expand Up @@ -180,6 +189,9 @@ spec:
(images.containers + images.initContainers + images.ephemeralContainers)
.filter(image, image.startsWith('ghcr.io/devantler-tech/') &&
!image.startsWith('ghcr.io/devantler-tech/ksail') &&
image != 'ghcr.io/devantler-tech/khoist' &&
!image.startsWith('ghcr.io/devantler-tech/khoist:') &&
!image.startsWith('ghcr.io/devantler-tech/khoist@') &&
!image.startsWith('ghcr.io/devantler-tech/provider-upjet-') &&
image != 'ghcr.io/devantler-tech/world-at-ruin/zone' &&
!image.startsWith('ghcr.io/devantler-tech/world-at-ruin/zone:') &&
Expand All @@ -205,6 +217,17 @@ spec:
first-party app image is not signed by the publish-app release
pipeline (keyless cosign, legacy full-commit identity or the explicitly
approved maintained revision — a release tag is not accepted)
- expression: >-
(images.containers + images.initContainers + images.ephemeralContainers)
.filter(image,
image == 'ghcr.io/devantler-tech/khoist' ||
image.startsWith('ghcr.io/devantler-tech/khoist:') ||
image.startsWith('ghcr.io/devantler-tech/khoist@'))
.map(image, verifyImageSignatures(image, [attestors.publishkhoist]))
.all(e, e > 0)
message: >-
KHoist image is not signed by its exact approved application publisher;
other first-party images retain their existing signer.
- expression: >-
(images.containers + images.initContainers + images.ephemeralContainers)
.filter(image,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ spec:
provider: cosign
matchOIDCIdentity:
- issuer: '^https://token\.actions\.githubusercontent\.com$'
subject: '^https://github\.com/devantler-tech/(actions/\.github/workflows/publish-app\.yaml@ea4df9004d4ab43b6c9af5b0c5f55712f7211328|\.github/\.github/workflows/publish-app\.yaml@(0c7b6c3509b73adbc7537e030935bb01987e625b|467f07582436a26b62cf4aa756423512f85ba0c0))$'
subject: '^https://github\.com/devantler-tech/(actions/\.github/workflows/publish-app\.yaml@ea4df9004d4ab43b6c9af5b0c5f55712f7211328|\.github/\.github/workflows/publish-app\.yaml@(2df24600b1113e291b87e300b91e59c160446134|467f07582436a26b62cf4aa756423512f85ba0c0))$'
40 changes: 1 addition & 39 deletions scripts/canonical-tenant-source-bindings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"consumer": "business-site",
"consumerRepository": "devantler-tech/business-site",
"workflow": "publish-app",
"kind": "published-upgrade",
"kind": "published-binding",
"sourcePath": "k8s/providers/hetzner/apps/business-site/tenant.yaml",
"constructorSpecSha256": "16939512acb4d4dccb1ebe11caa3f48fe49503c8a9a8a4c44aafd328a5a45fdb",
"tenantIdentity": {
Expand All @@ -31,44 +31,6 @@
"workflowRunId": 40247127226,
"workflowRunAttempt": 1,
"verifiedOn": "2026-10-26"
},
"previousBinding": {
"consumer": "business-site",
"consumerRepository": "devantler-tech/business-site",
"workflow": "publish-app",
"kind": "published-binding",
"sourcePath": "k8s/providers/hetzner/apps/business-site/tenant.yaml",
"constructorSpecSha256": "16939512acb4d4dccb1ebe11caa3f48fe49503c8a9a8a4c44aafd328a5a45fdb",
"tenantIdentity": {
"apiVersion": "kro.run/v1alpha1",
"kind": "Tenant",
"namespace": "flux-system",
"name": "business-site"
},
"sourceIdentity": {
"apiVersion": "source.toolkit.fluxcd.io/v1",
"kind": "OCIRepository",
"namespace": "business-site",
"name": "business-site",
"artifactURL": "oci://ghcr.io/devantler-tech/business-site/manifests",
"digest": "sha256:fa22e5796a39872feb8c75b5d56e2c6d68ff289230e2c5762f0bbfa0b30d6744"
},
"publication": {
"gitTag": "v1.0.3",
"sourceSHA": "498248e86e49024b72f3249b518e4dd1cef0104b",
"publisherIdentity": "devantler-tech/.github/.github/workflows/publish-app.yaml@467f07582436a26b62cf4aa756423512f85ba0c0",
"callerWorkflow": ".github/workflows/publish-runtime.yaml",
"event": "release",
"workflowRunId": 39921694859,
"workflowRunAttempt": 1,
"verifiedOn": "2026-10-25"
}
},
"previousObservation": {
"uidSha256": "38aff5c8aafbd89fc51dbbe131d27c9a3fb0e78108a20779d0e7a5680a6cc2d2",
"generation": 1,
"snapshotSha256": "610500b54e61b2d3e33cc2db548971e04816e05231e2df50dd6f255ddd92600e",
"observedOn": "2026-10-26"
}
}
]
Expand Down
4 changes: 2 additions & 2 deletions scripts/publish-workflow-approved-revisions.identities.tsv
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
consumer workflow applied_tag applied_digest applied_publisher_identity main_publisher_identity observed_on
.github publish-manifests 13.2.11 sha256:06e4ffb505a79d4bae0a2a903de89aeac0fdc2b17bc746db442e74711b54cf45 devantler-tech/.github/.github/workflows/publish-manifests.yaml@0f390329b590404c02565b27a263ee841fa02bce devantler-tech/.github/.github/workflows/publish-manifests.yaml@0f390329b590404c02565b27a263ee841fa02bce 2026-10-26
.github publish-manifests 13.2.15 sha256:bac246c18b873259d37e027018f02ae005cf4e1e385038973af9fbf8c9a9f72b devantler-tech/.github/.github/workflows/publish-manifests.yaml@0f390329b590404c02565b27a263ee841fa02bce devantler-tech/.github/.github/workflows/publish-manifests.yaml@0f390329b590404c02565b27a263ee841fa02bce 2026-10-26
ascoachingogvaner publish-app 1.14.1 sha256:a1a03ac21c9dc8a2c8650e423da422d188ec33e9065a8412844503e4e32671f1 devantler-tech/.github/.github/workflows/publish-app.yaml@0c7b6c3509b73adbc7537e030935bb01987e625b devantler-tech/.github/.github/workflows/publish-app.yaml@467f07582436a26b62cf4aa756423512f85ba0c0 2026-10-25
aws publish-manifests 1.1.5 sha256:94940512927d45480acd056eff885cd92d02921bee38bd163cd39dbf9a531289 devantler-tech/.github/.github/workflows/publish-manifests.yaml@2fa404276b0ce5c0527683b080e39045045c4e42 devantler-tech/.github/.github/workflows/publish-manifests.yaml@2fa404276b0ce5c0527683b080e39045045c4e42 2026-10-24
business-site publish-app 1.0.3 sha256:fa22e5796a39872feb8c75b5d56e2c6d68ff289230e2c5762f0bbfa0b30d6744 devantler-tech/.github/.github/workflows/publish-app.yaml@467f07582436a26b62cf4aa756423512f85ba0c0 devantler-tech/.github/.github/workflows/publish-app.yaml@467f07582436a26b62cf4aa756423512f85ba0c0 2026-10-26
business-site publish-app 2.0.0 sha256:2f43bf60c87e373ab99f9fc08f187287d9526b3f1b3212b6234e2ae80b9c5e82 devantler-tech/.github/.github/workflows/publish-app.yaml@467f07582436a26b62cf4aa756423512f85ba0c0 devantler-tech/.github/.github/workflows/publish-app.yaml@467f07582436a26b62cf4aa756423512f85ba0c0 2026-10-26
data-product-controller publish-app 5.4.0 sha256:b9b5ff9e0cedf1305528e058708e9729eed9aa39a5dcdd967b7e74197264fe65 devantler-tech/actions/.github/workflows/publish-app.yaml@ea4df9004d4ab43b6c9af5b0c5f55712f7211328 devantler-tech/.github/.github/workflows/publish-app.yaml@467f07582436a26b62cf4aa756423512f85ba0c0 2026-10-24
wedding-app publish-app 1.15.23 sha256:cf73b39c20cf222c12983027145e8438b3a9c3508359f544b82f736eba0490b4 devantler-tech/.github/.github/workflows/publish-app.yaml@0c7b6c3509b73adbc7537e030935bb01987e625b devantler-tech/.github/.github/workflows/publish-app.yaml@467f07582436a26b62cf4aa756423512f85ba0c0 2026-10-25
2 changes: 1 addition & 1 deletion scripts/publish-workflow-canonical-approvals.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ aws publish-manifests devantler-tech/.github 2fa404276b0ce5c0527683b080e39045045
ascoachingogvaner publish-app devantler-tech/.github 0c7b6c3509b73adbc7537e030935bb01987e625b
ascoachingogvaner publish-app devantler-tech/.github 467f07582436a26b62cf4aa756423512f85ba0c0
business-site publish-app devantler-tech/.github 467f07582436a26b62cf4aa756423512f85ba0c0
data-product-controller publish-app devantler-tech/.github 0c7b6c3509b73adbc7537e030935bb01987e625b
data-product-controller publish-app devantler-tech/.github 2df24600b1113e291b87e300b91e59c160446134
data-product-controller publish-app devantler-tech/.github 467f07582436a26b62cf4aa756423512f85ba0c0
wedding-app publish-app devantler-tech/.github 0c7b6c3509b73adbc7537e030935bb01987e625b
wedding-app publish-app devantler-tech/.github 467f07582436a26b62cf4aa756423512f85ba0c0
4 changes: 2 additions & 2 deletions scripts/shared-publish-workflow-matchers.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
1 k8s/bases/apps/ascoachingogvaner/oci-repository.yaml
1 k8s/bases/apps/github-config/oci-repository.yaml
1 k8s/bases/apps/wedding-app/oci-repository.yaml
1 k8s/bases/infrastructure/cluster-policies/best-practices/verify-app-images.yaml
2 k8s/bases/infrastructure/cluster-policies/best-practices/verify-app-images.yaml
1 k8s/bases/infrastructure/resource-graph-definitions/tenant/resource-graph-definition.yaml
1 k8s/providers/hetzner/apps/aws/oci-repository.yaml
1 k8s/providers/hetzner/apps/khoist-documentation/oci-repository.yaml
1 talos/cluster/verify-first-party-images.yaml
2 talos/cluster/verify-first-party-images.yaml
79 changes: 79 additions & 0 deletions scripts/tests/arc-staging/khoist_publisher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package arcstaging_test

import (
"path"
"regexp"
"strings"
"testing"
)

const khoistImage = "ghcr.io/devantler-tech/khoist"
const khoistPublisher = "https://github.com/devantler-tech/.github/.github/workflows/publish-app.yaml@2df24600b1113e291b87e300b91e59c160446134"

// Compile the complete committed CEL; only signature transport uses fixtures.
func TestKHoistPublisherIsIsolatedInAdmission(t *testing.T) {
evaluator := newImagePolicyEvaluator(t, readYAML(t, "k8s/bases/infrastructure/cluster-policies/best-practices/verify-app-images.yaml"))
for _, repository := range []string{khoistImage, khoistImage + "-other", khoistImage + "/child", "ghcr.io/devantler-tech/wedding-app", "ghcr.io/devantler-tech/data-product-controller"} {
for _, suffix := range []string{"", ":v1.0.0", "@sha256:" + strings.Repeat("a", 64), ":v1.0.0@sha256:" + strings.Repeat("a", 64)} {
image := repository + suffix
for _, slot := range []string{"containers", "initContainers", "ephemeralContainers"} {
t.Run(image+"/"+slot, func(t *testing.T) {
images := map[string][]string{"containers": {}, "initContainers": {}, "ephemeralContainers": {}}
images[slot] = []string{image}
admitted, calls := evaluator.evaluate(t, images, map[string]certificateFixture{image: {githubIssuer, khoistPublisher}})
equal(t, admitted, repository == khoistImage)
if repository != khoistImage {
return
}
if len(calls) != 1 || calls[0] != image+"/publishkhoist" {
t.Fatal("KHoist verification was skipped or routed through a shared attestor", calls)
}
for _, certificate := range []certificateFixture{
{"", ""}, {"https://other.example", khoistPublisher},
{githubIssuer, strings.Replace(khoistPublisher, "devantler-tech/", "other-owner/", 1)},
{githubIssuer, strings.Split(khoistPublisher, "@")[0] + "@refs/tags/v13.2.13"},
{githubIssuer, strings.Split(khoistPublisher, "@")[0] + "@" + strings.Repeat("b", 40)},
{githubIssuer, strings.Split(khoistPublisher, "@")[0] + "@0c7b6c3509b73adbc7537e030935bb01987e625b"},
} {
got, _ := evaluator.evaluate(t, images, map[string]certificateFixture{image: certificate})
equal(t, got, false)
}
for _, retained := range []string{
"https://github.com/devantler-tech/.github/.github/workflows/publish-app.yaml@467f07582436a26b62cf4aa756423512f85ba0c0",
"https://github.com/devantler-tech/actions/.github/workflows/publish-app.yaml@" + strings.Repeat("b", 40),
} {
got, _ := evaluator.evaluate(t, images, map[string]certificateFixture{image: {githubIssuer, retained}})
equal(t, got, true)
}
})
}
}
}
}

func TestKHoistPublisherIsIsolatedInFirstMatchingNodeRule(t *testing.T) {
rules := field(t, readYAML(t, "talos/cluster/verify-first-party-images.yaml"), "rules").([]any)
for _, repository := range []string{khoistImage, khoistImage + "-other", khoistImage + "/child", "ghcr.io/devantler-tech/wedding-app", "ghcr.io/devantler-tech/data-product-controller"} {
found := false
for _, rule := range rules {
matches, err := path.Match(field(t, rule, "image").(string), repository)
if err != nil {
t.Fatal(err)
}
if !matches {
continue
}
found = true
identity := regexp.MustCompile(field(t, rule, "keyless", "subjectRegex").(string))
equal(t, field(t, rule, "keyless", "issuer"), githubIssuer)
equal(t, identity.MatchString(khoistPublisher), repository == khoistImage)
break
}
// The existing one-level node catch-all does not select nested paths.
// Keep that baseline unchanged; the complete admission test above rejects
// this child, and any broadened KHoist node rule would fail the match test.
if !found && repository != khoistImage+"/child" {
t.Fatal("first-party image escaped node verification", repository)
}
}
}
18 changes: 16 additions & 2 deletions scripts/tests/refresh-flux-ghcr-auth/fake_kubectl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1200,7 +1200,7 @@ func fakeKubectlPatchConsolidatedImageValidatingPolicy(args []string, patchFile
spec, _ := patch["spec"].(map[string]any)
webhookConfiguration, _ := spec["webhookConfiguration"].(map[string]any)
attestors, _ := spec["attestors"].([]any)
if webhookConfiguration["timeoutSeconds"] != float64(30) || len(attestors) != 9 {
if webhookConfiguration["timeoutSeconds"] != float64(30) || len(attestors) != 10 {
return commandFailure(91, "consolidated image-validating policy patch omitted its timeout or attestors")
}
storageAttestorValid := false
Expand All @@ -1209,10 +1209,11 @@ func fakeKubectlPatchConsolidatedImageValidatingPolicy(args []string, patchFile
analysisAttestorValid := false
warZoneAttestorValid := false
mailAttestorValid := false
khoistAttestorValid := false
for _, rawAttestor := range attestors {
attestor, _ := rawAttestor.(map[string]any)
name, _ := attestor["name"].(string)
if name != "publishkubescapestorage" && name != "publishkubescapenodeagent" && name != "publishcorootnodeagent" && name != "publishwarzone" && name != "publishksailanalysis" && name != "publishprivatemail" {
if name != "publishkubescapestorage" && name != "publishkubescapenodeagent" && name != "publishcorootnodeagent" && name != "publishwarzone" && name != "publishksailanalysis" && name != "publishprivatemail" && name != "publishkhoist" {
continue
}
cosign, _ := attestor["cosign"].(map[string]any)
Expand All @@ -1237,6 +1238,8 @@ func fakeKubectlPatchConsolidatedImageValidatingPolicy(args []string, patchFile
warZoneAttestorValid = issuerValid && subject == "^https://github\\.com/devantler-tech/world-at-ruin/\\.github/workflows/server-cd\\.yaml@refs/tags/v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
case "publishprivatemail":
mailAttestorValid = issuerValid && subject == "^https://github\\.com/devantler-tech/platform/\\.github/workflows/publish-private-mail-bootstrap\\.yaml@refs/heads/main$"
case "publishkhoist":
khoistAttestorValid = issuerValid && subject == "^https://github\\.com/devantler-tech/(actions/\\.github/workflows/publish-app\\.yaml@[0-9a-f]{40}|\\.github/\\.github/workflows/publish-app\\.yaml@(2df24600b1113e291b87e300b91e59c160446134|467f07582436a26b62cf4aa756423512f85ba0c0))$"
}
}
validations, _ := spec["validations"].([]any)
Expand All @@ -1252,10 +1255,15 @@ func fakeKubectlPatchConsolidatedImageValidatingPolicy(args []string, patchFile
dedicatedRoutesWarZone := false
genericExcludesMail := false
dedicatedRoutesMail := false
genericExcludesKHoist := false
dedicatedRoutesKHoist := false
for _, rawValidation := range validations {
validation, _ := rawValidation.(map[string]any)
expression, _ := validation["expression"].(string)
if strings.Contains(expression, "attestors.publishapp") {
genericExcludesKHoist = strings.Contains(expression, "image != 'ghcr.io/devantler-tech/khoist'") &&
strings.Contains(expression, "!image.startsWith('ghcr.io/devantler-tech/khoist:')") &&
strings.Contains(expression, "!image.startsWith('ghcr.io/devantler-tech/khoist@')")
genericExcludesMail = strings.Contains(expression, "image != 'ghcr.io/devantler-tech/platform-private-mail-bootstrap'") &&
strings.Contains(expression, "!image.startsWith('ghcr.io/devantler-tech/platform-private-mail-bootstrap:')") &&
strings.Contains(expression, "!image.startsWith('ghcr.io/devantler-tech/platform-private-mail-bootstrap@')")
Expand Down Expand Up @@ -1305,8 +1313,14 @@ func fakeKubectlPatchConsolidatedImageValidatingPolicy(args []string, patchFile
strings.Contains(expression, "image.startsWith('ghcr.io/devantler-tech/platform-private-mail-bootstrap:')") &&
strings.Contains(expression, "image.startsWith('ghcr.io/devantler-tech/platform-private-mail-bootstrap@')")
}
if strings.Contains(expression, "attestors.publishkhoist") {
dedicatedRoutesKHoist = strings.Contains(expression, "image == 'ghcr.io/devantler-tech/khoist'") &&
strings.Contains(expression, "image.startsWith('ghcr.io/devantler-tech/khoist:')") &&
strings.Contains(expression, "image.startsWith('ghcr.io/devantler-tech/khoist@')")
}
}
if !storageAttestorValid || !kubescapeNodeAgentAttestorValid || !corootNodeAgentAttestorValid ||
!khoistAttestorValid || !genericExcludesKHoist || !dedicatedRoutesKHoist ||
!mailAttestorValid || !genericExcludesMail || !dedicatedRoutesMail ||
!analysisAttestorValid || !genericExcludesAnalysis || !dedicatedRoutesAnalysis ||
!warZoneAttestorValid || !genericExcludesWarZone || !dedicatedRoutesWarZone ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,12 +268,13 @@ expected_globs="$(printf '%s\n' \
'ghcr.io/devantler-tech/platform-coroot-node-agent' \
'ghcr.io/devantler-tech/world-at-ruin/zone' \
'ghcr.io/devantler-tech/platform-private-mail-bootstrap' \
'ghcr.io/devantler-tech/khoist' \
'ghcr.io/devantler-tech/*')"
if [ "$real_globs" != "$expected_globs" ]; then
echo "FAIL the real rules must retain all nine exact ordered image routes"
echo "FAIL the real rules must retain all ten exact ordered image routes"
failures=$((failures + 1))
else
echo "ok real rules: nine ordered rules, dedicated publishers before catch-all"
echo "ok real rules: ten ordered rules, dedicated publishers before catch-all"
fi

# The real file must also satisfy the completeness gate the fixtures exercise.
Expand Down
Loading
Loading