diff --git a/docs/guide/ingress/annotations.md b/docs/guide/ingress/annotations.md index 2d51c29b8..6ccb51440 100644 --- a/docs/guide/ingress/annotations.md +++ b/docs/guide/ingress/annotations.md @@ -533,6 +533,24 @@ Access control for LoadBalancer can be controlled with following annotations: ``` alb.ingress.kubernetes.io/inbound-cidrs: 10.0.0.0/24 ``` +- `alb.ingress.kubernetes.io/inbound-security-groups` specifies the SecurtityGroups that are allowed to access LoadBalancer. + + !!!note "Merge Behavior" + `inbound-security-groups` is merged across all Ingresses in IngressGroup, but is exclusive per listen-port. + + - the `inbound-security-groups` will only impact the ports defined for that Ingress. + - if same listen-port is defined by multiple Ingress within IngressGroup, `inbound-security-groups` should only be defined on one of the Ingress. + + !!!warning "" + this annotation will be ignored if `alb.ingress.kubernetes.io/security-groups` is specified. + + !!!tip "" + Both name or ID of securityGroups are supported. Name matches a `Name` tag, not the `groupName` attribute. + + !!!example + ``` + alb.ingress.kubernetes.io/inbound-security-groups: sg-xxxx, nameOfSg1, nameOfSg2 + ``` - `alb.ingress.kubernetes.io/security-group-prefix-lists` specifies the managed prefix lists that are allowed to access LoadBalancer. diff --git a/pkg/annotations/constants.go b/pkg/annotations/constants.go index b2bc9aad1..43afa8c0c 100644 --- a/pkg/annotations/constants.go +++ b/pkg/annotations/constants.go @@ -58,6 +58,7 @@ const ( IngressSuffixlsAttsAnnotationPrefix = "listener-attributes" IngressLBSuffixMultiClusterTargetGroup = "multi-cluster-target-group" IngressSuffixLoadBalancerCapacityReservation = "minimum-load-balancer-capacity" + IngressSuffixInboundSecurityGroups = "inbound-security-groups" // NLB annotation suffixes // prefixes service.beta.kubernetes.io, service.kubernetes.io diff --git a/pkg/ingress/model_build_listener.go b/pkg/ingress/model_build_listener.go index 32c118452..36b92cc31 100644 --- a/pkg/ingress/model_build_listener.go +++ b/pkg/ingress/model_build_listener.go @@ -122,6 +122,7 @@ type listenPortConfig struct { sslPolicy *string tlsCerts []string mutualAuthentication *elbv2model.MutualAuthenticationAttributes + securityGroupIDs []string } func (t *defaultModelBuildTask) computeIngressListenPortConfigByPort(ctx context.Context, ing *ClassifiedIngress) (map[int32]listenPortConfig, error) { @@ -129,10 +130,17 @@ func (t *defaultModelBuildTask) computeIngressListenPortConfigByPort(ctx context explicitSSLPolicy := t.computeIngressExplicitSSLPolicy(ctx, ing) var prefixListIDs []string t.annotationParser.ParseStringSliceAnnotation(annotations.IngressSuffixSecurityGroupPrefixLists, &prefixListIDs, ing.Ing.Annotations) + + securityGroupIDs, err := t.computeIngressExplicitSecurityGroupIDs(ctx, ing) + if err != nil { + return nil, err + } + inboundCIDRv4s, inboundCIDRV6s, err := t.computeIngressExplicitInboundCIDRs(ctx, ing) if err != nil { return nil, err } + mutualAuthenticationAttributes, err := t.computeIngressMutualAuthentication(ctx, ing) if err != nil { return nil, err @@ -161,10 +169,11 @@ func (t *defaultModelBuildTask) computeIngressListenPortConfigByPort(ctx context listenPortConfigByPort := make(map[int32]listenPortConfig, len(listenPorts)) for port, protocol := range listenPorts { cfg := listenPortConfig{ - protocol: protocol, - inboundCIDRv4s: inboundCIDRv4s, - inboundCIDRv6s: inboundCIDRV6s, - prefixLists: prefixListIDs, + protocol: protocol, + inboundCIDRv4s: inboundCIDRv4s, + inboundCIDRv6s: inboundCIDRV6s, + prefixLists: prefixListIDs, + securityGroupIDs: securityGroupIDs, } if protocol == elbv2model.ProtocolHTTPS { if len(explicitTLSCertARNs) == 0 { @@ -240,6 +249,20 @@ func (t *defaultModelBuildTask) computeIngressListenPorts(_ context.Context, ing return portAndProtocols, nil } +func (t *defaultModelBuildTask) computeIngressExplicitSecurityGroupIDs(ctx context.Context, ing *ClassifiedIngress) ([]string, error) { + var rawSecurityGroups []string + if exists := t.annotationParser.ParseStringSliceAnnotation(annotations.IngressSuffixInboundSecurityGroups, &rawSecurityGroups, ing.Ing.Annotations); !exists { + return nil, nil + } + + securityGroupIDs, err := t.sgResolver.ResolveViaNameOrID(ctx, rawSecurityGroups) + if err != nil { + return nil, fmt.Errorf("invalid %v settings on Ingress: %v: %w", annotations.IngressSuffixInboundSecurityGroups, k8s.NamespacedName(ing.Ing), err) + } + + return securityGroupIDs, nil +} + func (t *defaultModelBuildTask) computeIngressExplicitInboundCIDRs(_ context.Context, ing *ClassifiedIngress) ([]string, []string, error) { var rawInboundCIDRs []string fromIngressClassParams := false diff --git a/pkg/ingress/model_build_managed_sg.go b/pkg/ingress/model_build_managed_sg.go index 4a551ab90..cabbfe5e6 100644 --- a/pkg/ingress/model_build_managed_sg.go +++ b/pkg/ingress/model_build_managed_sg.go @@ -109,6 +109,18 @@ func (t *defaultModelBuildTask) buildManagedSecurityGroupIngressPermissions(_ co }, }) } + for _, sgID := range cfg.securityGroupIDs { + permissions = append(permissions, ec2model.IPPermission{ + IPProtocol: "tcp", + FromPort: awssdk.Int64(port), + ToPort: awssdk.Int64(port), + UserIDGroupPairs: []ec2model.UserIDGroupPair{ + { + GroupID: sgID, + }, + }, + }) + } } return permissions } diff --git a/pkg/ingress/model_builder.go b/pkg/ingress/model_builder.go index 1f8aacb12..8840c6abb 100644 --- a/pkg/ingress/model_builder.go +++ b/pkg/ingress/model_builder.go @@ -312,6 +312,9 @@ func (t *defaultModelBuildTask) mergeListenPortConfigs(_ context.Context, listen var mergedMtlsAttributesProvider *types.NamespacedName var mergedMtlsAttributes *elbv2model.MutualAuthenticationAttributes + var mergedSecurityGroupProvider *types.NamespacedName + mergedSecurityGroups := sets.NewString() + for _, cfg := range listenPortConfigs { if mergedProtocolProvider == nil { mergedProtocolProvider = &cfg.ingKey @@ -345,6 +348,17 @@ func (t *defaultModelBuildTask) mergeListenPortConfigs(_ context.Context, listen } } + if len(cfg.listenPortConfig.securityGroupIDs) != 0 { + cfgSecurityGroups := sets.NewString(cfg.listenPortConfig.securityGroupIDs...) + if mergedSecurityGroupProvider == nil { + mergedSecurityGroupProvider = &cfg.ingKey + mergedSecurityGroups = cfgSecurityGroups + } else if !mergedSecurityGroups.Equal(cfgSecurityGroups) { + return listenPortConfig{}, errors.Errorf("conflicting security groups, %v: %v | %v: %v", + *mergedSecurityGroupProvider, mergedSecurityGroups.List(), cfg.ingKey, cfgSecurityGroups.List()) + } + } + if cfg.listenPortConfig.sslPolicy != nil { if mergedSSLPolicyProvider == nil { mergedSSLPolicyProvider = &cfg.ingKey @@ -391,6 +405,7 @@ func (t *defaultModelBuildTask) mergeListenPortConfigs(_ context.Context, listen sslPolicy: mergedSSLPolicy, tlsCerts: mergedTLSCerts, mutualAuthentication: mergedMtlsAttributes, + securityGroupIDs: mergedSecurityGroups.List(), }, nil }