Skip to content

Commit 548701b

Browse files
Experimental UAA support
* New experimental helm values to configure UAA * The configured uaa url will be returned by the `/` endpoint * When UAA support is enabled `cf_on_k8s` in the `/` endpoint is false * This will make `cf login` talks to the configured UAA instead of reading the kubeconfig * Note that the cluster should be configured to trust the UAA via [oidc](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuring-the-api-server) fixes #2243 Co-authored-by: Georgi Sabev <[email protected]>
1 parent 0044437 commit 548701b

File tree

13 files changed

+209
-32
lines changed

13 files changed

+209
-32
lines changed

README.helm.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,11 @@ Here are all the values that can be set for the chart:
7878
- `eksContainerRegistryRoleARN` (_String_): Amazon Resource Name (ARN) of the IAM role to use to access the ECR registry from an EKS deployed Korifi. Required if containerRegistrySecret not set.
7979
- `experimental`: Experimental features. No guarantees are provided and breaking/backwards incompatible changes should be expected. These features are not recommended for use in production environments.
8080
- `managedServices`:
81-
- `include` (_Boolean_): Enable managed services support
81+
- `enabled` (_Boolean_): Enable managed services support
8282
- `trustInsecureBrokers` (_Boolean_): Disable service broker certificate validation. Not recommended to be set to 'true' in production environments
83+
- `uaa`:
84+
- `enabled` (_Boolean_): Enable UAA support
85+
- `url` (_String_): The url of a UAA instance
8386
- `generateIngressCertificates` (_Boolean_): Use `cert-manager` to generate self-signed certificates for the API and app endpoints.
8487
- `helm`:
8588
- `hooksImage` (_String_): Image for the helm hooks containing kubectl

api/config/config.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,21 @@ type (
4848
AuthProxyCACert string `yaml:"authProxyCACert"`
4949
LogLevel zapcore.Level `yaml:"logLevel"`
5050

51-
ExperimentalManagedServicesEnabled bool `yaml:"experimentalManagedServicesEnabled"`
51+
Experimental Experimental `yaml:"experimental"`
52+
}
53+
54+
Experimental struct {
55+
ManagedServices ManagedServices `yaml:"managedServices"`
56+
UAA UAA `yaml:"uaa"`
57+
}
58+
59+
ManagedServices struct {
60+
Enabled bool `yaml:"enabled"`
61+
}
62+
63+
UAA struct {
64+
Enabled bool `yaml:"enabled"`
65+
URL string `yaml:"url"`
5266
}
5367

5468
RoleLevel string

api/config/config_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ var _ = Describe("Config", func() {
4646
Stack: "lc-stack",
4747
StagingMemoryMB: 10,
4848
},
49-
"experimentalManagedServicesEnabled": true,
49+
"experimental": map[string]any{
50+
"managedServices": map[string]any{
51+
"enabled": true,
52+
},
53+
},
5054
}
5155
})
5256

@@ -89,7 +93,7 @@ var _ = Describe("Config", func() {
8993
StagingMemoryMB: 10,
9094
}))
9195
Expect(cfg.ContainerRegistryType).To(BeEmpty())
92-
Expect(cfg.ExperimentalManagedServicesEnabled).To(BeTrue())
96+
Expect(cfg.Experimental.ManagedServices.Enabled).To(BeTrue())
9397
})
9498

9599
When("the FQDN is not specified", func() {

api/handlers/root.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/http"
55
"net/url"
66

7+
"code.cloudfoundry.org/korifi/api/config"
78
"code.cloudfoundry.org/korifi/api/presenter"
89
"code.cloudfoundry.org/korifi/api/routing"
910
)
@@ -13,17 +14,19 @@ const (
1314
)
1415

1516
type Root struct {
16-
baseURL url.URL
17+
baseURL url.URL
18+
uaaConfig config.UAA
1719
}
1820

19-
func NewRoot(baseURL url.URL) *Root {
21+
func NewRoot(baseURL url.URL, uaaConfig config.UAA) *Root {
2022
return &Root{
21-
baseURL: baseURL,
23+
baseURL: baseURL,
24+
uaaConfig: uaaConfig,
2225
}
2326
}
2427

2528
func (h *Root) get(r *http.Request) (*routing.Response, error) {
26-
return routing.NewResponse(http.StatusOK).WithBody(presenter.ForRoot(h.baseURL)), nil
29+
return routing.NewResponse(http.StatusOK).WithBody(presenter.ForRoot(h.baseURL, h.uaaConfig)), nil
2730
}
2831

2932
func (h *Root) UnauthenticatedRoutes() []routing.Route {

api/handlers/root_test.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package handlers_test
33
import (
44
"net/http"
55

6+
"code.cloudfoundry.org/korifi/api/config"
67
"code.cloudfoundry.org/korifi/api/handlers"
78
. "code.cloudfoundry.org/korifi/tests/matchers"
89

@@ -11,14 +12,17 @@ import (
1112
)
1213

1314
var _ = Describe("Root", func() {
14-
var req *http.Request
15+
var (
16+
apiHandler *handlers.Root
17+
req *http.Request
18+
)
1519

1620
BeforeEach(func() {
17-
apiHandler := handlers.NewRoot(*serverURL)
18-
routerBuilder.LoadRoutes(apiHandler)
21+
apiHandler = handlers.NewRoot(*serverURL, config.UAA{})
1922
})
2023

2124
JustBeforeEach(func() {
25+
routerBuilder.LoadRoutes(apiHandler)
2226
routerBuilder.Build().ServeHTTP(rr, req)
2327
})
2428

@@ -34,9 +38,29 @@ var _ = Describe("Root", func() {
3438
Expect(rr).To(HaveHTTPHeaderWithValue("Content-Type", "application/json"))
3539

3640
Expect(rr).To(HaveHTTPBody(SatisfyAll(
41+
MatchJSONPath("$.cf_on_k8s", true),
3742
MatchJSONPath("$.links.self.href", "https://api.example.org"),
3843
MatchJSONPath("$.links.cloud_controller_v3.href", "https://api.example.org/v3"),
3944
)))
4045
})
46+
47+
When("UAA support is enabled", func() {
48+
BeforeEach(func() {
49+
apiHandler = handlers.NewRoot(*serverURL, config.UAA{
50+
Enabled: true,
51+
URL: "https://my.uaa",
52+
})
53+
})
54+
55+
It("returns the uaa config", func() {
56+
Expect(rr).To(HaveHTTPStatus(http.StatusOK))
57+
58+
Expect(rr).To(HaveHTTPBody(SatisfyAll(
59+
MatchJSONPath("$.cf_on_k8s", false),
60+
MatchJSONPath("$.links.uaa.href", "https://my.uaa"),
61+
MatchJSONPath("$.links.login.href", "https://my.uaa"),
62+
)))
63+
})
64+
})
4165
})
4266
})

api/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ func main() {
266266
chiMiddlewares.StripSlashes,
267267
)
268268

269-
if !cfg.ExperimentalManagedServicesEnabled {
269+
if !cfg.Experimental.ManagedServices.Enabled {
270270
routerBuilder.UseMiddleware(middleware.DisableManagedServices)
271271
}
272272

@@ -292,7 +292,7 @@ func main() {
292292

293293
apiHandlers := []routing.Routable{
294294
handlers.NewRootV3(*serverURL),
295-
handlers.NewRoot(*serverURL),
295+
handlers.NewRoot(*serverURL, cfg.Experimental.UAA),
296296
handlers.NewInfoV3(
297297
*serverURL,
298298
cfg.InfoConfig,

api/payloads/role.go

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,33 +50,42 @@ func (p RoleCreate) Validate() error {
5050
}
5151

5252
func (p RoleCreate) ToMessage() repositories.CreateRoleMessage {
53-
record := repositories.CreateRoleMessage{
53+
message := repositories.CreateRoleMessage{
5454
Type: p.Type,
5555
}
5656

5757
if p.Relationships.Space != nil {
58-
record.Space = p.Relationships.Space.Data.GUID
58+
message.Space = p.Relationships.Space.Data.GUID
5959
}
6060

6161
if p.Relationships.Organization != nil {
62-
record.Org = p.Relationships.Organization.Data.GUID
62+
message.Org = p.Relationships.Organization.Data.GUID
63+
}
64+
65+
message.Kind = rbacv1.UserKind
66+
message.User = p.Relationships.User.Data.Username
67+
68+
// For UAA Authenticated users, prefix the Origin as our Cluster uses the Orgin:User for
69+
// Authentication verification (via OIDC prefixs)
70+
// --kube-apiserver-arg oidc-username-prefix="<origin>:"
71+
// --kube-apiserver-arg oidc-groups-prefix="<origin>:"
72+
if p.Relationships.User.Data.Origin != "" {
73+
message.User = p.Relationships.User.Data.Origin + ":" + message.User
6374
}
6475

65-
record.Kind = rbacv1.UserKind
66-
record.User = p.Relationships.User.Data.Username
6776
if p.Relationships.User.Data.GUID != "" {
68-
record.User = p.Relationships.User.Data.GUID
77+
message.User = p.Relationships.User.Data.GUID
6978
}
7079

71-
if authorization.HasServiceAccountPrefix(record.User) {
72-
namespace, user := authorization.ServiceAccountNSAndName(record.User)
80+
if authorization.HasServiceAccountPrefix(message.User) {
81+
namespace, user := authorization.ServiceAccountNSAndName(message.User)
7382

74-
record.Kind = rbacv1.ServiceAccountKind
75-
record.User = user
76-
record.ServiceAccountNamespace = namespace
83+
message.Kind = rbacv1.ServiceAccountKind
84+
message.User = user
85+
message.ServiceAccountNamespace = namespace
7786
}
7887

79-
return record
88+
return message
8089
}
8190

8291
type RoleRelationships struct {
@@ -118,6 +127,7 @@ type UserRelationship struct {
118127
type UserRelationshipData struct {
119128
Username string `json:"username"`
120129
GUID string `json:"guid"`
130+
Origin string `json:"origin"`
121131
}
122132

123133
type RoleList struct {

api/payloads/role_test.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,28 @@ var _ = Describe("RoleCreate", func() {
9292
})
9393

9494
Context("ToMessage()", func() {
95+
var msg repositories.CreateRoleMessage
96+
97+
JustBeforeEach(func() {
98+
msg = roleCreate.ToMessage()
99+
})
100+
95101
It("converts to repo message correctly", func() {
96-
msg := roleCreate.ToMessage()
97102
Expect(msg.Type).To(Equal("space_manager"))
98103
Expect(msg.Space).To(Equal("cf-space-guid"))
99104
Expect(msg.User).To(Equal("cf-service-account"))
100105
Expect(msg.Kind).To(Equal(rbacv1.UserKind))
101106
})
107+
108+
When("user origin is specified", func() {
109+
BeforeEach(func() {
110+
createPayload.Relationships.User.Data.Origin = "my-origin"
111+
})
112+
113+
It("uses the origin in the message user", func() {
114+
Expect(msg.User).To(Equal("my-origin:cf-service-account"))
115+
})
116+
})
102117
})
103118

104119
When("the service account name is provided", func() {

api/presenter/root.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package presenter
22

3-
import "net/url"
3+
import (
4+
"net/url"
5+
6+
"code.cloudfoundry.org/korifi/api/config"
7+
)
48

59
type APILink struct {
610
Link
@@ -18,8 +22,8 @@ type RootResponse struct {
1822

1923
const V3APIVersion = "3.117.0+cf-k8s"
2024

21-
func ForRoot(baseURL url.URL) RootResponse {
22-
return RootResponse{
25+
func ForRoot(baseURL url.URL, uaaConfig config.UAA) RootResponse {
26+
rootResponse := RootResponse{
2327
Links: map[string]*APILink{
2428
"self": {
2529
Link: Link{
@@ -57,6 +61,22 @@ func ForRoot(baseURL url.URL) RootResponse {
5761
},
5862
CFOnK8s: true,
5963
}
64+
65+
if uaaConfig.Enabled {
66+
rootResponse.CFOnK8s = false
67+
rootResponse.Links["uaa"] = &APILink{
68+
Link: Link{
69+
HRef: uaaConfig.URL,
70+
},
71+
}
72+
rootResponse.Links["login"] = &APILink{
73+
Link: Link{
74+
HRef: uaaConfig.URL,
75+
},
76+
}
77+
}
78+
79+
return rootResponse
6080
}
6181

6282
type RootV3Response struct {

api/presenter/root_test.go

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"net/url"
66

7+
"code.cloudfoundry.org/korifi/api/config"
78
"code.cloudfoundry.org/korifi/api/presenter"
89

910
. "github.com/onsi/ginkgo/v2"
@@ -23,8 +24,14 @@ var _ = Describe("Root endpoints", func() {
2324
})
2425

2526
Context("/", func() {
27+
var uaaConfig config.UAA
28+
29+
BeforeEach(func() {
30+
uaaConfig = config.UAA{}
31+
})
32+
2633
JustBeforeEach(func() {
27-
response := presenter.ForRoot(*baseURL)
34+
response := presenter.ForRoot(*baseURL, uaaConfig)
2835
var err error
2936
output, err = json.Marshal(response)
3037
Expect(err).NotTo(HaveOccurred())
@@ -71,6 +78,62 @@ var _ = Describe("Root endpoints", func() {
7178
"cf_on_k8s": true
7279
}`))
7380
})
81+
82+
When("UAA support is enabled", func() {
83+
BeforeEach(func() {
84+
uaaConfig = config.UAA{
85+
Enabled: true,
86+
URL: "https://my.uaa",
87+
}
88+
})
89+
90+
It("produces expected root json", func() {
91+
Expect(output).To(MatchJSON(`{
92+
"links": {
93+
"app_ssh": null,
94+
"bits_service": null,
95+
"cloud_controller_v2": null,
96+
"cloud_controller_v3": {
97+
"href": "https://api.example.org/v3",
98+
"meta": {
99+
"version": "3.117.0+cf-k8s"
100+
}
101+
},
102+
"credhub": null,
103+
"log_cache": {
104+
"href": "https://api.example.org",
105+
"meta": {
106+
"version": ""
107+
}
108+
},
109+
"log_stream": null,
110+
"logging": null,
111+
"login": {
112+
"href": "https://my.uaa",
113+
"meta": {
114+
"version": ""
115+
}
116+
},
117+
"network_policy_v0": null,
118+
"network_policy_v1": null,
119+
"routing": null,
120+
"self": {
121+
"href": "https://api.example.org",
122+
"meta": {
123+
"version": ""
124+
}
125+
},
126+
"uaa": {
127+
"href": "https://my.uaa",
128+
"meta": {
129+
"version": ""
130+
}
131+
}
132+
},
133+
"cf_on_k8s": false
134+
}`))
135+
})
136+
})
74137
})
75138

76139
Context("/v3", func() {

0 commit comments

Comments
 (0)