Skip to content

Commit f64f634

Browse files
authored
feat: add DomainCA lookup option to PatchOIDC and Kcpsetup subroutines (#61)
* feat: add DomainCA lookup option to PatchOIDC and Kcpsetup subroutines * chore: fix tests * chore: fix tests
1 parent b6cec77 commit f64f634

File tree

7 files changed

+94
-37
lines changed

7 files changed

+94
-37
lines changed

internal/config/config.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ type OperatorConfig struct {
2525
Enabled bool `mapstructure:"subroutines-provider-secret-enabled" default:"true"`
2626
} `mapstructure:",squash"`
2727
PatchOIDC struct {
28-
ConfigMapName string `mapstructure:"subroutines-patch-oidc-configmap-name" default:"oidc-authentication-config"`
29-
Namespace string `mapstructure:"subroutines-patch-oidc-namespace" default:"platform-mesh-system"`
30-
BaseDomain string `mapstructure:"subroutines-patch-oidc-basedomain" default:"portal.dev.local:8443"`
28+
ConfigMapName string `mapstructure:"subroutines-patch-oidc-configmap-name" default:"oidc-authentication-config"`
29+
Namespace string `mapstructure:"subroutines-patch-oidc-namespace" default:"platform-mesh-system"`
30+
BaseDomain string `mapstructure:"subroutines-patch-oidc-basedomain" default:"portal.dev.local:8443"`
31+
DomainCALookup bool `mapstructure:"subroutines-patch-oidc-domain-ca-lookup" default:"false"`
3132
} `mapstructure:",squash"`
3233
} `mapstructure:",squash"`
3334
}

internal/controller/platformmesh_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func NewPlatformMeshReconciler(log *logger.Logger, mgr ctrl.Manager, cfg *config
8383
subs = append(subs, subroutines.NewDeploymentSubroutine(mgr.GetClient(), commonCfg, cfg))
8484
}
8585
if cfg.Subroutines.KcpSetup.Enabled {
86-
subs = append(subs, subroutines.NewKcpsetupSubroutine(mgr.GetClient(), &subroutines.Helper{}, dir+"/manifests/kcp", kcpUrl))
86+
subs = append(subs, subroutines.NewKcpsetupSubroutine(mgr.GetClient(), &subroutines.Helper{}, cfg, dir+"/manifests/kcp", kcpUrl))
8787
}
8888
if cfg.Subroutines.ProviderSecret.Enabled {
8989
subs = append(subs, subroutines.NewProviderSecretSubroutine(mgr.GetClient(), &subroutines.Helper{}, subroutines.DefaultHelmGetter{}, kcpUrl))

internal/controller/realm_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func NewRealmReconciler(mgr ctrl.Manager, log *logger.Logger, cfg *config.Operat
3232
cfg.Subroutines.PatchOIDC.ConfigMapName,
3333
cfg.Subroutines.PatchOIDC.Namespace,
3434
cfg.Subroutines.PatchOIDC.BaseDomain,
35+
cfg.Subroutines.PatchOIDC.DomainCALookup,
3536
),
3637
},
3738
"platform-mesh-operator",

manifests/kcp/workspace-authentication-configuration.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ metadata:
55
spec:
66
jwt:
77
- issuer:
8-
url: https://{{ .baseDomain }}/keycloak/realms/welcome
8+
url: https://{{ .baseDomainWithPort }}/keycloak/realms/welcome
99
audiences:
1010
- welcome
1111
audienceMatchPolicy: MatchAny
12+
{{- with .domainCA }}
13+
certificateAuthority: {{ . }}
14+
{{- end }}
1215
claimMappings:
1316
groups:
1417
claim: groups

pkg/subroutines/kcpsetup.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,23 @@ type KcpsetupSubroutine struct {
4646
kcpDirectory string
4747
// Cache for CA bundles to avoid redundant secret lookups
4848
caBundleCache map[string]string
49+
cfg *config.OperatorConfig
4950
}
5051

5152
const (
5253
KcpsetupSubroutineName = "KcpsetupSubroutine"
5354
KcpsetupSubroutineFinalizer = "platform-mesh.core.platform-mesh.io/finalizer"
5455
)
5556

56-
func NewKcpsetupSubroutine(client client.Client, helper KcpHelper, kcpdir string, kcpUrl string) *KcpsetupSubroutine {
57+
func NewKcpsetupSubroutine(client client.Client, helper KcpHelper, cfg *config.OperatorConfig, kcpdir string, kcpUrl string) *KcpsetupSubroutine {
5758
return &KcpsetupSubroutine{
5859
client: client,
5960
kcpDirectory: kcpdir,
6061
kcpUrl: kcpUrl,
6162
kcpHelper: helper,
6263
helm: DefaultHelmGetter{},
6364
caBundleCache: make(map[string]string),
65+
cfg: cfg,
6466
}
6567
}
6668

@@ -71,9 +73,6 @@ func (r *KcpsetupSubroutine) GetName() string {
7173
func (r *KcpsetupSubroutine) Finalize(
7274
ctx context.Context, runtimeObj runtimeobject.RuntimeObject,
7375
) (ctrl.Result, errors.OperatorError) {
74-
instance := runtimeObj.(*corev1alpha1.PlatformMesh)
75-
_ = instance
76-
7776
return ctrl.Result{}, nil // TODO: Implement
7877
}
7978

@@ -244,6 +243,12 @@ func (r *KcpsetupSubroutine) createKcpResources(ctx context.Context, config *res
244243
templateData["port"] = fmt.Sprintf("%d", inst.Spec.Exposure.Port)
245244
}
246245

246+
if templateData["port"] != "443" {
247+
templateData["baseDomainWithPort"] = fmt.Sprintf("%s:%s", templateData["baseDomain"], templateData["port"])
248+
} else {
249+
templateData["baseDomainWithPort"] = templateData["baseDomain"]
250+
}
251+
247252
err = r.applyDirStructure(ctx, dir, "root", config, templateData, inst)
248253
if err != nil {
249254
log.Err(err).Msg("Failed to apply dir structure")
@@ -296,6 +301,22 @@ func (r *KcpsetupSubroutine) getCABundleInventory(
296301
validatingB64Data := base64.StdEncoding.EncodeToString(validatingCaData)
297302
caBundles[validatingKey] = validatingB64Data
298303

304+
if r.cfg.Subroutines.PatchOIDC.DomainCALookup {
305+
domainCA, err := r.getCaBundle(ctx, &corev1alpha1.WebhookConfiguration{
306+
SecretData: "tls.crt",
307+
SecretRef: corev1alpha1.SecretReference{
308+
Name: "domain-certificate-ca",
309+
Namespace: "platform-mesh-system",
310+
},
311+
})
312+
if err != nil {
313+
log.Error().Err(err).Msg("Failed to get Domain CA bundle")
314+
return nil, errors.Wrap(err, "Failed to get Domain CA bundle")
315+
}
316+
317+
caBundles["domainCA"] = base64.StdEncoding.EncodeToString(domainCA)
318+
}
319+
299320
// Cache the results
300321
r.caBundleCache = caBundles
301322

pkg/subroutines/kcpsetup_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (s *KcpsetupTestSuite) Test_getCABundleInventory() {
149149

150150
// Test case 2: Secret not found
151151
// Create a new instance to clear the cache
152-
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, s.helperMock, ManifestStructureTest, "")
152+
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, s.helperMock, &config.OperatorConfig{}, ManifestStructureTest, "")
153153

154154
// Mock the mutating webhook secret lookup to return error
155155
s.clientMock.EXPECT().
@@ -224,7 +224,7 @@ func (s *KcpsetupTestSuite) SetupTest() {
224224
s.clientMock = new(mocks.Client)
225225
s.helperMock = new(mocks.KcpHelper)
226226
s.log, _ = logger.New(logger.DefaultConfig())
227-
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, s.helperMock, ManifestStructureTest, "https://kcp.example.com")
227+
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, s.helperMock, &config.OperatorConfig{}, ManifestStructureTest, "https://kcp.example.com")
228228
}
229229

230230
func (s *KcpsetupTestSuite) TearDownTest() {
@@ -430,15 +430,15 @@ func (s *KcpsetupTestSuite) TestProcess() {
430430
s.Assert().Equal(ctrl.Result{}, result)
431431

432432
// Test error case - create a new instance to clear the cache
433-
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, s.helperMock, ManifestStructureTest, "https://kcp.example.com")
433+
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, s.helperMock, &config.OperatorConfig{}, ManifestStructureTest, "https://kcp.example.com")
434434
}
435435

436436
func (s *KcpsetupTestSuite) Test_getAPIExportHashInventory() {
437437
// mocks
438438
mockKcpClient := new(mocks.Client)
439439
mockedKcpHelper := new(mocks.KcpHelper)
440440
mockedKcpHelper.EXPECT().NewKcpClient(mock.Anything, mock.Anything).Return(mockKcpClient, nil).Times(3)
441-
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, mockedKcpHelper, ManifestStructureTest, "")
441+
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, mockedKcpHelper, &config.OperatorConfig{}, ManifestStructureTest, "")
442442

443443
apiexport := &kcpapiv1alpha.APIExport{
444444
Status: kcpapiv1alpha.APIExportStatus{
@@ -515,7 +515,7 @@ func (s *KcpsetupTestSuite) Test_Constructor() {
515515
helper := &subroutines.Helper{}
516516

517517
// create new test object
518-
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, helper, ManifestStructureTest, "")
518+
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, helper, &config.OperatorConfig{}, ManifestStructureTest, "")
519519
}
520520

521521
func (s *KcpsetupTestSuite) TestFinalizers() {
@@ -581,7 +581,7 @@ func (s *KcpsetupTestSuite) TestCreateWorkspaces() {
581581
// test err1 - expect error when NewKcpClient fails
582582
mockedKcpHelper := new(mocks.KcpHelper)
583583
mockedKcpHelper.EXPECT().NewKcpClient(mock.Anything, mock.Anything).Return(nil, errors.New("failed to create client"))
584-
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, mockedKcpHelper, ManifestStructureTest, "")
584+
s.testObj = subroutines.NewKcpsetupSubroutine(s.clientMock, mockedKcpHelper, &config.OperatorConfig{}, ManifestStructureTest, "")
585585

586586
err := s.testObj.CreateKcpResources(context.Background(), &rest.Config{}, ManifestStructureTest, &corev1alpha1.PlatformMesh{})
587587
s.Assert().Error(err)
@@ -592,7 +592,7 @@ func (s *KcpsetupTestSuite) TestCreateWorkspaces() {
592592
mockKcpClient := new(mocks.Client)
593593
mockedKcpHelper = new(mocks.KcpHelper)
594594
mockedKcpHelper.EXPECT().NewKcpClient(mock.Anything, mock.Anything).Return(mockKcpClient, nil)
595-
s.testObj = subroutines.NewKcpsetupSubroutine(mockedK8sClient, mockedKcpHelper, ManifestStructureTest, "")
595+
s.testObj = subroutines.NewKcpsetupSubroutine(mockedK8sClient, mockedKcpHelper, &config.OperatorConfig{}, ManifestStructureTest, "")
596596

597597
// Mock both webhook secret lookups for CA bundle inventory
598598
webhookConfig := subroutines.DEFAULT_WEBHOOK_CONFIGURATION
@@ -671,7 +671,7 @@ func (s *KcpsetupTestSuite) TestCreateWorkspaces() {
671671
mockKcpClient = new(mocks.Client)
672672
mockedKcpHelper = new(mocks.KcpHelper)
673673
mockedKcpHelper.EXPECT().NewKcpClient(mock.Anything, mock.Anything).Return(mockKcpClient, nil)
674-
s.testObj = subroutines.NewKcpsetupSubroutine(mockedK8sClient, mockedKcpHelper, ManifestStructureTest, "")
674+
s.testObj = subroutines.NewKcpsetupSubroutine(mockedK8sClient, mockedKcpHelper, &config.OperatorConfig{}, ManifestStructureTest, "")
675675

676676
// Mock both secret lookups again (they should be cached from previous call)
677677
// Since we're creating a new instance, the cache is cleared, so we need to mock again

pkg/subroutines/patch_oidc.go

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,31 @@ import (
88
"github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject"
99
"github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine"
1010
"github.com/platform-mesh/golang-commons/errors"
11-
"gopkg.in/yaml.v3"
1211
ctrl "sigs.k8s.io/controller-runtime"
1312
"sigs.k8s.io/controller-runtime/pkg/client"
1413
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
14+
"sigs.k8s.io/yaml"
1515

1616
corev1 "k8s.io/api/core/v1"
17-
"k8s.io/apiserver/pkg/apis/apiserver"
17+
apiserverv1beta1 "k8s.io/apiserver/pkg/apis/apiserver/v1beta1"
1818
"k8s.io/utils/ptr"
1919
)
2020

2121
type PatchOIDCSubroutine struct {
22-
cl client.Client
23-
baseDomain string
24-
configMapName string
25-
namespace string
22+
cl client.Client
23+
baseDomain string
24+
configMapName string
25+
namespace string
26+
domainCALookup bool
2627
}
2728

28-
func NewPatchOIDCSubroutine(cl client.Client, configMapName, namespace, baseDomain string) *PatchOIDCSubroutine {
29+
func NewPatchOIDCSubroutine(cl client.Client, configMapName, namespace, baseDomain string, domainCALookup bool) *PatchOIDCSubroutine {
2930
return &PatchOIDCSubroutine{
30-
cl: cl,
31-
baseDomain: baseDomain,
32-
configMapName: configMapName,
33-
namespace: namespace,
31+
cl: cl,
32+
baseDomain: baseDomain,
33+
configMapName: configMapName,
34+
namespace: namespace,
35+
domainCALookup: domainCALookup,
3436
}
3537
}
3638

@@ -49,13 +51,13 @@ func (p *PatchOIDCSubroutine) Finalize(ctx context.Context, instance runtimeobje
4951
oidcCM.Data = map[string]string{}
5052
}
5153

52-
var structuredAuth apiserver.AuthenticationConfiguration
54+
var structuredAuth apiserverv1beta1.AuthenticationConfiguration
5355
err := yaml.Unmarshal([]byte(configYaml), &structuredAuth)
5456
if err != nil {
5557
return err
5658
}
5759

58-
structuredAuth.JWT = slices.DeleteFunc(structuredAuth.JWT, func(j apiserver.JWTAuthenticator) bool {
60+
structuredAuth.JWT = slices.DeleteFunc(structuredAuth.JWT, func(j apiserverv1beta1.JWTAuthenticator) bool {
5961
return j.Issuer.URL == fmt.Sprintf("https://%s/keycloak/realms/%s", p.baseDomain, name)
6062
})
6163

@@ -91,37 +93,66 @@ func (p *PatchOIDCSubroutine) Process(ctx context.Context, instance runtimeobjec
9193
oidcCM.SetName(p.configMapName)
9294
oidcCM.SetNamespace(p.namespace)
9395

96+
var domainCA string
97+
if p.domainCALookup {
98+
var domainCASecret corev1.Secret
99+
err := p.cl.Get(ctx, client.ObjectKey{Name: "domain-certificate-ca", Namespace: p.namespace}, &domainCASecret)
100+
if err != nil {
101+
return ctrl.Result{}, errors.NewOperatorError(err, true, true)
102+
}
103+
104+
domainCA = string(domainCASecret.Data["tls.crt"])
105+
}
106+
94107
_, err := controllerutil.CreateOrPatch(ctx, p.cl, &oidcCM, func() error {
95108

96109
configYaml, ok := oidcCM.Data["config.yaml"]
97110
if !ok {
98111
oidcCM.Data = map[string]string{}
99112
}
100113

101-
var structuredAuth apiserver.AuthenticationConfiguration
114+
var structuredAuth apiserverv1beta1.AuthenticationConfiguration
102115
err := yaml.Unmarshal([]byte(configYaml), &structuredAuth)
103116
if err != nil {
104117
return err
105118
}
106119

107-
structuredAuth.JWT = append(structuredAuth.JWT, apiserver.JWTAuthenticator{
108-
Issuer: apiserver.Issuer{
120+
oidcConfig := apiserverv1beta1.JWTAuthenticator{
121+
Issuer: apiserverv1beta1.Issuer{
109122
URL: fmt.Sprintf("https://%s/keycloak/realms/%s", p.baseDomain, name),
110123
Audiences: []string{name},
111-
AudienceMatchPolicy: apiserver.AudienceMatchPolicyMatchAny,
124+
AudienceMatchPolicy: apiserverv1beta1.AudienceMatchPolicyMatchAny,
112125
},
113-
ClaimMappings: apiserver.ClaimMappings{
114-
Username: apiserver.PrefixedClaimOrExpression{
126+
ClaimMappings: apiserverv1beta1.ClaimMappings{
127+
Username: apiserverv1beta1.PrefixedClaimOrExpression{
115128
Claim: "email",
116129
Prefix: ptr.To(""),
117130
},
118-
Groups: apiserver.PrefixedClaimOrExpression{
131+
Groups: apiserverv1beta1.PrefixedClaimOrExpression{
119132
Claim: "groups",
120133
Prefix: ptr.To(""),
121134
},
122135
},
136+
}
137+
138+
if p.domainCALookup {
139+
oidcConfig.Issuer.CertificateAuthority = domainCA
140+
}
141+
142+
idx := slices.IndexFunc(structuredAuth.JWT, func(j apiserverv1beta1.JWTAuthenticator) bool {
143+
return j.Issuer.URL == fmt.Sprintf("https://%s/keycloak/realms/%s", p.baseDomain, name)
123144
})
124145

146+
if idx != -1 {
147+
structuredAuth.JWT[idx] = oidcConfig
148+
} else {
149+
structuredAuth.JWT = append(structuredAuth.JWT, oidcConfig)
150+
}
151+
152+
if structuredAuth.GroupVersionKind().Empty() {
153+
structuredAuth.SetGroupVersionKind(apiserverv1beta1.ConfigSchemeGroupVersion.WithKind("AuthenticationConfiguration"))
154+
}
155+
125156
rawYaml, err := yaml.Marshal(&structuredAuth)
126157
if err != nil {
127158
return err

0 commit comments

Comments
 (0)