Skip to content

Commit a9269e6

Browse files
committed
[feat i2g]support auth translate
1 parent cd4514c commit a9269e6

File tree

10 files changed

+557
-36
lines changed

10 files changed

+557
-36
lines changed

pkg/ingress2gateway/translate/annotation_coverage_test.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ func TestAllIngressAnnotationsCovered(t *testing.T) {
7979
annotations.IngressSuffixUseRegexPathMatch,
8080
annotations.IngressSuffixSSLRedirect,
8181
},
82+
Authentication: {
83+
annotations.IngressSuffixAuthType,
84+
annotations.IngressSuffixAuthIDPCognito,
85+
annotations.IngressSuffixAuthIDPOIDC,
86+
annotations.IngressSuffixAuthOnUnauthenticatedRequest,
87+
annotations.IngressSuffixAuthScope,
88+
annotations.IngressSuffixAuthSessionCookie,
89+
annotations.IngressSuffixAuthSessionTimeout,
90+
},
8291
}
8392

8493
// Planned: annotations not yet implemented.
@@ -97,13 +106,6 @@ func TestAllIngressAnnotationsCovered(t *testing.T) {
97106
// transforms.* translation is implemented in translate_transform_helper.go.
98107
},
99108
Authentication: {
100-
annotations.IngressSuffixAuthType,
101-
annotations.IngressSuffixAuthIDPCognito,
102-
annotations.IngressSuffixAuthIDPOIDC,
103-
annotations.IngressSuffixAuthOnUnauthenticatedRequest,
104-
annotations.IngressSuffixAuthScope,
105-
annotations.IngressSuffixAuthSessionCookie,
106-
annotations.IngressSuffixAuthSessionTimeout,
107109
annotations.IngressSuffixJwtValidation,
108110
},
109111
FrontendNLB: {
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package translate
2+
3+
import (
4+
"fmt"
5+
6+
gatewayv1beta1 "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1"
7+
annotations "sigs.k8s.io/aws-load-balancer-controller/pkg/annotations"
8+
"sigs.k8s.io/aws-load-balancer-controller/pkg/ingress"
9+
)
10+
11+
const (
12+
authTypeNone = "none"
13+
authTypeCognito = "cognito"
14+
authTypeOIDC = "oidc"
15+
)
16+
17+
// buildAuthAction reads auth-type and related annotations and returns a
18+
// ListenerRuleConfiguration Action for authenticate-cognito or authenticate-oidc.
19+
// Returns (nil, nil) when auth-type is "none" or absent.
20+
func buildAuthAction(annos map[string]string) (*gatewayv1beta1.Action, error) {
21+
authType := getString(annos, annotations.IngressSuffixAuthType)
22+
if authType == "" || authType == authTypeNone {
23+
return nil, nil
24+
}
25+
26+
switch authType {
27+
case authTypeCognito:
28+
return buildAuthCognitoAction(annos)
29+
case authTypeOIDC:
30+
return buildOIDCAction(annos)
31+
default:
32+
return nil, fmt.Errorf("unsupported auth-type %q", authType)
33+
}
34+
}
35+
36+
// buildAuthCognitoAction parses auth-idp-cognito JSON and shared auth annotations
37+
// into an authenticate-cognito Action.
38+
func buildAuthCognitoAction(annos map[string]string) (*gatewayv1beta1.Action, error) {
39+
var idp ingress.AuthIDPConfigCognito
40+
exists, err := ingressAnnotationParser.ParseJSONAnnotation(annotations.IngressSuffixAuthIDPCognito, &idp, annos)
41+
if err != nil {
42+
return nil, fmt.Errorf("failed to parse %s annotation: %w", annotations.IngressSuffixAuthIDPCognito, err)
43+
}
44+
if !exists {
45+
return nil, fmt.Errorf("auth-type is %q but %s annotation is missing", authTypeCognito, annotations.IngressSuffixAuthIDPCognito)
46+
}
47+
48+
scope := getOptionalAuthScope(annos)
49+
onUnauth := getOptionalAuthOnUnauthenticatedRequest(annos)
50+
sessionCookie := getOptionalAuthSessionCookieName(annos)
51+
sessionTimeout := getOptionalAuthSessionTimeout(annos)
52+
53+
cfg := &gatewayv1beta1.AuthenticateCognitoActionConfig{
54+
UserPoolArn: idp.UserPoolARN,
55+
UserPoolClientID: idp.UserPoolClientID,
56+
UserPoolDomain: idp.UserPoolDomain,
57+
}
58+
59+
if scope != nil {
60+
cfg.Scope = scope
61+
}
62+
if onUnauth != nil {
63+
cognitoEnum := gatewayv1beta1.AuthenticateCognitoActionConditionalBehaviorEnum(*onUnauth)
64+
cfg.OnUnauthenticatedRequest = &cognitoEnum
65+
}
66+
if sessionCookie != nil {
67+
cfg.SessionCookieName = sessionCookie
68+
}
69+
if sessionTimeout != nil {
70+
cfg.SessionTimeout = sessionTimeout
71+
}
72+
73+
if len(idp.AuthenticationRequestExtraParams) > 0 {
74+
params := make(map[string]string, len(idp.AuthenticationRequestExtraParams))
75+
for k, v := range idp.AuthenticationRequestExtraParams {
76+
params[k] = v
77+
}
78+
cfg.AuthenticationRequestExtraParams = &params
79+
}
80+
81+
return &gatewayv1beta1.Action{
82+
Type: gatewayv1beta1.ActionTypeAuthenticateCognito,
83+
AuthenticateCognitoConfig: cfg,
84+
}, nil
85+
}
86+
87+
// buildOIDCAction parses auth-idp-oidc JSON and shared auth annotations
88+
// into an authenticate-oidc Action. The Secret reference (secretName) from
89+
// the annotation JSON is preserved as Secret.Name on the CRD.
90+
func buildOIDCAction(annos map[string]string) (*gatewayv1beta1.Action, error) {
91+
var idp ingress.AuthIDPConfigOIDC
92+
exists, err := ingressAnnotationParser.ParseJSONAnnotation(annotations.IngressSuffixAuthIDPOIDC, &idp, annos)
93+
if err != nil {
94+
return nil, fmt.Errorf("failed to parse %s annotation: %w", annotations.IngressSuffixAuthIDPOIDC, err)
95+
}
96+
if !exists {
97+
return nil, fmt.Errorf("auth-type is %q but %s annotation is missing", authTypeOIDC, annotations.IngressSuffixAuthIDPOIDC)
98+
}
99+
100+
scope := getOptionalAuthScope(annos)
101+
onUnauth := getOptionalAuthOnUnauthenticatedRequest(annos)
102+
sessionCookie := getOptionalAuthSessionCookieName(annos)
103+
sessionTimeout := getOptionalAuthSessionTimeout(annos)
104+
105+
cfg := &gatewayv1beta1.AuthenticateOidcActionConfig{
106+
Issuer: idp.Issuer,
107+
AuthorizationEndpoint: idp.AuthorizationEndpoint,
108+
TokenEndpoint: idp.TokenEndpoint,
109+
UserInfoEndpoint: idp.UserInfoEndpoint,
110+
Secret: &gatewayv1beta1.Secret{Name: idp.SecretName},
111+
}
112+
113+
if scope != nil {
114+
cfg.Scope = scope
115+
}
116+
if onUnauth != nil {
117+
oidcEnum := gatewayv1beta1.AuthenticateOidcActionConditionalBehaviorEnum(*onUnauth)
118+
cfg.OnUnauthenticatedRequest = &oidcEnum
119+
}
120+
if sessionCookie != nil {
121+
cfg.SessionCookieName = sessionCookie
122+
}
123+
if sessionTimeout != nil {
124+
cfg.SessionTimeout = sessionTimeout
125+
}
126+
127+
if len(idp.AuthenticationRequestExtraParams) > 0 {
128+
params := make(map[string]string, len(idp.AuthenticationRequestExtraParams))
129+
for k, v := range idp.AuthenticationRequestExtraParams {
130+
params[k] = v
131+
}
132+
cfg.AuthenticationRequestExtraParams = &params
133+
}
134+
135+
return &gatewayv1beta1.Action{
136+
Type: gatewayv1beta1.ActionTypeAuthenticateOIDC,
137+
AuthenticateOIDCConfig: cfg,
138+
}, nil
139+
}
140+
141+
// getOptionalAuthScope returns the auth-scope annotation value, or nil if not set.
142+
// The CRD has kubebuilder:default="openid" so omitting it lets the webhook fill the default.
143+
func getOptionalAuthScope(annos map[string]string) *string {
144+
v := getString(annos, annotations.IngressSuffixAuthScope)
145+
if v == "" {
146+
return nil
147+
}
148+
return &v
149+
}
150+
151+
// getOptionalAuthOnUnauthenticatedRequest returns the annotation value, or nil if not set.
152+
// The CRD has kubebuilder:default="authenticate".
153+
func getOptionalAuthOnUnauthenticatedRequest(annos map[string]string) *string {
154+
v := getString(annos, annotations.IngressSuffixAuthOnUnauthenticatedRequest)
155+
if v == "" {
156+
return nil
157+
}
158+
return &v
159+
}
160+
161+
// getOptionalAuthSessionCookieName returns the annotation value, or nil if not set.
162+
// The CRD has kubebuilder:default="AWSELBAuthSessionCookie".
163+
func getOptionalAuthSessionCookieName(annos map[string]string) *string {
164+
v := getString(annos, annotations.IngressSuffixAuthSessionCookie)
165+
if v == "" {
166+
return nil
167+
}
168+
return &v
169+
}
170+
171+
// getOptionalAuthSessionTimeout returns the annotation value, or nil if not set.
172+
// The CRD has kubebuilder:default=604800.
173+
func getOptionalAuthSessionTimeout(annos map[string]string) *int64 {
174+
return getInt64(annos, annotations.IngressSuffixAuthSessionTimeout)
175+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package translate
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
gatewayv1beta1 "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1"
9+
)
10+
11+
func TestBuildAuthAction_None(t *testing.T) {
12+
annos := map[string]string{
13+
"alb.ingress.kubernetes.io/auth-type": "none",
14+
}
15+
action, err := buildAuthAction(annos)
16+
require.NoError(t, err)
17+
assert.Nil(t, action)
18+
}
19+
20+
func TestBuildAuthAction_NoAnnotation(t *testing.T) {
21+
action, err := buildAuthAction(map[string]string{})
22+
require.NoError(t, err)
23+
assert.Nil(t, action)
24+
}
25+
26+
func TestBuildAuthAction_UnknownType(t *testing.T) {
27+
annos := map[string]string{
28+
"alb.ingress.kubernetes.io/auth-type": "foobar",
29+
}
30+
_, err := buildAuthAction(annos)
31+
require.Error(t, err)
32+
assert.Contains(t, err.Error(), "unsupported auth-type")
33+
}
34+
35+
func TestBuildAuthAction_CognitoFull(t *testing.T) {
36+
annos := map[string]string{
37+
"alb.ingress.kubernetes.io/auth-type": "cognito",
38+
"alb.ingress.kubernetes.io/auth-idp-cognito": `{"userPoolARN":"arn:aws:cognito-idp:us-west-2:123456789:userpool/us-west-2_abc","userPoolClientID":"my-client-id","userPoolDomain":"my-domain"}`,
39+
"alb.ingress.kubernetes.io/auth-scope": "email openid",
40+
"alb.ingress.kubernetes.io/auth-on-unauthenticated-request": "deny",
41+
"alb.ingress.kubernetes.io/auth-session-cookie": "my-cookie",
42+
"alb.ingress.kubernetes.io/auth-session-timeout": "86400",
43+
}
44+
action, err := buildAuthAction(annos)
45+
require.NoError(t, err)
46+
require.NotNil(t, action)
47+
48+
assert.Equal(t, gatewayv1beta1.ActionTypeAuthenticateCognito, action.Type)
49+
require.NotNil(t, action.AuthenticateCognitoConfig)
50+
51+
cfg := action.AuthenticateCognitoConfig
52+
assert.Equal(t, "arn:aws:cognito-idp:us-west-2:123456789:userpool/us-west-2_abc", cfg.UserPoolArn)
53+
assert.Equal(t, "my-client-id", cfg.UserPoolClientID)
54+
assert.Equal(t, "my-domain", cfg.UserPoolDomain)
55+
56+
require.NotNil(t, cfg.Scope)
57+
assert.Equal(t, "email openid", *cfg.Scope)
58+
59+
require.NotNil(t, cfg.OnUnauthenticatedRequest)
60+
assert.Equal(t, gatewayv1beta1.AuthenticateCognitoActionConditionalBehaviorEnumDeny, *cfg.OnUnauthenticatedRequest)
61+
62+
require.NotNil(t, cfg.SessionCookieName)
63+
assert.Equal(t, "my-cookie", *cfg.SessionCookieName)
64+
65+
require.NotNil(t, cfg.SessionTimeout)
66+
assert.Equal(t, int64(86400), *cfg.SessionTimeout)
67+
68+
assert.Nil(t, cfg.AuthenticationRequestExtraParams)
69+
}
70+
71+
func TestBuildAuthAction_CognitoExtraParams(t *testing.T) {
72+
annos := map[string]string{
73+
"alb.ingress.kubernetes.io/auth-type": "cognito",
74+
"alb.ingress.kubernetes.io/auth-idp-cognito": `{"userPoolARN":"arn:pool","userPoolClientID":"cid","userPoolDomain":"dom","authenticationRequestExtraParams":{"display":"page","prompt":"login"}}`,
75+
}
76+
action, err := buildAuthAction(annos)
77+
require.NoError(t, err)
78+
require.NotNil(t, action)
79+
80+
cfg := action.AuthenticateCognitoConfig
81+
require.NotNil(t, cfg.AuthenticationRequestExtraParams)
82+
assert.Equal(t, "page", (*cfg.AuthenticationRequestExtraParams)["display"])
83+
assert.Equal(t, "login", (*cfg.AuthenticationRequestExtraParams)["prompt"])
84+
}
85+
86+
func TestBuildAuthAction_CognitoMissingIDP(t *testing.T) {
87+
annos := map[string]string{
88+
"alb.ingress.kubernetes.io/auth-type": "cognito",
89+
}
90+
_, err := buildAuthAction(annos)
91+
require.Error(t, err)
92+
assert.Contains(t, err.Error(), "missing")
93+
}
94+
95+
func TestBuildAuthAction_OIDCFull(t *testing.T) {
96+
annos := map[string]string{
97+
"alb.ingress.kubernetes.io/auth-type": "oidc",
98+
"alb.ingress.kubernetes.io/auth-idp-oidc": `{"issuer":"https://example.com","authorizationEndpoint":"https://auth.example.com","tokenEndpoint":"https://token.example.com","userInfoEndpoint":"https://userinfo.example.com","secretName":"my-k8s-secret"}`,
99+
"alb.ingress.kubernetes.io/auth-scope": "email openid profile",
100+
"alb.ingress.kubernetes.io/auth-on-unauthenticated-request": "allow",
101+
"alb.ingress.kubernetes.io/auth-session-cookie": "oidc-cookie",
102+
"alb.ingress.kubernetes.io/auth-session-timeout": "7200",
103+
}
104+
action, err := buildAuthAction(annos)
105+
require.NoError(t, err)
106+
require.NotNil(t, action)
107+
108+
assert.Equal(t, gatewayv1beta1.ActionTypeAuthenticateOIDC, action.Type)
109+
require.NotNil(t, action.AuthenticateOIDCConfig)
110+
111+
cfg := action.AuthenticateOIDCConfig
112+
assert.Equal(t, "https://example.com", cfg.Issuer)
113+
assert.Equal(t, "https://auth.example.com", cfg.AuthorizationEndpoint)
114+
assert.Equal(t, "https://token.example.com", cfg.TokenEndpoint)
115+
assert.Equal(t, "https://userinfo.example.com", cfg.UserInfoEndpoint)
116+
117+
// Secret reference preserved by name
118+
require.NotNil(t, cfg.Secret)
119+
assert.Equal(t, "my-k8s-secret", cfg.Secret.Name)
120+
121+
require.NotNil(t, cfg.Scope)
122+
assert.Equal(t, "email openid profile", *cfg.Scope)
123+
124+
require.NotNil(t, cfg.OnUnauthenticatedRequest)
125+
assert.Equal(t, gatewayv1beta1.AuthenticateOidcActionConditionalBehaviorEnumAllow, *cfg.OnUnauthenticatedRequest)
126+
127+
require.NotNil(t, cfg.SessionCookieName)
128+
assert.Equal(t, "oidc-cookie", *cfg.SessionCookieName)
129+
130+
require.NotNil(t, cfg.SessionTimeout)
131+
assert.Equal(t, int64(7200), *cfg.SessionTimeout)
132+
133+
assert.Nil(t, cfg.AuthenticationRequestExtraParams)
134+
}
135+
136+
func TestBuildAuthAction_OIDCExtraParams(t *testing.T) {
137+
annos := map[string]string{
138+
"alb.ingress.kubernetes.io/auth-type": "oidc",
139+
"alb.ingress.kubernetes.io/auth-idp-oidc": `{"issuer":"https://ex.com","authorizationEndpoint":"https://auth.ex.com","tokenEndpoint":"https://tok.ex.com","userInfoEndpoint":"https://ui.ex.com","secretName":"sec","authenticationRequestExtraParams":{"display":"page","prompt":"consent"}}`,
140+
}
141+
action, err := buildAuthAction(annos)
142+
require.NoError(t, err)
143+
require.NotNil(t, action)
144+
145+
cfg := action.AuthenticateOIDCConfig
146+
require.NotNil(t, cfg.AuthenticationRequestExtraParams)
147+
assert.Equal(t, "page", (*cfg.AuthenticationRequestExtraParams)["display"])
148+
assert.Equal(t, "consent", (*cfg.AuthenticationRequestExtraParams)["prompt"])
149+
}
150+
151+
func TestBuildAuthAction_OIDCMissingIDP(t *testing.T) {
152+
annos := map[string]string{
153+
"alb.ingress.kubernetes.io/auth-type": "oidc",
154+
}
155+
_, err := buildAuthAction(annos)
156+
require.Error(t, err)
157+
assert.Contains(t, err.Error(), "missing")
158+
}
159+
160+
func TestBuildAuthAction_OIDCSecretNamePreserved(t *testing.T) {
161+
annos := map[string]string{
162+
"alb.ingress.kubernetes.io/auth-type": "oidc",
163+
"alb.ingress.kubernetes.io/auth-idp-oidc": `{"issuer":"https://ex.com","authorizationEndpoint":"https://auth.ex.com","tokenEndpoint":"https://tok.ex.com","userInfoEndpoint":"https://ui.ex.com","secretName":"my-special-secret"}`,
164+
}
165+
action, err := buildAuthAction(annos)
166+
require.NoError(t, err)
167+
require.NotNil(t, action)
168+
require.NotNil(t, action.AuthenticateOIDCConfig)
169+
require.NotNil(t, action.AuthenticateOIDCConfig.Secret)
170+
assert.Equal(t, "my-special-secret", action.AuthenticateOIDCConfig.Secret.Name)
171+
}

0 commit comments

Comments
 (0)