Skip to content

Conversation

@CodeBleu
Copy link

@CodeBleu CodeBleu commented Jun 9, 2025

* Annotation added to allow setting of Source CIDR for Load Balancer rule

Adding annotation like the following will create the rule with source CIDR set:
    annotations:
        service.beta.kubernetes.io/cloudstack-load-balancer-source-cidrs: "1.2.3.4/32,5.6.7.8/32"

image

    * Annotation added to allow setting of Source CIDR for Load Balancer
      rule
@CodeBleu CodeBleu requested a review from Pearl1594 June 9, 2025 18:23
@CodeBleu CodeBleu added the enhancement New feature or request label Oct 10, 2025
Copy link

@kiranchavala kiranchavala left a comment

Choose a reason for hiding this comment

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

LGTM

Tested by building a docker image based on the pr

Before the fix

After you expose a service, there is no source cidr list populated

k expose deploy/nginx-deployment --port=80 --type=LoadBalancer

Screenshot 2025-10-15 at 1 00 08 PM

After fix, there is source cidr populated on the loadbalancer rule

Screenshot 2025-10-15 at 12 59 42 PM

Tested with normal service.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx-deployment2
  namespace: default
  annotations:
    service.beta.kubernetes.io/cloudstack-load-balancer-source-cidrs: "1.2.3.4/32,5.6.7.8/32"
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 30558
  externalTrafficPolicy: Cluster
  allocateLoadBalancerNodePorts: true
  sessionAffinity: None
Screenshot 2025-10-15 at 1 26 53 PM

@CodeBleu
Copy link
Author

@Pearl1594 Do you mind doing a quick review of this? I believe I need 2 approvals and a test output before I can merge.

Copy link

@DaanHoogland DaanHoogland left a comment

Choose a reason for hiding this comment

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

clgtm

Comment on lines +615 to +632
// Read the source CIDR annotation
sourceCIDRs, ok := service.Annotations[ServiceAnnotationLoadBalancerSourceCidrs]
var cidrList []string
if ok && sourceCIDRs != "" {
cidrList = strings.Split(sourceCIDRs, ",")
for i, cidr := range cidrList {
cidr = strings.TrimSpace(cidr)
if _, _, err := net.ParseCIDR(cidr); err != nil {
return nil, fmt.Errorf("invalid CIDR in annotation %s: %s", ServiceAnnotationLoadBalancerSourceCidrs, cidr)
}
cidrList[i] = cidr
}
} else {
cidrList = []string{defaultAllowedCIDR}
}

// Set the CIDR list in the parameters
p.SetCidrlist(cidrList)

Choose a reason for hiding this comment

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

i’d rather see

Suggested change
// Read the source CIDR annotation
sourceCIDRs, ok := service.Annotations[ServiceAnnotationLoadBalancerSourceCidrs]
var cidrList []string
if ok && sourceCIDRs != "" {
cidrList = strings.Split(sourceCIDRs, ",")
for i, cidr := range cidrList {
cidr = strings.TrimSpace(cidr)
if _, _, err := net.ParseCIDR(cidr); err != nil {
return nil, fmt.Errorf("invalid CIDR in annotation %s: %s", ServiceAnnotationLoadBalancerSourceCidrs, cidr)
}
cidrList[i] = cidr
}
} else {
cidrList = []string{defaultAllowedCIDR}
}
// Set the CIDR list in the parameters
p.SetCidrlist(cidrList)
// Set the CIDR list in the parameters
p.SetCidrlist(readTheSourceCidrAnnotation(service))

and

func readTheSourceCidrAnnotation(service *corev1.Service) []string {
	// Read the source CIDR annotation
	sourceCIDRs, ok := service.Annotations[ServiceAnnotationLoadBalancerSourceCidrs]
	var cidrList []string
	if ok && sourceCIDRs != "" {
		cidrList = strings.Split(sourceCIDRs, ",")
		for i, cidr := range cidrList {
			cidr = strings.TrimSpace(cidr)
			if _, _, err := net.ParseCIDR(cidr); err != nil {
				return nil, fmt.Errorf("invalid CIDR in annotation %s: %s", ServiceAnnotationLoadBalancerSourceCidrs, cidr)
			}
			cidrList[i] = cidr
		}
	} else {
		cidrList = []string{defaultAllowedCIDR}
	}
    return cidrList
}

(no waranty on the syntax)

@vishesh92 vishesh92 added this to the 1.2.0 milestone Nov 24, 2025
@vishesh92 vishesh92 requested a review from Copilot November 24, 2025 09:41
Copilot finished reviewing on behalf of vishesh92 November 24, 2025 09:49
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for configuring source CIDR restrictions on CloudStack load balancer rules through a new Kubernetes service annotation. This allows users to restrict which IP ranges can access their load balancers by setting service.beta.kubernetes.io/cloudstack-load-balancer-source-cidrs on their Service resources.

Key Changes:

  • Added new annotation constant ServiceAnnotationLoadBalancerSourceCidrs for specifying allowed source CIDRs
  • Implemented CIDR validation and parsing logic in createLoadBalancerRule
  • Modified function signature to pass the service object for annotation access

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +618 to +629
if ok && sourceCIDRs != "" {
cidrList = strings.Split(sourceCIDRs, ",")
for i, cidr := range cidrList {
cidr = strings.TrimSpace(cidr)
if _, _, err := net.ParseCIDR(cidr); err != nil {
return nil, fmt.Errorf("invalid CIDR in annotation %s: %s", ServiceAnnotationLoadBalancerSourceCidrs, cidr)
}
cidrList[i] = cidr
}
} else {
cidrList = []string{defaultAllowedCIDR}
}
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

[nitpick] The CIDR parsing and validation logic (lines 618-629) is embedded directly in createLoadBalancerRule. Consider extracting this into a helper function (e.g., parseCIDRList(cidrs string) ([]string, error)) to:

  1. Improve testability - the validation logic could be unit tested independently
  2. Enable reuse - if firewall or Network ACL rules need to respect this annotation in the future (which they should for security consistency)
  3. Simplify the createLoadBalancerRule function

This would follow the pattern established by existing helper functions like getStringFromServiceAnnotation and getBoolFromServiceAnnotation.

Copilot uses AI. Check for mistakes.
for i, cidr := range cidrList {
cidr = strings.TrimSpace(cidr)
if _, _, err := net.ParseCIDR(cidr); err != nil {
return nil, fmt.Errorf("invalid CIDR in annotation %s: %s", ServiceAnnotationLoadBalancerSourceCidrs, cidr)
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The error message doesn't include the underlying error from net.ParseCIDR(cidr), which could provide valuable debugging information. Consider wrapping the error:

return nil, fmt.Errorf("invalid CIDR %q in annotation %s: %w", cidr, ServiceAnnotationLoadBalancerSourceCidrs, err)

This would give users more context about why the CIDR is invalid (e.g., "invalid CIDR notation" vs "CIDR address has host bits set").

Suggested change
return nil, fmt.Errorf("invalid CIDR in annotation %s: %s", ServiceAnnotationLoadBalancerSourceCidrs, cidr)
return nil, fmt.Errorf("invalid CIDR %q in annotation %s: %w", cidr, ServiceAnnotationLoadBalancerSourceCidrs, err)

Copilot uses AI. Check for mistakes.
Comment on lines +615 to +632
// Read the source CIDR annotation
sourceCIDRs, ok := service.Annotations[ServiceAnnotationLoadBalancerSourceCidrs]
var cidrList []string
if ok && sourceCIDRs != "" {
cidrList = strings.Split(sourceCIDRs, ",")
for i, cidr := range cidrList {
cidr = strings.TrimSpace(cidr)
if _, _, err := net.ParseCIDR(cidr); err != nil {
return nil, fmt.Errorf("invalid CIDR in annotation %s: %s", ServiceAnnotationLoadBalancerSourceCidrs, cidr)
}
cidrList[i] = cidr
}
} else {
cidrList = []string{defaultAllowedCIDR}
}

// Set the CIDR list in the parameters
p.SetCidrlist(cidrList)
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The annotation ServiceAnnotationLoadBalancerSourceCidrs only affects the load balancer rule's CIDR list, but doesn't affect firewall rules (which use service.Spec.LoadBalancerSourceRanges at line 188) or Network ACL rules (which are hardcoded to 0.0.0.0/0 at line 922).

This creates an inconsistent security posture where:

  1. The load balancer rule may restrict access to specific CIDRs via the annotation
  2. But the firewall/ACL rules could allow broader access, effectively bypassing the restriction

Recommendation: Consider one of these approaches:

  • Option 1 (Preferred): Use a unified CIDR source that checks the annotation first, falls back to service.Spec.LoadBalancerSourceRanges, then to defaultAllowedCIDR. Apply this to all three security mechanisms (LB rules, firewall rules, and Network ACLs).
  • Option 2: Document clearly that users must set both the annotation AND service.Spec.LoadBalancerSourceRanges to the same values for consistent security enforcement.
  • Option 3: Have the annotation take precedence and automatically apply it to firewall and ACL rules as well.

Option 1 would provide the most intuitive and secure behavior, as it aligns with Kubernetes' standard LoadBalancerSourceRanges field while allowing the CloudStack-specific annotation to override it when needed.

Copilot uses AI. Check for mistakes.
ServiceAnnotationLoadBalancerProxyProtocol = "service.beta.kubernetes.io/cloudstack-load-balancer-proxy-protocol"

ServiceAnnotationLoadBalancerProxyProtocol = "service.beta.kubernetes.io/cloudstack-load-balancer-proxy-protocol"
ServiceAnnotationLoadBalancerLoadbalancerHostname = "service.beta.kubernetes.io/cloudstack-load-balancer-hostname"
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The new annotation ServiceAnnotationLoadBalancerSourceCidrs lacks a documentation comment explaining its purpose and usage. For consistency with ServiceAnnotationLoadBalancerProxyProtocol (lines 41-44), consider adding a comment that describes:

  • What the annotation does
  • The expected format (comma-separated CIDRs)
  • Any version requirements or constraints
Suggested change
ServiceAnnotationLoadBalancerLoadbalancerHostname = "service.beta.kubernetes.io/cloudstack-load-balancer-hostname"
ServiceAnnotationLoadBalancerLoadbalancerHostname = "service.beta.kubernetes.io/cloudstack-load-balancer-hostname"
// ServiceAnnotationLoadBalancerSourceCidrs is the annotation used on the
// service to specify the allowed source CIDRs for a CloudStack load balancer.
// The value should be a comma-separated list of CIDRs (e.g., "10.0.0.0/8,192.168.1.0/24").
// If not specified, the default is to allow all sources ("0.0.0.0/0").
// There are no specific CloudStack version requirements for this annotation.

Copilot uses AI. Check for mistakes.
@vishesh92
Copy link
Member

Code looks good. But it doesn't handle when the CIDR list is updated in the annotation.

@CodeBleu
Copy link
Author

Code looks good. But it doesn't handle when the CIDR list is updated in the annotation.

This is because that API didn't exist when the PR was first created. - apache/cloudstack#11568

Can we merge this as is, and maybe create an issue for the update piece as well as some code refactoring suggestions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants