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
4 changes: 2 additions & 2 deletions backend/cmd/headlamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -1285,8 +1285,8 @@ func (c *HeadlampConfig) helmRouteReleaseHandler(
// Create a copy of the context to avoid modifying the cached context
context = context.Copy()

// If headlamp is running in cluster, use the token from the cookie for oidc auth
if c.UseInCluster && context.OidcConf != nil {
// If running in cluster or explicitly enabled via flag, use the token from the cookie for oidc auth
if (c.UseInCluster || c.OidcUseCookie) && context.OidcConf != nil {
setTokenFromCookie(r, clusterName)
}

Expand Down
25 changes: 25 additions & 0 deletions backend/cmd/headlamp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1972,3 +1972,28 @@ func TestHandleClusterServiceProxy(t *testing.T) {
assert.Equal(t, "OK", rr.Body.String())
}
}

// TestOidcUseCookieLogic verifies that the mechanism for promoting an OIDC token
// from a cookie to the Authorization header works as expected.
func TestOidcUseCookieLogic(t *testing.T) {
clusterName := "test-cluster-oidc"
testToken := "fake-token-for-testing"
cookieName := "headlamp-auth-" + clusterName + ".0"

req, err := http.NewRequestWithContext(context.Background(), "GET", "/api/v1/clusters/"+clusterName, nil)
assert.NoError(t, err)

req.AddCookie(&http.Cookie{
Name: cookieName,
Value: testToken,
})

setTokenFromCookie(req, clusterName)

got := req.Header.Get("Authorization")
want := "Bearer " + testToken

if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
1 change: 1 addition & 0 deletions backend/cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func buildHeadlampCFG(conf *config.Config, kubeConfigStore kubeconfig.ContextSto
TLSCertPath: conf.TLSCertPath,
TLSKeyPath: conf.TLSKeyPath,
SessionTTL: conf.SessionTTL,
OidcUseCookie: conf.OidcUseCookie,
}
}

Expand Down
9 changes: 6 additions & 3 deletions backend/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type Config struct {
OidcValidatorIdpIssuerURL string `koanf:"oidc-validator-idp-issuer-url"`
OidcScopes string `koanf:"oidc-scopes"`
OidcUseAccessToken bool `koanf:"oidc-use-access-token"`
OidcUseCookie bool `koanf:"oidc-use-cookie"`
OidcSkipTLSVerify bool `koanf:"oidc-skip-tls-verify"`
OidcCAFile string `koanf:"oidc-ca-file"`
MeUsernamePath string `koanf:"me-username-path"`
Expand All @@ -88,10 +89,11 @@ type Config struct {
}

func (c *Config) Validate() error {
if !c.InCluster && (c.OidcClientID != "" || c.OidcClientSecret != "" || c.OidcIdpIssuerURL != "" ||
if !c.InCluster && !c.OidcUseCookie && (c.OidcClientID != "" || c.OidcClientSecret != "" || c.OidcIdpIssuerURL != "" ||
c.OidcValidatorClientID != "" || c.OidcValidatorIdpIssuerURL != "") {
return errors.New(`oidc-client-id, oidc-client-secret, oidc-idp-issuer-url, oidc-validator-client-id,
oidc-validator-idp-issuer-url, flags are only meant to be used in inCluster mode`)
return errors.New("oidc-client-id, oidc-client-secret, oidc-idp-issuer-url, " +
"oidc-validator-client-id, oidc-validator-idp-issuer-url, flags are only " +
"meant to be used in inCluster mode or with --oidc-use-cookie")
}

// OIDC TLS verification warning.
Expand Down Expand Up @@ -461,6 +463,7 @@ func addOIDCFlags(f *flag.FlagSet) {
f.Bool("oidc-skip-tls-verify", false, "Skip TLS verification for OIDC")
f.String("oidc-ca-file", "", "CA file for OIDC")
f.Bool("oidc-use-access-token", false, "Setup oidc to pass through the access_token instead of the default id_token")
f.Bool("oidc-use-cookie", false, "Enable OIDC cookie usage even when not running in-cluster")
f.Bool("oidc-use-pkce", false, "Use PKCE (Proof Key for Code Exchange) for enhanced security in OIDC flow")
f.String("me-username-path", DefaultMeUsernamePath,
"Comma separated JMESPath expressions used to read username from the JWT payload")
Expand Down
10 changes: 9 additions & 1 deletion backend/pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ func TestParseBasic(t *testing.T) {
assert.Equal(t, config.DefaultMeUsernamePath, conf.MeUsernamePath)
},
},
{
name: "oidc_use_cookie",
args: []string{"go run ./cmd", "--oidc-use-cookie", "--oidc-client-id=my-id"},
verify: func(t *testing.T, conf *config.Config) {
assert.Equal(t, true, conf.OidcUseCookie)
assert.Equal(t, "my-id", conf.OidcClientID)
},
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -173,7 +181,7 @@ func TestParseErrors(t *testing.T) {
{
name: "oidc_settings_without_incluster",
args: []string{"go run ./cmd", "-oidc-client-id=noClient"},
errorContains: "are only meant to be used in inCluster mode",
errorContains: "flags are only meant to be used in inCluster mode or with --oidc-use-cookie",
},
{
name: "invalid_base_url",
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/headlampconfig/headlampConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,5 @@ type HeadlampCFG struct {
TLSCertPath string
TLSKeyPath string
SessionTTL int
OidcUseCookie bool
}
1 change: 1 addition & 0 deletions charts/headlamp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ $ helm install my-headlamp headlamp/headlamp \
| config.oidc.issuerURL | string | `""` | OIDC issuer URL |
| config.oidc.scopes | string | `""` | OIDC scopes to be used |
| config.oidc.usePKCE | bool | `false` | Use PKCE (Proof Key for Code Exchange) for enhanced security in OIDC flow |
| config.oidc.useCookie | bool | `false` | Enable using OIDC cookie for authentication outside of cluster |
| config.oidc.secret.create | bool | `true` | Create OIDC secret using provided values |
| config.oidc.secret.name | string | `"oidc"` | Name of the OIDC secret |
| config.oidc.externalSecret.enabled | bool | `false` | Enable using external secret for OIDC |
Expand Down
3 changes: 3 additions & 0 deletions charts/headlamp/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ spec:
{{- if .Values.config.watchPlugins }}
- "-watch-plugins-changes"
{{- end }}
{{- if .Values.config.oidc.useCookie }}
- "-oidc-use-cookie"
{{- end }}
{{- with .Values.config.pluginsDir}}
- "-plugins-dir={{ . }}"
{{- end }}
Expand Down
2 changes: 2 additions & 0 deletions charts/headlamp/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ config:
useAccessToken: false
# -- Use PKCE (Proof Key for Code Exchange) for enhanced security in OIDC flow
usePKCE: false
# -- Enable using OIDC cookie for authentication outside of cluster
useCookie: false

# Option 3:
# @param config.oidc - External OIDC secret configuration
Expand Down