Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
86552d6
feat(rover-ctl): implement PatchAuthentication to map clientAuthMetho…
stefan-ctrl Apr 27, 2026
90e88b3
Merge branch 'main' into client-authentication-method-roverctl-only
stefan-ctrl Apr 27, 2026
82994b5
test(rover-server): add tests to verify ctl output
stefan-ctrl Apr 27, 2026
de29049
Merge branch 'client-authentication-method-roverctl-only' of https://…
stefan-ctrl Apr 27, 2026
55eeea7
test(rover-handler): update tests to ensure authentication remains un…
stefan-ctrl Apr 27, 2026
dad4b83
fix(timestamps): update snapshot timestamps to reflect correct timezo…
stefan-ctrl Apr 27, 2026
08f3b3c
Merge branch 'main' into client-authentication-method-roverctl-only
stefan-ctrl Apr 27, 2026
9bc4511
feat(rover): implement mapping for Rover authentication methods and a…
stefan-ctrl Apr 27, 2026
d9fafe0
chore: make generate & manifest
stefan-ctrl Apr 27, 2026
63a4d66
refactor: align with tokenRequest values
stefan-ctrl Apr 28, 2026
848caa5
Merge branch 'main' into client-authentication-method-roverctl-only
stefan-ctrl Apr 28, 2026
bd3e69a
Merge branch 'main' into client-authentication-method-roverctl-only
stefan-ctrl Apr 28, 2026
59b6a0a
chore: make generate & manifests
stefan-ctrl Apr 28, 2026
8de6c66
refactor: move to fuzzbuzz converter
stefan-ctrl Apr 28, 2026
10ae067
refactor: align tokenRequests values for RFC 7591
stefan-ctrl Apr 28, 2026
fe7df33
refactor: replace hardcoded tokenRequest values with constants from r…
stefan-ctrl Apr 28, 2026
4e08c17
Merge branch 'main' into client-authentication-method-roverctl-only
stefan-ctrl Apr 28, 2026
5e931dd
feat(tokenRequest): add initial tokenRequest values for RFC 7591 clie…
stefan-ctrl Apr 28, 2026
e56556a
Merge remote-tracking branch 'origin/main' into client-authentication…
stefan-ctrl May 5, 2026
4484ab4
feat(auth): normalize clientAuthMethod values in PatchAuthentication …
stefan-ctrl May 5, 2026
ec4f672
Merge remote-tracking branch 'origin/main' into client-authentication…
stefan-ctrl May 13, 2026
99d318d
chore: solve some local issues
stefan-ctrl May 13, 2026
b2bd0ff
Merge branch 'main' into client-authentication-method-roverctl-only
stefan-ctrl May 13, 2026
9627521
Merge branch 'main' into client-authentication-method-roverctl-only
stefan-ctrl May 13, 2026
5dcb703
refactor: introduce enum
stefan-ctrl May 13, 2026
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
50 changes: 50 additions & 0 deletions rover-ctl/pkg/handlers/v0/rover.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/json"
"maps"
"net/http"
"strings"

"github.com/pkg/errors"
"github.com/telekom/controlplane/rover-ctl/pkg/handlers/common"
Expand Down Expand Up @@ -67,10 +68,59 @@ func PatchRoverRequest(ctx context.Context, obj types.Object) error {
}
}

PatchAuthentication(spec)

obj.SetContent(spec)
return nil
}

// clientAuthMethodMapping maps rover-ctl YAML values to rover-server API enum values.
var clientAuthMethodMapping = map[string]string{
"basic": "BASIC",
"body": "POST", // Configures client authentication method, according to RFC 6749
}

// PatchAuthentication maps spec.authentication.m2m.clientAuthMethod from the rover-ctl
// YAML format (basic/body) to the rover-server API format (BASIC/POST) and restructures
Comment thread
stefan-ctrl marked this conversation as resolved.
Outdated
// it into spec.authentication.clientAuthMethod.
func PatchAuthentication(spec map[string]any) {
auth, exists := spec["authentication"]
if !exists {
return
}
authMap, ok := auth.(map[string]any)
if !ok {
return
}

m2m, exists := authMap["m2m"]
if !exists {
return
}
m2mMap, ok := m2m.(map[string]any)
if !ok {
return
}

clientAuthMethod, exists := m2mMap["clientAuthMethod"]
if !exists {
return
}
clientAuthMethodStr, ok := clientAuthMethod.(string)
if !ok {
return
}

apiValue, valid := clientAuthMethodMapping[strings.ToLower(clientAuthMethodStr)]
if !valid {
return
}

spec["authentication"] = map[string]any{
"clientAuthMethod": apiValue,
}
}

func PatchExposures(exposures []any) []map[string]any {
if len(exposures) == 0 {
return nil
Expand Down
116 changes: 116 additions & 0 deletions rover-ctl/pkg/handlers/v0/rover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,122 @@ var _ = Describe("Rover Handler", func() {
})
})

Describe("PatchAuthentication", func() {
It("should map 'basic' to 'BASIC' in authentication.clientAuthMethod", func() {
obj := &types.UnstructuredObject{
Content: map[string]any{
"spec": map[string]any{
"authentication": map[string]any{
"m2m": map[string]any{
"clientAuthMethod": "basic",
},
},
},
},
}

err := v0.PatchRoverRequest(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())

content := obj.GetContent()
auth, ok := content["authentication"].(map[string]any)
Expect(ok).To(BeTrue())
Expect(auth["clientAuthMethod"]).To(Equal("BASIC"))
})

It("should map 'body' to 'POST' in authentication.clientAuthMethod", func() {
obj := &types.UnstructuredObject{
Content: map[string]any{
"spec": map[string]any{
"authentication": map[string]any{
"m2m": map[string]any{
"clientAuthMethod": "body",
},
},
},
},
}

err := v0.PatchRoverRequest(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())

content := obj.GetContent()
auth, ok := content["authentication"].(map[string]any)
Expect(ok).To(BeTrue())
Expect(auth["clientAuthMethod"]).To(Equal("POST"))
})

Comment thread
stefan-ctrl marked this conversation as resolved.
It("should not add authentication when it is missing", func() {
obj := &types.UnstructuredObject{
Content: map[string]any{
"spec": map[string]any{},
},
}

err := v0.PatchRoverRequest(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())

content := obj.GetContent()
Expect(content).NotTo(HaveKey("authentication"))
})

It("should leave authentication untouched when clientAuthMethod has invalid value", func() {
obj := &types.UnstructuredObject{
Content: map[string]any{
"spec": map[string]any{
"authentication": map[string]any{
"m2m": map[string]any{
"clientAuthMethod": "invalid",
},
},
},
},
}

err := v0.PatchRoverRequest(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())

content := obj.GetContent()
Expect(content).To(HaveKey("authentication"))
})

It("should leave authentication untouched when format is not a map", func() {
obj := &types.UnstructuredObject{
Content: map[string]any{
"spec": map[string]any{
"authentication": "not a map",
},
},
}

err := v0.PatchRoverRequest(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())

content := obj.GetContent()
Expect(content).To(HaveKey("authentication"))
})

It("should leave authentication untouched when already in rover-server format", func() {
obj := &types.UnstructuredObject{
Content: map[string]any{
"spec": map[string]any{
"authentication": map[string]any{
"clientAuthMethod": "BASIC",
},
},
},
}

err := v0.PatchRoverRequest(context.Background(), obj)
Expect(err).NotTo(HaveOccurred())

content := obj.GetContent()
auth, ok := content["authentication"].(map[string]any)
Expect(ok).To(BeTrue())
Expect(auth["clientAuthMethod"]).To(Equal("BASIC"))
})
})

Describe("ResetSecret", func() {
It("should send a reset secret request and return new credentials", func() {
// Configure mock to return successful response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1035,3 +1035,79 @@
"team": "hyperion"
}
---

[Rover Controller Update rover resource should accept clientAuthMethod BASIC as produced by rover-ctl - 1]
{
"exposures": [
{
"approval": "SIMPLE",
"basePath": "/eni/distr/v1",
"type": "api",
"upstream": "https://httpbin.org/anything",
"visibility": "WORLD"
}
],
"id": "eni--hyperion--rover-local-sub",
"name": "rover-local-sub",
"status": {
"errors": [
{
"cause": "NoApproval",
"message": "Approval is either rejected or suspended"
}
],
"processingState": "done",
"state": "blocked",
"warnings": [
{
"message": "Atleast one sub-resource is being processed"
}
]
},
"subscriptions": [
{
"basePath": "/eni/distr/v1",
"type": "api"
}
],
"zone": "dataplane1"
}
---

[Rover Controller Update rover resource should accept clientAuthMethod POST as produced by rover-ctl - 1]
{
"exposures": [
{
"approval": "SIMPLE",
"basePath": "/eni/distr/v1",
"type": "api",
"upstream": "https://httpbin.org/anything",
"visibility": "WORLD"
}
],
"id": "eni--hyperion--rover-local-sub",
"name": "rover-local-sub",
"status": {
"errors": [
{
"cause": "NoApproval",
"message": "Approval is either rejected or suspended"
}
],
"processingState": "done",
"state": "blocked",
"warnings": [
{
"message": "Atleast one sub-resource is being processed"
}
]
},
"subscriptions": [
{
"basePath": "/eni/distr/v1",
"type": "api"
}
],
"zone": "dataplane1"
}
---
26 changes: 26 additions & 0 deletions rover-server/internal/controller/rover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,32 @@ var _ = Describe("Rover Controller", func() {
responseGroup, err := ExecuteRequest(req, groupToken)
ExpectStatusWithBody(responseGroup, err, http.StatusForbidden, "application/problem+json")
})

It("should accept clientAuthMethod BASIC as produced by rover-ctl", func() {
body := api.RoverUpdateRequest{
Zone: "dataplane1",
Authentication: api.Authentication{
ClientAuthMethod: api.BASIC,
},
}
jsonBody, _ := json.Marshal(body)
req := httptest.NewRequest(http.MethodPut, "/rovers/eni--hyperion--rover-local-sub", bytes.NewReader(jsonBody))
responseGroup, err := ExecuteRequest(req, groupToken)
ExpectStatusWithBody(responseGroup, err, http.StatusAccepted, "application/json")
})

It("should accept clientAuthMethod POST as produced by rover-ctl", func() {
body := api.RoverUpdateRequest{
Zone: "dataplane1",
Authentication: api.Authentication{
ClientAuthMethod: api.POST,
},
}
jsonBody, _ := json.Marshal(body)
req := httptest.NewRequest(http.MethodPut, "/rovers/eni--hyperion--rover-local-sub", bytes.NewReader(jsonBody))
responseGroup, err := ExecuteRequest(req, groupToken)
ExpectStatusWithBody(responseGroup, err, http.StatusAccepted, "application/json")
})
})

Context("Reset rover secret", func() {
Expand Down
Loading