Add new resource for Kubernetes API proxy (#279)

This adds a new resource which deploys a Kubernetes API server proxy
that can be used to access the API server without tokens through
Netbird.

Part of #274 

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

* **New Features**
* Added ClusterProxy custom resource for cluster API proxying
capabilities

* **Documentation**
  * Added ClusterProxy API reference documentation with schema details

* **Examples**
* Added example ClusterProxy configuration and RBAC setup for cluster
proxy targets

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/netbirdio/kubernetes-operator/pull/279?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Philip Laine
2026-06-01 11:41:48 +02:00
committed by GitHub
parent a3546e4804
commit 5dd73dcc80
19 changed files with 1177 additions and 31 deletions
+78
View File
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: BSD-3-Clause
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ClusterProxySpec defines the desired state of ClusterProxy.
type ClusterProxySpec struct {
// ClusterName is the name of the Kubernetes cluster.
// +required
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
ClusterName string `json:"clusterName"`
// APIServer is the URL of the Kubernetes API server to proxy requests to.
// +required
// +kubebuilder:default="https://kubernetes.default.svc.cluster.local"
APIServer string `json:"apiServer"`
// ServiceAccountName is a reference to the service account used for impersonation.
// +required
ServiceAccountName string `json:"serviceAccountName"`
}
// ClusterProxyStatus defines the observed state of ClusterProxy.
type ClusterProxyStatus struct {
// ObservedGeneration is the last reconciled generation.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Conditions holds the conditions for the ClusterProxy.
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
// ClusterProxy is the Schema for the clusterproxies API
type ClusterProxy struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// +required
Spec ClusterProxySpec `json:"spec"`
// +kubebuilder:default={"observedGeneration":-1}
Status ClusterProxyStatus `json:"status,omitempty"`
}
// GetConditions returns the status conditions of the object.
func (n *ClusterProxy) GetConditions() []metav1.Condition {
return n.Status.Conditions
}
// SetConditions sets the status conditions on the object.
func (n *ClusterProxy) SetConditions(conditions []metav1.Condition) {
n.Status.Conditions = conditions
}
// +kubebuilder:object:root=true
// ClusterProxyList contains a list of ClusterProxy
type ClusterProxyList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitzero"`
Items []ClusterProxy `json:"items"`
}
func init() {
SchemeBuilder.Register(&ClusterProxy{}, &ClusterProxyList{})
}
+112 -16
View File
@@ -7,39 +7,135 @@
package v1alpha1
import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterProxy) DeepCopyInto(out *ClusterProxy) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProxy.
func (in *ClusterProxy) DeepCopy() *ClusterProxy {
if in == nil {
return nil
}
out := new(ClusterProxy)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ClusterProxy) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterProxyList) DeepCopyInto(out *ClusterProxyList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]ClusterProxy, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProxyList.
func (in *ClusterProxyList) DeepCopy() *ClusterProxyList {
if in == nil {
return nil
}
out := new(ClusterProxyList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ClusterProxyList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterProxySpec) DeepCopyInto(out *ClusterProxySpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProxySpec.
func (in *ClusterProxySpec) DeepCopy() *ClusterProxySpec {
if in == nil {
return nil
}
out := new(ClusterProxySpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterProxyStatus) DeepCopyInto(out *ClusterProxyStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterProxyStatus.
func (in *ClusterProxyStatus) DeepCopy() *ClusterProxyStatus {
if in == nil {
return nil
}
out := new(ClusterProxyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ContainerOverride) DeepCopyInto(out *ContainerOverride) {
*out = *in
if in.Env != nil {
in, out := &in.Env, &out.Env
*out = make([]v1.EnvVar, len(*in))
*out = make([]corev1.EnvVar, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.SecurityContext != nil {
in, out := &in.SecurityContext, &out.SecurityContext
*out = new(v1.SecurityContext)
*out = new(corev1.SecurityContext)
(*in).DeepCopyInto(*out)
}
if in.StartupProbe != nil {
in, out := &in.StartupProbe, &out.StartupProbe
*out = new(v1.Probe)
*out = new(corev1.Probe)
(*in).DeepCopyInto(*out)
}
if in.LivenessProbe != nil {
in, out := &in.LivenessProbe, &out.LivenessProbe
*out = new(v1.Probe)
*out = new(corev1.Probe)
(*in).DeepCopyInto(*out)
}
if in.ReadinessProbe != nil {
in, out := &in.ReadinessProbe, &out.ReadinessProbe
*out = new(v1.Probe)
*out = new(corev1.Probe)
(*in).DeepCopyInto(*out)
}
}
@@ -158,7 +254,7 @@ func (in *GroupReference) DeepCopyInto(out *GroupReference) {
}
if in.LocalRef != nil {
in, out := &in.LocalRef, &out.LocalRef
*out = new(v1.LocalObjectReference)
*out = new(corev1.LocalObjectReference)
**out = **in
}
}
@@ -193,7 +289,7 @@ func (in *GroupStatus) DeepCopyInto(out *GroupStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -298,7 +394,7 @@ func (in *NetworkResourceStatus) DeepCopyInto(out *NetworkResourceStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -400,7 +496,7 @@ func (in *NetworkRouterStatus) DeepCopyInto(out *NetworkRouterStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -481,7 +577,7 @@ func (in *SetupKeySpec) DeepCopyInto(out *SetupKeySpec) {
*out = *in
if in.Duration != nil {
in, out := &in.Duration, &out.Duration
*out = new(metav1.Duration)
*out = new(v1.Duration)
**out = **in
}
if in.AutoGroups != nil {
@@ -508,7 +604,7 @@ func (in *SetupKeyStatus) DeepCopyInto(out *SetupKeyStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -590,7 +686,7 @@ func (in *SidecarProfileSpec) DeepCopyInto(out *SidecarProfileSpec) {
out.SetupKeyRef = in.SetupKeyRef
if in.PodSelector != nil {
in, out := &in.PodSelector, &out.PodSelector
*out = new(metav1.LabelSelector)
*out = new(v1.LabelSelector)
(*in).DeepCopyInto(*out)
}
if in.ExtraDNSLabels != nil {
@@ -620,7 +716,7 @@ func (in *SidecarProfileStatus) DeepCopyInto(out *SidecarProfileStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -661,7 +757,7 @@ func (in *WorkloadOverride) DeepCopyInto(out *WorkloadOverride) {
}
if in.PodTemplate != nil {
in, out := &in.PodTemplate, &out.PodTemplate
*out = new(v1.PodTemplateSpec)
*out = new(corev1.PodTemplateSpec)
(*in).DeepCopyInto(*out)
}
}