Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OIDC configuration auto discovery support #3747

Closed
wants to merge 1 commit into from
Closed
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
8 changes: 8 additions & 0 deletions docs/guide/ingress/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,14 @@ ALB supports authentication with Cognito or OIDC. See [Authenticate Users Using
```
alb.ingress.kubernetes.io/auth-idp-oidc: '{"issuer":"https://example.com","authorizationEndpoint":"https://authorization.example.com","tokenEndpoint":"https://token.example.com","userInfoEndpoint":"https://userinfo.example.com","secretName":"my-k8s-secret"}'
```

!!!tip ""
This annotation allows fetching OIDC configuration dynamically from the specified Discovery Endpoint (`https://example.auth0.com`). The endpoint should provide the necessary details: `issuer`, `authorizationEndpoint`, `tokenEndpoint`, and `userInfoEndpoint` in its JSON response. (Note: `discoveryEndpoint` will override other OIDC configuration fields)

!!example
```
alb.ingress.kubernetes.io/auth-idp-oidc: '{"discoveryEndpoint":"https://example.auth0.com","secretName":"my-k8s-secret","authenticationRequestExtraParams":{"key":"value"}}'
```

- <a name="auth-on-unauthenticated-request">`alb.ingress.kubernetes.io/auth-on-unauthenticated-request`</a> specifies the behavior if the user is not authenticated.

Expand Down
17 changes: 17 additions & 0 deletions pkg/ingress/auth_config_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package ingress

import (
"context"

"github.com/pkg/errors"
"sigs.k8s.io/aws-load-balancer-controller/pkg/annotations"
"sigs.k8s.io/aws-load-balancer-controller/pkg/networking"
)

const (
Expand All @@ -12,6 +14,10 @@ const (
defaultAuthSessionCookieName = "AWSELBAuthSessionCookie"
defaultAuthSessionTimeout = 604800
defaultAuthOnUnauthenticatedRequest = "authenticate"
defaultIssuerKey = "issuer"
defaultAuthorizationEndpointKey = "authorization_endpoint"
defaultTokenEndpointKey = "token_endpoint"
defaultUserInfoEndpointKey = "userinfo_endpoint"
)

// Auth config for Service / Ingresses
Expand Down Expand Up @@ -114,6 +120,17 @@ func (b *defaultAuthConfigBuilder) buildAuthIDPConfigOIDC(_ context.Context, svc
if !exists {
return nil, nil
}
if authIDP.DiscoveryEndpoint == "" {
return &authIDP, nil
}
oidcConfig, err := networking.GetOIDCConfiguration(authIDP.DiscoveryEndpoint)
if err != nil {
return nil, err
}
authIDP.Issuer = oidcConfig[defaultIssuerKey]
authIDP.AuthorizationEndpoint = oidcConfig[defaultAuthorizationEndpointKey]
authIDP.TokenEndpoint = oidcConfig[defaultTokenEndpointKey]
authIDP.UserInfoEndpoint = oidcConfig[defaultUserInfoEndpointKey]
return &authIDP, nil
}

Expand Down
63 changes: 62 additions & 1 deletion pkg/ingress/auth_config_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package ingress

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"sigs.k8s.io/aws-load-balancer-controller/pkg/annotations"
"testing"
)

func Test_defaultAuthConfigBuilder_Build(t *testing.T) {
Expand Down Expand Up @@ -179,6 +180,66 @@ func Test_defaultAuthConfigBuilder_Build(t *testing.T) {
SessionTimeout: 86400,
},
},
{
name: "oidc configuration using auto discovery",
args: args{
svcAndIngAnnotations: map[string]string{
"alb.ingress.kubernetes.io/auth-idp-oidc": `{"discoveryEndpoint":"https://example.auth0.com","secretName":"my-k8s-secret","authenticationRequestExtraParams":{"key":"value"}}`,
"alb.ingress.kubernetes.io/auth-on-unauthenticated-request": "deny",
"alb.ingress.kubernetes.io/auth-scope": "email",
"alb.ingress.kubernetes.io/auth-session-cookie": "my-cookie",
"alb.ingress.kubernetes.io/auth-session-timeout": "86400",
},
},
want: AuthConfig{
Type: AuthTypeNone,
IDPConfigOIDC: &AuthIDPConfigOIDC{
Issuer: "https://example.auth0.com/",
AuthorizationEndpoint: "https://example.auth0.com/authorize",
TokenEndpoint: "https://example.auth0.com/oauth/token",
UserInfoEndpoint: "https://example.auth0.com/userinfo",
SecretName: "my-k8s-secret",
DiscoveryEndpoint: "https://example.auth0.com",
AuthenticationRequestExtraParams: map[string]string{
"key": "value",
},
},
OnUnauthenticatedRequest: "deny",
Scope: "email",
SessionCookieName: "my-cookie",
SessionTimeout: 86400,
},
},
{
name: "oidc configuration using auto discovery should ovveride issuer, authorizationEndpoint, tokenEndpoint and userInfoEndpoint",
args: args{
svcAndIngAnnotations: map[string]string{
"alb.ingress.kubernetes.io/auth-idp-oidc": `{"issuer":"https://example.com","authorizationEndpoint":"https://authorization.example.com","tokenEndpoint":"https://token.example.com","userInfoEndpoint":"https://userinfo.example.com","discoveryEndpoint":"https://example.auth0.com","secretName":"my-k8s-secret","authenticationRequestExtraParams":{"key":"value"}}`,
"alb.ingress.kubernetes.io/auth-on-unauthenticated-request": "deny",
"alb.ingress.kubernetes.io/auth-scope": "email",
"alb.ingress.kubernetes.io/auth-session-cookie": "my-cookie",
"alb.ingress.kubernetes.io/auth-session-timeout": "86400",
},
},
want: AuthConfig{
Type: AuthTypeNone,
IDPConfigOIDC: &AuthIDPConfigOIDC{
Issuer: "https://example.auth0.com/",
AuthorizationEndpoint: "https://example.auth0.com/authorize",
TokenEndpoint: "https://example.auth0.com/oauth/token",
UserInfoEndpoint: "https://example.auth0.com/userinfo",
SecretName: "my-k8s-secret",
DiscoveryEndpoint: "https://example.auth0.com",
AuthenticationRequestExtraParams: map[string]string{
"key": "value",
},
},
OnUnauthenticatedRequest: "deny",
Scope: "email",
SessionCookieName: "my-cookie",
SessionTimeout: 86400,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions pkg/ingress/config_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,4 +413,8 @@ type AuthIDPConfigOIDC struct {
// The query parameters (up to 10) to include in the redirect request to the authorization endpoint.
// +optional
AuthenticationRequestExtraParams map[string]string `json:"authenticationRequestExtraParams,omitempty"`

// For IdP that supports discovery
// +optional
DiscoveryEndpoint string `json:"discoveryEndpoint,omitempty"`
}
43 changes: 42 additions & 1 deletion pkg/networking/utils.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
package networking

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/netip"

awssdk "github.com/aws/aws-sdk-go/aws"
ec2sdk "github.com/aws/aws-sdk-go/service/ec2"
"net/netip"
)

const (
OIDCSuffix = ".well-known/openid-configuration"
issuerKey = "issuer"
authorizationEndpointKey = "authorization_endpoint"
tokenEndpointKey = "token_endpoint"
userInfoEndpointKey = "userinfo_endpoint"
)

// ParseCIDRs will parse CIDRs in string format into parsed IPPrefix
Expand Down Expand Up @@ -72,3 +85,31 @@ func GetSubnetAssociatedIPv6CIDRs(subnet *ec2sdk.Subnet) ([]netip.Prefix, error)
}
return ipv6CIDRs, nil
}

// GetOIDCConfiguration retrieves the OIDC configuration from the specified discoveryEndpoint.
// should return a map with the following keys: issuer, authorization_endpoint, token_endpoint, userinfo_endpoint
func GetOIDCConfiguration(discoveryEndpoint string) (map[string]string, error) {
discoveryEndpointUrl := fmt.Sprintf("%s/%s", discoveryEndpoint, OIDCSuffix)
req, err := http.NewRequest(http.MethodGet, discoveryEndpointUrl, nil)
if err != nil {
return nil, err
}
response, err := http.DefaultClient.Do(req)
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get OIDC configuration. status code: %d", response.StatusCode)
}
defer response.Body.Close()
if err != nil {
return nil, err
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, err
}
var ret map[string]string
json.Unmarshal([]byte(body), &ret)
if ret[issuerKey] == "" || ret[authorizationEndpointKey] == "" || ret[tokenEndpointKey] == "" || ret[userInfoEndpointKey] == "" {
return nil, fmt.Errorf("missing OIDC configuration for url: %s", discoveryEndpointUrl)
}
return ret, nil
}
Loading