Skip to content

Commit

Permalink
Add OIDC configuration auto discovery support
Browse files Browse the repository at this point in the history
Signed-off-by: Omer Aplatony <[email protected]>
  • Loading branch information
omerap12 committed Aug 9, 2024
1 parent e5d625f commit 487a69e
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 2 deletions.
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"
authorizationEndpointKey = "authorization_endpoint"
tokenEndpointKey = "token_endpoint"
userInfoEndpointKey = "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[authorizationEndpointKey]
authIDP.TokenEndpoint = oidcConfig[tokenEndpointKey]
authIDP.UserInfoEndpoint = oidcConfig[userInfoEndpointKey]
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"`
}
44 changes: 43 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,32 @@ 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
}
defer response.Body.Close()
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
}

0 comments on commit 487a69e

Please sign in to comment.