Add support for policy auto-creation

This commit is contained in:
M Essam Hamed
2025-04-16 14:45:23 +02:00
committed by M. Essam
parent 924f17f525
commit 38445c58fa
12 changed files with 712 additions and 212 deletions
+22 -9
View File
@@ -33,6 +33,7 @@ var (
errUnknownProtocol = fmt.Errorf("Unknown protocol")
errKubernetesAPI = fmt.Errorf("kubernetes API error")
errNetBirdAPI = fmt.Errorf("netbird API error")
errInvalidValue = fmt.Errorf("invalid value")
)
const (
@@ -77,16 +78,28 @@ func (r *NBPolicyReconciler) mapResources(ctx context.Context, nbPolicy *netbird
}
for _, resource := range resources {
if resource.Status.PolicyName != nil && util.Contains(util.SplitTrim(*resource.Status.PolicyName, ","), nbPolicy.Name) {
// Groups
groups = append(groups, resource.Status.Groups...)
generatedBy := nbPolicy.Annotations["netbird.io/generated-by"]
generatedBy = strings.ReplaceAll(generatedBy, "/", "-")
if resource.Status.PolicyName == nil {
continue
}
resourcePolicies := util.SplitTrim(*resource.Status.PolicyName, ",")
for _, p := range resource.Spec.TCPPorts {
portMapping[protocolTCP][p] = nil
}
for _, p := range resource.Spec.UDPPorts {
portMapping[protocolUDP][p] = nil
}
if generatedBy == "" && !util.Contains(resourcePolicies, nbPolicy.Name) {
continue
}
if generatedBy != "" && !util.Contains(resourcePolicies, strings.ReplaceAll(nbPolicy.Name, "-"+generatedBy, "")) {
continue
}
// Groups
groups = append(groups, resource.Status.Groups...)
for _, p := range resource.Spec.TCPPorts {
portMapping[protocolTCP][p] = nil
}
for _, p := range resource.Spec.UDPPorts {
portMapping[protocolUDP][p] = nil
}
}
+242 -104
View File
@@ -14,6 +14,7 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"github.com/go-logr/logr"
netbirdiov1 "github.com/netbirdio/kubernetes-operator/api/v1"
@@ -25,10 +26,12 @@ import (
// NBResourceReconciler reconciles a NBResource object
type NBResourceReconciler struct {
client.Client
Scheme *runtime.Scheme
APIKey string
ManagementURL string
netbird *netbird.Client
Scheme *runtime.Scheme
APIKey string
ManagementURL string
AllowAutomaticPolicyCreation bool
ClusterName string
netbird *netbird.Client
}
// Reconcile is part of the main kubernetes reconciliation loop which aims to
@@ -96,6 +99,7 @@ func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request)
err = r.handlePolicy(ctx, req, nbResource, groupIDs, logger)
if err != nil {
nbResource.Status.Conditions = netbirdiov1.NBConditionFalse("internalError", fmt.Sprintf("Error occurred handling policy changes: %v", err))
return ctrl.Result{}, err
}
nbResource.Status.Conditions = netbirdiov1.NBConditionTrue()
@@ -103,115 +107,236 @@ func (r *NBResourceReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, nil
}
func (r *NBResourceReconciler) handlePolicyCreate(ctx context.Context, nbResource *netbirdiov1.NBResource, req ctrl.Request, policy string, nbPolicy *netbirdiov1.NBPolicy, logger logr.Logger) error {
if len(nbResource.Spec.PolicySourceGroups) == 0 {
logger.Error(errInvalidValue, "Cannot auto-generate policy, missing source groups.")
return fmt.Errorf("cannot auto-generate policy, missing source groups")
}
name := nbResource.Spec.PolicyFriendlyName[policy]
if name == "" {
name = fmt.Sprintf("Autogenerated policy for resource %s/%s in cluster %s", nbResource.Namespace, nbResource.Name, r.ClusterName)
}
generatedName := fmt.Sprintf("%s-%s-%s", policy, req.Namespace, req.Name)
*nbPolicy = netbirdiov1.NBPolicy{
ObjectMeta: v1.ObjectMeta{
Name: generatedName,
Annotations: map[string]string{"netbird.io/generated-by": req.NamespacedName.String()},
Finalizers: []string{"netbird.io/cleanup"},
},
Spec: netbirdiov1.NBPolicySpec{
Name: name,
Description: "Generated by " + req.NamespacedName.String(),
SourceGroups: nbResource.Spec.PolicySourceGroups,
Bidirectional: true,
},
}
err := r.Client.Create(ctx, nbPolicy)
if errors.IsAlreadyExists(err) {
err = r.Client.Get(ctx, types.NamespacedName{Name: generatedName}, nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "err", err)
return err
}
if nbPolicy.Annotations == nil {
nbPolicy.Annotations = make(map[string]string)
}
nbPolicy.Annotations["netbird.io/generated-by"] = req.NamespacedName.String()
nbPolicy.Spec = netbirdiov1.NBPolicySpec{
Name: name,
Description: "Generated by " + req.NamespacedName.String(),
SourceGroups: nbResource.Spec.PolicySourceGroups,
Bidirectional: true,
}
err = r.Client.Update(ctx, nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "err", err)
return err
}
} else if err != nil {
logger.Error(errKubernetesAPI, "err", err)
return err
}
if nbResource.Status.PolicyNameMapping == nil {
nbResource.Status.PolicyNameMapping = make(map[string]string)
}
nbResource.Status.PolicyNameMapping[policy] = generatedName
nbResource.Status.PolicySourceGroups = nbResource.Spec.PolicySourceGroups
nbResource.Status.PolicyFriendlyName = nbResource.Spec.PolicyFriendlyName
nbPolicy.Status.ManagedServiceList = append(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
err = r.Client.Status().Update(ctx, nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "err", err)
return err
}
return nil
}
func (r *NBResourceReconciler) handlePolicyAddUpdate(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, policy string, groupIDs []string, logger logr.Logger) error {
var nbPolicy netbirdiov1.NBPolicy
updatePolicyStatus := false
kubernetesPolicyName := policy
if v, ok := nbResource.Status.PolicyNameMapping[policy]; ok {
kubernetesPolicyName = v
}
err := r.Client.Get(ctx, types.NamespacedName{Name: kubernetesPolicyName}, &nbPolicy)
if errors.IsNotFound(err) && r.AllowAutomaticPolicyCreation {
err = r.handlePolicyCreate(ctx, nbResource, req, policy, &nbPolicy, logger)
if err != nil {
return err
}
} else if errors.IsNotFound(err) && !r.AllowAutomaticPolicyCreation {
logger.Info("automatic policy creation is not allowed")
return nil
} else if err != nil {
logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", policy)
return err
}
if !util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) {
nbPolicy.Status.ManagedServiceList = append(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
updatePolicyStatus = true
}
if !util.Equivalent(nbResource.Spec.TCPPorts, nbResource.Status.TCPPorts) {
nbResource.Status.TCPPorts = nbResource.Spec.TCPPorts
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
updatePolicyStatus = true
}
if !util.Equivalent(nbResource.Spec.UDPPorts, nbResource.Status.UDPPorts) {
nbResource.Status.UDPPorts = nbResource.Spec.UDPPorts
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
updatePolicyStatus = true
}
if !util.Equivalent(nbResource.Status.Groups, groupIDs) {
nbResource.Status.Groups = groupIDs
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
updatePolicyStatus = true
}
if _, ok := nbResource.Status.PolicyNameMapping[policy]; ok {
updatePolicySpec := false
if v, ok := nbPolicy.Annotations["netbird.io/generated-by"]; !ok || v != req.NamespacedName.String() {
if nbPolicy.Annotations == nil {
nbPolicy.Annotations = make(map[string]string)
}
nbPolicy.Annotations["netbird.io/generated-by"] = req.NamespacedName.String()
updatePolicySpec = true
}
if v, ok := nbResource.Spec.PolicyFriendlyName[policy]; ok {
if nbPolicy.Spec.Name != v {
nbPolicy.Spec.Name = v
updatePolicySpec = true
}
} else {
if nbPolicy.Spec.Name != fmt.Sprintf("Autogenerated policy for resource %s/%s in cluster %s", nbResource.Namespace, nbResource.Name, r.ClusterName) {
nbPolicy.Spec.Name = fmt.Sprintf("Autogenerated policy for resource %s/%s in cluster %s", nbResource.Namespace, nbResource.Name, r.ClusterName)
updatePolicySpec = true
}
}
if nbPolicy.Spec.Description != "Generated by "+req.NamespacedName.String() {
nbPolicy.Spec.Description = "Generated by " + req.NamespacedName.String()
updatePolicySpec = true
}
if !util.Equivalent(nbPolicy.Spec.SourceGroups, nbResource.Spec.PolicySourceGroups) {
nbPolicy.Spec.SourceGroups = nbResource.Spec.PolicySourceGroups
updatePolicySpec = true
}
if updatePolicySpec {
err := r.Client.Update(ctx, &nbPolicy)
if err != nil {
return err
}
}
}
if updatePolicyStatus {
err := r.Client.Status().Update(ctx, &nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", policy)
return err
}
}
return nil
}
func (r *NBResourceReconciler) handlePolicyDelete(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, specPolicies []string, policy string, logger logr.Logger) error {
var nbPolicy netbirdiov1.NBPolicy
if !util.Contains(specPolicies, policy) {
kubeName := policy
if v, ok := nbResource.Status.PolicyNameMapping[policy]; ok {
kubeName = v
}
err := r.Client.Get(ctx, types.NamespacedName{Name: kubeName}, &nbPolicy)
if !errors.IsNotFound(err) {
if err != nil {
logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", policy)
return err
}
if _, ok := nbResource.Status.PolicyNameMapping[policy]; ok {
// Delete Policy
err := r.Client.Delete(ctx, &nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "error deleting NBPolicy", "err", err, "policyName", policy)
return err
}
delete(nbResource.Status.PolicyNameMapping, policy)
} else if util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) {
nbPolicy.Status.ManagedServiceList = util.Without(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
err := r.Client.Status().Update(ctx, &nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", policy)
return err
}
}
}
}
return nil
}
// handlePolicy update NBPolicy if defined to add self reference to policy status
func (r *NBResourceReconciler) handlePolicy(ctx context.Context, req ctrl.Request, nbResource *netbirdiov1.NBResource, groupIDs []string, logger logr.Logger) error {
if nbResource.Status.PolicyName == nil && nbResource.Spec.PolicyName == "" {
return nil
}
var nbPolicy netbirdiov1.NBPolicy
if nbResource.Spec.PolicyName == "" && nbResource.Status.PolicyName != nil {
// Remove self reference from policy status
policies := util.SplitTrim(*nbResource.Status.PolicyName, ",")
for _, policyName := range policies {
err := r.Client.Get(ctx, types.NamespacedName{Name: policyName}, &nbPolicy)
nbResource.Status.PolicyName = nil
if err != nil {
logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", policyName)
return err
}
if util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) {
nbPolicy.Status.ManagedServiceList = util.Without(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
err := r.Client.Status().Update(ctx, &nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", policyName)
return err
}
}
specPolicies := util.SplitTrim(nbResource.Spec.PolicyName, ",")
var statusPolicies []string
if nbResource.Status.PolicyName != nil {
statusPolicies = util.SplitTrim(*nbResource.Status.PolicyName, ",")
}
for _, policy := range specPolicies {
err := r.handlePolicyAddUpdate(ctx, req, nbResource, policy, groupIDs, logger)
if err != nil {
return err
}
} else {
specPolicies := util.SplitTrim(nbResource.Spec.PolicyName, ",")
var statusPolicies []string
if nbResource.Status.PolicyName != nil {
statusPolicies = util.SplitTrim(*nbResource.Status.PolicyName, ",")
}
for _, policy := range statusPolicies {
err := r.handlePolicyDelete(ctx, req, nbResource, specPolicies, policy, logger)
if err != nil {
return err
}
}
for _, policy := range specPolicies {
updatePolicyStatus := false
err := r.Client.Get(ctx, types.NamespacedName{Name: policy}, &nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", policy)
return err
}
if !util.Contains(statusPolicies, policy) {
// New
if !util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) {
nbPolicy.Status.ManagedServiceList = append(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
updatePolicyStatus = true
}
} else {
// Check update
if !util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) {
nbPolicy.Status.ManagedServiceList = append(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
updatePolicyStatus = true
}
if !util.Equivalent(nbResource.Spec.TCPPorts, nbResource.Status.TCPPorts) {
nbResource.Status.TCPPorts = nbResource.Spec.TCPPorts
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
updatePolicyStatus = true
}
if !util.Equivalent(nbResource.Spec.UDPPorts, nbResource.Status.UDPPorts) {
nbResource.Status.UDPPorts = nbResource.Spec.UDPPorts
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
updatePolicyStatus = true
}
if !util.Equivalent(nbResource.Status.Groups, groupIDs) {
nbResource.Status.Groups = groupIDs
nbPolicy.Status.LastUpdatedAt = &v1.Time{Time: time.Now()}
updatePolicyStatus = true
}
}
if updatePolicyStatus {
err := r.Client.Status().Update(ctx, &nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", policy)
return err
}
}
}
for _, policy := range statusPolicies {
// Delete
if !util.Contains(specPolicies, policy) {
err := r.Client.Get(ctx, types.NamespacedName{Name: policy}, &nbPolicy)
if !errors.IsNotFound(err) {
if err != nil {
logger.Error(errKubernetesAPI, "error getting NBPolicy", "err", err, "policyName", policy)
return err
}
if util.Contains(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String()) {
nbPolicy.Status.ManagedServiceList = util.Without(nbPolicy.Status.ManagedServiceList, req.NamespacedName.String())
err := r.Client.Status().Update(ctx, &nbPolicy)
if err != nil {
logger.Error(errKubernetesAPI, "error updating NBPolicy", "err", err, "policyName", policy)
return err
}
}
}
}
}
if nbResource.Status.PolicyName == nil || *nbResource.Status.PolicyName != nbResource.Spec.PolicyName {
nbResource.Status.PolicyName = &nbResource.Spec.PolicyName
}
if nbResource.Status.PolicyName == nil || *nbResource.Status.PolicyName != nbResource.Spec.PolicyName {
nbResource.Status.PolicyName = &nbResource.Spec.PolicyName
}
return nil
@@ -510,5 +635,18 @@ func (r *NBResourceReconciler) SetupWithManager(mgr ctrl.Manager) error {
For(&netbirdiov1.NBResource{}).
Named("nbresource").
Watches(&netbirdiov1.NBGroup{}, handler.EnqueueRequestForOwner(r.Scheme, mgr.GetRESTMapper(), &netbirdiov1.NBResource{})).
Watches(&netbirdiov1.NBPolicy{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
if v, ok := obj.GetAnnotations()["netbird.io/generated-by"]; ok {
return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Namespace: strings.Split(v, "/")[0],
Name: strings.Split(v, "/")[1],
},
},
}
}
return nil
})).
Complete(r)
}
+286 -79
View File
@@ -26,6 +26,7 @@ import (
var _ = Describe("NBResource Controller", func() {
Context("When reconciling a resource", func() {
const resourceName = "test-resource"
const policyGenName = "test-gen"
ctx := context.Background()
@@ -45,9 +46,10 @@ var _ = Describe("NBResource Controller", func() {
server = httptest.NewServer(mux)
netbirdClient = netbird.New(server.URL, "ABC")
controllerReconciler = &NBResourceReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
netbird: netbirdClient,
Client: k8sClient,
Scheme: k8sClient.Scheme(),
netbird: netbirdClient,
ClusterName: "kubernetes",
}
By("creating the custom resource for the Kind NBResource")
@@ -315,65 +317,48 @@ var _ = Describe("NBResource Controller", func() {
})
When("Policy is specified", Ordered, func() {
BeforeAll(func() {
nbPolicy := &netbirdiov1.NBPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-a",
},
Spec: netbirdiov1.NBPolicySpec{
Name: "Test A",
SourceGroups: []string{"All"},
},
}
Expect(k8sClient.Create(ctx, nbPolicy)).To(Succeed())
When("Policy Exists", func() {
BeforeAll(func() {
nbPolicy := &netbirdiov1.NBPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-a",
},
Spec: netbirdiov1.NBPolicySpec{
Name: "Test A",
SourceGroups: []string{"All"},
},
}
Expect(k8sClient.Create(ctx, nbPolicy)).To(Succeed())
nbPolicy = &netbirdiov1.NBPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-b",
},
Spec: netbirdiov1.NBPolicySpec{
Name: "Test B",
SourceGroups: []string{"All"},
},
}
Expect(k8sClient.Create(ctx, nbPolicy)).To(Succeed())
})
AfterAll(func() {
nbPolicy := &netbirdiov1.NBPolicy{}
err := k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)
if !errors.IsNotFound(err) {
Expect(k8sClient.Delete(ctx, nbPolicy)).To(Succeed())
}
nbPolicy = &netbirdiov1.NBPolicy{}
err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-b"}, nbPolicy)
if !errors.IsNotFound(err) {
Expect(k8sClient.Delete(ctx, nbPolicy)).To(Succeed())
}
})
It("should update policy status", func() {
nbresource.Spec.PolicyName = "test-a"
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
nbPolicy = &netbirdiov1.NBPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-b",
},
Spec: netbirdiov1.NBPolicySpec{
Name: "Test B",
SourceGroups: []string{"All"},
},
}
Expect(k8sClient.Create(ctx, nbPolicy)).To(Succeed())
})
Expect(err).NotTo(HaveOccurred())
nbPolicy := &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource"))
})
AfterAll(func() {
nbPolicy := &netbirdiov1.NBPolicy{}
err := k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)
if !errors.IsNotFound(err) {
Expect(k8sClient.Delete(ctx, nbPolicy)).To(Succeed())
}
When("Policy is updated", func() {
It("should remove old reference and add new reference", func() {
nbresource.Spec.PolicyName = "test-b"
nbPolicy = &netbirdiov1.NBPolicy{}
err = k8sClient.Get(ctx, types.NamespacedName{Name: "test-b"}, nbPolicy)
if !errors.IsNotFound(err) {
Expect(k8sClient.Delete(ctx, nbPolicy)).To(Succeed())
}
})
It("should update policy status", func() {
nbresource.Spec.PolicyName = "test-a"
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
nbresource.Status.PolicyName = util.Ptr("test-a")
Expect(k8sClient.Status().Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
@@ -381,35 +366,257 @@ var _ = Describe("NBResource Controller", func() {
nbPolicy := &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).NotTo(ContainElement("default/test-resource"))
nbPolicy = &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-b"}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource"))
})
When("Policy is updated", func() {
It("should remove old reference and add new reference", func() {
nbresource.Spec.PolicyName = "test-b"
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
nbresource.Status.PolicyName = util.Ptr("test-a")
Expect(k8sClient.Status().Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
nbPolicy := &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).NotTo(ContainElement("default/test-resource"))
nbPolicy = &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-b"}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource"))
})
})
When("Policy is removed", func() {
It("should remove old reference", func() {
nbPolicy := &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed())
nbPolicy.Status.ManagedServiceList = []string{"default/test-resource"}
Expect(k8sClient.Status().Update(ctx, nbPolicy)).To(Succeed())
nbresource.Spec.PolicyName = ""
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
nbresource.Status.PolicyName = util.Ptr("test-a")
Expect(k8sClient.Status().Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
nbPolicy = &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).NotTo(ContainElement("default/test-resource"))
})
})
})
When("Policy is removed", func() {
It("should remove old reference", func() {
nbPolicy := &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed())
nbPolicy.Status.ManagedServiceList = []string{"default/test-resource"}
Expect(k8sClient.Status().Update(ctx, nbPolicy)).To(Succeed())
nbresource.Spec.PolicyName = ""
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
nbresource.Status.PolicyName = util.Ptr("test-a")
Expect(k8sClient.Status().Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
When("Policy doesn't exist", func() {
When("Policy auto-creation is enabled", func() {
BeforeEach(func() {
controllerReconciler.AllowAutomaticPolicyCreation = true
})
AfterEach(func() {
nbPolicy := &netbirdiov1.NBPolicy{}
err := k8sClient.Get(ctx, types.NamespacedName{Name: "test-gen-" + nbresource.Namespace + "-" + nbresource.Name}, nbPolicy)
if !errors.IsNotFound(err) {
if len(nbPolicy.Finalizers) > 0 {
nbPolicy.Finalizers = nil
Expect(k8sClient.Update(ctx, nbPolicy)).To(Succeed())
}
err = k8sClient.Delete(ctx, nbPolicy)
if !errors.IsNotFound(err) {
Expect(err).NotTo(HaveOccurred())
}
}
})
Expect(err).NotTo(HaveOccurred())
nbPolicy = &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-a"}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).NotTo(ContainElement("default/test-resource"))
It("should create policy", func() {
nbresource.Spec.PolicyName = policyGenName
nbresource.Spec.PolicySourceGroups = []string{"test"}
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed())
Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName))
nbPolicy := &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource"))
})
When("Source groups is not defined", func() {
It("should return error", func() {
nbresource.Spec.PolicyName = policyGenName
nbresource.Spec.PolicySourceGroups = nil
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).To(HaveOccurred())
})
})
When("Friendly name is specified", func() {
It("should override policy name", func() {
nbresource.Spec.PolicyName = policyGenName
nbresource.Spec.PolicySourceGroups = []string{"test"}
nbresource.Spec.PolicyFriendlyName = map[string]string{policyGenName: "UnitTest"}
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed())
Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName))
nbPolicy := &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource"))
Expect(nbPolicy.Spec.Name).To(Equal("UnitTest"))
})
})
When("Policy already exists", func() {
It("should update it", func() {
nbPolicy := &netbirdiov1.NBPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-gen-default-test-resource",
},
Spec: netbirdiov1.NBPolicySpec{
Name: "Test",
Description: "Test",
SourceGroups: []string{"toast"},
Bidirectional: false,
},
}
Expect(k8sClient.Create(ctx, nbPolicy)).To(Succeed())
nbresource.Spec.PolicyName = policyGenName
nbresource.Spec.PolicySourceGroups = []string{"test"}
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed())
Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName))
nbPolicy = &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource"))
Expect(nbPolicy.Spec.Name).To(Equal("Autogenerated policy for resource default/test-resource in cluster kubernetes"))
Expect(nbPolicy.Spec.Bidirectional).To(BeTrue())
})
})
When("Policy settings are updated", func() {
It("should update it", func() {
nbresource.Spec.PolicyName = policyGenName
nbresource.Spec.PolicySourceGroups = []string{"test"}
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed())
nbresource.Spec.PolicyFriendlyName = map[string]string{policyGenName: "UnitTest"}
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed())
Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName))
nbPolicy := &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Status.ManagedServiceList).To(ContainElement("default/test-resource"))
Expect(nbPolicy.Spec.Name).To(Equal("UnitTest"))
})
})
When("Policy is changed outside controller", func() {
It("should update it", func() {
nbresource.Spec.PolicyName = policyGenName
nbresource.Spec.PolicySourceGroups = []string{"test"}
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed())
Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName))
nbPolicy := &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed())
nbPolicy.Spec.Name = "Meow"
nbPolicy.Annotations = nil
nbPolicy.Spec.Description = "woeM"
nbPolicy.Spec.SourceGroups = []string{"est"}
Expect(k8sClient.Update(ctx, nbPolicy)).To(Succeed())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed())
Expect(nbPolicy.Spec.Name).To(Equal("Autogenerated policy for resource default/test-resource in cluster kubernetes"))
Expect(nbPolicy.Annotations["netbird.io/generated-by"]).To(Equal("default/test-resource"))
Expect(nbPolicy.Spec.Description).To(Equal("Generated by default/test-resource"))
Expect(nbPolicy.Spec.SourceGroups).To(BeEquivalentTo([]string{"test"}))
})
})
When("Policy is removed", func() {
It("should delete NBPolicy", func() {
nbresource.Spec.PolicyName = policyGenName
nbresource.Spec.PolicySourceGroups = []string{"test"}
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient.Get(ctx, typeNamespacedName, nbresource)).To(Succeed())
Expect(nbresource.Status.PolicyNameMapping).To(HaveKey(policyGenName))
nbPolicy := &netbirdiov1.NBPolicy{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed())
nbresource.Spec.PolicyName = ""
Expect(k8sClient.Update(ctx, nbresource)).To(Succeed())
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: nbresource.Status.PolicyNameMapping[policyGenName]}, nbPolicy)).To(Succeed())
Expect(nbPolicy.DeletionTimestamp).NotTo(BeNil())
})
})
})
})
})
+25 -6
View File
@@ -31,12 +31,14 @@ type ServiceReconciler struct {
const (
// ServiceExposeAnnotation Service annotation for exposing
ServiceExposeAnnotation = "netbird.io/expose"
serviceGroupsAnnotation = "netbird.io/groups"
serviceResourceAnnotation = "netbird.io/resource-name"
servicePolicyAnnotation = "netbird.io/policy"
servicePortsAnnotation = "netbird.io/policy-ports"
serviceProtocolAnnotation = "netbird.io/policy-protocol"
ServiceExposeAnnotation = "netbird.io/expose"
serviceGroupsAnnotation = "netbird.io/groups"
serviceResourceAnnotation = "netbird.io/resource-name"
servicePolicyAnnotation = "netbird.io/policy"
servicePortsAnnotation = "netbird.io/policy-ports"
serviceProtocolAnnotation = "netbird.io/policy-protocol"
servicePolicySourceGroupsAnnotation = "netbird.io/policy-source-groups"
servicePolicyNameAnnotation = "netbird.io/policy-name"
)
var (
@@ -241,6 +243,23 @@ func (r *ServiceReconciler) applyPolicy(nbResource *netbirdiov1.NBResource, svc
}
}
if v, ok := svc.Annotations[servicePolicySourceGroupsAnnotation]; ok {
nbResource.Spec.PolicySourceGroups = util.SplitTrim(v, ",")
} else {
nbResource.Spec.PolicySourceGroups = nil
}
policyFriendlyNameList := util.SplitTrim(svc.Annotations[servicePolicyNameAnnotation], ",")
nbResource.Spec.PolicyFriendlyName = make(map[string]string)
for _, v := range policyFriendlyNameList {
friendlyNameMap := util.SplitTrim(v, ":")
if len(friendlyNameMap) != 2 {
logger.Info("Invalid number of : found in annotation", "annotation", servicePolicyNameAnnotation, "value", v)
continue
}
nbResource.Spec.PolicyFriendlyName[friendlyNameMap[0]] = friendlyNameMap[1]
}
for _, p := range svc.Spec.Ports {
switch p.Protocol {
case corev1.ProtocolTCP:
@@ -440,6 +440,48 @@ var _ = Describe("Service Controller", func() {
Expect(nbResource.Spec.UDPPorts).To(BeEmpty())
})
})
When("policy friendly name changes", func() {
It("should update policy friendly name in NBResource spec", func() {
nbResource := &netbirdiov1.NBResource{}
Expect(k8sClient.Get(ctx, typeNamespacedName, nbResource)).To(Succeed())
nbResource.Spec.PolicyName = policyName
Expect(k8sClient.Update(ctx, nbResource)).To(Succeed())
service.Annotations[servicePolicyAnnotation] = policyName
service.Annotations[servicePolicyNameAnnotation] = "test:toast,meow:meow"
Expect(k8sClient.Update(ctx, service)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
nbResource = &netbirdiov1.NBResource{}
Expect(k8sClient.Get(ctx, typeNamespacedName, nbResource)).To(Succeed())
Expect(nbResource.Spec.PolicyFriendlyName).To(BeEquivalentTo(map[string]string{"test": "toast", "meow": "meow"}))
})
})
When("policy source groups changes", func() {
It("should update policy source groups in NBResource spec", func() {
nbResource := &netbirdiov1.NBResource{}
Expect(k8sClient.Get(ctx, typeNamespacedName, nbResource)).To(Succeed())
nbResource.Spec.PolicyName = policyName
Expect(k8sClient.Update(ctx, nbResource)).To(Succeed())
service.Annotations[servicePolicyAnnotation] = policyName
service.Annotations[servicePolicySourceGroupsAnnotation] = "test"
Expect(k8sClient.Update(ctx, service)).To(Succeed())
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
nbResource = &netbirdiov1.NBResource{}
Expect(k8sClient.Get(ctx, typeNamespacedName, nbResource)).To(Succeed())
Expect(nbResource.Spec.PolicySourceGroups).To(BeEquivalentTo([]string{"test"}))
})
})
When("resource name changes", func() {
It("should update name in NBResource spec", func() {
service.Annotations[serviceResourceAnnotation] = "meow"