Skip to content

Commit 168038d

Browse files
committed
add support for ssl-redirect annotation
1 parent 59b6a88 commit 168038d

File tree

9 files changed

+681
-2
lines changed

9 files changed

+681
-2
lines changed

docs/guide/ingress/annotations.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ You can add annotations to kubernetes Ingress and Service objects to customize t
2929
|[alb.ingress.kubernetes.io/waf-acl-id](#waf-acl-id)|string|N/A|Ingress|Exclusive|
3030
|[alb.ingress.kubernetes.io/shield-advanced-protection](#shield-advanced-protection)|boolean|N/A|Ingress|Exclusive|
3131
|[alb.ingress.kubernetes.io/listen-ports](#listen-ports)|json|'[{"HTTP": 80}]' \| '[{"HTTPS": 443}]'|Ingress|Merge|
32+
|[alb.ingress.kubernetes.io/ssl-redirect](#ssl-redirect)|integer|N/A|Ingress|Exclusive|
3233
|[alb.ingress.kubernetes.io/inbound-cidrs](#inbound-cidrs)|stringList|0.0.0.0/0, ::/0|Ingress|Exclusive|
3334
|[alb.ingress.kubernetes.io/certificate-arn](#certificate-arn)|stringList|N/A|Ingress|Merge|
3435
|[alb.ingress.kubernetes.io/ssl-policy](#ssl-policy)|string|ELBSecurityPolicy-2016-08|Ingress|Exclusive|
@@ -117,6 +118,22 @@ Traffic Listening can be controlled with following annotations:
117118
```
118119
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}, {"HTTP": 8080}, {"HTTPS": 8443}]'
119120
```
121+
122+
- <a name="ssl-redirect">`alb.ingress.kubernetes.io/ssl-redirect`</a> enables SSLRedirect and specifies the SSL port that redirects to.
123+
124+
!!!note "Merge Behavior"
125+
`ssl-redirect` is exclusive across all Ingresses in IngressGroup.
126+
127+
- Once defined on a single Ingress, it impacts every Ingress within IngressGroup.
128+
129+
!!!note ""
130+
- Once enabled SSLRedirect, every HTTP listener will be configured with default action which redirects to HTTPS, other rules will be ignored.
131+
- The SSL port that redirects to must exists on LoadBalancer. See [alb.ingress.kubernetes.io/listen-ports](#listen-ports) for the listen ports configuration.
132+
133+
!!!example
134+
```
135+
alb.ingress.kubernetes.io/ssl-redirect: '443'
136+
```
120137

121138
- <a name="ip-address-type">`alb.ingress.kubernetes.io/ip-address-type`</a> specifies the [IP address type](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#ip-address-type) of ALB.
122139

pkg/annotations/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const (
1919
IngressSuffixShieldAdvancedProtection = "shield-advanced-protection"
2020
IngressSuffixSecurityGroups = "security-groups"
2121
IngressSuffixListenPorts = "listen-ports"
22+
IngressSuffixSSLRedirect = "ssl-redirect"
2223
IngressSuffixInboundCIDRs = "inbound-cidrs"
2324
IngressSuffixCertificateARN = "certificate-arn"
2425
IngressSuffixSSLPolicy = "ssl-policy"

pkg/ingress/model_build_actions.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ingress
22

33
import (
44
"context"
5+
"fmt"
56
awssdk "github.com/aws/aws-sdk-go/aws"
67
"github.com/pkg/errors"
78
corev1 "k8s.io/api/core/v1"
@@ -241,3 +242,14 @@ func (t *defaultModelBuildTask) build404Action(_ context.Context) elbv2model.Act
241242
},
242243
}
243244
}
245+
246+
func (t *defaultModelBuildTask) buildSSLRedirectAction(_ context.Context, sslRedirectConfig SSLRedirectConfig) elbv2model.Action {
247+
return elbv2model.Action{
248+
Type: elbv2model.ActionTypeRedirect,
249+
RedirectConfig: &elbv2model.RedirectActionConfig{
250+
Port: awssdk.String(fmt.Sprintf("%v", sslRedirectConfig.SSLPort)),
251+
Protocol: awssdk.String(string(elbv2model.ProtocolHTTPS)),
252+
StatusCode: sslRedirectConfig.StatusCode,
253+
},
254+
}
255+
}

pkg/ingress/model_build_actions_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,39 @@ func Test_defaultModelBuildTask_buildAuthenticateOIDCAction(t *testing.T) {
248248
})
249249
}
250250
}
251+
252+
func Test_defaultModelBuildTask_buildSSLRedirectAction(t *testing.T) {
253+
type args struct {
254+
sslRedirectConfig SSLRedirectConfig
255+
}
256+
tests := []struct {
257+
name string
258+
args args
259+
want elbv2model.Action
260+
}{
261+
{
262+
name: "SSLRedirect to 443 with 301",
263+
args: args{
264+
sslRedirectConfig: SSLRedirectConfig{
265+
SSLPort: 443,
266+
StatusCode: "HTTP_301",
267+
},
268+
},
269+
want: elbv2model.Action{
270+
Type: elbv2model.ActionTypeRedirect,
271+
RedirectConfig: &elbv2model.RedirectActionConfig{
272+
Port: awssdk.String("443"),
273+
Protocol: awssdk.String("HTTPS"),
274+
StatusCode: "HTTP_301",
275+
},
276+
},
277+
},
278+
}
279+
for _, tt := range tests {
280+
t.Run(tt.name, func(t1 *testing.T) {
281+
task := &defaultModelBuildTask{}
282+
got := task.buildSSLRedirectAction(context.Background(), tt.args.sslRedirectConfig)
283+
assert.Equal(t, tt.want, got)
284+
})
285+
}
286+
}

pkg/ingress/model_build_listener.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ func (t *defaultModelBuildTask) buildListenerSpec(ctx context.Context, lbARN cor
4949
}
5050

5151
func (t *defaultModelBuildTask) buildListenerDefaultActions(ctx context.Context, protocol elbv2model.Protocol, ingList []*networking.Ingress) ([]elbv2model.Action, error) {
52+
if t.sslRedirectConfig != nil && protocol == elbv2model.ProtocolHTTP {
53+
return []elbv2model.Action{t.buildSSLRedirectAction(ctx, *t.sslRedirectConfig)}, nil
54+
}
55+
5256
ingsWithDefaultBackend := make([]*networking.Ingress, 0, len(ingList))
5357
for _, ing := range ingList {
5458
if ing.Spec.Backend != nil {

pkg/ingress/model_build_listener_rules.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import (
1212
)
1313

1414
func (t *defaultModelBuildTask) buildListenerRules(ctx context.Context, lsARN core.StringToken, port int64, protocol elbv2model.Protocol, ingList []*networking.Ingress) error {
15+
if t.sslRedirectConfig != nil && protocol == elbv2model.ProtocolHTTP {
16+
return nil
17+
}
18+
1519
var rules []Rule
1620
for _, ing := range ingList {
1721
for _, rule := range ing.Spec.Rules {

pkg/ingress/model_builder.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ingress
33
import (
44
"context"
55
awssdk "github.com/aws/aws-sdk-go/aws"
6+
elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2"
67
"github.com/go-logr/logr"
78
"github.com/pkg/errors"
89
networking "k8s.io/api/networking/v1beta1"
@@ -136,8 +137,9 @@ type defaultModelBuildTask struct {
136137
ruleOptimizer RuleOptimizer
137138
logger logr.Logger
138139

139-
ingGroup Group
140-
stack core.Stack
140+
ingGroup Group
141+
sslRedirectConfig *SSLRedirectConfig
142+
stack core.Stack
141143

142144
defaultTags map[string]string
143145
defaultIPAddressType elbv2model.IPAddressType
@@ -194,6 +196,11 @@ func (t *defaultModelBuildTask) run(ctx context.Context) error {
194196
if err != nil {
195197
return err
196198
}
199+
200+
t.sslRedirectConfig, err = t.buildSSLRedirectConfig(ctx, listenPortConfigByPort)
201+
if err != nil {
202+
return err
203+
}
197204
for port, cfg := range listenPortConfigByPort {
198205
ingList := ingListByPort[port]
199206
ls, err := t.buildListener(ctx, lb.LoadBalancerARN(), port, cfg, ingList)
@@ -276,3 +283,36 @@ func (t *defaultModelBuildTask) mergeListenPortConfigs(_ context.Context, listen
276283
tlsCerts: mergedTLSCerts.List(),
277284
}, nil
278285
}
286+
287+
// buildSSLRedirectConfig computes the SSLRedirect config for the IngressGroup. Returns nil if there is no SSLRedirect configured.
288+
func (t *defaultModelBuildTask) buildSSLRedirectConfig(ctx context.Context, listenPortConfigByPort map[int64]listenPortConfig) (*SSLRedirectConfig, error) {
289+
explicitSSLRedirectPorts := sets.Int64{}
290+
for _, ing := range t.ingGroup.Members {
291+
var rawSSLRedirectPort int64
292+
exists, err := t.annotationParser.ParseInt64Annotation(annotations.IngressSuffixSSLRedirect, &rawSSLRedirectPort, ing.Annotations)
293+
if err != nil {
294+
return nil, errors.Wrapf(err, "ingress: %v", k8s.NamespacedName(ing))
295+
}
296+
if exists {
297+
explicitSSLRedirectPorts.Insert(rawSSLRedirectPort)
298+
}
299+
}
300+
301+
if len(explicitSSLRedirectPorts) == 0 {
302+
return nil, nil
303+
}
304+
if len(explicitSSLRedirectPorts) > 1 {
305+
return nil, errors.Errorf("conflicting sslRedirect port: %v", explicitSSLRedirectPorts.List())
306+
}
307+
rawSSLRedirectPort, _ := explicitSSLRedirectPorts.PopAny()
308+
if listenPortConfig, ok := listenPortConfigByPort[rawSSLRedirectPort]; !ok {
309+
return nil, errors.Errorf("listener does not exist for SSLRedirect port: %v", rawSSLRedirectPort)
310+
} else if listenPortConfig.protocol != elbv2model.ProtocolHTTPS {
311+
return nil, errors.Errorf("listener protocol non-SSL for SSLRedirect port: %v", rawSSLRedirectPort)
312+
}
313+
314+
return &SSLRedirectConfig{
315+
SSLPort: rawSSLRedirectPort,
316+
StatusCode: elbv2sdk.RedirectActionStatusCodeEnumHttp301,
317+
}, nil
318+
}

0 commit comments

Comments
 (0)