Skip to content
Open
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
21 changes: 7 additions & 14 deletions controller/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,25 +341,18 @@ func extractOIDCConfigs(reader client.Reader, namespace string) []login.OIDCConf
return nil
}

// Try new config format first
rawConfig, ok := configmap.Data["config"]
if ok {
var cfg config.Config
if err := yaml.UnmarshalStrict([]byte(rawConfig), &cfg); err == nil {
return jwtAuthenticatorsToOIDCConfigs(cfg.Authentication.JWT)
}
if !ok {
return nil
}

// Fall back to legacy authentication format
rawAuth, ok := configmap.Data["authentication"]
if ok {
var auth config.Authentication
if err := yaml.Unmarshal([]byte(rawAuth), &auth); err == nil {
return jwtAuthenticatorsToOIDCConfigs(auth.JWT)
}
var cfg config.Config
if err := yaml.UnmarshalStrict([]byte(rawConfig), &cfg); err != nil {
setupLog.Error(err, "unable to parse config for OIDC config extraction")
return nil
}

return nil
return jwtAuthenticatorsToOIDCConfigs(cfg.Authentication.JWT)
Comment thread
raballew marked this conversation as resolved.
Copy link
Copy Markdown
Member

@raballew raballew Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Now that oidc.LoadAuthenticationConfiguration has been removed, the AuthenticationConfiguration API type in controller/api/v1alpha1/authenticationconfiguration_types.go appears to be unreferenced by any Go code. It is still registered in the scheme via init() and has generated deep copy functions in zz_generated.deepcopy.go, making it dead code.

AI-generated, human reviewed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct observation -- with controller/internal/oidc/config.go deleted in this PR, the AuthenticationConfiguration type in controller/api/v1alpha1/authenticationconfiguration_types.go is no longer referenced by any Go code. However, removing it also requires regenerating zz_generated.deepcopy.go (via controller-gen) and updating the scheme registration, which expands the scope of this cleanup PR. I think this is better handled as a follow-up to keep the review surface contained. I will note it in the PR description and open a tracking issue.

}

// jwtAuthenticatorsToOIDCConfigs converts JWT authenticators to login OIDC configs
Expand Down
260 changes: 260 additions & 0 deletions controller/cmd/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
/*
Copyright 2024.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"testing"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestExtractOIDCConfigs(t *testing.T) {
scheme := runtime.NewScheme()
if err := corev1.AddToScheme(scheme); err != nil {
t.Fatalf("failed to add corev1 to scheme: %v", err)
}

tests := []struct {
name string
configmap *corev1.ConfigMap
wantCount int
wantNil bool
}{
{
name: "valid config key with JWT authenticators",
configmap: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "jumpstarter-controller",
Namespace: "default",
},
Data: map[string]string{
"config": `
authentication:
internal:
prefix: "internal:"
tokenLifetime: "43800h"
jwt:
- issuer:
url: "https://dex.example.com"
audiences:
- "jumpstarter"
claimMappings:
username:
claim: "email"
prefix: ""
`,
},
},
wantCount: 1,
},
{
name: "valid config key with multiple JWT authenticators including localhost",
configmap: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "jumpstarter-controller",
Namespace: "default",
},
Data: map[string]string{
"config": `
authentication:
internal:
prefix: "internal:"
tokenLifetime: "43800h"
jwt:
- issuer:
url: "https://localhost:8085"
audiences:
- "jumpstarter"
claimMappings:
username:
claim: "sub"
prefix: ""
- issuer:
url: "https://dex.example.com"
audiences:
- "jumpstarter"
claimMappings:
username:
claim: "email"
prefix: ""
`,
},
},
wantCount: 1, // localhost issuer should be skipped
},
{
name: "missing config key returns nil",
configmap: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "jumpstarter-controller",
Namespace: "default",
},
Data: map[string]string{
"router": `default: {endpoint: "router.example.com:443"}`,
},
},
wantNil: true,
},
{
name: "invalid YAML in config key returns nil",
configmap: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "jumpstarter-controller",
Namespace: "default",
},
Data: map[string]string{
"config": `{{{invalid yaml`,
},
},
wantNil: true,
},
{
name: "missing configmap returns nil",
configmap: nil,
wantNil: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
builder := fake.NewClientBuilder().WithScheme(scheme)
if tt.configmap != nil {
builder = builder.WithObjects(tt.configmap)
}
fakeClient := builder.Build()

configs := extractOIDCConfigs(fakeClient.(client.Reader), "default")

if tt.wantNil {
if configs != nil {
t.Errorf("expected nil, got %v", configs)
}
return
}

if configs == nil {
t.Fatal("expected non-nil configs, got nil")
}

if len(configs) != tt.wantCount {
t.Errorf("expected %d configs, got %d", tt.wantCount, len(configs))
}
})
}
}

func TestExtractOIDCConfigsContent(t *testing.T) {
scheme := runtime.NewScheme()
if err := corev1.AddToScheme(scheme); err != nil {
t.Fatalf("failed to add corev1 to scheme: %v", err)
}

configmap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "jumpstarter-controller",
Namespace: "default",
},
Data: map[string]string{
"config": `
authentication:
internal:
prefix: "internal:"
tokenLifetime: "43800h"
jwt:
- issuer:
url: "https://dex.example.com"
audiences:
- "jumpstarter"
- "jumpstarter-cli"
claimMappings:
username:
claim: "email"
prefix: ""
`,
},
}

fakeClient := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(configmap).
Build()

configs := extractOIDCConfigs(fakeClient.(client.Reader), "default")

if len(configs) != 1 {
t.Fatalf("expected 1 config, got %d", len(configs))
}

cfg := configs[0]
if cfg.Issuer != "https://dex.example.com" {
t.Errorf("expected issuer 'https://dex.example.com', got %q", cfg.Issuer)
}
if cfg.ClientID != "jumpstarter-cli" {
t.Errorf("expected clientID 'jumpstarter-cli', got %q", cfg.ClientID)
}
if len(cfg.Audiences) != 2 {
t.Errorf("expected 2 audiences, got %d", len(cfg.Audiences))
}
}

// TestExtractOIDCConfigsIgnoresLegacyKey is a dedicated regression test
// ensuring that a ConfigMap containing only the legacy "authentication" key
// (without the "config" key) does NOT produce any OIDC configurations.
// This prevents accidental reintroduction of the legacy fallback removed in
// this PR.
func TestExtractOIDCConfigsIgnoresLegacyKey(t *testing.T) {
scheme := runtime.NewScheme()
if err := corev1.AddToScheme(scheme); err != nil {
t.Fatalf("failed to add corev1 to scheme: %v", err)
}

configmap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "jumpstarter-controller",
Namespace: "default",
},
Data: map[string]string{
// Only the legacy key -- this must NOT be read
"authentication": `
jwt:
- issuer:
url: "https://dex.example.com"
audiences:
- "jumpstarter"
claimMappings:
username:
claim: "email"
prefix: ""
`,
},
}

fakeClient := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(configmap).
Build()

configs := extractOIDCConfigs(fakeClient.(client.Reader), "default")

if configs != nil {
t.Errorf("expected nil when only legacy 'authentication' key is present, got %v", configs)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,6 @@ class Model(BaseModel):
enabled: Optional[bool] = Field(
None, description="Whether to enable jumpstarter controller"
)
authenticationConfig: Optional[str] = None
config: Optional[JumpstarterConfig] = None
namespace: Optional[str] = Field(
None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ metadata:
deployment.timestamp: {{ .Values.global.timestamp | quote }}
{{ end }}
data:
# backwards compatibility
# TODO: remove in 0.7.0
{{ if .Values.authenticationConfig }}
authentication: {{- .Values.authenticationConfig | toYaml | indent 1 }}
{{ end }}
config: |
{{ .Values.config | toYaml | indent 4 }}
router: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1342,18 +1342,6 @@
"description": "Whether to enable jumpstarter controller",
"title": "Enabled"
},
"authenticationConfig": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Authenticationconfig"
},
"config": {
"anyOf": [
{
Expand Down
25 changes: 0 additions & 25 deletions controller/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package config
import (
"context"
"fmt"
"time"

"github.com/jumpstarter-dev/jumpstarter-controller/internal/oidc"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/yaml"
Expand Down Expand Up @@ -67,29 +65,6 @@ func LoadConfiguration(
return nil, "", nil, nil, nil, err
}

rawAuthenticationConfiguration, ok := configmap.Data["authentication"]
if ok {
// backwards compatibility
// TODO: remove in 0.7.0
authenticator, prefix, err := oidc.LoadAuthenticationConfiguration(
ctx,
scheme,
[]byte(rawAuthenticationConfiguration),
signer,
certificateAuthority,
)
if err != nil {
return nil, "", nil, nil, nil, err
}

return authenticator, prefix, router, []grpc.ServerOption{
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: 1 * time.Second,
PermitWithoutStream: true,
}),
}, &Provisioning{Enabled: false}, nil
}

rawConfig, ok := configmap.Data["config"]
if !ok {
return nil, "", nil, nil, nil, fmt.Errorf("LoadConfiguration: missing config section")
Expand Down
Loading
Loading