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
}