diff --git a/.ko.yaml b/.ko.yaml index ca3fa6cb29..cb5940edab 100644 --- a/.ko.yaml +++ b/.ko.yaml @@ -1,4 +1,4 @@ -defaultBaseImage: public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-nonroot:2022-07-27-1658910674.2 +defaultBaseImage: public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-nonroot:2023-02-22-1677092456.2 builds: - env: - CGO_ENABLED=0 diff --git a/Makefile b/Makefile index 053aa090b7..26d0dbc738 100644 --- a/Makefile +++ b/Makefile @@ -162,13 +162,17 @@ lint: echo "TODO" .PHONY: quick-ci -quick-ci: verify-versions verify-generate +quick-ci: verify-versions verify-generate verify-crds echo "Done!" .PHONY: verify-generate verify-generate: hack/verify-generate.sh +.PHONY: verify-crds +verify-crds: + hack/verify-crds.sh + .PHONY: verify-versions verify-versions: hack/verify-versions.sh diff --git a/controllers/elbv2/eventhandlers/endpointslices.go b/controllers/elbv2/eventhandlers/endpointslices.go index ba2467c861..b11a65d82d 100644 --- a/controllers/elbv2/eventhandlers/endpointslices.go +++ b/controllers/elbv2/eventhandlers/endpointslices.go @@ -5,7 +5,7 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" - discv1 "k8s.io/api/discovery/v1beta1" + discv1 "k8s.io/api/discovery/v1" "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" diff --git a/controllers/elbv2/eventhandlers/endpointslices_test.go b/controllers/elbv2/eventhandlers/endpointslices_test.go index d10b2b569a..5c1335b352 100644 --- a/controllers/elbv2/eventhandlers/endpointslices_test.go +++ b/controllers/elbv2/eventhandlers/endpointslices_test.go @@ -8,7 +8,7 @@ import ( "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" - discv1 "k8s.io/api/discovery/v1beta1" + discv1 "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" diff --git a/controllers/elbv2/targetgroupbinding_controller.go b/controllers/elbv2/targetgroupbinding_controller.go index d365766027..c263bbb545 100644 --- a/controllers/elbv2/targetgroupbinding_controller.go +++ b/controllers/elbv2/targetgroupbinding_controller.go @@ -24,7 +24,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" - discv1 "k8s.io/api/discovery/v1beta1" + discv1 "k8s.io/api/discovery/v1" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" "sigs.k8s.io/aws-load-balancer-controller/controllers/elbv2/eventhandlers" diff --git a/docs/install/iam_policy.json b/docs/install/iam_policy.json index a8d47c8ba6..7944f2a128 100644 --- a/docs/install/iam_policy.json +++ b/docs/install/iam_policy.json @@ -196,6 +196,28 @@ } } }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags" + ], + "Resource": [ + "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "StringEquals": { + "elasticloadbalancing:CreateAction": [ + "CreateTargetGroup", + "CreateLoadBalancer" + ] + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, { "Effect": "Allow", "Action": [ diff --git a/docs/install/iam_policy_cn.json b/docs/install/iam_policy_cn.json index f545a2a351..a0d5edd5c0 100644 --- a/docs/install/iam_policy_cn.json +++ b/docs/install/iam_policy_cn.json @@ -177,6 +177,28 @@ "arn:aws-cn:elasticloadbalancing:*:*:listener-rule/app/*/*/*" ] }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags" + ], + "Resource": [ + "arn:aws-cn:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws-cn:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws-cn:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "StringEquals": { + "elasticloadbalancing:CreateAction": [ + "CreateTargetGroup", + "CreateLoadBalancer" + ] + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, { "Effect": "Allow", "Action": [ diff --git a/docs/install/iam_policy_us-gov.json b/docs/install/iam_policy_us-gov.json index f6acb4eade..85a4ba2142 100644 --- a/docs/install/iam_policy_us-gov.json +++ b/docs/install/iam_policy_us-gov.json @@ -177,6 +177,28 @@ "arn:aws-us-gov:elasticloadbalancing:*:*:listener-rule/app/*/*/*" ] }, + { + "Effect": "Allow", + "Action": [ + "elasticloadbalancing:AddTags" + ], + "Resource": [ + "arn:aws-us-gov:elasticloadbalancing:*:*:targetgroup/*/*", + "arn:aws-us-gov:elasticloadbalancing:*:*:loadbalancer/net/*/*", + "arn:aws-us-gov:elasticloadbalancing:*:*:loadbalancer/app/*/*" + ], + "Condition": { + "StringEquals": { + "elasticloadbalancing:CreateAction": [ + "CreateTargetGroup", + "CreateLoadBalancer" + ] + }, + "Null": { + "aws:RequestTag/elbv2.k8s.aws/cluster": "false" + } + } + }, { "Effect": "Allow", "Action": [ diff --git a/go.mod b/go.mod index afa402f801..e379ce1147 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.44.184 + github.com/evanphx/json-patch v5.6.0+incompatible github.com/gavv/httpexpect/v2 v2.9.0 github.com/go-logr/logr v1.2.3 github.com/golang/mock v1.6.0 @@ -23,6 +24,7 @@ require ( k8s.io/cli-runtime v0.26.1 k8s.io/client-go v0.26.1 sigs.k8s.io/controller-runtime v0.14.1 + sigs.k8s.io/yaml v1.3.0 ) require ( @@ -50,7 +52,6 @@ require ( github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/color v1.7.0 // indirect @@ -160,5 +161,4 @@ require ( sigs.k8s.io/kustomize/api v0.12.1 // indirect sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/hack/verify-crds.sh b/hack/verify-crds.sh new file mode 100755 index 0000000000..6e7b13127a --- /dev/null +++ b/hack/verify-crds.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# Copyright 2023 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +make crds + +changed_files=$(git status --porcelain --untracked-files=no || true) +if [ -n "${changed_files}" ]; then + echo "Detected that CRD generation is needed; run 'make crds'" + echo "changed files:" + printf "%s\n" "${changed_files}" + echo "git diff:" + git --no-pager diff + echo "To fix: run 'make crds'" + exit 1 +fi diff --git a/helm/aws-load-balancer-controller/crds/crds.yaml b/helm/aws-load-balancer-controller/crds/crds.yaml index 62169e27c9..8e036901eb 100644 --- a/helm/aws-load-balancer-controller/crds/crds.yaml +++ b/helm/aws-load-balancer-controller/crds/crds.yaml @@ -36,10 +36,14 @@ spec: description: IngressClassParams is the Schema for the IngressClassParams API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -47,7 +51,8 @@ spec: description: IngressClassParamsSpec defines the desired state of IngressClassParams properties: group: - description: Group defines the IngressGroup for all Ingresses that belong to IngressClass with this IngressClassParams. + description: Group defines the IngressGroup for all Ingresses that + belong to IngressClass with this IngressClassParams. properties: name: description: Name is the name of IngressGroup. @@ -56,13 +61,16 @@ spec: - name type: object ipAddressType: - description: IPAddressType defines the ip address type for all Ingresses that belong to IngressClass with this IngressClassParams. + description: IPAddressType defines the ip address type for all Ingresses + that belong to IngressClass with this IngressClassParams. enum: - ipv4 - dualstack type: string loadBalancerAttributes: - description: LoadBalancerAttributes define the custom attributes to LoadBalancers for all Ingress that that belong to IngressClass with this IngressClassParams. + description: LoadBalancerAttributes define the custom attributes to + LoadBalancers for all Ingress that that belong to IngressClass with + this IngressClassParams. items: description: Attributes defines custom attributes on resources. properties: @@ -78,21 +86,33 @@ spec: type: object type: array namespaceSelector: - description: NamespaceSelector restrict the namespaces of Ingresses that are allowed to specify the IngressClass with this IngressClassParams. * if absent or present but empty, it selects all namespaces. + description: NamespaceSelector restrict the namespaces of Ingresses + that are allowed to specify the IngressClass with this IngressClassParams. + * if absent or present but empty, it selects all namespaces. properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector applies + to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -104,21 +124,28 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. type: object type: object x-kubernetes-map-type: atomic scheme: - description: Scheme defines the scheme for all Ingresses that belong to IngressClass with this IngressClassParams. + description: Scheme defines the scheme for all Ingresses that belong + to IngressClass with this IngressClassParams. enum: - internal - internet-facing type: string subnets: - description: Subnets defines the subnets for all Ingresses that belong to IngressClass with this IngressClassParams. + description: Subnets defines the subnets for all Ingresses that belong + to IngressClass with this IngressClassParams. properties: ids: - description: IDs specify the resource IDs of subnets. Exactly one of this or `tags` must be specified. + description: IDs specify the resource IDs of subnets. Exactly + one of this or `tags` must be specified. items: description: SubnetID specifies a subnet ID. pattern: subnet-[0-9a-f]+ @@ -130,11 +157,15 @@ spec: items: type: string type: array - description: Tags specifies subnets in the load balancer's VPC where each tag specified in the map key contains one of the values in the corresponding value list. Exactly one of this or `ids` must be specified. + description: Tags specifies subnets in the load balancer's VPC + where each tag specified in the map key contains one of the + values in the corresponding value list. Exactly one of this + or `ids` must be specified. type: object type: object tags: - description: Tags defines list of Tags on AWS resources provisioned for Ingresses that belong to IngressClass with this IngressClassParams. + description: Tags defines list of Tags on AWS resources provisioned + for Ingresses that belong to IngressClass with this IngressClassParams. items: description: Tag defines a AWS Tag on resources. properties: @@ -198,10 +229,14 @@ spec: description: TargetGroupBinding is the Schema for the TargetGroupBinding API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -209,28 +244,37 @@ spec: description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding properties: networking: - description: networking provides the networking setup for ELBV2 LoadBalancer to access targets in TargetGroup. + description: networking provides the networking setup for ELBV2 LoadBalancer + to access targets in TargetGroup. properties: ingress: - description: List of ingress rules to allow ELBV2 LoadBalancer to access targets in TargetGroup. + description: List of ingress rules to allow ELBV2 LoadBalancer + to access targets in TargetGroup. items: properties: from: - description: List of peers which should be able to access the targets in TargetGroup. At least one NetworkingPeer should be specified. + description: List of peers which should be able to access + the targets in TargetGroup. At least one NetworkingPeer + should be specified. items: - description: NetworkingPeer defines the source/destination peer for networking rules. + description: NetworkingPeer defines the source/destination + peer for networking rules. properties: ipBlock: - description: IPBlock defines an IPBlock peer. If specified, none of the other fields can be set. + description: IPBlock defines an IPBlock peer. If specified, + none of the other fields can be set. properties: cidr: - description: CIDR is the network CIDR. Both IPV4 or IPV6 CIDR are accepted. + description: CIDR is the network CIDR. Both IPV4 + or IPV6 CIDR are accepted. type: string required: - cidr type: object securityGroup: - description: SecurityGroup defines a SecurityGroup peer. If specified, none of the other fields can be set. + description: SecurityGroup defines a SecurityGroup + peer. If specified, none of the other fields can + be set. properties: groupID: description: GroupID is the EC2 SecurityGroupID. @@ -241,17 +285,25 @@ spec: type: object type: array ports: - description: List of ports which should be made accessible on the targets in TargetGroup. If ports is empty or unspecified, it defaults to all ports with TCP. + description: List of ports which should be made accessible + on the targets in TargetGroup. If ports is empty or unspecified, + it defaults to all ports with TCP. items: properties: port: anyOf: - type: integer - type: string - description: The port which traffic must match. When NodePort endpoints(instance TargetType) is used, this must be a numerical port. When Port endpoints(ip TargetType) is used, this can be either numerical or named port on pods. if port is unspecified, it defaults to all ports. + description: The port which traffic must match. When + NodePort endpoints(instance TargetType) is used, + this must be a numerical port. When Port endpoints(ip + TargetType) is used, this can be either numerical + or named port on pods. if port is unspecified, it + defaults to all ports. x-kubernetes-int-or-string: true protocol: - description: The protocol which traffic must match. If protocol is unspecified, it defaults to TCP. + description: The protocol which traffic must match. + If protocol is unspecified, it defaults to TCP. enum: - TCP - UDP @@ -265,7 +317,8 @@ spec: type: array type: object serviceRef: - description: serviceRef is a reference to a Kubernetes Service and ServicePort. + description: serviceRef is a reference to a Kubernetes Service and + ServicePort. properties: name: description: Name is the name of the Service. @@ -281,10 +334,12 @@ spec: - port type: object targetGroupARN: - description: targetGroupARN is the Amazon Resource Name (ARN) for the TargetGroup. + description: targetGroupARN is the Amazon Resource Name (ARN) for + the TargetGroup. type: string targetType: - description: targetType is the TargetType of TargetGroup. If unspecified, it will be automatically inferred. + description: targetType is the TargetType of TargetGroup. If unspecified, + it will be automatically inferred. enum: - instance - ip @@ -333,10 +388,14 @@ spec: description: TargetGroupBinding is the Schema for the TargetGroupBinding API properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -344,35 +403,46 @@ spec: description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding properties: ipAddressType: - description: ipAddressType specifies whether the target group is of type IPv4 or IPv6. If unspecified, it will be automatically inferred. + description: ipAddressType specifies whether the target group is of + type IPv4 or IPv6. If unspecified, it will be automatically inferred. enum: - ipv4 - ipv6 type: string networking: - description: networking defines the networking rules to allow ELBV2 LoadBalancer to access targets in TargetGroup. + description: networking defines the networking rules to allow ELBV2 + LoadBalancer to access targets in TargetGroup. properties: ingress: - description: List of ingress rules to allow ELBV2 LoadBalancer to access targets in TargetGroup. + description: List of ingress rules to allow ELBV2 LoadBalancer + to access targets in TargetGroup. items: - description: NetworkingIngressRule defines a particular set of traffic that is allowed to access TargetGroup's targets. + description: NetworkingIngressRule defines a particular set + of traffic that is allowed to access TargetGroup's targets. properties: from: - description: List of peers which should be able to access the targets in TargetGroup. At least one NetworkingPeer should be specified. + description: List of peers which should be able to access + the targets in TargetGroup. At least one NetworkingPeer + should be specified. items: - description: NetworkingPeer defines the source/destination peer for networking rules. + description: NetworkingPeer defines the source/destination + peer for networking rules. properties: ipBlock: - description: IPBlock defines an IPBlock peer. If specified, none of the other fields can be set. + description: IPBlock defines an IPBlock peer. If specified, + none of the other fields can be set. properties: cidr: - description: CIDR is the network CIDR. Both IPV4 or IPV6 CIDR are accepted. + description: CIDR is the network CIDR. Both IPV4 + or IPV6 CIDR are accepted. type: string required: - cidr type: object securityGroup: - description: SecurityGroup defines a SecurityGroup peer. If specified, none of the other fields can be set. + description: SecurityGroup defines a SecurityGroup + peer. If specified, none of the other fields can + be set. properties: groupID: description: GroupID is the EC2 SecurityGroupID. @@ -383,18 +453,27 @@ spec: type: object type: array ports: - description: List of ports which should be made accessible on the targets in TargetGroup. If ports is empty or unspecified, it defaults to all ports with TCP. + description: List of ports which should be made accessible + on the targets in TargetGroup. If ports is empty or unspecified, + it defaults to all ports with TCP. items: - description: NetworkingPort defines the port and protocol for networking rules. + description: NetworkingPort defines the port and protocol + for networking rules. properties: port: anyOf: - type: integer - type: string - description: The port which traffic must match. When NodePort endpoints(instance TargetType) is used, this must be a numerical port. When Port endpoints(ip TargetType) is used, this can be either numerical or named port on pods. if port is unspecified, it defaults to all ports. + description: The port which traffic must match. When + NodePort endpoints(instance TargetType) is used, + this must be a numerical port. When Port endpoints(ip + TargetType) is used, this can be either numerical + or named port on pods. if port is unspecified, it + defaults to all ports. x-kubernetes-int-or-string: true protocol: - description: The protocol which traffic must match. If protocol is unspecified, it defaults to TCP. + description: The protocol which traffic must match. + If protocol is unspecified, it defaults to TCP. enum: - TCP - UDP @@ -408,21 +487,32 @@ spec: type: array type: object nodeSelector: - description: node selector for instance type target groups to only register certain nodes + description: node selector for instance type target groups to only + register certain nodes properties: matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. properties: key: - description: key is the label key that the selector applies to. + description: key is the label key that the selector applies + to. type: string operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. type: string values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. items: type: string type: array @@ -434,12 +524,17 @@ spec: matchLabels: additionalProperties: type: string - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. type: object type: object x-kubernetes-map-type: atomic serviceRef: - description: serviceRef is a reference to a Kubernetes Service and ServicePort. + description: serviceRef is a reference to a Kubernetes Service and + ServicePort. properties: name: description: Name is the name of the Service. @@ -455,11 +550,13 @@ spec: - port type: object targetGroupARN: - description: targetGroupARN is the Amazon Resource Name (ARN) for the TargetGroup. + description: targetGroupARN is the Amazon Resource Name (ARN) for + the TargetGroup. minLength: 1 type: string targetType: - description: targetType is the TargetType of TargetGroup. If unspecified, it will be automatically inferred. + description: targetType is the TargetType of TargetGroup. If unspecified, + it will be automatically inferred. enum: - instance - ip diff --git a/pkg/backend/endpoint_resolver.go b/pkg/backend/endpoint_resolver.go index 5fbe1f01c7..7212938bbb 100644 --- a/pkg/backend/endpoint_resolver.go +++ b/pkg/backend/endpoint_resolver.go @@ -8,7 +8,7 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" - discovery "k8s.io/api/discovery/v1beta1" + discovery "k8s.io/api/discovery/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" diff --git a/pkg/backend/endpoint_resolver_test.go b/pkg/backend/endpoint_resolver_test.go index dcbc2ee9c8..88eb68f173 100644 --- a/pkg/backend/endpoint_resolver_test.go +++ b/pkg/backend/endpoint_resolver_test.go @@ -25,7 +25,7 @@ import ( "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" - discovery "k8s.io/api/discovery/v1beta1" + discovery "k8s.io/api/discovery/v1" ) func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) { diff --git a/pkg/backend/endpoint_types.go b/pkg/backend/endpoint_types.go index b006bacb90..76d017bd5f 100644 --- a/pkg/backend/endpoint_types.go +++ b/pkg/backend/endpoint_types.go @@ -2,7 +2,7 @@ package backend import ( corev1 "k8s.io/api/core/v1" - discv1 "k8s.io/api/discovery/v1beta1" + discv1 "k8s.io/api/discovery/v1" "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/aws-load-balancer-controller/pkg/k8s" ) diff --git a/pkg/ingress/config_types.go b/pkg/ingress/config_types.go index d90d59aa37..6e2da610e7 100644 --- a/pkg/ingress/config_types.go +++ b/pkg/ingress/config_types.go @@ -322,7 +322,7 @@ type RuleCondition struct { SourceIPConfig *SourceIPConditionConfig `json:"sourceIPConfig"` } -func (c *RuleCondition) validate() error { +func (c *RuleCondition) Validate() error { switch c.Field { case RuleConditionFieldHostHeader: if c.HostHeaderConfig == nil { diff --git a/pkg/ingress/enhanced_backend_builder.go b/pkg/ingress/enhanced_backend_builder.go index ea23922444..c9a0ba14f2 100644 --- a/pkg/ingress/enhanced_backend_builder.go +++ b/pkg/ingress/enhanced_backend_builder.go @@ -165,7 +165,7 @@ func (b *defaultEnhancedBackendBuilder) buildConditions(_ context.Context, ingAn return nil, err } for _, condition := range conditions { - if err := condition.validate(); err != nil { + if err := condition.Validate(); err != nil { return nil, err } } diff --git a/pkg/ingress/model_builder_test.go b/pkg/ingress/model_builder_test.go index d95cc105ef..234562cb92 100644 --- a/pkg/ingress/model_builder_test.go +++ b/pkg/ingress/model_builder_test.go @@ -2,16 +2,19 @@ package ingress import ( "context" + "encoding/json" "testing" "time" awssdk "github.com/aws/aws-sdk-go/aws" ec2sdk "github.com/aws/aws-sdk-go/service/ec2" elbv2sdk "github.com/aws/aws-sdk-go/service/elbv2" + jsonpatch "github.com/evanphx/json-patch" "github.com/go-logr/logr" "github.com/golang/mock/gomock" "github.com/pkg/errors" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" networking "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -29,262 +32,10 @@ import ( networkingpkg "sigs.k8s.io/aws-load-balancer-controller/pkg/networking" testclient "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/yaml" ) -func Test_defaultModelBuilder_Build(t *testing.T) { - type resolveViaDiscoveryCall struct { - subnets []*ec2sdk.Subnet - err error - } - type env struct { - svcs []*corev1.Service - } - type listLoadBalancersCall struct { - matchedLBs []elbv2.LoadBalancerWithTags - err error - } - type describeSecurityGroupsResult struct { - securityGroups []*ec2sdk.SecurityGroup - err error - } - type fields struct { - resolveViaDiscoveryCalls []resolveViaDiscoveryCall - listLoadBalancersCalls []listLoadBalancersCall - describeSecurityGroupsResult []describeSecurityGroupsResult - backendSecurityGroup string - enableBackendSG bool - } - type args struct { - ingGroup Group - } - - ns_1_svc_1 := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "svc-1", - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "http", - Port: 80, - TargetPort: intstr.FromInt(8080), - NodePort: 32768, - }, - }, - }, - } - ns_1_svc_2 := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "svc-2", - Annotations: map[string]string{ - "alb.ingress.kubernetes.io/target-type": "instance", - "alb.ingress.kubernetes.io/backend-protocol": "HTTP", - "alb.ingress.kubernetes.io/healthcheck-protocol": "HTTP", - "alb.ingress.kubernetes.io/healthcheck-port": "traffic-port", - "alb.ingress.kubernetes.io/healthcheck-path": "/", - "alb.ingress.kubernetes.io/healthcheck-interval-seconds": "15", - "alb.ingress.kubernetes.io/healthcheck-timeout-seconds": "5", - "alb.ingress.kubernetes.io/healthy-threshold-count": "2", - "alb.ingress.kubernetes.io/unhealthy-threshold-count": "2", - "alb.ingress.kubernetes.io/success-codes": "200", - }, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "http", - Port: 80, - TargetPort: intstr.FromInt(8080), - NodePort: 32768, - }, - }, - }, - } - ns_1_svc_3 := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "svc-3", - Annotations: map[string]string{ - "alb.ingress.kubernetes.io/target-type": "ip", - "alb.ingress.kubernetes.io/backend-protocol": "HTTPS", - "alb.ingress.kubernetes.io/healthcheck-protocol": "HTTPS", - "alb.ingress.kubernetes.io/healthcheck-port": "9090", - "alb.ingress.kubernetes.io/healthcheck-path": "/health-check", - "alb.ingress.kubernetes.io/healthcheck-interval-seconds": "20", - "alb.ingress.kubernetes.io/healthcheck-timeout-seconds": "10", - "alb.ingress.kubernetes.io/healthy-threshold-count": "7", - "alb.ingress.kubernetes.io/unhealthy-threshold-count": "5", - "alb.ingress.kubernetes.io/success-codes": "200-300", - }, - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "https", - Port: 443, - TargetPort: intstr.FromInt(8443), - NodePort: 32768, - }, - }, - }, - } - - ns_1_svc_ipv6 := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "svc-ipv6", - }, - Spec: corev1.ServiceSpec{ - IPFamilies: []corev1.IPFamily{corev1.IPv6Protocol}, - Ports: []corev1.ServicePort{ - { - Name: "https", - Port: 443, - TargetPort: intstr.FromInt(8443), - NodePort: 32768, - }, - }, - }, - } - - svcWithNamedTargetPort := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "svc-named-targetport", - }, - Spec: corev1.ServiceSpec{ - Ports: []corev1.ServicePort{ - { - Name: "https", - Port: 443, - TargetPort: intstr.FromString("target-port"), - NodePort: 32768, - }, - }, - }, - } - - resolveViaDiscoveryCallForInternalLB := resolveViaDiscoveryCall{ - subnets: []*ec2sdk.Subnet{ - { - SubnetId: awssdk.String("subnet-a"), - CidrBlock: awssdk.String("192.168.0.0/19"), - }, - { - SubnetId: awssdk.String("subnet-b"), - CidrBlock: awssdk.String("192.168.32.0/19"), - }, - }, - } - resolveViaDiscoveryCallForInternetFacingLB := resolveViaDiscoveryCall{ - subnets: []*ec2sdk.Subnet{ - { - SubnetId: awssdk.String("subnet-c"), - CidrBlock: awssdk.String("192.168.64.0/19"), - }, - { - SubnetId: awssdk.String("subnet-d"), - CidrBlock: awssdk.String("192.168.96.0/19"), - }, - }, - } - - listLoadBalancerCallForEmptyLB := listLoadBalancersCall{ - matchedLBs: []elbv2.LoadBalancerWithTags{}, - } - - tests := []struct { - name string - env env - defaultTargetType string - enableIPTargetType *bool - args args - fields fields - wantStackJSON string - wantErr error - }{ - { - name: "Ingress - vanilla internal", - env: env{ - svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, - }, - fields: fields{ - resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternalLB}, - listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, - enableBackendSG: true, - }, - args: args{ - ingGroup: Group{ - ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, - Members: []ClassifiedIngress{ - { - Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "ing-1", - }, - Spec: networking.IngressSpec{ - Rules: []networking.IngressRule{ - { - Host: "app-1.example.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/svc-1", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_1.Name, - Port: networking.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - { - Path: "/svc-2", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_2.Name, - Port: networking.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - { - Host: "app-2.example.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/svc-3", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_3.Name, - Port: networking.ServiceBackendPort{ - Name: "https", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantStackJSON: ` +const baseStackJSON = ` { "id":"ns-1/ing-1", "resources":{ @@ -683,11 +434,266 @@ func Test_defaultModelBuilder_Build(t *testing.T) { } } } -}`, - }, - { - name: "Ingress - backend SG feature disabled", - env: env{ +}` + +func Test_defaultModelBuilder_Build(t *testing.T) { + type resolveViaDiscoveryCall struct { + subnets []*ec2sdk.Subnet + err error + } + type env struct { + svcs []*corev1.Service + } + type listLoadBalancersCall struct { + matchedLBs []elbv2.LoadBalancerWithTags + err error + } + type describeSecurityGroupsResult struct { + securityGroups []*ec2sdk.SecurityGroup + err error + } + type fields struct { + resolveViaDiscoveryCalls []resolveViaDiscoveryCall + listLoadBalancersCalls []listLoadBalancersCall + describeSecurityGroupsResult []describeSecurityGroupsResult + backendSecurityGroup string + enableBackendSG bool + } + type args struct { + ingGroup Group + } + + ns_1_svc_1 := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "svc-1", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: 80, + TargetPort: intstr.FromInt(8080), + NodePort: 32768, + }, + }, + }, + } + ns_1_svc_2 := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "svc-2", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/target-type": "instance", + "alb.ingress.kubernetes.io/backend-protocol": "HTTP", + "alb.ingress.kubernetes.io/healthcheck-protocol": "HTTP", + "alb.ingress.kubernetes.io/healthcheck-port": "traffic-port", + "alb.ingress.kubernetes.io/healthcheck-path": "/", + "alb.ingress.kubernetes.io/healthcheck-interval-seconds": "15", + "alb.ingress.kubernetes.io/healthcheck-timeout-seconds": "5", + "alb.ingress.kubernetes.io/healthy-threshold-count": "2", + "alb.ingress.kubernetes.io/unhealthy-threshold-count": "2", + "alb.ingress.kubernetes.io/success-codes": "200", + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: 80, + TargetPort: intstr.FromInt(8080), + NodePort: 32768, + }, + }, + }, + } + ns_1_svc_3 := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "svc-3", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/target-type": "ip", + "alb.ingress.kubernetes.io/backend-protocol": "HTTPS", + "alb.ingress.kubernetes.io/healthcheck-protocol": "HTTPS", + "alb.ingress.kubernetes.io/healthcheck-port": "9090", + "alb.ingress.kubernetes.io/healthcheck-path": "/health-check", + "alb.ingress.kubernetes.io/healthcheck-interval-seconds": "20", + "alb.ingress.kubernetes.io/healthcheck-timeout-seconds": "10", + "alb.ingress.kubernetes.io/healthy-threshold-count": "7", + "alb.ingress.kubernetes.io/unhealthy-threshold-count": "5", + "alb.ingress.kubernetes.io/success-codes": "200-300", + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "https", + Port: 443, + TargetPort: intstr.FromInt(8443), + NodePort: 32768, + }, + }, + }, + } + + ns_1_svc_ipv6 := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "svc-ipv6", + }, + Spec: corev1.ServiceSpec{ + IPFamilies: []corev1.IPFamily{corev1.IPv6Protocol}, + Ports: []corev1.ServicePort{ + { + Name: "https", + Port: 443, + TargetPort: intstr.FromInt(8443), + NodePort: 32768, + }, + }, + }, + } + + svcWithNamedTargetPort := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "svc-named-targetport", + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "https", + Port: 443, + TargetPort: intstr.FromString("target-port"), + NodePort: 32768, + }, + }, + }, + } + + resolveViaDiscoveryCallForInternalLB := resolveViaDiscoveryCall{ + subnets: []*ec2sdk.Subnet{ + { + SubnetId: awssdk.String("subnet-a"), + CidrBlock: awssdk.String("192.168.0.0/19"), + }, + { + SubnetId: awssdk.String("subnet-b"), + CidrBlock: awssdk.String("192.168.32.0/19"), + }, + }, + } + resolveViaDiscoveryCallForInternetFacingLB := resolveViaDiscoveryCall{ + subnets: []*ec2sdk.Subnet{ + { + SubnetId: awssdk.String("subnet-c"), + CidrBlock: awssdk.String("192.168.64.0/19"), + }, + { + SubnetId: awssdk.String("subnet-d"), + CidrBlock: awssdk.String("192.168.96.0/19"), + }, + }, + } + + listLoadBalancerCallForEmptyLB := listLoadBalancersCall{ + matchedLBs: []elbv2.LoadBalancerWithTags{}, + } + + tests := []struct { + name string + env env + defaultTargetType string + enableIPTargetType *bool + args args + fields fields + wantStackPatch string + wantErr string + }{ + { + name: "Ingress - vanilla internal", + env: env{ + svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, + }, + fields: fields{ + resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternalLB}, + listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, + enableBackendSG: true, + }, + args: args{ + ingGroup: Group{ + ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "ing-1", + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "app-1.example.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/svc-1", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_1.Name, + Port: networking.ServiceBackendPort{ + Name: "http", + }, + }, + }, + }, + { + Path: "/svc-2", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_2.Name, + Port: networking.ServiceBackendPort{ + Name: "http", + }, + }, + }, + }, + }, + }, + }, + }, + { + Host: "app-2.example.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/svc-3", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_3.Name, + Port: networking.ServiceBackendPort{ + Name: "https", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantStackPatch: "{}", + }, + { + name: "Ingress - backend SG feature disabled", + env: env{ svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, }, fields: fields{ @@ -764,2340 +770,821 @@ func Test_defaultModelBuilder_Build(t *testing.T) { }, }, }, - wantStackJSON: ` + wantStackPatch: ` { - "id":"ns-1/ing-1", - "resources":{ - "AWS::EC2::SecurityGroup":{ - "ManagedLBSecurityGroup":{ - "spec":{ - "groupName":"k8s-ns1-ing1-bd83176788", - "description":"[k8s] Managed SecurityGroup for LoadBalancer", - "ingress":[ - { - "ipProtocol":"tcp", - "fromPort":80, - "toPort":80, - "ipRanges":[ - { - "cidrIP":"0.0.0.0/0" - } - ] - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::Listener":{ - "80":{ - "spec":{ - "loadBalancerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" - }, - "port":80, - "protocol":"HTTP", - "defaultActions":[ - { - "type":"fixed-response", - "fixedResponseConfig":{ - "contentType":"text/plain", - "statusCode":"404" - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::ListenerRule":{ - "80:1":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":1, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-1.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-1" - ] - } - } - ] - } - }, - "80:2":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":2, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-2:http/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-1.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-2" - ] - } - } - ] - } - }, - "80:3":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":3, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-2.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-3" - ] - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::LoadBalancer":{ - "LoadBalancer":{ - "spec":{ - "name":"k8s-ns1-ing1-b7e914000d", - "type":"application", - "scheme":"internal", - "ipAddressType":"ipv4", - "subnetMapping":[ - { - "subnetID":"subnet-a" - }, - { - "subnetID":"subnet-b" - } - ], - "securityGroups":[ - { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::TargetGroup":{ - "ns-1/ing-1-svc-1:http":{ - "spec":{ - "name":"k8s-ns1-svc1-9889425938", - "targetType":"instance", - "ipAddressType":"ipv4", - "port":32768, - "protocol":"HTTP", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - }, - "ns-1/ing-1-svc-2:http":{ - "spec":{ - "name":"k8s-ns1-svc2-9889425938", - "targetType":"instance", - "ipAddressType":"ipv4", - "port":32768, - "protocol":"HTTP", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - }, - "ns-1/ing-1-svc-3:https":{ - "spec":{ - "name":"k8s-ns1-svc3-bf42870fba", - "targetType":"ip", - "ipAddressType":"ipv4", - "port":8443, - "protocol":"HTTPS", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":9090, - "protocol":"HTTPS", - "path":"/health-check", - "matcher":{ - "httpCode":"200-300" - }, - "intervalSeconds":20, - "timeoutSeconds":10, - "healthyThresholdCount":7, - "unhealthyThresholdCount":5 - } - } - } - }, - "K8S::ElasticLoadBalancingV2::TargetGroupBinding":{ - "ns-1/ing-1-svc-1:http":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc1-9889425938", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" - }, - "targetType":"instance", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-1", - "port":"http" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - } - } - } - ], - "ports":[ - { - "port": 32768, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - }, - "ns-1/ing-1-svc-2:http":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc2-9889425938", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-2:http/status/targetGroupARN" - }, - "targetType":"instance", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-2", - "port":"http" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - } - } - } - ], - "ports":[ - { - "port": 32768, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - }, - "ns-1/ing-1-svc-3:https":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc3-bf42870fba", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" - }, - "targetType":"ip", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-3", - "port":"https" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - } - } - } - ], - "ports":[ - { - "port": 8443, - "protocol":"TCP" - } - ] - }, - { - "from":[ - { - "securityGroup":{ - "groupID": { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - } - } - } - ], - "ports":[ - { - "port": 9090, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - } - } - } -}`, - }, - { - name: "Ingress - vanilla internet-facing", - env: env{ - svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, - }, - fields: fields{ - resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternetFacingLB}, - listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, - enableBackendSG: true, - }, - args: args{ - ingGroup: Group{ - ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, - Members: []ClassifiedIngress{ - { - Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "ing-1", - Annotations: map[string]string{ - "alb.ingress.kubernetes.io/scheme": "internet-facing", - }, - }, - Spec: networking.IngressSpec{ - Rules: []networking.IngressRule{ - { - Host: "app-1.example.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/svc-1", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_1.Name, - Port: networking.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - { - Path: "/svc-2", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_2.Name, - Port: networking.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - { - Host: "app-2.example.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/svc-3", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_3.Name, - Port: networking.ServiceBackendPort{ - Name: "https", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantStackJSON: ` -{ - "id":"ns-1/ing-1", - "resources":{ - "AWS::EC2::SecurityGroup":{ - "ManagedLBSecurityGroup":{ - "spec":{ - "groupName":"k8s-ns1-ing1-bd83176788", - "description":"[k8s] Managed SecurityGroup for LoadBalancer", - "ingress":[ - { - "ipProtocol":"tcp", - "fromPort":80, - "toPort":80, - "ipRanges":[ - { - "cidrIP":"0.0.0.0/0" - } - ] - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::Listener":{ - "80":{ - "spec":{ - "loadBalancerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" - }, - "port":80, - "protocol":"HTTP", - "defaultActions":[ - { - "type":"fixed-response", - "fixedResponseConfig":{ - "contentType":"text/plain", - "statusCode":"404" - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::ListenerRule":{ - "80:1":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":1, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-1.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-1" - ] - } - } - ] - } - }, - "80:2":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":2, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-2:http/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-1.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-2" - ] - } - } - ] - } - }, - "80:3":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":3, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-2.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-3" - ] - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::LoadBalancer":{ - "LoadBalancer":{ - "spec":{ - "name":"k8s-ns1-ing1-159dd7a143", - "type":"application", - "scheme":"internet-facing", - "ipAddressType":"ipv4", - "subnetMapping":[ - { - "subnetID":"subnet-c" - }, - { - "subnetID":"subnet-d" - } - ], - "securityGroups":[ - { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - }, - "sg-auto" - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::TargetGroup":{ - "ns-1/ing-1-svc-1:http":{ - "spec":{ - "name":"k8s-ns1-svc1-9889425938", - "targetType":"instance", - "ipAddressType":"ipv4", - "port":32768, - "protocol":"HTTP", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - }, - "ns-1/ing-1-svc-2:http":{ - "spec":{ - "name":"k8s-ns1-svc2-9889425938", - "targetType":"instance", - "ipAddressType":"ipv4", - "port":32768, - "protocol":"HTTP", - "protocolVersion": "HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - }, - "ns-1/ing-1-svc-3:https":{ - "spec":{ - "name":"k8s-ns1-svc3-bf42870fba", - "targetType":"ip", - "ipAddressType":"ipv4", - "port":8443, - "protocol":"HTTPS", - "protocolVersion": "HTTP1", - "healthCheckConfig":{ - "port":9090, - "protocol":"HTTPS", - "path":"/health-check", - "matcher":{ - "httpCode":"200-300" - }, - "intervalSeconds":20, - "timeoutSeconds":10, - "healthyThresholdCount":7, - "unhealthyThresholdCount":5 - } - } - } - }, - "K8S::ElasticLoadBalancingV2::TargetGroupBinding":{ - "ns-1/ing-1-svc-1:http":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc1-9889425938", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" - }, - "targetType":"instance", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-1", - "port":"http" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { -"port": 32768, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - }, - "ns-1/ing-1-svc-2:http":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc2-9889425938", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-2:http/status/targetGroupARN" - }, - "targetType":"instance", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-2", - "port":"http" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { -"port": 32768, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - }, - "ns-1/ing-1-svc-3:https":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc3-bf42870fba", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" - }, - "targetType":"ip", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-3", - "port":"https" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { - "port": 8443, - "protocol":"TCP" - } - ] - }, - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { - "port": 9090, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - } - } - } -}`, - }, - { - name: "Ingress - using acm and internet-facing", - env: env{ - svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, - }, - fields: fields{ - resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternetFacingLB}, - listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, - enableBackendSG: true, - }, - args: args{ - ingGroup: Group{ - ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, - Members: []ClassifiedIngress{ - { - Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "ing-1", - Annotations: map[string]string{ - "alb.ingress.kubernetes.io/scheme": "internet-facing", - "alb.ingress.kubernetes.io/certificate-arn": "arn:aws:acm:us-east-1:9999999:certificate/22222222,arn:aws:acm:us-east-1:9999999:certificate/33333333,arn:aws:acm:us-east-1:9999999:certificate/11111111,,arn:aws:acm:us-east-1:9999999:certificate/11111111", - }, - }, - Spec: networking.IngressSpec{ - Rules: []networking.IngressRule{ - { - Host: "app-1.example.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/svc-1", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_1.Name, - Port: networking.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - { - Path: "/svc-2", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_2.Name, - Port: networking.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - { - Host: "app-2.example.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/svc-3", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_3.Name, - Port: networking.ServiceBackendPort{ - Name: "https", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantStackJSON: ` -{ - "id":"ns-1/ing-1", - "resources":{ - "AWS::EC2::SecurityGroup":{ - "ManagedLBSecurityGroup":{ - "spec":{ - "groupName":"k8s-ns1-ing1-bd83176788", - "description":"[k8s] Managed SecurityGroup for LoadBalancer", - "ingress":[ - { - "ipProtocol":"tcp", - "fromPort":443, - "toPort":443, - "ipRanges":[ - { - "cidrIP":"0.0.0.0/0" - } - ] - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::Listener":{ - "443":{ - "spec":{ - "loadBalancerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" - }, - "certificates": [ - { - "certificateARN": "arn:aws:acm:us-east-1:9999999:certificate/22222222" - }, - { - "certificateARN": "arn:aws:acm:us-east-1:9999999:certificate/33333333" - }, - { - "certificateARN": "arn:aws:acm:us-east-1:9999999:certificate/11111111" - } - ], - "port":443, - "protocol":"HTTPS", - "sslPolicy":"ELBSecurityPolicy-2016-08", - "defaultActions":[ - { - "type":"fixed-response", - "fixedResponseConfig":{ - "contentType":"text/plain", - "statusCode":"404" - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::ListenerRule":{ - "443:1":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/443/status/listenerARN" - }, - "priority":1, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-1.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-1" - ] - } - } - ] - } - }, - "443:2":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/443/status/listenerARN" - }, - "priority":2, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-2:http/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-1.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-2" - ] - } - } - ] - } - }, - "443:3":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/443/status/listenerARN" - }, - "priority":3, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-2.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-3" - ] - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::LoadBalancer":{ - "LoadBalancer":{ - "spec":{ - "name":"k8s-ns1-ing1-159dd7a143", - "type":"application", - "scheme":"internet-facing", - "ipAddressType":"ipv4", - "subnetMapping":[ - { - "subnetID":"subnet-c" - }, - { - "subnetID":"subnet-d" - } - ], - "securityGroups":[ - { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - }, - "sg-auto" - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::TargetGroup":{ - "ns-1/ing-1-svc-1:http":{ - "spec":{ - "name":"k8s-ns1-svc1-9889425938", - "targetType":"instance", - "ipAddressType":"ipv4", - "port":32768, - "protocol":"HTTP", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - }, - "ns-1/ing-1-svc-2:http":{ - "spec":{ - "name":"k8s-ns1-svc2-9889425938", - "targetType":"instance", - "ipAddressType":"ipv4", - "port":32768, - "protocol":"HTTP", - "protocolVersion": "HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - }, - "ns-1/ing-1-svc-3:https":{ - "spec":{ - "name":"k8s-ns1-svc3-bf42870fba", - "targetType":"ip", - "ipAddressType":"ipv4", - "port":8443, - "protocol":"HTTPS", - "protocolVersion": "HTTP1", - "healthCheckConfig":{ - "port":9090, - "protocol":"HTTPS", - "path":"/health-check", - "matcher":{ - "httpCode":"200-300" - }, - "intervalSeconds":20, - "timeoutSeconds":10, - "healthyThresholdCount":7, - "unhealthyThresholdCount":5 - } - } - } - }, - "K8S::ElasticLoadBalancingV2::TargetGroupBinding":{ - "ns-1/ing-1-svc-1:http":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc1-9889425938", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" - }, - "targetType":"instance", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-1", - "port":"http" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { - "port": 32768, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - }, - "ns-1/ing-1-svc-2:http":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc2-9889425938", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-2:http/status/targetGroupARN" - }, - "targetType":"instance", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-2", - "port":"http" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { - "port": 32768, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - }, - "ns-1/ing-1-svc-3:https":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc3-bf42870fba", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" - }, - "targetType":"ip", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-3", - "port":"https" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { - "port": 8443, - "protocol":"TCP" - } - ] - }, -{ - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { - "port": 9090, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - } - } - } -}`, - }, - { - name: "Ingress - referenced same service port with both name and port", - env: env{ - svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, - }, - fields: fields{ - resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternalLB}, - listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, - enableBackendSG: true, - }, - args: args{ - ingGroup: Group{ - ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, - Members: []ClassifiedIngress{ - { - Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "ing-1", - }, - Spec: networking.IngressSpec{ - Rules: []networking.IngressRule{ - { - Host: "app-1.example.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/svc-1-name", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_1.Name, - Port: networking.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - { - Path: "/svc-1-port", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_1.Name, - Port: networking.ServiceBackendPort{ - Number: 80, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantStackJSON: ` -{ - "id":"ns-1/ing-1", - "resources":{ - "AWS::EC2::SecurityGroup":{ - "ManagedLBSecurityGroup":{ - "spec":{ - "groupName":"k8s-ns1-ing1-bd83176788", - "description":"[k8s] Managed SecurityGroup for LoadBalancer", - "ingress":[ - { - "ipProtocol":"tcp", - "fromPort":80, - "toPort":80, - "ipRanges":[ - { - "cidrIP":"0.0.0.0/0" - } - ] - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::Listener":{ - "80":{ - "spec":{ - "loadBalancerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" - }, - "port":80, - "protocol":"HTTP", - "defaultActions":[ - { - "type":"fixed-response", - "fixedResponseConfig":{ - "contentType":"text/plain", - "statusCode":"404" - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::ListenerRule":{ - "80:1":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":1, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-1.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-1-name" - ] - } - } - ] - } - }, - "80:2":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":2, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:80/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-1.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-1-port" - ] - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::LoadBalancer":{ - "LoadBalancer":{ - "spec":{ - "name":"k8s-ns1-ing1-b7e914000d", - "type":"application", - "scheme":"internal", - "ipAddressType":"ipv4", - "subnetMapping":[ - { - "subnetID":"subnet-a" - }, - { - "subnetID":"subnet-b" - } - ], - "securityGroups":[ - { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - }, - "sg-auto" - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::TargetGroup":{ - "ns-1/ing-1-svc-1:80":{ - "spec":{ - "name":"k8s-ns1-svc1-90b7d93b18", - "targetType":"instance", - "ipAddressType":"ipv4", - "port":32768, - "protocol":"HTTP", - "protocolVersion": "HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - }, - "ns-1/ing-1-svc-1:http":{ - "spec":{ - "name":"k8s-ns1-svc1-9889425938", - "targetType":"instance", - "ipAddressType":"ipv4", - "port":32768, - "protocol":"HTTP", - "protocolVersion": "HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - } - }, - "K8S::ElasticLoadBalancingV2::TargetGroupBinding":{ - "ns-1/ing-1-svc-1:80":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc1-90b7d93b18", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:80/status/targetGroupARN" - }, - "targetType":"instance", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-1", - "port":80 - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { - "port": 32768, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - }, - "ns-1/ing-1-svc-1:http":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc1-9889425938", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" - }, - "targetType":"instance", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-1", - "port":"http" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { - "port": 32768, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - } - } - } -}`, - }, - { - name: "Ingress - not using subnet auto-discovery and internal", - env: env{ - svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, - }, - fields: fields{ - listLoadBalancersCalls: []listLoadBalancersCall{ - { - matchedLBs: []elbv2.LoadBalancerWithTags{ - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("lb-1"), - AvailabilityZones: []*elbv2sdk.AvailabilityZone{ - { - SubnetId: awssdk.String("subnet-e"), - }, - { - SubnetId: awssdk.String("subnet-f"), - }, - }, - Scheme: awssdk.String("internal"), - }, - Tags: map[string]string{ - "elbv2.k8s.aws/cluster": "cluster-name", - "ingress.k8s.aws/stack": "ns-1/ing-1", - }, - }, - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("lb-2"), - AvailabilityZones: []*elbv2sdk.AvailabilityZone{ - { - SubnetId: awssdk.String("subnet-e"), - }, - { - SubnetId: awssdk.String("subnet-f"), - }, - }, - Scheme: awssdk.String("internal"), - }, - Tags: map[string]string{ - "keyA": "valueA2", - "keyB": "valueB2", - }, - }, - { - LoadBalancer: &elbv2sdk.LoadBalancer{ - LoadBalancerArn: awssdk.String("lb-3"), - AvailabilityZones: []*elbv2sdk.AvailabilityZone{ - { - SubnetId: awssdk.String("subnet-e"), - }, - { - SubnetId: awssdk.String("subnet-f"), - }, - }, - Scheme: awssdk.String("internal"), - }, - Tags: map[string]string{ - "keyA": "valueA3", - "keyB": "valueB3", - }, - }, - }, - }, - }, - enableBackendSG: true, - }, - args: args{ - ingGroup: Group{ - ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, - Members: []ClassifiedIngress{ - { - Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "ing-1", - }, - Spec: networking.IngressSpec{ - Rules: []networking.IngressRule{ - { - Host: "app-1.example.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/svc-1", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_1.Name, - Port: networking.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - { - Path: "/svc-2", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_2.Name, - Port: networking.ServiceBackendPort{ - Name: "http", - }, - }, - }, - }, - }, - }, - }, - }, - { - Host: "app-2.example.com", - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/svc-3", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_3.Name, - Port: networking.ServiceBackendPort{ - Name: "https", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantStackJSON: ` -{ - "id":"ns-1/ing-1", - "resources":{ - "AWS::EC2::SecurityGroup":{ - "ManagedLBSecurityGroup":{ - "spec":{ - "groupName":"k8s-ns1-ing1-bd83176788", - "description":"[k8s] Managed SecurityGroup for LoadBalancer", - "ingress":[ - { - "ipProtocol":"tcp", - "fromPort":80, - "toPort":80, - "ipRanges":[ - { - "cidrIP":"0.0.0.0/0" - } - ] - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::Listener":{ - "80":{ - "spec":{ - "loadBalancerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" - }, - "port":80, - "protocol":"HTTP", - "defaultActions":[ - { - "type":"fixed-response", - "fixedResponseConfig":{ - "contentType":"text/plain", - "statusCode":"404" - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::ListenerRule":{ - "80:1":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":1, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-1.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-1" - ] - } - } - ] - } - }, - "80:2":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":2, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-2:http/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-1.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-2" - ] - } - } - ] - } - }, - "80:3":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":3, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-2.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-3" - ] - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::LoadBalancer":{ - "LoadBalancer":{ - "spec":{ - "name":"k8s-ns1-ing1-b7e914000d", - "type":"application", - "scheme":"internal", - "ipAddressType":"ipv4", - "subnetMapping":[ - { - "subnetID":"subnet-e" - }, - { - "subnetID":"subnet-f" - } - ], - "securityGroups":[ - { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - }, - "sg-auto" - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::TargetGroup":{ - "ns-1/ing-1-svc-1:http":{ - "spec":{ - "name":"k8s-ns1-svc1-9889425938", - "targetType":"instance", - "ipAddressType":"ipv4", - "port":32768, - "protocol":"HTTP", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - }, - "ns-1/ing-1-svc-2:http":{ - "spec":{ - "name":"k8s-ns1-svc2-9889425938", - "targetType":"instance", - "ipAddressType":"ipv4", - "port":32768, - "protocol":"HTTP", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - }, - "ns-1/ing-1-svc-3:https":{ - "spec":{ - "name":"k8s-ns1-svc3-bf42870fba", - "targetType":"ip", - "ipAddressType":"ipv4", - "port":8443, - "protocol":"HTTPS", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":9090, - "protocol":"HTTPS", - "path":"/health-check", - "matcher":{ - "httpCode":"200-300" - }, - "intervalSeconds":20, - "timeoutSeconds":10, - "healthyThresholdCount":7, - "unhealthyThresholdCount":5 - } - } - } - }, - "K8S::ElasticLoadBalancingV2::TargetGroupBinding":{ - "ns-1/ing-1-svc-1:http":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc1-9889425938", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" - }, - "targetType":"instance", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-1", - "port":"http" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { + "resources": { + "AWS::ElasticLoadBalancingV2::LoadBalancer": { + "LoadBalancer": { + "spec": { + "securityGroups": [ + { + "$ref": "#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" + } + ] + } + } + }, + "K8S::ElasticLoadBalancingV2::TargetGroupBinding": { + "ns-1/ing-1-svc-1:http": { + "spec": { + "template": { + "spec": { + "networking": { + "ingress": [ + { + "from": [ + { + "securityGroup": { + "groupID": { + "$ref": "#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" + } + } + } + ], + "ports": [ + { "port": 32768, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - }, - "ns-1/ing-1-svc-2:http":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc2-9889425938", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-2:http/status/targetGroupARN" - }, - "targetType":"instance", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-2", - "port":"http" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { + "protocol": "TCP" + } + ] + } + ] + } + } + } + } + }, + "ns-1/ing-1-svc-2:http": { + "spec": { + "template": { + "spec": { + "networking": { + "ingress": [ + { + "from": [ + { + "securityGroup": { + "groupID": { + "$ref": "#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" + } + } + } + ], + "ports": [ + { "port": 32768, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - }, - "ns-1/ing-1-svc-3:https":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc3-bf42870fba", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" - }, - "targetType":"ip", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-3", - "port":"https" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { + "protocol": "TCP" + } + ] + } + ] + } + } + } + } + }, + "ns-1/ing-1-svc-3:https": { + "spec": { + "template": { + "spec": { + "networking": { + "ingress": [ + { + "from": [ + { + "securityGroup": { + "groupID": { + "$ref": "#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" + } + } + } + ], + "ports": [ + { "port": 8443, - "protocol":"TCP" - } - ] - }, + "protocol": "TCP" + } + ] + }, + { + "from": [ + { + "securityGroup": { + "groupID": { + "$ref": "#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" + } + } + } + ], + "ports": [ + { + "port": 9090, + "protocol": "TCP" + } + ] + } + ] + } + } + } + } + } + } + } +}`, + }, + { + name: "Ingress - vanilla internet-facing", + env: env{ + svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, + }, + fields: fields{ + resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternetFacingLB}, + listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, + enableBackendSG: true, + }, + args: args{ + ingGroup: Group{ + ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "ing-1", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/scheme": "internet-facing", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "app-1.example.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/svc-1", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_1.Name, + Port: networking.ServiceBackendPort{ + Name: "http", + }, + }, + }, + }, + { + Path: "/svc-2", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_2.Name, + Port: networking.ServiceBackendPort{ + Name: "http", + }, + }, + }, + }, + }, + }, + }, + }, + { + Host: "app-2.example.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/svc-3", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_3.Name, + Port: networking.ServiceBackendPort{ + Name: "https", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantStackPatch: ` +{ + "resources": { + "AWS::ElasticLoadBalancingV2::LoadBalancer": { + "LoadBalancer": { + "spec": { + "name": "k8s-ns1-ing1-159dd7a143", + "scheme": "internet-facing", + "subnetMapping": [ + { + "subnetID": "subnet-c" + }, + { + "subnetID": "subnet-d" + } + ] + } + } + } + } +}`, + }, + { + name: "Ingress - using acm and internet-facing", + env: env{ + svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, + }, + fields: fields{ + resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternetFacingLB}, + listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, + enableBackendSG: true, + }, + args: args{ + ingGroup: Group{ + ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "ing-1", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/scheme": "internet-facing", + "alb.ingress.kubernetes.io/certificate-arn": "arn:aws:acm:us-east-1:9999999:certificate/22222222,arn:aws:acm:us-east-1:9999999:certificate/33333333,arn:aws:acm:us-east-1:9999999:certificate/11111111,,arn:aws:acm:us-east-1:9999999:certificate/11111111", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "app-1.example.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/svc-1", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_1.Name, + Port: networking.ServiceBackendPort{ + Name: "http", + }, + }, + }, + }, + { + Path: "/svc-2", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_2.Name, + Port: networking.ServiceBackendPort{ + Name: "http", + }, + }, + }, + }, + }, + }, + }, + }, + { + Host: "app-2.example.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/svc-3", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_3.Name, + Port: networking.ServiceBackendPort{ + Name: "https", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantStackPatch: ` +{ + "resources": { + "AWS::EC2::SecurityGroup": { + "ManagedLBSecurityGroup": { + "spec": { + "ingress": [ + { + "fromPort": 443, + "ipProtocol": "tcp", + "ipRanges": [ + { + "cidrIP": "0.0.0.0/0" + } + ], + "toPort": 443 + } + ] + } + } + }, + "AWS::ElasticLoadBalancingV2::Listener": { + "443": { + "spec": { + "certificates": [ + { + "certificateARN": "arn:aws:acm:us-east-1:9999999:certificate/22222222" + }, + { + "certificateARN": "arn:aws:acm:us-east-1:9999999:certificate/33333333" + }, + { + "certificateARN": "arn:aws:acm:us-east-1:9999999:certificate/11111111" + } + ], + "defaultActions": [ + { + "fixedResponseConfig": { + "contentType": "text/plain", + "statusCode": "404" + }, + "type": "fixed-response" + } + ], + "loadBalancerARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" + }, + "port": 443, + "protocol": "HTTPS", + "sslPolicy": "ELBSecurityPolicy-2016-08" + } + }, + "80": null + }, + "AWS::ElasticLoadBalancingV2::ListenerRule": { + "443:1": { + "spec": { + "actions": [ + { + "forwardConfig": { + "targetGroups": [ { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { - "port": 9090, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - } - } - } + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:http/status/targetGroupARN" + } + } + ] + }, + "type": "forward" + } + ], + "conditions": [ + { + "field": "host-header", + "hostHeaderConfig": { + "values": [ + "app-1.example.com" + ] + } + }, + { + "field": "path-pattern", + "pathPatternConfig": { + "values": [ + "/svc-1" + ] + } + } + ], + "listenerARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::Listener/443/status/listenerARN" + }, + "priority": 1 + } + }, + "443:2": { + "spec": { + "actions": [ + { + "forwardConfig": { + "targetGroups": [ + { + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-2:http/status/targetGroupARN" + } + } + ] + }, + "type": "forward" + } + ], + "conditions": [ + { + "field": "host-header", + "hostHeaderConfig": { + "values": [ + "app-1.example.com" + ] + } + }, + { + "field": "path-pattern", + "pathPatternConfig": { + "values": [ + "/svc-2" + ] + } + } + ], + "listenerARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::Listener/443/status/listenerARN" + }, + "priority": 2 + } + }, + "443:3": { + "spec": { + "actions": [ + { + "forwardConfig": { + "targetGroups": [ + { + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" + } + } + ] + }, + "type": "forward" + } + ], + "conditions": [ + { + "field": "host-header", + "hostHeaderConfig": { + "values": [ + "app-2.example.com" + ] + } + }, + { + "field": "path-pattern", + "pathPatternConfig": { + "values": [ + "/svc-3" + ] + } + } + ], + "listenerARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::Listener/443/status/listenerARN" + }, + "priority": 3 + } + }, + "80:1": null, + "80:2": null, + "80:3": null + }, + "AWS::ElasticLoadBalancingV2::LoadBalancer": { + "LoadBalancer": { + "spec": { + "name": "k8s-ns1-ing1-159dd7a143", + "scheme": "internet-facing", + "subnetMapping": [ + { + "subnetID": "subnet-c" + }, + { + "subnetID": "subnet-d" + } + ] + } + } + } + } }`, }, { - name: "Ingress - deletion protection enabled error", + name: "Ingress - referenced same service port with both name and port", env: env{ svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, }, + fields: fields{ + resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternalLB}, + listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, + enableBackendSG: true, + }, args: args{ ingGroup: Group{ ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, - InactiveMembers: []*networking.Ingress{ + Members: []ClassifiedIngress{ { - ObjectMeta: metav1.ObjectMeta{ - Namespace: "default", - Name: "hello-ingress", - Annotations: map[string]string{ - "kubernetes.io/ingress.class": "alb", - "alb.ingress.kubernetes.io/load-balancer-attributes": "deletion_protection.enabled=true", + Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "ing-1", + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "app-1.example.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/svc-1-name", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_1.Name, + Port: networking.ServiceBackendPort{ + Name: "http", + }, + }, + }, + }, + { + Path: "/svc-1-port", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_1.Name, + Port: networking.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, }, - Finalizers: []string{ - "ingress.k8s.aws/resources", + }, + }, + }, + }, + }, + wantStackPatch: ` +{ + "resources": { + "AWS::ElasticLoadBalancingV2::ListenerRule": { + "80:1": { + "spec": { + "conditions": [ + { + "field": "host-header", + "hostHeaderConfig": { + "values": [ + "app-1.example.com" + ] + } + }, + { + "field": "path-pattern", + "pathPatternConfig": { + "values": [ + "/svc-1-name" + ] + } + } + ] + } + }, + "80:2": { + "spec": { + "actions": [ + { + "forwardConfig": { + "targetGroups": [ + { + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:80/status/targetGroupARN" + } + } + ] + }, + "type": "forward" + } + ], + "conditions": [ + { + "field": "host-header", + "hostHeaderConfig": { + "values": [ + "app-1.example.com" + ] + } + }, + { + "field": "path-pattern", + "pathPatternConfig": { + "values": [ + "/svc-1-port" + ] + } + } + ] + } + }, + "80:3": null + }, + "AWS::ElasticLoadBalancingV2::TargetGroup": { + "ns-1/ing-1-svc-1:80": { + "spec": { + "healthCheckConfig": { + "healthyThresholdCount": 2, + "intervalSeconds": 15, + "matcher": { + "httpCode": "200" + }, + "path": "/", + "port": "traffic-port", + "protocol": "HTTP", + "timeoutSeconds": 5, + "unhealthyThresholdCount": 2 + }, + "ipAddressType": "ipv4", + "name": "k8s-ns1-svc1-90b7d93b18", + "port": 32768, + "protocol": "HTTP", + "protocolVersion": "HTTP1", + "targetType": "instance" + } + }, + "ns-1/ing-1-svc-2:http": null, + "ns-1/ing-1-svc-3:https": null + }, + "K8S::ElasticLoadBalancingV2::TargetGroupBinding": { + "ns-1/ing-1-svc-1:80": { + "spec": { + "template": { + "metadata": { + "creationTimestamp": null, + "name": "k8s-ns1-svc1-90b7d93b18", + "namespace": "ns-1" + }, + "spec": { + "ipAddressType": "ipv4", + "networking": { + "ingress": [ + { + "from": [ + { + "securityGroup": { + "groupID": "sg-auto" + } + } + ], + "ports": [ + { + "port": 32768, + "protocol": "TCP" + } + ] + } + ] + }, + "serviceRef": { + "name": "svc-1", + "port": 80 + }, + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-1:80/status/targetGroupARN" + }, + "targetType": "instance" + } + } + } + }, + "ns-1/ing-1-svc-2:http": null, + "ns-1/ing-1-svc-3:https": null + } + } +}`, + }, + { + name: "Ingress - not using subnet auto-discovery and internal", + env: env{ + svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, + }, + fields: fields{ + listLoadBalancersCalls: []listLoadBalancersCall{ + { + matchedLBs: []elbv2.LoadBalancerWithTags{ + { + LoadBalancer: &elbv2sdk.LoadBalancer{ + LoadBalancerArn: awssdk.String("lb-1"), + AvailabilityZones: []*elbv2sdk.AvailabilityZone{ + { + SubnetId: awssdk.String("subnet-e"), + }, + { + SubnetId: awssdk.String("subnet-f"), + }, + }, + Scheme: awssdk.String("internal"), + }, + Tags: map[string]string{ + "elbv2.k8s.aws/cluster": "cluster-name", + "ingress.k8s.aws/stack": "ns-1/ing-1", + }, + }, + { + LoadBalancer: &elbv2sdk.LoadBalancer{ + LoadBalancerArn: awssdk.String("lb-2"), + AvailabilityZones: []*elbv2sdk.AvailabilityZone{ + { + SubnetId: awssdk.String("subnet-e"), + }, + { + SubnetId: awssdk.String("subnet-f"), + }, + }, + Scheme: awssdk.String("internal"), }, - DeletionTimestamp: &metav1.Time{ - Time: time.Now(), + Tags: map[string]string{ + "keyA": "valueA2", + "keyB": "valueB2", }, }, - }, - }, - }, - }, - wantErr: errors.New("deletion_protection is enabled, cannot delete the ingress: hello-ingress"), - }, - { - name: "Ingress - with SG annotation", - env: env{ - svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, - }, - fields: fields{ - resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternalLB}, - listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, - describeSecurityGroupsResult: []describeSecurityGroupsResult{ - { - securityGroups: []*ec2sdk.SecurityGroup{ { - GroupId: awssdk.String("sg-manual"), + LoadBalancer: &elbv2sdk.LoadBalancer{ + LoadBalancerArn: awssdk.String("lb-3"), + AvailabilityZones: []*elbv2sdk.AvailabilityZone{ + { + SubnetId: awssdk.String("subnet-e"), + }, + { + SubnetId: awssdk.String("subnet-f"), + }, + }, + Scheme: awssdk.String("internal"), + }, + Tags: map[string]string{ + "keyA": "valueA3", + "keyB": "valueB3", + }, }, }, }, }, - backendSecurityGroup: "sg-backend", - enableBackendSG: true, + enableBackendSG: true, }, args: args{ ingGroup: Group{ ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, Members: []ClassifiedIngress{ { - Ing: &networking.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "ing-1", - Annotations: map[string]string{ - "alb.ingress.kubernetes.io/security-groups": "sg-manual", - "alb.ingress.kubernetes.io/scheme": "internet-facing", - "alb.ingress.kubernetes.io/target-type": "instance", - }, - }, + Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "ing-1", + }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ + { + Host: "app-1.example.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/svc-1", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_1.Name, + Port: networking.ServiceBackendPort{ + Name: "http", + }, + }, + }, + }, + { + Path: "/svc-2", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_2.Name, + Port: networking.ServiceBackendPort{ + Name: "http", + }, + }, + }, + }, + }, + }, + }, + }, { Host: "app-2.example.com", IngressRuleValue: networking.IngressRuleValue{ @@ -3125,146 +1612,58 @@ func Test_defaultModelBuilder_Build(t *testing.T) { }, }, }, - wantStackJSON: ` + wantStackPatch: ` { - "id":"ns-1/ing-1", - "resources":{ - "AWS::ElasticLoadBalancingV2::Listener":{ - "80":{ - "spec":{ - "loadBalancerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" - }, - "port":80, - "protocol":"HTTP", - "defaultActions":[ - { - "type":"fixed-response", - "fixedResponseConfig":{ - "contentType":"text/plain", - "statusCode":"404" - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::ListenerRule":{ - "80:1":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":1, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"host-header", - "hostHeaderConfig":{ - "values":[ - "app-2.example.com" - ] - } - }, - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/svc-3" - ] - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::LoadBalancer":{ - "LoadBalancer":{ - "spec":{ - "name":"k8s-ns1-ing1-159dd7a143", - "type":"application", - "scheme":"internet-facing", - "ipAddressType":"ipv4", - "subnetMapping":[ - { - "subnetID":"subnet-a" - }, - { - "subnetID":"subnet-b" - } - ], - "securityGroups":[ - "sg-manual" - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::TargetGroup":{ - "ns-1/ing-1-svc-3:https":{ - "spec":{ - "name":"k8s-ns1-svc3-bf42870fba", - "targetType":"ip", - "ipAddressType":"ipv4", - "port":8443, - "protocol":"HTTPS", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":9090, - "protocol":"HTTPS", - "path":"/health-check", - "matcher":{ - "httpCode":"200-300" - }, - "intervalSeconds":20, - "timeoutSeconds":10, - "healthyThresholdCount":7, - "unhealthyThresholdCount":5 - } - } - } - }, - "K8S::ElasticLoadBalancingV2::TargetGroupBinding":{ - "ns-1/ing-1-svc-3:https":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svc3-bf42870fba", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" - }, - "targetType":"ip", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-3", - "port":"https" - } - } - } - } - } - } - } + "resources": { + "AWS::ElasticLoadBalancingV2::LoadBalancer": { + "LoadBalancer": { + "spec": { + "subnetMapping": [ + { + "subnetID": "subnet-e" + }, + { + "subnetID": "subnet-f" + } + ] + } + } + } + } }`, }, { - name: "Ingress - with SG annotation, backend SG feature disabled, managed backend sg set to true", + name: "Ingress - deletion protection enabled error", + env: env{ + svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, + }, + args: args{ + ingGroup: Group{ + ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, + InactiveMembers: []*networking.Ingress{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "hello-ingress", + Annotations: map[string]string{ + "kubernetes.io/ingress.class": "alb", + "alb.ingress.kubernetes.io/load-balancer-attributes": "deletion_protection.enabled=true", + }, + Finalizers: []string{ + "ingress.k8s.aws/resources", + }, + DeletionTimestamp: &metav1.Time{ + Time: time.Now(), + }, + }, + }, + }, + }, + }, + wantErr: "deletion_protection is enabled, cannot delete the ingress: hello-ingress", + }, + { + name: "Ingress - with SG annotation", env: env{ svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, }, @@ -3281,7 +1680,7 @@ func Test_defaultModelBuilder_Build(t *testing.T) { }, }, backendSecurityGroup: "sg-backend", - enableBackendSG: false, + enableBackendSG: true, }, args: args{ ingGroup: Group{ @@ -3293,10 +1692,9 @@ func Test_defaultModelBuilder_Build(t *testing.T) { Namespace: "ns-1", Name: "ing-1", Annotations: map[string]string{ - "alb.ingress.kubernetes.io/security-groups": "sg-manual", - "alb.ingress.kubernetes.io/scheme": "internet-facing", - "alb.ingress.kubernetes.io/target-type": "instance", - "alb.ingress.kubernetes.io/manage-backend-security-group-rules": "true", + "alb.ingress.kubernetes.io/security-groups": "sg-manual", + "alb.ingress.kubernetes.io/scheme": "internet-facing", + "alb.ingress.kubernetes.io/target-type": "instance", }, }, Spec: networking.IngressSpec{ @@ -3328,243 +1726,150 @@ func Test_defaultModelBuilder_Build(t *testing.T) { }, }, }, - wantErr: errors.New("backendSG feature is required to manage worker node SG rules when frontendSG manually specified"), + wantStackPatch: ` +{ + "resources": { + "AWS::EC2::SecurityGroup": null, + "AWS::ElasticLoadBalancingV2::ListenerRule": { + "80:1": { + "spec": { + "actions": [ + { + "forwardConfig": { + "targetGroups": [ + { + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-3:https/status/targetGroupARN" + } + } + ] + }, + "type": "forward" + } + ], + "conditions": [ + { + "field": "host-header", + "hostHeaderConfig": { + "values": [ + "app-2.example.com" + ] + } + }, + { + "field": "path-pattern", + "pathPatternConfig": { + "values": [ + "/svc-3" + ] + } + } + ] + } + }, + "80:2": null, + "80:3": null + }, + "AWS::ElasticLoadBalancingV2::LoadBalancer": { + "LoadBalancer": { + "spec": { + "name": "k8s-ns1-ing1-159dd7a143", + "scheme": "internet-facing", + "securityGroups": [ + "sg-manual" + ] + } + } + }, + "AWS::ElasticLoadBalancingV2::TargetGroup": { + "ns-1/ing-1-svc-1:http": null, + "ns-1/ing-1-svc-2:http": null + }, + "K8S::ElasticLoadBalancingV2::TargetGroupBinding": { + "ns-1/ing-1-svc-1:http": null, + "ns-1/ing-1-svc-2:http": null, + "ns-1/ing-1-svc-3:https": { + "spec": { + "template": { + "spec": { + "networking": null + } + } + } + } + } + } +}`, }, { - name: "Ingress with IPv6 service", + name: "Ingress - with SG annotation, backend SG feature disabled, managed backend sg set to true", env: env{ - svcs: []*corev1.Service{ns_1_svc_ipv6}, + svcs: []*corev1.Service{ns_1_svc_1, ns_1_svc_2, ns_1_svc_3}, }, fields: fields{ resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternalLB}, listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, - enableBackendSG: true, + describeSecurityGroupsResult: []describeSecurityGroupsResult{ + { + securityGroups: []*ec2sdk.SecurityGroup{ + { + GroupId: awssdk.String("sg-manual"), + }, + }, + }, + }, + backendSecurityGroup: "sg-backend", + enableBackendSG: false, }, args: args{ ingGroup: Group{ ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, Members: []ClassifiedIngress{ { - Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns-1", - Name: "ing-1", - Annotations: map[string]string{ - "alb.ingress.kubernetes.io/target-type": "ip", - "alb.ingress.kubernetes.io/ip-address-type": "dualstack", + Ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "ing-1", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/security-groups": "sg-manual", + "alb.ingress.kubernetes.io/scheme": "internet-facing", + "alb.ingress.kubernetes.io/target-type": "instance", + "alb.ingress.kubernetes.io/manage-backend-security-group-rules": "true", + }, }, - }, Spec: networking.IngressSpec{ Rules: []networking.IngressRule{ - { - IngressRuleValue: networking.IngressRuleValue{ - HTTP: &networking.HTTPIngressRuleValue{ - Paths: []networking.HTTPIngressPath{ - { - Path: "/", - Backend: networking.IngressBackend{ - Service: &networking.IngressServiceBackend{ - Name: ns_1_svc_ipv6.Name, - Port: networking.ServiceBackendPort{ - Name: "https", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - wantStackJSON: ` -{ - "id":"ns-1/ing-1", - "resources":{ - "AWS::EC2::SecurityGroup":{ - "ManagedLBSecurityGroup":{ - "spec":{ - "groupName":"k8s-ns1-ing1-bd83176788", - "description":"[k8s] Managed SecurityGroup for LoadBalancer", - "ingress":[ - { - "ipProtocol":"tcp", - "fromPort":80, - "toPort":80, - "ipRanges":[ - { - "cidrIP":"0.0.0.0/0" - } - ] - }, - { - "ipProtocol":"tcp", - "fromPort":80, - "toPort":80, - "ipv6Ranges":[ - { - "cidrIPv6":"::/0" - } - ] - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::Listener":{ - "80":{ - "spec":{ - "loadBalancerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" - }, - "port":80, - "protocol":"HTTP", - "defaultActions":[ - { - "type":"fixed-response", - "fixedResponseConfig":{ - "contentType":"text/plain", - "statusCode":"404" - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::ListenerRule":{ - "80:1":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":1, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-ipv6:https/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/" - ] - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::LoadBalancer":{ - "LoadBalancer":{ - "spec":{ - "name":"k8s-ns1-ing1-b7e914000d", - "type":"application", - "scheme":"internal", - "ipAddressType":"dualstack", - "subnetMapping":[ - { - "subnetID":"subnet-a" - }, - { - "subnetID":"subnet-b" - } - ], - "securityGroups":[ - { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - }, - "sg-auto" - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::TargetGroup":{ - "ns-1/ing-1-svc-ipv6:https":{ - "spec":{ - "name":"k8s-ns1-svcipv6-c387b9e773", - "targetType":"ip", - "ipAddressType":"ipv6", - "port":8443, - "protocol":"HTTP", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - } - }, - "K8S::ElasticLoadBalancingV2::TargetGroupBinding":{ - "ns-1/ing-1-svc-ipv6:https":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svcipv6-c387b9e773", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-ipv6:https/status/targetGroupARN" - }, - "targetType":"ip", - "ipAddressType":"ipv6", - "serviceRef":{ - "name":"svc-ipv6", - "port":"https" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { - "port": 8443, - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - } - } - } -}`, + { + Host: "app-2.example.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/svc-3", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: ns_1_svc_3.Name, + Port: networking.ServiceBackendPort{ + Name: "https", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: "backendSG feature is required to manage worker node SG rules when frontendSG manually specified", }, { - name: "Ingress with IPv6 service but not dualstack", + name: "Ingress with IPv6 service", env: env{ svcs: []*corev1.Service{ns_1_svc_ipv6}, }, @@ -3582,7 +1887,8 @@ func Test_defaultModelBuilder_Build(t *testing.T) { Namespace: "ns-1", Name: "ing-1", Annotations: map[string]string{ - "alb.ingress.kubernetes.io/target-type": "ip", + "alb.ingress.kubernetes.io/target-type": "ip", + "alb.ingress.kubernetes.io/ip-address-type": "dualstack", }, }, Spec: networking.IngressSpec{ @@ -3613,14 +1919,157 @@ func Test_defaultModelBuilder_Build(t *testing.T) { }, }, }, - wantErr: errors.New("ingress: ns-1/ing-1: unsupported IPv6 configuration, lb not dual-stack"), + wantStackPatch: ` +{ + "resources": { + "AWS::EC2::SecurityGroup": { + "ManagedLBSecurityGroup": { + "spec": { + "ingress": [ + { + "fromPort": 80, + "ipProtocol": "tcp", + "ipRanges": [ + { + "cidrIP": "0.0.0.0/0" + } + ], + "toPort": 80 + }, + { + "fromPort": 80, + "ipProtocol": "tcp", + "ipv6Ranges": [ + { + "cidrIPv6": "::/0" + } + ], + "toPort": 80 + } + ] + } + } + }, + "AWS::ElasticLoadBalancingV2::ListenerRule": { + "80:1": { + "spec": { + "actions": [ + { + "forwardConfig": { + "targetGroups": [ + { + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-ipv6:https/status/targetGroupARN" + } + } + ] + }, + "type": "forward" + } + ], + "conditions": [ + { + "field": "path-pattern", + "pathPatternConfig": { + "values": [ + "/" + ] + } + } + ] + } + }, + "80:2": null, + "80:3": null + }, + "AWS::ElasticLoadBalancingV2::LoadBalancer": { + "LoadBalancer": { + "spec": { + "ipAddressType": "dualstack" + } + } + }, + "AWS::ElasticLoadBalancingV2::TargetGroup": { + "ns-1/ing-1-svc-1:http": null, + "ns-1/ing-1-svc-2:http": null, + "ns-1/ing-1-svc-3:https": null, + "ns-1/ing-1-svc-ipv6:https": { + "spec": { + "healthCheckConfig": { + "healthyThresholdCount": 2, + "intervalSeconds": 15, + "matcher": { + "httpCode": "200" + }, + "path": "/", + "port": "traffic-port", + "protocol": "HTTP", + "timeoutSeconds": 5, + "unhealthyThresholdCount": 2 + }, + "ipAddressType": "ipv6", + "name": "k8s-ns1-svcipv6-c387b9e773", + "port": 8443, + "protocol": "HTTP", + "protocolVersion": "HTTP1", + "targetType": "ip" + } + } + }, + "K8S::ElasticLoadBalancingV2::TargetGroupBinding": { + "ns-1/ing-1-svc-1:http": null, + "ns-1/ing-1-svc-2:http": null, + "ns-1/ing-1-svc-3:https": null, + "ns-1/ing-1-svc-ipv6:https": { + "spec": { + "template": { + "metadata": { + "creationTimestamp": null, + "name": "k8s-ns1-svcipv6-c387b9e773", + "namespace": "ns-1" + }, + "spec": { + "ipAddressType": "ipv6", + "networking": { + "ingress": [ + { + "from": [ + { + "securityGroup": { + "groupID": "sg-auto" + } + } + ], + "ports": [ + { + "port": 8443, + "protocol": "TCP" + } + ] + } + ] + }, + "serviceRef": { + "name": "svc-ipv6", + "port": "https" + }, + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-ipv6:https/status/targetGroupARN" + }, + "targetType": "ip" + } + } + } + } + } + } +}`, }, { - name: "target type IP with enableIPTargetType set to false", + name: "Ingress with IPv6 service but not dualstack", env: env{ - svcs: []*corev1.Service{svcWithNamedTargetPort}, + svcs: []*corev1.Service{ns_1_svc_ipv6}, }, - enableIPTargetType: awssdk.Bool(false), fields: fields{ resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternalLB}, listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, @@ -3648,7 +2097,7 @@ func Test_defaultModelBuilder_Build(t *testing.T) { Path: "/", Backend: networking.IngressBackend{ Service: &networking.IngressServiceBackend{ - Name: svcWithNamedTargetPort.Name, + Name: ns_1_svc_ipv6.Name, Port: networking.ServiceBackendPort{ Name: "https", }, @@ -3666,13 +2115,14 @@ func Test_defaultModelBuilder_Build(t *testing.T) { }, }, }, - wantErr: errors.New("ingress: ns-1/ing-1: unsupported targetType: ip when EnableIPTargetType is false"), + wantErr: "ingress: ns-1/ing-1: unsupported IPv6 configuration, lb not dual-stack", }, { - name: "target type IP with named target port", + name: "target type IP with enableIPTargetType set to false", env: env{ svcs: []*corev1.Service{svcWithNamedTargetPort}, }, + enableIPTargetType: awssdk.Bool(false), fields: fields{ resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternalLB}, listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, @@ -3718,176 +2168,167 @@ func Test_defaultModelBuilder_Build(t *testing.T) { }, }, }, - wantStackJSON: ` -{ - "id":"ns-1/ing-1", - "resources":{ - "AWS::EC2::SecurityGroup":{ - "ManagedLBSecurityGroup":{ - "spec":{ - "groupName":"k8s-ns1-ing1-bd83176788", - "description":"[k8s] Managed SecurityGroup for LoadBalancer", - "ingress":[ - { - "ipProtocol":"tcp", - "fromPort":80, - "toPort":80, - "ipRanges":[ - { - "cidrIP":"0.0.0.0/0" - } - ] - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::Listener":{ - "80":{ - "spec":{ - "loadBalancerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" - }, - "port":80, - "protocol":"HTTP", - "defaultActions":[ - { - "type":"fixed-response", - "fixedResponseConfig":{ - "contentType":"text/plain", - "statusCode":"404" - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::ListenerRule":{ - "80:1":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":1, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-named-targetport:https/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/" - ] - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::LoadBalancer":{ - "LoadBalancer":{ - "spec":{ - "name":"k8s-ns1-ing1-b7e914000d", - "type":"application", - "scheme":"internal", - "ipAddressType":"ipv4", - "subnetMapping":[ - { - "subnetID":"subnet-a" - }, - { - "subnetID":"subnet-b" - } - ], - "securityGroups":[ - { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - }, - "sg-auto" - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::TargetGroup":{ - "ns-1/ing-1-svc-named-targetport:https":{ - "spec":{ - "name":"k8s-ns1-svcnamed-3430e53ee8", - "targetType":"ip", - "ipAddressType":"ipv4", - "port":1, - "protocol":"HTTP", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - } - }, - "K8S::ElasticLoadBalancingV2::TargetGroupBinding":{ - "ns-1/ing-1-svc-named-targetport:https":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svcnamed-3430e53ee8", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-named-targetport:https/status/targetGroupARN" - }, - "targetType":"ip", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-named-targetport", - "port":"https" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { + wantErr: "ingress: ns-1/ing-1: unsupported targetType: ip when EnableIPTargetType is false", + }, + { + name: "target type IP with named target port", + env: env{ + svcs: []*corev1.Service{svcWithNamedTargetPort}, + }, + fields: fields{ + resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForInternalLB}, + listLoadBalancersCalls: []listLoadBalancersCall{listLoadBalancerCallForEmptyLB}, + enableBackendSG: true, + }, + args: args{ + ingGroup: Group{ + ID: GroupID{Namespace: "ns-1", Name: "ing-1"}, + Members: []ClassifiedIngress{ + { + Ing: &networking.Ingress{ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "ing-1", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/target-type": "ip", + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: svcWithNamedTargetPort.Name, + Port: networking.ServiceBackendPort{ + Name: "https", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantStackPatch: ` +{ + "resources": { + "AWS::ElasticLoadBalancingV2::ListenerRule": { + "80:1": { + "spec": { + "actions": [ + { + "forwardConfig": { + "targetGroups": [ + { + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-named-targetport:https/status/targetGroupARN" + } + } + ] + }, + "type": "forward" + } + ], + "conditions": [ + { + "field": "path-pattern", + "pathPatternConfig": { + "values": [ + "/" + ] + } + } + ] + } + }, + "80:2": null, + "80:3": null + }, + "AWS::ElasticLoadBalancingV2::TargetGroup": { + "ns-1/ing-1-svc-1:http": null, + "ns-1/ing-1-svc-2:http": null, + "ns-1/ing-1-svc-3:https": null, + "ns-1/ing-1-svc-named-targetport:https": { + "spec": { + "healthCheckConfig": { + "healthyThresholdCount": 2, + "intervalSeconds": 15, + "matcher": { + "httpCode": "200" + }, + "path": "/", + "port": "traffic-port", + "protocol": "HTTP", + "timeoutSeconds": 5, + "unhealthyThresholdCount": 2 + }, + "ipAddressType": "ipv4", + "name": "k8s-ns1-svcnamed-3430e53ee8", + "port": 1, + "protocol": "HTTP", + "protocolVersion": "HTTP1", + "targetType": "ip" + } + } + }, + "K8S::ElasticLoadBalancingV2::TargetGroupBinding": { + "ns-1/ing-1-svc-1:http": null, + "ns-1/ing-1-svc-2:http": null, + "ns-1/ing-1-svc-3:https": null, + "ns-1/ing-1-svc-named-targetport:https": { + "spec": { + "template": { + "metadata": { + "creationTimestamp": null, + "name": "k8s-ns1-svcnamed-3430e53ee8", + "namespace": "ns-1" + }, + "spec": { + "ipAddressType": "ipv4", + "networking": { + "ingress": [ + { + "from": [ + { + "securityGroup": { + "groupID": "sg-auto" + } + } + ], + "ports": [ + { "port": "target-port", - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - } - } - } + "protocol": "TCP" + } + ] + } + ] + }, + "serviceRef": { + "name": "svc-named-targetport", + "port": "https" + }, + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-named-targetport:https/status/targetGroupARN" + }, + "targetType": "ip" + } + } + } + } + } + } }`, }, { @@ -3938,176 +2379,115 @@ func Test_defaultModelBuilder_Build(t *testing.T) { }, }, defaultTargetType: "ip", - wantStackJSON: ` + wantStackPatch: ` { - "id":"ns-1/ing-1", - "resources":{ - "AWS::EC2::SecurityGroup":{ - "ManagedLBSecurityGroup":{ - "spec":{ - "groupName":"k8s-ns1-ing1-bd83176788", - "description":"[k8s] Managed SecurityGroup for LoadBalancer", - "ingress":[ - { - "ipProtocol":"tcp", - "fromPort":80, - "toPort":80, - "ipRanges":[ - { - "cidrIP":"0.0.0.0/0" - } - ] - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::Listener":{ - "80":{ - "spec":{ - "loadBalancerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" - }, - "port":80, - "protocol":"HTTP", - "defaultActions":[ - { - "type":"fixed-response", - "fixedResponseConfig":{ - "contentType":"text/plain", - "statusCode":"404" - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::ListenerRule":{ - "80:1":{ - "spec":{ - "listenerARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::Listener/80/status/listenerARN" - }, - "priority":1, - "actions":[ - { - "type":"forward", - "forwardConfig":{ - "targetGroups":[ - { - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-named-targetport:https/status/targetGroupARN" - } - } - ] - } - } - ], - "conditions":[ - { - "field":"path-pattern", - "pathPatternConfig":{ - "values":[ - "/" - ] - } - } - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::LoadBalancer":{ - "LoadBalancer":{ - "spec":{ - "name":"k8s-ns1-ing1-b7e914000d", - "type":"application", - "scheme":"internal", - "ipAddressType":"ipv4", - "subnetMapping":[ - { - "subnetID":"subnet-a" - }, - { - "subnetID":"subnet-b" - } - ], - "securityGroups":[ - { - "$ref":"#/resources/AWS::EC2::SecurityGroup/ManagedLBSecurityGroup/status/groupID" - }, - "sg-auto" - ] - } - } - }, - "AWS::ElasticLoadBalancingV2::TargetGroup":{ - "ns-1/ing-1-svc-named-targetport:https":{ - "spec":{ - "name":"k8s-ns1-svcnamed-3430e53ee8", - "targetType":"ip", - "ipAddressType":"ipv4", - "port":1, - "protocol":"HTTP", - "protocolVersion":"HTTP1", - "healthCheckConfig":{ - "port":"traffic-port", - "protocol":"HTTP", - "path":"/", - "matcher":{ - "httpCode":"200" - }, - "intervalSeconds":15, - "timeoutSeconds":5, - "healthyThresholdCount":2, - "unhealthyThresholdCount":2 - } - } - } - }, - "K8S::ElasticLoadBalancingV2::TargetGroupBinding":{ - "ns-1/ing-1-svc-named-targetport:https":{ - "spec":{ - "template":{ - "metadata":{ - "name":"k8s-ns1-svcnamed-3430e53ee8", - "namespace":"ns-1", - "creationTimestamp":null - }, - "spec":{ - "targetGroupARN":{ - "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-named-targetport:https/status/targetGroupARN" - }, - "targetType":"ip", - "ipAddressType":"ipv4", - "serviceRef":{ - "name":"svc-named-targetport", - "port":"https" - }, - "networking":{ - "ingress":[ - { - "from":[ - { - "securityGroup":{ - "groupID": "sg-auto" - } - } - ], - "ports":[ - { + "resources": { + "AWS::ElasticLoadBalancingV2::ListenerRule": { + "80:1": { + "spec": { + "actions": [ + { + "forwardConfig": { + "targetGroups": [ + { + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-named-targetport:https/status/targetGroupARN" + } + } + ] + }, + "type": "forward" + } + ], + "conditions": [ + { + "field": "path-pattern", + "pathPatternConfig": { + "values": [ + "/" + ] + } + } + ] + } + }, + "80:2": null, + "80:3": null + }, + "AWS::ElasticLoadBalancingV2::TargetGroup": { + "ns-1/ing-1-svc-1:http": null, + "ns-1/ing-1-svc-2:http": null, + "ns-1/ing-1-svc-3:https": null, + "ns-1/ing-1-svc-named-targetport:https": { + "spec": { + "healthCheckConfig": { + "healthyThresholdCount": 2, + "intervalSeconds": 15, + "matcher": { + "httpCode": "200" + }, + "path": "/", + "port": "traffic-port", + "protocol": "HTTP", + "timeoutSeconds": 5, + "unhealthyThresholdCount": 2 + }, + "ipAddressType": "ipv4", + "name": "k8s-ns1-svcnamed-3430e53ee8", + "port": 1, + "protocol": "HTTP", + "protocolVersion": "HTTP1", + "targetType": "ip" + } + } + }, + "K8S::ElasticLoadBalancingV2::TargetGroupBinding": { + "ns-1/ing-1-svc-1:http": null, + "ns-1/ing-1-svc-2:http": null, + "ns-1/ing-1-svc-3:https": null, + "ns-1/ing-1-svc-named-targetport:https": { + "spec": { + "template": { + "metadata": { + "creationTimestamp": null, + "name": "k8s-ns1-svcnamed-3430e53ee8", + "namespace": "ns-1" + }, + "spec": { + "ipAddressType": "ipv4", + "networking": { + "ingress": [ + { + "from": [ + { + "securityGroup": { + "groupID": "sg-auto" + } + } + ], + "ports": [ + { "port": "target-port", - "protocol":"TCP" - } - ] - } - ] - } - } - } - } - } - } - } + "protocol": "TCP" + } + ] + } + ] + }, + "serviceRef": { + "name": "svc-named-targetport", + "port": "https" + }, + "targetGroupARN": { + "$ref": "#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/ns-1/ing-1-svc-named-targetport:https/status/targetGroupARN" + }, + "targetType": "ip" + } + } + } + } + } + } }`, }, } @@ -4190,13 +2570,50 @@ func Test_defaultModelBuilder_Build(t *testing.T) { } gotStack, _, _, err := b.Build(context.Background(), tt.args.ingGroup) - if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) + if tt.wantErr != "" { + assert.EqualError(t, err, tt.wantErr) } else { - assert.NoError(t, err) + require.NoError(t, err) + + wantStackJSON, err := jsonpatch.MergePatch([]byte(baseStackJSON), []byte(tt.wantStackPatch)) + require.NoError(t, err, "patching wantStack") + var wantStack struct { + ID string `json:"id"` + Resources map[string]map[string]interface{} `json:"resources"` + } + err = json.Unmarshal(wantStackJSON, &wantStack) + require.NoError(t, err, "unmarshalling wantStack") + + // Set explicit null on all creationTimestamps in the binding, as JSON merge patches can't do that. + for binding, value := range wantStack.Resources["K8S::ElasticLoadBalancingV2::TargetGroupBinding"] { + if bindingMap, ok := value.(map[string]interface{}); ok { + if specMap, ok := bindingMap["spec"].(map[string]interface{}); ok { + if templateMap, ok := specMap["template"].(map[string]interface{}); ok { + if metadataMap, ok := templateMap["metadata"].(map[string]interface{}); ok { + metadataMap["creationTimestamp"] = nil + templateMap["metadata"] = metadataMap + specMap["template"] = templateMap + bindingMap["spec"] = specMap + wantStack.Resources["K8S::ElasticLoadBalancingV2::TargetGroupBinding"][binding] = bindingMap + } + } + } + } + } + + wantStackYAML, _ := yaml.Marshal(wantStack) + stackJSON, err := stackMarshaller.Marshal(gotStack) - assert.NoError(t, err) - assert.JSONEq(t, tt.wantStackJSON, stackJSON) + assert.NoError(t, err, "marshalling stack") + var stack interface{} + _ = json.Unmarshal([]byte(stackJSON), &stack) + gotStackYAML, _ := yaml.Marshal(stack) + + eq := assert.Equal(t, string(wantStackYAML), string(gotStackYAML)) + if !eq { + patch, _ := jsonpatch.CreateMergePatch([]byte(baseStackJSON), []byte(stackJSON)) + t.Log(string(patch)) + } } }) } diff --git a/pkg/runtime/concise_logger.go b/pkg/runtime/concise_logger.go index 0948c7c45a..6acd2dda34 100644 --- a/pkg/runtime/concise_logger.go +++ b/pkg/runtime/concise_logger.go @@ -17,7 +17,7 @@ type conciseLogger struct { } func (r *conciseLogger) WithValues(keysAndValues ...interface{}) logr.LogSink { - return &conciseLogger{r.LogSink.WithValues(keysAndValues)} + return &conciseLogger{r.LogSink.WithValues(keysAndValues...)} } func (r *conciseLogger) WithName(name string) logr.LogSink { diff --git a/webhooks/networking/ingress_validator.go b/webhooks/networking/ingress_validator.go index 7f1e855852..4397972b64 100644 --- a/webhooks/networking/ingress_validator.go +++ b/webhooks/networking/ingress_validator.go @@ -2,6 +2,7 @@ package networking import ( "context" + "fmt" awssdk "github.com/aws/aws-sdk-go/aws" "github.com/go-logr/logr" @@ -59,6 +60,9 @@ func (v *ingressValidator) ValidateCreate(ctx context.Context, obj runtime.Objec if err := v.checkIngressClassUsage(ctx, ing, nil); err != nil { return err } + if err := v.checkIngressAnnotationConditions(ing); err != nil { + return err + } return nil } @@ -74,6 +78,9 @@ func (v *ingressValidator) ValidateUpdate(ctx context.Context, obj runtime.Objec if err := v.checkIngressClassUsage(ctx, ing, oldIng); err != nil { return err } + if err := v.checkIngressAnnotationConditions(ing); err != nil { + return err + } return nil } @@ -164,6 +171,33 @@ func (v *ingressValidator) checkIngressClassUsage(ctx context.Context, ing *netw return nil } +// checkGroupNameAnnotationUsage checks the validity of "conditions.${conditions-name}" annotation. +func (v *ingressValidator) checkIngressAnnotationConditions(ing *networking.Ingress) error { + for _, rule := range ing.Spec.Rules { + for _, path := range rule.HTTP.Paths { + var conditions []ingress.RuleCondition + annotationKey := fmt.Sprintf("conditions.%v", path.Backend.Service.Name) + _, err := v.annotationParser.ParseJSONAnnotation(annotationKey, &conditions, ing.Annotations) + if err != nil { + return err + } + + for _, condition := range conditions { + if err := condition.Validate(); err != nil { + return fmt.Errorf("ignoring Ingress %s/%s since invalid alb.ingress.kubernetes.io/conditions.%s annotation: %w", + ing.Namespace, + ing.Name, + path.Backend.Service.Name, + err, + ) + } + } + } + } + + return nil +} + // +kubebuilder:webhook:path=/validate-networking-v1-ingress,mutating=false,failurePolicy=fail,groups=networking.k8s.io,resources=ingresses,verbs=create;update,versions=v1,name=vingress.elbv2.k8s.aws,sideEffects=None,matchPolicy=Equivalent,webhookVersions=v1,admissionReviewVersions=v1beta1 func (v *ingressValidator) SetupWithManager(mgr ctrl.Manager) { diff --git a/webhooks/networking/ingress_validator_test.go b/webhooks/networking/ingress_validator_test.go index aad3fa4368..0419522a19 100644 --- a/webhooks/networking/ingress_validator_test.go +++ b/webhooks/networking/ingress_validator_test.go @@ -787,3 +787,119 @@ func Test_ingressValidator_checkIngressClassUsage(t *testing.T) { }) } } + +func Test_ingressValidator_checkIngressAnnotationConditions(t *testing.T) { + type fields struct { + disableIngressGroupAnnotation bool + } + type args struct { + ing *networking.Ingress + } + tests := []struct { + name string + fields fields + args args + wantErr error + }{ + { + name: "ingress has valid condition", + fields: fields{ + disableIngressGroupAnnotation: false, + }, + args: args{ + ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "ing-1", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/condition.svc-1": `[{"field":"query-string","queryStringConfig":{"values":[{"key":"paramA","value":"paramAValue"}]}}]`, + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/ing-1-path", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: "svc-1", + Port: networking.ServiceBackendPort{ + Name: "https", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: nil, + }, + { + name: "ingress has invalid condition", + fields: fields{ + disableIngressGroupAnnotation: false, + }, + args: args{ + ing: &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "ing-1", + Annotations: map[string]string{ + "alb.ingress.kubernetes.io/conditions.svc-1": `[{"field":"query-string","queryStringConfig":{"values":[{"key":"paramA","value":""}]}}]`, + }, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/ing-1-path", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: "svc-1", + Port: networking.ServiceBackendPort{ + Name: "https", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: errors.New("ignoring Ingress ns-1/ing-1 since invalid alb.ingress.kubernetes.io/conditions.svc-1 annotation: invalid queryStringConfig: value cannot be empty"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + annotationParser := annotations.NewSuffixAnnotationParser("alb.ingress.kubernetes.io") + classAnnotationMatcher := ingress.NewDefaultClassAnnotationMatcher("alb") + v := &ingressValidator{ + annotationParser: annotationParser, + classAnnotationMatcher: classAnnotationMatcher, + disableIngressGroupAnnotation: tt.fields.disableIngressGroupAnnotation, + logger: logr.Discard(), + } + err := v.checkIngressAnnotationConditions(tt.args.ing) + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +}