Skip to content

Add support for optionally enforcing NLB security groups on PrivateLi… #3594

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 4, 2024

Conversation

wweiwei-li
Copy link
Collaborator

@wweiwei-li wweiwei-li commented Feb 29, 2024

…nk traffic

Issue

#3386

Description

This is a feature to support optionally enforce NLB security groups on PrivateLink traffic. For the change, added a new annotation aws-load-balancer-inbound-sg-rules-on-private-link-traffic to configure whether to apply security group rules to traffic sent to the load balancer through AWS PrivateLink. The value of it can be set to "off" and "on". After creating new NLB, will check if the annotation is specified. If yes, will call updateSDKLoadBalancerWithSecurityGroups, where added the logic to check if we need to call setSecurityGroup to update EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic.

Checklist

  • Added tests that cover your change (if possible)
  • Added/modified documentation as required (such as the README.md, or the docs directory)
  • Manually tested
  • Made sure the title of the PR is a good description that can go into the release notes

BONUS POINTS checklist: complete for good vibes and maybe prizes?! 🤯

  • Backfilled missing tests for code in same general area 🎉
  • Refactored something and made the world a better place 🌟

Copy link

linux-foundation-easycla bot commented Feb 29, 2024

CLA Signed

The committers listed above are authorized under a signed CLA.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. label Feb 29, 2024
@k8s-ci-robot
Copy link
Contributor

Welcome @wweiwei-li!

It looks like this is your first PR to kubernetes-sigs/aws-load-balancer-controller 🎉. Please refer to our pull request process documentation to help your PR have a smooth ride to approval.

You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. Here is the bot commands documentation.

You can also check if kubernetes-sigs/aws-load-balancer-controller has its own contribution guidelines.

You may want to refer to our testing guide if you run into trouble with your tests not passing.

If you are having difficulty getting your pull request seen, please follow the recommended escalation practices. Also, for tips and tricks in the contribution process you may want to read the Kubernetes contributor cheat sheet. We want to make sure your contribution gets all the attention it needs!

Thank you, and welcome to Kubernetes. 😃

@k8s-ci-robot k8s-ci-robot added the needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. label Feb 29, 2024
@k8s-ci-robot
Copy link
Contributor

Hi @wweiwei-li. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@k8s-ci-robot k8s-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Feb 29, 2024
case string(elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOff):
return elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOff, nil
default:
return "", errors.Errorf("unknown securityGroupsInboundRulesOnPrivateLink status: %v", securityGroupsInboundRulesOnPrivateLink)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should modify this error message to give them suggestion to use the allowed values as well. Something like errors.Errorf("Invalid value for securityGroupsInboundRulesOnPrivateLink status: %v, value must be one of [%v, %v]", securityGroupsInboundRulesOnPrivateLink), string(elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOn), string(elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOff)

@dims
Copy link
Member

dims commented Mar 1, 2024

/check-cla
/easycla

@shraddhabang
Copy link
Collaborator

/ok-to-test

@k8s-ci-robot k8s-ci-robot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Mar 12, 2024
@wweiwei-li wweiwei-li force-pushed the nlbSecurityGroup branch 2 times, most recently from fedae1a to 2189784 Compare March 13, 2024 05:03
@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. and removed cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. labels Mar 13, 2024
SecurityGroupsInboundRulesOnPrivateLinkOn SecurityGroupsInboundRulesOnPrivateLinkStatus = "on"
SecurityGroupsInboundRulesOnPrivateLinkOff SecurityGroupsInboundRulesOnPrivateLinkStatus = "off"
)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we move these to pkg/model/ec2/security_group.go instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is okay to move these to pkg/model/ec2/security_group.go. One concern is this field is actually a load balancer field, even though it is related to security groups

@@ -75,6 +76,12 @@ func (m *defaultLoadBalancerManager) Create(ctx context.Context, resLB *elbv2mod
return elbv2model.LoadBalancerStatus{}, err
}

if resLB.Spec.Type == elbv2model.LoadBalancerTypeNetwork && string(*resLB.Spec.SecurityGroupsInboundRulesOnPrivateLink) != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why need to convert it to string? can we just check if it's nil or not? Same for all the string() != "" below

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you suggested if user does not specify the annotation, we will skip adding it as a spec. In this case, we will no longer have resLB.Spec.SecurityGroupsInboundRulesOnPrivateLink = "", so we can just check if it is nil.


if resLB.Spec.Type == elbv2model.LoadBalancerTypeNetwork {
desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic := string(*resLB.Spec.SecurityGroupsInboundRulesOnPrivateLink)
currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic := "on"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why need a default value for currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic? let's say, if the resLB.Spec.SecurityGroupsInboundRulesOnPrivateLink is on, and there's no such setting on sdkLB, so the currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic defaults to on, we ignore this situation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I was thinking it was "on" by default, but it is not. I will the set currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic based on sdkLB as below.

	var currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic string
	if sdkLB.LoadBalancer.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic != nil {
		currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic = awssdk.StringValue(sdkLB.LoadBalancer.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic)
		}

@@ -173,6 +179,22 @@ func (t *defaultModelBuildTask) buildLoadBalancerIPAddressType(_ context.Context
}
}

func (t *defaultModelBuildTask) buildSecurityGroupsInboundRulesOnPrivateLink(_ context.Context) (elbv2model.SecurityGroupsInboundRulesOnPrivateLinkStatus, error) {
securityGroupsInboundRulesOnPrivateLink := ""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just a var securityGroupsInboundRulesOnPrivateLink string

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can just use var securityGroupsInboundRulesOnPrivateLink string

func (t *defaultModelBuildTask) buildSecurityGroupsInboundRulesOnPrivateLink(_ context.Context) (elbv2model.SecurityGroupsInboundRulesOnPrivateLinkStatus, error) {
securityGroupsInboundRulesOnPrivateLink := ""
if exists := t.annotationParser.ParseStringAnnotation(annotations.SvcLBSuffixEnforceSGInboundRulesOnPrivateLinkTraffic, &securityGroupsInboundRulesOnPrivateLink, t.service.Annotations); !exists {
return "", nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if user does not specify the annotation, can we just skip adding it as a spec in the LB? it's a bit strange to have "securityGroupsInboundRulesOnPrivateLink":"".
And I think we should have the default value to "off", for backward compatibility.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I agree if user does not specify the annotation, we should just skip adding it as a spec in the LB.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just note : Default value for this is "On".

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need unit tests added for the cases: 1)annotation setting to "on",
2)annotation setting to "off"
3)no annotation at all.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some code in pkg/service/model_builder_test.go. When the annotation is set to "on", the build model will have "securityGroupsInboundRulesOnPrivateLink":"on". If the annotation is set to "off", the build model will have "securityGroupsInboundRulesOnPrivateLink":"off". If there is no annotation, "securityGroupsInboundRulesOnPrivateLink" will not be included. Is this sufficient?

return elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOff, nil
default:
return "", errors.Errorf("Invalid value for securityGroupsInboundRulesOnPrivateLink status: %v, value must be one of [%v, %v]", securityGroupsInboundRulesOnPrivateLink, string(elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOn), string(elbv2model.SecurityGroupsInboundRulesOnPrivateLinkOff))
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will be confusing that it says only accept "on" and "off", but it actually allows an empty string "".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It won't allow empty string "". An empty string will also fall into the condition of an invalid value.

@oliviassss
Copy link
Collaborator

oliviassss commented Mar 13, 2024

@wweiwei-li, thanks for the contribution. I left some comments.

The value of it can be set to "off" and "on". It is optional and if it is default to "on"

For backward compatibility, I think we shouldn't have "on" as default.
If the user doesn't specify the annotation, we skip adding any spec to their NLBs, nothing changed; if the annotation exists, we validate the value and add the spec accordingly.

@shraddhabang
Copy link
Collaborator

/lgtm
/approve

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Mar 14, 2024
@oliviassss
Copy link
Collaborator

/lgtm
/assign @M00nF1sh

@oliviassss oliviassss added the tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges. label Mar 15, 2024
SvcLBSuffixLoadBalancerAttributes = "aws-load-balancer-attributes"
SvcLBSuffixLoadBalancerSecurityGroups = "aws-load-balancer-security-groups"
SvcLBSuffixManageSGRules = "aws-load-balancer-manage-backend-security-group-rules"
SvcLBSuffixEnforceSGInboundRulesOnPrivateLinkTraffic = "aws-load-balancer-inbound-sg-rules-on-private-link-traffic"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be something like

aws-load-balancer-enforce-sg-inbound-rules-on-pl-traffic

or

aws-load-balancer-enforce-sg-inbound-rules-on-private-link-traffic

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I like aws-load-balancer-enforce-sg-inbound-rules-on-private-link-traffic but annotation must be 63 characters or less, so we decided to use aws-load-balancer-inbound-sg-rules-on-private-link-traffic

@@ -75,6 +76,12 @@ func (m *defaultLoadBalancerManager) Create(ctx context.Context, resLB *elbv2mod
return elbv2model.LoadBalancerStatus{}, err
}

if resLB.Spec.Type == elbv2model.LoadBalancerTypeNetwork && resLB.Spec.SecurityGroupsInboundRulesOnPrivateLink != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic can only be set with this SetSecurityGroups call after LB creation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can only set it after LB creation

@@ -186,20 +193,52 @@ func (m *defaultLoadBalancerManager) updateSDKLoadBalancerWithSecurityGroups(ctx
}
desiredSecurityGroups := sets.NewString(awssdk.StringValueSlice(securityGroups)...)
currentSecurityGroups := sets.NewString(awssdk.StringValueSlice(sdkLB.LoadBalancer.SecurityGroups)...)
if desiredSecurityGroups.Equal(currentSecurityGroups) {

if desiredSecurityGroups.Equal(currentSecurityGroups) && (resLB.Spec.Type != elbv2model.LoadBalancerTypeNetwork || resLB.Spec.SecurityGroupsInboundRulesOnPrivateLink == nil) {
Copy link
Collaborator

@M00nF1sh M00nF1sh Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's add a method that checks whether SGInboundRulesOnPrivateLink shall be enforced
something like

isEnforceSGInboundRulesOnPrivateLinkUpdated(sdkLB, resLB) {
   ...checks sdkLB.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic & resLB.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic
}

then if desiredSecurityGroups.Equal(currentSecurityGroups) && !isEnforceSGInboundRulesOnPrivateLinkUpdated(...) { return nil}

and later, simple set req. EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic based on resLB.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrap up all checks into isEnforceSGInboundRulesOnPrivateLinkUpdated(sdkLB, resLB) method is a great idea. I made change based on your suggestion. To be able to use current and desired value in logs, I also return currentEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic, desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic value. Please let me know if it looks good to you.

@k8s-ci-robot k8s-ci-robot removed the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Mar 21, 2024
@@ -87,6 +92,22 @@ func (t *defaultModelBuildTask) buildLoadBalancerSpec(ctx context.Context, schem
LoadBalancerAttributes: lbAttributes,
Tags: tags,
}

if securityGroupsInboundRulesOnPrivateLink != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd prefer to use below:

if securityGroupsInboundRulesOnPrivateLink != ""  {
   spec.SecurityGroupsInboundRulesOnPrivateLink = &securityGroupsInboundRulesOnPrivateLink
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point, will update

@@ -177,6 +198,22 @@ func (t *defaultModelBuildTask) buildLoadBalancerIPAddressType(_ context.Context
}
}

func (t *defaultModelBuildTask) buildSecurityGroupsInboundRulesOnPrivateLink(_ context.Context) (elbv2model.SecurityGroupsInboundRulesOnPrivateLinkStatus, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we change the return value to be *elbv2model.SecurityGroupsInboundRulesOnPrivateLinkStatus, error
thus we can return nil instead of "" to represent "not set".
The reason why this is better is technically "" is not a valid value for SecurityGroupsInboundRulesOnPrivateLinkStatus enum.

return nil
}

if !desiredSecurityGroups.Equal(currentSecurityGroups) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Nit, since there is only one change call technically, i'd prefer to use a single log call
    var changeDescriptions []string
    if !desiredSecurityGroups.Equal(currentSecurityGroups) {
    changeDescriptions = append(changeDescriptions, "changeSecurityGroups", changeSecurityGroupsDesc)
    }
    req := &elbv2sdk.SetSecurityGroupsInput{
    LoadBalancerArn: sdkLB.LoadBalancer.LoadBalancerArn,
    SecurityGroups: securityGroups,
    }

if ! isEnforceSGInboundRulesOnPrivateLinkUpdated {
changeDescriptions = append(changeDescriptions, "changeEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic", changeEnforceSecurityGroupInboundRulesOnPrivateLinkTrafficDesc)
req.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: awssdk.String(desiredEnforceSecurityGroupInboundRulesOnPrivateLinkTraffic),
}
m.logger.Info("modifying loadBalancer security group settings",
"stackID", resLB.Stack().StackID(),
"resourceID", resLB.ID(),
"arn", awssdk.StringValue(sdkLB.LoadBalancer.LoadBalancerArn),
...changeDescriptions)
2. per the current log, req.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic is only set when there is a change. So if the security group changed but isEnforceSGInboundRulesOnPrivateLinkUpdated is false, then req.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic will be nil, what's the API behavior under such case, will it untouch the existing EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic settings on LB or reset it to default value(something we don't want).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Yes, we can switch to use a single log call.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Good question, it will untouch the existing EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic settings

Copy link
Collaborator

@M00nF1sh M00nF1sh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm
/approve

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Apr 4, 2024
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: M00nF1sh, shraddhabang, wweiwei-li

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 4, 2024
@k8s-ci-robot k8s-ci-robot merged commit 30a15d5 into kubernetes-sigs:main Apr 4, 2024
@vumdao
Copy link

vumdao commented Apr 11, 2024

Hi, can we know when this ticket is released?

@elaineclsouza
Copy link

Any predictions on when this will be published in the release?

@VanessaDiniz
Copy link

I'm also waiting

@william-araujo-dock
Copy link

When will you implement this feature?

@wweiwei-li
Copy link
Collaborator Author

Hey, This feature will be released in V2.8.0 targeting the end of this week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants