Skip to content

Commit 0e31423

Browse files
committed
Add InboundCIDRs field to IngressClassParams (kubernetes-sigs#3089)
1 parent 20881ad commit 0e31423

File tree

9 files changed

+273
-4
lines changed

9 files changed

+273
-4
lines changed

apis/elbv2/v1beta1/ingressclassparams_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ type IngressClassParamsSpec struct {
9999
// +optional
100100
Scheme *LoadBalancerScheme `json:"scheme,omitempty"`
101101

102+
// InboundCIDRs specifies the CIDRs that are allowed to access the Ingresses that belong to IngressClass with this IngressClassParams.
103+
// +optional
104+
InboundCIDRs []string `json:"inboundCIDRs,omitempty"`
105+
102106
// SSLPolicy specifies the SSL Policy for all Ingresses that belong to IngressClass with this IngressClassParams.
103107
// +optional
104108
SSLPolicy string `json:"sslPolicy,omitEmpty"`

apis/elbv2/v1beta1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/elbv2.k8s.aws_ingressclassparams.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ spec:
6161
required:
6262
- name
6363
type: object
64+
inboundCIDRs:
65+
description: InboundCIDRs specifies the CIDRs that are allowed to
66+
access the Ingresses that belong to IngressClass with this IngressClassParams.
67+
items:
68+
type: string
69+
type: array
6470
ipAddressType:
6571
description: IPAddressType defines the ip address type for all Ingresses
6672
that belong to IngressClass with this IngressClassParams.

docs/guide/ingress/ingress_class.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ Cluster administrators can use the `scheme` field to restrict the scheme for all
135135
1. If `scheme` specified, all Ingresses with this IngressClass will have the specified scheme.
136136
2. If `scheme` un-specified, Ingresses with this IngressClass can continue to use `alb.ingress.kubernetes.io/scheme annotation` to specify scheme.
137137

138+
#### spec.inboundCIDRs
139+
140+
Cluster administrators can use the optional `inboundCIDRs` field to specify the CIDRs that are allowed to access the load balancers that belong to this IngressClass.
141+
If the field is specified, LBC will ignore the `alb.ingress.kubernetes.io/inbound-cidrs` annotation.
142+
138143
#### spec.sslPolicy
139144

140145
Cluster administrators can use the optional `sslPolicy` field to specify the SSL policy for the load balancers that belong to this IngressClass.

helm/aws-load-balancer-controller/crds/crds.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ spec:
6060
required:
6161
- name
6262
type: object
63+
inboundCIDRs:
64+
description: InboundCIDRs specifies the CIDRs that are allowed to
65+
access the Ingresses that belong to IngressClass with this IngressClassParams.
66+
items:
67+
type: string
68+
type: array
6369
ipAddressType:
6470
description: IPAddressType defines the ip address type for all Ingresses
6571
that belong to IngressClass with this IngressClassParams.

pkg/ingress/model_build_listener.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ type listenPortConfig struct {
107107
func (t *defaultModelBuildTask) computeIngressListenPortConfigByPort(ctx context.Context, ing *ClassifiedIngress) (map[int64]listenPortConfig, error) {
108108
explicitTLSCertARNs := t.computeIngressExplicitTLSCertARNs(ctx, ing.Ing)
109109
explicitSSLPolicy := t.computeIngressExplicitSSLPolicy(ctx, ing)
110-
inboundCIDRv4s, inboundCIDRV6s, err := t.computeIngressExplicitInboundCIDRs(ctx, ing.Ing)
110+
inboundCIDRv4s, inboundCIDRV6s, err := t.computeIngressExplicitInboundCIDRs(ctx, ing)
111111
if err != nil {
112112
return nil, err
113113
}
@@ -209,15 +209,24 @@ func (t *defaultModelBuildTask) computeIngressListenPorts(_ context.Context, ing
209209
return portAndProtocols, nil
210210
}
211211

212-
func (t *defaultModelBuildTask) computeIngressExplicitInboundCIDRs(_ context.Context, ing *networking.Ingress) ([]string, []string, error) {
212+
func (t *defaultModelBuildTask) computeIngressExplicitInboundCIDRs(_ context.Context, ing *ClassifiedIngress) ([]string, []string, error) {
213213
var rawInboundCIDRs []string
214-
_ = t.annotationParser.ParseStringSliceAnnotation(annotations.IngressSuffixInboundCIDRs, &rawInboundCIDRs, ing.Annotations)
214+
fromIngressClassParams := false
215+
if ing.IngClassConfig.IngClassParams != nil && len(ing.IngClassConfig.IngClassParams.Spec.InboundCIDRs) != 0 {
216+
rawInboundCIDRs = ing.IngClassConfig.IngClassParams.Spec.InboundCIDRs
217+
fromIngressClassParams = true
218+
} else {
219+
_ = t.annotationParser.ParseStringSliceAnnotation(annotations.IngressSuffixInboundCIDRs, &rawInboundCIDRs, ing.Ing.Annotations)
220+
}
215221

216222
var inboundCIDRv4s, inboundCIDRv6s []string
217223
for _, cidr := range rawInboundCIDRs {
218224
_, _, err := net.ParseCIDR(cidr)
219225
if err != nil {
220-
return nil, nil, errors.Wrapf(err, "invalid %v settings on Ingress: %v", annotations.IngressSuffixInboundCIDRs, k8s.NamespacedName(ing))
226+
if fromIngressClassParams {
227+
return nil, nil, fmt.Errorf("invalid CIDR in IngressClassParams InboundCIDR %s: %w", cidr, err)
228+
}
229+
return nil, nil, fmt.Errorf("invalid %v settings on Ingress: %v: %w", annotations.IngressSuffixInboundCIDRs, k8s.NamespacedName(ing.Ing), err)
221230
}
222231
if strings.Contains(cidr, ":") {
223232
inboundCIDRv6s = append(inboundCIDRv6s, cidr)

pkg/ingress/model_builder_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,6 +1474,132 @@ func Test_defaultModelBuilder_Build(t *testing.T) {
14741474
"ns-1/ing-1-svc-3:https": null
14751475
}
14761476
}
1477+
}`,
1478+
},
1479+
{
1480+
name: "Ingress - inboundCIDRs in IngressClassParams",
1481+
env: env{
1482+
svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3},
1483+
},
1484+
fields: fields{
1485+
resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternalLB},
1486+
listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB},
1487+
enableBackendSG: true,
1488+
},
1489+
args: args{
1490+
ingGroup: Group{
1491+
ID: GroupID{Namespace: "ns-1", Name: "ing-1"},
1492+
Members: []ClassifiedIngress{
1493+
{
1494+
IngClassConfig: ClassConfiguration{
1495+
IngClassParams: &v1beta1.IngressClassParams{
1496+
Spec: v1beta1.IngressClassParamsSpec{
1497+
InboundCIDRs: []string{
1498+
"10.0.0.0/8",
1499+
"172.16.0.0/12",
1500+
},
1501+
},
1502+
},
1503+
},
1504+
Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{
1505+
Namespace: "ns-1",
1506+
Name: "ing-1",
1507+
Annotations: map[string]string{
1508+
"alb.ingress.kubernetes.io/inbound-cidrs": "20.0.0.0/8",
1509+
},
1510+
},
1511+
Spec: networking.IngressSpec{
1512+
Rules: []networking.IngressRule{
1513+
{
1514+
Host: "app-1.example.com",
1515+
IngressRuleValue: networking.IngressRuleValue{
1516+
HTTP: &networking.HTTPIngressRuleValue{
1517+
Paths: []networking.HTTPIngressPath{
1518+
{
1519+
Path: "/svc-1",
1520+
Backend: networking.IngressBackend{
1521+
Service: &networking.IngressServiceBackend{
1522+
Name: ns_1_svc_1.Name,
1523+
Port: networking.ServiceBackendPort{
1524+
Name: "http",
1525+
},
1526+
},
1527+
},
1528+
},
1529+
{
1530+
Path: "/svc-2",
1531+
Backend: networking.IngressBackend{
1532+
Service: &networking.IngressServiceBackend{
1533+
Name: ns_1_svc_2.Name,
1534+
Port: networking.ServiceBackendPort{
1535+
Name: "http",
1536+
},
1537+
},
1538+
},
1539+
},
1540+
},
1541+
},
1542+
},
1543+
},
1544+
{
1545+
Host: "app-2.example.com",
1546+
IngressRuleValue: networking.IngressRuleValue{
1547+
HTTP: &networking.HTTPIngressRuleValue{
1548+
Paths: []networking.HTTPIngressPath{
1549+
{
1550+
Path: "/svc-3",
1551+
Backend: networking.IngressBackend{
1552+
Service: &networking.IngressServiceBackend{
1553+
Name: ns_1_svc_3.Name,
1554+
Port: networking.ServiceBackendPort{
1555+
Name: "https",
1556+
},
1557+
},
1558+
},
1559+
},
1560+
},
1561+
},
1562+
},
1563+
},
1564+
},
1565+
},
1566+
},
1567+
},
1568+
},
1569+
},
1570+
},
1571+
wantStackPatch: `
1572+
{
1573+
"resources": {
1574+
"AWS::EC2::SecurityGroup": {
1575+
"ManagedLBSecurityGroup": {
1576+
"spec": {
1577+
"ingress": [
1578+
{
1579+
"fromPort": 80,
1580+
"ipProtocol": "tcp",
1581+
"ipRanges": [
1582+
{
1583+
"cidrIP": "10.0.0.0/8"
1584+
}
1585+
],
1586+
"toPort": 80
1587+
},
1588+
{
1589+
"fromPort": 80,
1590+
"ipProtocol": "tcp",
1591+
"ipRanges": [
1592+
{
1593+
"cidrIP": "172.16.0.0/12"
1594+
}
1595+
],
1596+
"toPort": 80
1597+
}
1598+
]
1599+
}
1600+
}
1601+
}
1602+
}
14771603
}`,
14781604
},
14791605
{

webhooks/elbv2/ingressclassparams_validator.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package elbv2
22

33
import (
44
"context"
5+
"fmt"
6+
"net"
7+
"strings"
58

69
"k8s.io/apimachinery/pkg/runtime"
710
"k8s.io/apimachinery/pkg/util/validation/field"
@@ -30,6 +33,7 @@ func (v *ingressClassParamsValidator) Prototype(_ admission.Request) (runtime.Ob
3033
func (v *ingressClassParamsValidator) ValidateCreate(ctx context.Context, obj runtime.Object) error {
3134
icp := obj.(*elbv2api.IngressClassParams)
3235
allErrs := field.ErrorList{}
36+
allErrs = append(allErrs, v.checkInboundCIDRs(icp)...)
3337
allErrs = append(allErrs, v.checkSubnetSelectors(icp)...)
3438

3539
return allErrs.ToAggregate()
@@ -38,6 +42,7 @@ func (v *ingressClassParamsValidator) ValidateCreate(ctx context.Context, obj ru
3842
func (v *ingressClassParamsValidator) ValidateUpdate(ctx context.Context, obj runtime.Object, oldObj runtime.Object) error {
3943
icp := obj.(*elbv2api.IngressClassParams)
4044
allErrs := field.ErrorList{}
45+
allErrs = append(allErrs, v.checkInboundCIDRs(icp)...)
4146
allErrs = append(allErrs, v.checkSubnetSelectors(icp)...)
4247

4348
return allErrs.ToAggregate()
@@ -47,6 +52,43 @@ func (v *ingressClassParamsValidator) ValidateDelete(ctx context.Context, obj ru
4752
return nil
4853
}
4954

55+
// checkInboundCIDRs will check for valid inboundCIDRs.
56+
func (v *ingressClassParamsValidator) checkInboundCIDRs(icp *elbv2api.IngressClassParams) (allErrs field.ErrorList) {
57+
for idx, cidr := range icp.Spec.InboundCIDRs {
58+
fieldPath := field.NewPath("spec", "inboundCIDRs").Index(idx)
59+
allErrs = append(allErrs, validateCIDR(cidr, fieldPath)...)
60+
}
61+
62+
return allErrs
63+
}
64+
65+
// validateCIDR will check for a valid CIDR.
66+
func validateCIDR(cidr string, fieldPath *field.Path) field.ErrorList {
67+
allErrs := field.ErrorList{}
68+
69+
ip, ipNet, err := net.ParseCIDR(cidr)
70+
if err != nil {
71+
detail := "Could not be parsed as a CIDR"
72+
if !strings.Contains(cidr, "/") {
73+
ip := net.ParseIP(cidr)
74+
if ip != nil {
75+
if ip.To4() != nil && !strings.Contains(cidr, ":") {
76+
detail += fmt.Sprintf(" (did you mean \"%s/32\")", cidr)
77+
} else {
78+
detail += fmt.Sprintf(" (did you mean \"%s/64\")", cidr)
79+
}
80+
}
81+
}
82+
allErrs = append(allErrs, field.Invalid(fieldPath, cidr, detail))
83+
} else if !ip.Equal(ipNet.IP) {
84+
maskSize, _ := ipNet.Mask.Size()
85+
detail := fmt.Sprintf("Network contains bits outside prefix (did you mean \"%s/%d\")", ipNet.IP, maskSize)
86+
allErrs = append(allErrs, field.Invalid(fieldPath, cidr, detail))
87+
}
88+
89+
return allErrs
90+
}
91+
5092
// checkSubnetSelectors will check for valid SubnetSelectors
5193
func (v *ingressClassParamsValidator) checkSubnetSelectors(icp *elbv2api.IngressClassParams) (allErrs field.ErrorList) {
5294
if icp.Spec.Subnets != nil {

webhooks/elbv2/ingressclassparams_validator_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,72 @@ func Test_ingressClassParamsValidator_ValidateCreate(t *testing.T) {
1818
name: "empty",
1919
obj: &elbv2api.IngressClassParams{},
2020
},
21+
{
22+
name: "inboundCIDRs is valid CIDR list",
23+
obj: &elbv2api.IngressClassParams{
24+
Spec: elbv2api.IngressClassParamsSpec{
25+
InboundCIDRs: []string{
26+
"10.0.0.0/8",
27+
"2001:DB8::/32",
28+
},
29+
},
30+
},
31+
},
32+
{
33+
name: "inboundCIDRs IPv4 no length",
34+
obj: &elbv2api.IngressClassParams{
35+
Spec: elbv2api.IngressClassParamsSpec{
36+
InboundCIDRs: []string{
37+
"192.168.0.1",
38+
},
39+
},
40+
},
41+
wantErr: "spec.inboundCIDRs[0]: Invalid value: \"192.168.0.1\": Could not be parsed as a CIDR (did you mean \"192.168.0.1/32\")",
42+
},
43+
{
44+
name: "inboundCIDRs IPv6 no length",
45+
obj: &elbv2api.IngressClassParams{
46+
Spec: elbv2api.IngressClassParamsSpec{
47+
InboundCIDRs: []string{
48+
"2001:DB8::",
49+
},
50+
},
51+
},
52+
wantErr: "spec.inboundCIDRs[0]: Invalid value: \"2001:DB8::\": Could not be parsed as a CIDR (did you mean \"2001:DB8::/64\")",
53+
},
54+
{
55+
name: "inboundCIDRs bits outside prefix",
56+
obj: &elbv2api.IngressClassParams{
57+
Spec: elbv2api.IngressClassParamsSpec{
58+
InboundCIDRs: []string{
59+
"10.128.0.0/8",
60+
},
61+
},
62+
},
63+
wantErr: "spec.inboundCIDRs[0]: Invalid value: \"10.128.0.0/8\": Network contains bits outside prefix (did you mean \"10.0.0.0/8\")",
64+
},
65+
{
66+
name: "inboundCIDRs empty string",
67+
obj: &elbv2api.IngressClassParams{
68+
Spec: elbv2api.IngressClassParamsSpec{
69+
InboundCIDRs: []string{
70+
"",
71+
},
72+
},
73+
},
74+
wantErr: "spec.inboundCIDRs[0]: Invalid value: \"\": Could not be parsed as a CIDR",
75+
},
76+
{
77+
name: "inboundCIDRs domain",
78+
obj: &elbv2api.IngressClassParams{
79+
Spec: elbv2api.IngressClassParamsSpec{
80+
InboundCIDRs: []string{
81+
"invalid.example.com",
82+
},
83+
},
84+
},
85+
wantErr: "spec.inboundCIDRs[0]: Invalid value: \"invalid.example.com\": Could not be parsed as a CIDR",
86+
},
2187
{
2288
name: "subnet is valid ID list",
2389
obj: &elbv2api.IngressClassParams{

0 commit comments

Comments
 (0)