Add groups option to cluster proxy (#297)

This change adds an option to specify groups a cluster proxy peer is a
member of.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added optional `spec.groups` to the ClusterProxy custom resource to
associate group references by `id`, `name`, or `localRef`.
* CRD schema includes OpenAPI validation to enforce that each group
reference specifies exactly one selector.

* **Bug Fixes**
* Improved deep-copy behavior for ClusterProxy spec so group references
are copied safely and don’t share underlying slices.

* **Documentation**
* Updated API reference docs and README API table to include the new
`groups` field.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Philip Laine
2026-06-16 10:49:57 +02:00
committed by GitHub
parent 67e0ce4e40
commit da90b6ae42
9 changed files with 102 additions and 1 deletions
+1
View File
@@ -28,3 +28,4 @@ helm upgrade --install --create-namespace -n netbird netbird-operator oci://ghcr
| [NetworkRouter](docs/api-reference.md#networkrouter) | `netbird.io/v1alpha1` | | [NetworkRouter](docs/api-reference.md#networkrouter) | `netbird.io/v1alpha1` |
| [SetupKey](docs/api-reference.md#setupkey) | `netbird.io/v1alpha1` | | [SetupKey](docs/api-reference.md#setupkey) | `netbird.io/v1alpha1` |
| [SidecarProfile](docs/api-reference.md#sidecarprofile) | `netbird.io/v1alpha1` | | [SidecarProfile](docs/api-reference.md#sidecarprofile) | `netbird.io/v1alpha1` |
| [ClusterProxy](docs/api-reference.md#clusterproxy) | `netbird.io/v1alpha1` |
+4
View File
@@ -21,6 +21,10 @@ type ClusterProxySpec struct {
// ServiceAccountName is a reference to the service account used for impersonation. // ServiceAccountName is a reference to the service account used for impersonation.
// +required // +required
ServiceAccountName string `json:"serviceAccountName"` ServiceAccountName string `json:"serviceAccountName"`
// Groups are references to groups that the peer will be a part of.
// +optional
Groups []GroupReference `json:"groups,omitempty"`
} }
// ClusterProxyStatus defines the observed state of ClusterProxy. // ClusterProxyStatus defines the observed state of ClusterProxy.
+8 -1
View File
@@ -17,7 +17,7 @@ func (in *ClusterProxy) DeepCopyInto(out *ClusterProxy) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status) in.Status.DeepCopyInto(&out.Status)
} }
@@ -74,6 +74,13 @@ func (in *ClusterProxyList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterProxySpec) DeepCopyInto(out *ClusterProxySpec) { func (in *ClusterProxySpec) DeepCopyInto(out *ClusterProxySpec) {
*out = *in *out = *in
if in.Groups != nil {
in, out := &in.Groups, &out.Groups
*out = make([]GroupReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProxySpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProxySpec.
@@ -57,6 +57,37 @@ spec:
x-kubernetes-validations: x-kubernetes-validations:
- message: Value is immutable - message: Value is immutable
rule: self == oldSelf rule: self == oldSelf
groups:
description: Groups are references to groups that the peer will be
a part of.
items:
properties:
id:
description: ID is the id of the group.
type: string
localRef:
description: LocalReference is a reference to a group in the
same namespace.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
name:
description: Name is the name of the group.
type: string
type: object
x-kubernetes-validations:
- message: Exactly one of id, name, or localRef must be set
rule: (has(self.id)?1:0)+(has(self.name)?1:0)+(has(self.localRef)?1:0)==1
type: array
serviceAccountName: serviceAccountName:
description: ServiceAccountName is a reference to the service account description: ServiceAccountName is a reference to the service account
used for impersonation. used for impersonation.
@@ -57,6 +57,37 @@ spec:
x-kubernetes-validations: x-kubernetes-validations:
- message: Value is immutable - message: Value is immutable
rule: self == oldSelf rule: self == oldSelf
groups:
description: Groups are references to groups that the peer will be
a part of.
items:
properties:
id:
description: ID is the id of the group.
type: string
localRef:
description: LocalReference is a reference to a group in the
same namespace.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
name:
description: Name is the name of the group.
type: string
type: object
x-kubernetes-validations:
- message: Exactly one of id, name, or localRef must be set
rule: (has(self.id)?1:0)+(has(self.name)?1:0)+(has(self.localRef)?1:0)==1
type: array
serviceAccountName: serviceAccountName:
description: ServiceAccountName is a reference to the service account description: ServiceAccountName is a reference to the service account
used for impersonation. used for impersonation.
+2
View File
@@ -55,6 +55,7 @@ _Appears in:_
| `clusterName` _string_ | ClusterName is the name of the Kubernetes cluster. | | Required: \{\} <br /> | | `clusterName` _string_ | ClusterName is the name of the Kubernetes cluster. | | Required: \{\} <br /> |
| `apiServer` _string_ | APIServer is the URL of the Kubernetes API server to proxy requests to. | https://kubernetes.default.svc.cluster.local | Required: \{\} <br /> | | `apiServer` _string_ | APIServer is the URL of the Kubernetes API server to proxy requests to. | https://kubernetes.default.svc.cluster.local | Required: \{\} <br /> |
| `serviceAccountName` _string_ | ServiceAccountName is a reference to the service account used for impersonation. | | Required: \{\} <br /> | | `serviceAccountName` _string_ | ServiceAccountName is a reference to the service account used for impersonation. | | Required: \{\} <br /> |
| `groups` _[GroupReference](#groupreference) array_ | Groups are references to groups that the peer will be a part of. | | Optional: \{\} <br /> |
#### ClusterProxyStatus #### ClusterProxyStatus
@@ -158,6 +159,7 @@ Group is the Schema for the groups API.
_Appears in:_ _Appears in:_
- [ClusterProxySpec](#clusterproxyspec)
- [NetworkResourceSpec](#networkresourcespec) - [NetworkResourceSpec](#networkresourcespec)
- [SetupKeySpec](#setupkeyspec) - [SetupKeySpec](#setupkeyspec)
@@ -67,6 +67,16 @@ func (r *ClusterProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request
WithEphemeral(true). WithEphemeral(true).
WithAllowExtraDnsLabels(true), WithAllowExtraDnsLabels(true),
) )
for _, group := range clusterProxy.Spec.Groups {
switch {
case group.ID != nil:
setupKeyAC.Spec.AutoGroups = append(setupKeyAC.Spec.AutoGroups, *nbv1alpha1ac.GroupReference().WithID(*group.ID))
case group.Name != nil:
setupKeyAC.Spec.AutoGroups = append(setupKeyAC.Spec.AutoGroups, *nbv1alpha1ac.GroupReference().WithName(*group.Name))
case group.LocalRef != nil:
setupKeyAC.Spec.AutoGroups = append(setupKeyAC.Spec.AutoGroups, *nbv1alpha1ac.GroupReference().WithLocalRef(*group.LocalRef))
}
}
err = r.Client.Apply(ctx, setupKeyAC, client.ForceOwnership) err = r.Client.Apply(ctx, setupKeyAC, client.ForceOwnership)
if err != nil { if err != nil {
return ctrl.Result{}, err return ctrl.Result{}, err
@@ -15,6 +15,8 @@ type ClusterProxySpecApplyConfiguration struct {
APIServer *string `json:"apiServer,omitempty"` APIServer *string `json:"apiServer,omitempty"`
// ServiceAccountName is a reference to the service account used for impersonation. // ServiceAccountName is a reference to the service account used for impersonation.
ServiceAccountName *string `json:"serviceAccountName,omitempty"` ServiceAccountName *string `json:"serviceAccountName,omitempty"`
// Groups are references to groups that the peer will be a part of.
Groups []GroupReferenceApplyConfiguration `json:"groups,omitempty"`
} }
// ClusterProxySpecApplyConfiguration constructs a declarative configuration of the ClusterProxySpec type for use with // ClusterProxySpecApplyConfiguration constructs a declarative configuration of the ClusterProxySpec type for use with
@@ -46,3 +48,16 @@ func (b *ClusterProxySpecApplyConfiguration) WithServiceAccountName(value string
b.ServiceAccountName = &value b.ServiceAccountName = &value
return b return b
} }
// WithGroups adds the given value to the Groups field in the declarative configuration
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
// If called multiple times, values provided by each call will be appended to the Groups field.
func (b *ClusterProxySpecApplyConfiguration) WithGroups(values ...*GroupReferenceApplyConfiguration) *ClusterProxySpecApplyConfiguration {
for i := range values {
if values[i] == nil {
panic("nil value passed to WithGroups")
}
b.Groups = append(b.Groups, *values[i])
}
return b
}