Skip to content

Commit 8ba34e2

Browse files
authored
enable http and https listener attributes (#3948)
1 parent 13538cb commit 8ba34e2

File tree

7 files changed

+201
-20
lines changed

7 files changed

+201
-20
lines changed

docs/guide/ingress/annotations.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ You can add annotations to kubernetes Ingress and Service objects to customize t
6060
| [alb.ingress.kubernetes.io/target-node-labels](#target-node-labels) | stringMap |N/A| Ingress,Service | N/A |
6161
| [alb.ingress.kubernetes.io/mutual-authentication](#mutual-authentication) | json |N/A| Ingress | Exclusive |
6262
| [alb.ingress.kubernetes.io/multi-cluster-target-group](#multi-cluster-target-group) | boolean |N/A| Ingress, Service | N/A |
63+
| [alb.ingress.kubernetes.io/listener-attributes.${Protocol}-${Port}](#listener-attributes) | stringMap |N/A| Ingress |Merge|
6364

6465
## IngressGroup
6566
IngressGroup feature enables you to group multiple Ingress resources together.
@@ -903,6 +904,14 @@ Custom attributes to LoadBalancers and TargetGroups can be controlled with follo
903904
alb.ingress.kubernetes.io/multi-cluster-target-group: "true"
904905
```
905906
907+
- <a name="listener-attributes">`alb.ingress.kubernetes.io/listener-attributes.${Protocol}-${Port}`</a> specifies Listener Attributes which should be applied to listener.
908+
909+
!!!example
910+
- Server header enablement attribute
911+
```
912+
alb.ingress.kubernetes.io/listener-attributes.HTTP-80: routing.http.response.server.enabled=true
913+
```
914+
906915
907916
## Resource Tags
908917
The AWS Load Balancer Controller automatically applies following tags to the AWS resources (ALB/TargetGroups/SecurityGroups/Listener/ListenerRule) it creates:

pkg/deploy/elbv2/listener_manager.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import (
2323
)
2424

2525
var PROTOCOLS_SUPPORTING_LISTENER_ATTRIBUTES = map[elbv2model.Protocol]bool{
26-
elbv2model.ProtocolHTTP: false,
27-
elbv2model.ProtocolHTTPS: false,
26+
elbv2model.ProtocolHTTP: true,
27+
elbv2model.ProtocolHTTPS: true,
2828
elbv2model.ProtocolTCP: true,
2929
elbv2model.ProtocolUDP: false,
3030
elbv2model.ProtocolTLS: false,

pkg/ingress/model_build_listener.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,14 @@ func (t *defaultModelBuildTask) fetchTrustStoreArnFromName(ctx context.Context,
426426

427427
func (t *defaultModelBuildTask) buildIngressGroupListenerAttributes(ctx context.Context, ingList []ClassifiedIngress, listenerProtocol elbv2model.Protocol, port int32) ([]elbv2model.ListenerAttribute, error) {
428428
rawIngGrouplistenerAttributes := make(map[string]string)
429+
ingClassAttributes := make(map[string]string)
430+
if len(ingList) > 0 {
431+
var err error
432+
ingClassAttributes, err = t.buildIngressClassListenerAttributes(ingList[0].IngClassConfig, listenerProtocol, port)
433+
if err != nil {
434+
return nil, err
435+
}
436+
}
429437
for _, ing := range ingList {
430438
ingAttributes, err := t.buildIngressListenerAttributes(ctx, ing.Ing.Annotations, port, listenerProtocol)
431439
if err != nil {
@@ -435,18 +443,23 @@ func (t *defaultModelBuildTask) buildIngressGroupListenerAttributes(ctx context.
435443
attributeKey := attribute.Key
436444
attributeValue := attribute.Value
437445
if existingAttributeValue, exists := rawIngGrouplistenerAttributes[attributeKey]; exists && existingAttributeValue != attributeValue {
438-
return nil, errors.Errorf("conflicting attributes %v: %v | %v", attributeKey, existingAttributeValue, attributeValue)
446+
if ingClassValue, exists := ingClassAttributes[attributeKey]; exists {
447+
// Conflict is resolved by ingClassAttributes, show a warning
448+
t.logger.Info("listener attribute conflict resolved by ingress class",
449+
"attributeKey", attributeKey,
450+
"existingValue", existingAttributeValue,
451+
"newValue", attributeValue,
452+
"ingClassValue", ingClassValue)
453+
} else {
454+
// Conflict is not resolved by ingClassAttributes, return an error
455+
return nil, errors.Errorf("conflicting listener attributes %v: %v | %v for ingress %s/%s",
456+
attributeKey, existingAttributeValue, attributeValue, ing.Ing.Namespace, ing.Ing.Name)
457+
}
439458
}
440459
rawIngGrouplistenerAttributes[attributeKey] = attributeValue
441460
}
442461
}
443-
if len(ingList) > 0 {
444-
ingClassAttributes, err := t.buildIngressClassListenerAttributes(ingList[0].IngClassConfig, listenerProtocol, port)
445-
if err != nil {
446-
return nil, err
447-
}
448-
rawIngGrouplistenerAttributes = algorithm.MergeStringMap(ingClassAttributes, rawIngGrouplistenerAttributes)
449-
}
462+
rawIngGrouplistenerAttributes = algorithm.MergeStringMap(ingClassAttributes, rawIngGrouplistenerAttributes)
450463
attributes := make([]elbv2model.ListenerAttribute, 0, len(rawIngGrouplistenerAttributes))
451464
for attrKey, attrValue := range rawIngGrouplistenerAttributes {
452465
attributes = append(attributes, elbv2model.ListenerAttribute{

pkg/ingress/model_build_listener_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/stretchr/testify/assert"
88
networking "k8s.io/api/networking/v1"
99
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
1011
"sigs.k8s.io/aws-load-balancer-controller/pkg/annotations"
1112
"sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
1213
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
@@ -272,6 +273,89 @@ func Test_buildListenerAttributes(t *testing.T) {
272273
},
273274
},
274275
},
276+
{
277+
name: "Ignore conflicting value when the key is specified by ingress class param",
278+
fields: fields{
279+
ingGroup: Group{
280+
ID: GroupID{Name: "explicit-group"},
281+
Members: []ClassifiedIngress{
282+
{
283+
Ing: &networking.Ingress{
284+
ObjectMeta: metav1.ObjectMeta{
285+
Namespace: "awesome-ns",
286+
Name: "ing-6",
287+
Annotations: map[string]string{
288+
"alb.ingress.kubernetes.io/listen-ports": `[{"HTTP": 80}]`,
289+
"alb.ingress.kubernetes.io/listener-attributes.HTTP-80": "attrKey1=attrValue1",
290+
},
291+
},
292+
},
293+
IngClassConfig: ClassConfiguration{
294+
IngClassParams: &elbv2api.IngressClassParams{
295+
ObjectMeta: metav1.ObjectMeta{
296+
Name: "awesome-class",
297+
},
298+
Spec: elbv2api.IngressClassParamsSpec{
299+
Listeners: []elbv2api.Listener{
300+
{
301+
Protocol: "HTTP",
302+
Port: 80,
303+
ListenerAttributes: []elbv2api.Attribute{
304+
{
305+
Key: "attrKey1",
306+
Value: "attrValue1",
307+
},
308+
},
309+
},
310+
},
311+
},
312+
},
313+
},
314+
},
315+
{
316+
Ing: &networking.Ingress{
317+
ObjectMeta: metav1.ObjectMeta{
318+
Namespace: "awesome-ns",
319+
Name: "ing-7",
320+
Annotations: map[string]string{
321+
"alb.ingress.kubernetes.io/listen-ports": `[{"HTTP": 80}]`,
322+
"alb.ingress.kubernetes.io/listener-attributes.HTTP-80": "attrKey1=attrValue2",
323+
},
324+
},
325+
},
326+
IngClassConfig: ClassConfiguration{
327+
IngClassParams: &elbv2api.IngressClassParams{
328+
ObjectMeta: metav1.ObjectMeta{
329+
Name: "awesome-class",
330+
},
331+
Spec: elbv2api.IngressClassParamsSpec{
332+
Listeners: []elbv2api.Listener{
333+
{
334+
Protocol: "HTTP",
335+
Port: 80,
336+
ListenerAttributes: []elbv2api.Attribute{
337+
{
338+
Key: "attrKey1",
339+
Value: "attrValue1",
340+
},
341+
},
342+
},
343+
},
344+
},
345+
},
346+
},
347+
},
348+
},
349+
},
350+
},
351+
wantErr: false,
352+
wantValue: []elbv2model.ListenerAttribute{
353+
{
354+
Key: "attrKey1",
355+
Value: "attrValue1",
356+
},
357+
},
358+
},
275359
}
276360
for _, tt := range tests {
277361
t.Run(tt.name, func(t *testing.T) {

pkg/ingress/model_build_load_balancer_attributes.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import (
99
// buildIngressGroupLoadBalancerAttributes builds the LB attributes for a group of Ingresses.
1010
func (t *defaultModelBuildTask) buildIngressGroupLoadBalancerAttributes(ingList []ClassifiedIngress) (map[string]string, error) {
1111
ingGroupAttributes := make(map[string]string)
12+
ingClassAttributes := make(map[string]string)
13+
if len(ingList) > 0 {
14+
var err error
15+
ingClassAttributes, err = t.buildIngressClassLoadBalancerAttributes(ingList[0].IngClassConfig)
16+
if err != nil {
17+
return nil, err
18+
}
19+
}
1220
for _, ing := range ingList {
1321
ingAttributes, err := t.buildIngressLoadBalancerAttributes(ing)
1422
if err != nil {
@@ -18,18 +26,22 @@ func (t *defaultModelBuildTask) buildIngressGroupLoadBalancerAttributes(ingList
1826
for attrKey, attrValue := range ingAttributes {
1927
existingAttrValue, exists := ingGroupAttributes[attrKey]
2028
if exists && existingAttrValue != attrValue {
21-
return nil, errors.Errorf("conflicting attributes %v: %v | %v", attrKey, existingAttrValue, attrValue)
29+
if ingClassValue, exists := ingClassAttributes[attrKey]; exists {
30+
// Conflict is resolved by ingClassAttributes, show a warning
31+
t.logger.Info("load balancer attribute conflict resolved by ingress class",
32+
"attributeKey", attrKey,
33+
"existingValue", existingAttrValue,
34+
"newValue", attrValue,
35+
"ingClassValue", ingClassValue)
36+
} else {
37+
// Conflict is not resolved by ingClassAttributes, return an error
38+
return nil, errors.Errorf("conflicting load balancer attributes %v: %v | %v", attrKey, existingAttrValue, attrValue)
39+
}
2240
}
2341
ingGroupAttributes[attrKey] = attrValue
2442
}
2543
}
26-
if len(ingList) > 0 {
27-
ingClassAttributes, err := t.buildIngressClassLoadBalancerAttributes(ingList[0].IngClassConfig)
28-
if err != nil {
29-
return nil, err
30-
}
31-
return algorithm.MergeStringMap(ingClassAttributes, ingGroupAttributes), nil
32-
}
44+
ingGroupAttributes = algorithm.MergeStringMap(ingClassAttributes, ingGroupAttributes)
3345
return ingGroupAttributes, nil
3446
}
3547

pkg/ingress/model_build_load_balancer_attributes_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ package ingress
22

33
import (
44
"fmt"
5+
"testing"
6+
57
"github.com/pkg/errors"
68
"github.com/stretchr/testify/assert"
79
networking "k8s.io/api/networking/v1"
810
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
911
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
1012
"sigs.k8s.io/aws-load-balancer-controller/pkg/annotations"
11-
"testing"
1213
)
1314

1415
func Test_defaultModelBuildTask_buildIngressGroupLoadBalancerAttributes(t *testing.T) {
@@ -82,7 +83,7 @@ func Test_defaultModelBuildTask_buildIngressGroupLoadBalancerAttributes(t *testi
8283
},
8384
},
8485
},
85-
wantErr: errors.New("conflicting attributes deletion_protection.enabled: true | false"),
86+
wantErr: errors.New("conflicting load balancer attributes deletion_protection.enabled: true | false"),
8687
},
8788
{
8889
name: "non-empty annotation attributes from single Ingress, non-empty IngressClass attributes - has overlap attributes",

test/e2e/ingress/vanilla_ingress_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/gavv/httpexpect/v2"
1212
. "github.com/onsi/ginkgo/v2"
1313
. "github.com/onsi/gomega"
14+
"github.com/pkg/errors"
1415
corev1 "k8s.io/api/core/v1"
1516
networking "k8s.io/api/networking/v1"
1617
apierrs "k8s.io/apimachinery/pkg/api/errors"
@@ -777,6 +778,56 @@ var _ = Describe("vanilla ingress tests", func() {
777778
}
778779
})
779780
})
781+
782+
Context("with `alb.ingress.kubernetes.io/listener-attributes.{Protocol}-{Port}` variant settings", func() {
783+
It("with 'alb.ingress.kubernetes.io/listener-attributes.{Protocol}-{Port}' annotation explicitly specified, one ALB shall be created and functional", func() {
784+
appBuilder := manifest.NewFixedResponseServiceBuilder()
785+
ingBuilder := manifest.NewIngressBuilder()
786+
dp, svc := appBuilder.Build(sandboxNS.Name, "app", tf.Options.TestImageRegistry)
787+
ingBackend := networking.IngressBackend{
788+
Service: &networking.IngressServiceBackend{
789+
Name: svc.Name,
790+
Port: networking.ServiceBackendPort{
791+
Number: 80,
792+
},
793+
},
794+
}
795+
annotation := map[string]string{
796+
"kubernetes.io/ingress.class": "alb",
797+
"alb.ingress.kubernetes.io/scheme": "internet-facing",
798+
"alb.ingress.kubernetes.io/listen-ports": `[{"HTTP": 80}]`,
799+
"alb.ingress.kubernetes.io/listener-attributes.HTTP-80": "routing.http.response.server.enabled=false",
800+
}
801+
if tf.Options.IPFamily == "IPv6" {
802+
annotation["alb.ingress.kubernetes.io/ip-address-type"] = "dualstack"
803+
annotation["alb.ingress.kubernetes.io/target-type"] = "ip"
804+
}
805+
ing := ingBuilder.
806+
AddHTTPRoute("", networking.HTTPIngressPath{Path: "/path", PathType: &exact, Backend: ingBackend}).
807+
WithAnnotations(annotation).Build(sandboxNS.Name, "ing")
808+
resStack := fixture.NewK8SResourceStack(tf, dp, svc, ing)
809+
err := resStack.Setup(ctx)
810+
Expect(err).NotTo(HaveOccurred())
811+
812+
defer resStack.TearDown(ctx)
813+
814+
lbARN, lbDNS := ExpectOneLBProvisionedForIngress(ctx, tf, ing)
815+
sdkListeners, err := tf.LBManager.GetLoadBalancerListeners(ctx, lbARN)
816+
817+
Eventually(func() bool {
818+
return verifyListenerAttributes(ctx, tf, *sdkListeners[0].ListenerArn, map[string]string{
819+
"routing.http.response.server.enabled": "false",
820+
}) == nil
821+
}, utils.PollTimeoutShort, utils.PollIntervalMedium).Should(BeTrue())
822+
823+
// test traffic
824+
ExpectLBDNSBeAvailable(ctx, tf, lbARN, lbDNS)
825+
httpExp := httpexpect.New(tf.LoggerReporter, fmt.Sprintf("http://%v", lbDNS))
826+
httpExp.GET("/path").Expect().
827+
Status(http.StatusOK).
828+
Body().Equal("Hello World!")
829+
})
830+
})
780831
})
781832

782833
// ExpectOneLBProvisionedForIngress expects one LoadBalancer provisioned for Ingress.
@@ -820,3 +871,14 @@ func ExpectLBDNSBeAvailable(ctx context.Context, tf *framework.Framework, lbARN
820871
Expect(err).NotTo(HaveOccurred())
821872
tf.Logger.Info("dns becomes available", "dns", lbDNS)
822873
}
874+
875+
func verifyListenerAttributes(ctx context.Context, f *framework.Framework, lsARN string, expectedAttrs map[string]string) error {
876+
lsAttrs, err := f.LBManager.GetListenerAttributes(ctx, lsARN)
877+
Expect(err).NotTo(HaveOccurred())
878+
for _, attr := range lsAttrs {
879+
if val, ok := expectedAttrs[awssdk.ToString(attr.Key)]; ok && val != awssdk.ToString(attr.Value) {
880+
return errors.Errorf("Attribute %v, expected %v, actual %v", awssdk.ToString(attr.Key), val, awssdk.ToString(attr.Value))
881+
}
882+
}
883+
return nil
884+
}

0 commit comments

Comments
 (0)