Skip to content

Commit 5ee39dc

Browse files
authored
Merge branch 'main' into dependabot/github_actions/actions/setup-node-7.0.0
2 parents 0b0b336 + 43a4af7 commit 5ee39dc

13 files changed

Lines changed: 1483 additions & 29 deletions

File tree

.github/workflows/build.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
permissions:
3131
contents: read
3232
steps:
33-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
33+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
3434
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
3535
with:
3636
node-version: '22'
@@ -60,7 +60,7 @@ jobs:
6060
permissions:
6161
contents: read
6262
steps:
63-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
63+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
6464
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
6565
with:
6666
node-version: '22'
@@ -93,7 +93,7 @@ jobs:
9393
packages: write
9494
id-token: write
9595
steps:
96-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
96+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
9797
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
9898
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
9999
if: github.event_name != 'pull_request'
@@ -159,7 +159,7 @@ jobs:
159159
packages: write
160160
id-token: write
161161
steps:
162-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
162+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
163163
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
164164

165165
# Authenticate to dhi.io to pull hardened base images (golang, alpine).
@@ -227,7 +227,7 @@ jobs:
227227
permissions:
228228
contents: read
229229
steps:
230-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
230+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
231231
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
232232
# Single-platform, load: true build — separate from build-controlplane's
233233
# multi-platform buildx output below, which can't be docker-loaded.
@@ -306,7 +306,7 @@ jobs:
306306
packages: write
307307
id-token: write
308308
steps:
309-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
309+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
310310
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
311311
- uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
312312
if: github.event_name != 'pull_request'
@@ -358,7 +358,7 @@ jobs:
358358
permissions:
359359
contents: read
360360
steps:
361-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
361+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
362362
- name: Compose up
363363
run: docker compose -f deploy/docker/compose.yaml up -d --build --wait
364364
- name: Smoke test

.github/workflows/docs-publish.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
# once Pages is bootstrapped if you want the smaller scope.
5252
pages: write
5353
steps:
54-
- uses: actions/checkout@v6
54+
- uses: actions/checkout@v7.0.0
5555

5656
- uses: actions/setup-python@v6
5757
with:

.github/workflows/docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
permissions:
4040
contents: read
4141
steps:
42-
- uses: actions/checkout@v6
42+
- uses: actions/checkout@v7.0.0
4343

4444
- uses: actions/setup-python@v6
4545
with:

.github/workflows/release.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
permissions:
4545
contents: read
4646
steps:
47-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
47+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
4848
with:
4949
fetch-depth: 0
5050

@@ -70,7 +70,7 @@ jobs:
7070
version: ${{ steps.version.outputs.version }}
7171
hashes: ${{ steps.hash.outputs.hashes }}
7272
steps:
73-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
73+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
7474
with:
7575
fetch-depth: 0 # GoReleaser needs full history for changelog
7676

@@ -85,7 +85,7 @@ jobs:
8585

8686
# GoReleaser builds cross-platform binaries and creates the
8787
# GitHub Release with archives and checksums.
88-
- uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
88+
- uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
8989
id: goreleaser
9090
with:
9191
distribution: goreleaser
@@ -180,7 +180,7 @@ jobs:
180180
packages: write
181181
id-token: write # Cosign chart signing
182182
steps:
183-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
183+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
184184

185185
- name: Install Helm
186186
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0

.github/workflows/vet-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121

2222
steps:
2323
- name: Checkout
24-
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
24+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
2525

2626
- name: Run vet
2727
id: vet

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ require (
3030
google.golang.org/grpc v1.81.1
3131
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
3232
gopkg.in/yaml.v3 v3.0.1
33-
k8s.io/api v0.36.1
34-
k8s.io/apimachinery v0.36.1
33+
k8s.io/api v0.36.2
34+
k8s.io/apimachinery v0.36.2
3535
k8s.io/client-go v0.36.0
3636
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5
3737
oras.land/oras-go/v2 v2.6.2

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -416,12 +416,12 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
416416
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
417417
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
418418
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
419-
k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
420-
k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
419+
k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY=
420+
k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg=
421421
k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0=
422422
k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug=
423-
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
424-
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
423+
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ=
424+
k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4=
425425
k8s.io/client-go v0.36.0 h1:pOYi7C4RHChYjMiHpZSpSbIM6ZxVbRXBy7CuiIwqA3c=
426426
k8s.io/client-go v0.36.0/go.mod h1:ZKKcpwF0aLYfkHFCjillCKaTK/yBkEDHTDXCFY6AS9Y=
427427
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=

internal/controlplane/api/endpoints_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,147 @@ func TestHandleQuickTest_UnreachableInstance(t *testing.T) {
335335
t.Error("expected non-empty error field for unreachable instance")
336336
}
337337
}
338+
339+
// ── Config endpoints redact secrets over the wire ──────────────────────
340+
//
341+
// These are the end-to-end proof that "never send a raw secret to the
342+
// client" actually holds across the HTTP boundary, not just inside
343+
// configmgmt: each test seeds a real secret directly via ConfigStore,
344+
// then asserts the HTTP JSON response differs from what a second,
345+
// direct ConfigStore.Current() call shows is actually stored.
346+
347+
const secretConfigFixture = `{"identifiers":[{"name":"a","type":"hmac","config":{"keys":{"k1":{"secret":"super-secret-hmac-key"}}}}]}`
348+
349+
func TestHandleGetConfig_RedactsSecret(t *testing.T) {
350+
s := newTestServer(t)
351+
registerInstance(s, "inst", "local", "default", "http://upstream")
352+
if _, err := s.ConfigStore.PushConfig(t.Context(), "local", "inst", secretConfigFixture, "op", "seed"); err != nil {
353+
t.Fatalf("seed config: %v", err)
354+
}
355+
356+
req := httptest.NewRequest(http.MethodGet, "/v1/controlplane/instances/local/inst/config", nil)
357+
req.SetPathValue("cluster", "local")
358+
req.SetPathValue("name", "inst")
359+
rr := httptest.NewRecorder()
360+
s.Mux.ServeHTTP(rr, req)
361+
362+
if rr.Code != http.StatusOK {
363+
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
364+
}
365+
var resp configmgmt.ConfigVersion
366+
if err := json.NewDecoder(rr.Body).Decode(&resp); err != nil {
367+
t.Fatalf("decode response: %v", err)
368+
}
369+
370+
stored, ok := s.ConfigStore.Current("local", "inst")
371+
if !ok {
372+
t.Fatal("expected a stored version")
373+
}
374+
if resp.Content == stored.Content {
375+
t.Error("HTTP response content matches the raw stored content — secret was not redacted")
376+
}
377+
if strings.Contains(resp.Content, "super-secret-hmac-key") {
378+
t.Error("raw secret value leaked into the HTTP response body")
379+
}
380+
if !strings.Contains(stored.Content, "super-secret-hmac-key") {
381+
t.Error("expected the real secret to still be present in the store internally")
382+
}
383+
}
384+
385+
func TestHandlePushConfig_ResponseRedactedStorePersistsReal(t *testing.T) {
386+
s := newTestServer(t)
387+
registerInstance(s, "inst", "local", "default", "http://upstream")
388+
389+
body, _ := json.Marshal(map[string]string{"content": secretConfigFixture, "author": "op"})
390+
req := httptest.NewRequest(http.MethodPost, "/v1/controlplane/instances/local/inst/config", strings.NewReader(string(body)))
391+
req.SetPathValue("cluster", "local")
392+
req.SetPathValue("name", "inst")
393+
req.Header.Set("Content-Type", "application/json")
394+
rr := httptest.NewRecorder()
395+
s.Mux.ServeHTTP(rr, req)
396+
397+
if rr.Code != http.StatusCreated {
398+
t.Fatalf("expected 201, got %d: %s", rr.Code, rr.Body.String())
399+
}
400+
if strings.Contains(rr.Body.String(), "super-secret-hmac-key") {
401+
t.Error("raw secret value leaked into the push response body")
402+
}
403+
404+
stored, ok := s.ConfigStore.Current("local", "inst")
405+
if !ok {
406+
t.Fatal("expected a stored version")
407+
}
408+
if !strings.Contains(stored.Content, "super-secret-hmac-key") {
409+
t.Error("expected the real secret to be persisted, push response redaction must not affect what's stored")
410+
}
411+
}
412+
413+
func TestHandleConfigHistory_RedactsEveryVersion(t *testing.T) {
414+
s := newTestServer(t)
415+
registerInstance(s, "inst", "local", "default", "http://upstream")
416+
if _, err := s.ConfigStore.PushConfig(t.Context(), "local", "inst", secretConfigFixture, "op", "v1"); err != nil {
417+
t.Fatalf("seed v1: %v", err)
418+
}
419+
v2 := `{"identifiers":[{"name":"a","type":"hmac","config":{"keys":{"k1":{"secret":"second-secret-value"}}}}]}`
420+
if _, err := s.ConfigStore.PushConfig(t.Context(), "local", "inst", v2, "op", "v2"); err != nil {
421+
t.Fatalf("seed v2: %v", err)
422+
}
423+
424+
req := httptest.NewRequest(http.MethodGet, "/v1/controlplane/instances/local/inst/config/history", nil)
425+
req.SetPathValue("cluster", "local")
426+
req.SetPathValue("name", "inst")
427+
rr := httptest.NewRecorder()
428+
s.Mux.ServeHTTP(rr, req)
429+
430+
if rr.Code != http.StatusOK {
431+
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
432+
}
433+
var history []configmgmt.ConfigVersion
434+
if err := json.NewDecoder(rr.Body).Decode(&history); err != nil {
435+
t.Fatalf("decode response: %v", err)
436+
}
437+
if len(history) != 2 {
438+
t.Fatalf("expected 2 versions, got %d", len(history))
439+
}
440+
for _, v := range history {
441+
if strings.Contains(v.Content, "super-secret-hmac-key") || strings.Contains(v.Content, "second-secret-value") {
442+
t.Errorf("version %d: raw secret leaked into history response", v.Version)
443+
}
444+
}
445+
}
446+
447+
func TestHandleConfigRollback_ResponseRedacted(t *testing.T) {
448+
s := newTestServer(t)
449+
registerInstance(s, "inst", "local", "default", "http://upstream")
450+
v1, err := s.ConfigStore.PushConfig(t.Context(), "local", "inst", secretConfigFixture, "op", "v1")
451+
if err != nil {
452+
t.Fatalf("seed v1: %v", err)
453+
}
454+
v2 := `{"identifiers":[{"name":"a","type":"hmac","config":{"keys":{"k1":{"secret":"second-secret-value"}}}}]}`
455+
if _, err := s.ConfigStore.PushConfig(t.Context(), "local", "inst", v2, "op", "v2"); err != nil {
456+
t.Fatalf("seed v2: %v", err)
457+
}
458+
459+
body, _ := json.Marshal(map[string]any{"version": v1.Version, "author": "op"})
460+
req := httptest.NewRequest(http.MethodPost, "/v1/controlplane/instances/local/inst/config/rollback", strings.NewReader(string(body)))
461+
req.SetPathValue("cluster", "local")
462+
req.SetPathValue("name", "inst")
463+
req.Header.Set("Content-Type", "application/json")
464+
rr := httptest.NewRecorder()
465+
s.Mux.ServeHTTP(rr, req)
466+
467+
if rr.Code != http.StatusOK {
468+
t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
469+
}
470+
if strings.Contains(rr.Body.String(), "super-secret-hmac-key") {
471+
t.Error("raw secret value leaked into the rollback response body")
472+
}
473+
474+
stored, ok := s.ConfigStore.Current("local", "inst")
475+
if !ok {
476+
t.Fatal("expected a stored version")
477+
}
478+
if !strings.Contains(stored.Content, "super-secret-hmac-key") {
479+
t.Error("expected the rolled-back real secret to be persisted internally")
480+
}
481+
}

internal/controlplane/api/server.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,12 @@ func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
731731
writeError(w, http.StatusNotFound, "no config stored for this instance")
732732
return
733733
}
734-
writeJSON(w, http.StatusOK, v)
734+
redacted, err := configmgmt.RedactConfigVersion(v)
735+
if err != nil {
736+
writeError(w, http.StatusInternalServerError, "failed to redact config: "+err.Error())
737+
return
738+
}
739+
writeJSON(w, http.StatusOK, redacted)
735740
}
736741

737742
// handlePushConfig validates and stores a new config version.
@@ -766,7 +771,17 @@ func (s *Server) handlePushConfig(w http.ResponseWriter, r *http.Request) {
766771

767772
logger := log.FromContext(r.Context())
768773
logger.Info("config pushed", "cluster", cluster, "instance", name, "version", v.Version)
769-
writeJSON(w, http.StatusCreated, v)
774+
775+
// The push above has already succeeded and persisted -- a redaction
776+
// failure here is a 500 with no data loss, not a failed write. The
777+
// console's next GET/history fetch succeeds once the underlying bug
778+
// (if any) is fixed.
779+
redacted, err := configmgmt.RedactConfigVersion(v)
780+
if err != nil {
781+
writeError(w, http.StatusInternalServerError, "failed to redact config: "+err.Error())
782+
return
783+
}
784+
writeJSON(w, http.StatusCreated, redacted)
770785
}
771786

772787
// handleConfigHistory returns all config versions for an instance.
@@ -780,10 +795,16 @@ func (s *Server) handleConfigHistory(w http.ResponseWriter, r *http.Request) {
780795
}
781796

782797
history := s.ConfigStore.History(cluster, name)
783-
if history == nil {
784-
history = []configmgmt.ConfigVersion{}
798+
redacted := make([]configmgmt.ConfigVersion, len(history))
799+
for i, v := range history {
800+
rv, err := configmgmt.RedactConfigVersion(v)
801+
if err != nil {
802+
writeError(w, http.StatusInternalServerError, "failed to redact config: "+err.Error())
803+
return
804+
}
805+
redacted[i] = rv
785806
}
786-
writeJSON(w, http.StatusOK, history)
807+
writeJSON(w, http.StatusOK, redacted)
787808
}
788809

789810
// handleConfigRollback rolls back to a specific config version.
@@ -817,7 +838,13 @@ func (s *Server) handleConfigRollback(w http.ResponseWriter, r *http.Request) {
817838

818839
logger := log.FromContext(r.Context())
819840
logger.Info("config rolled back", "cluster", cluster, "instance", name, "toVersion", req.Version, "newVersion", v.Version)
820-
writeJSON(w, http.StatusOK, v)
841+
842+
redacted, err := configmgmt.RedactConfigVersion(v)
843+
if err != nil {
844+
writeError(w, http.StatusInternalServerError, "failed to redact config: "+err.Error())
845+
return
846+
}
847+
writeJSON(w, http.StatusOK, redacted)
821848
}
822849

823850
// --- Route handlers (Phase 3) ---

0 commit comments

Comments
 (0)