Skip to content

Commit

Permalink
Merge pull request #15 from RocketChat/reconcile_atlas_firewall
Browse files Browse the repository at this point in the history
Add rancher nodes to atlas firewall
  • Loading branch information
r0zbot committed Sep 11, 2023
2 parents 7187cae + 53f2e0f commit 622629e
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 2 deletions.
5 changes: 4 additions & 1 deletion api/v1alpha1/mongodbcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,14 @@ type MongoDBClusterSpec struct {
// +kubebuilder:default=mongodb
PrefixTemplate string `json:"prefixTemplate,omitempty"`

// Append this prefix to all default/generated usernames for this cluster. Will be overriden if "username" is specified.
// Append this prefix to all default/generated usernames for this cluster. Will be overridden if "username" is specified.
UserNamePrefix string `json:"userNamePrefix,omitempty"`

// If this is set, Atlas API will be used instead of the regular mongo auth path.
UseAtlasApi bool `json:"useAtlasApi,omitempty"`

// If this is set, along with useAtlasApi, all the kubernetes nodes on the cluster will be added to the Atlas firewall. The only available value right now is "rancher-annotation", which uses the rke.cattle.io/external-ip annotation.
AtlasNodeIPAccessStrategy string `json:"atlasNodeIpAccessStrategy,omitempty"`
}

// MongoDBClusterStatus defines the observed state of MongoDBCluster
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ spec:
type: object
spec:
properties:
atlasNodeIpAccessStrategy:
description: If this is set, along with useAtlasApi, all the kubernetes
nodes on the cluster will be added to the Atlas firewall. The only
available value right now is "rancher-annotation", which uses the
rke.cattle.io/external-ip annotation.
type: string
connectionSecret:
description: Secret in which Airlock will look for a ConnectionString
or Atlas credentials, that will be used to connect to the cluster.
Expand Down Expand Up @@ -70,7 +76,7 @@ spec:
type: boolean
userNamePrefix:
description: Append this prefix to all default/generated usernames
for this cluster. Will be overriden if "username" is specified.
for this cluster. Will be overridden if "username" is specified.
type: string
required:
- connectionSecret
Expand Down
3 changes: 3 additions & 0 deletions config/samples/airlock_v1alpha1_mongodbcluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ spec:
# Optional. Append this prefix to all default/generated usernames for this cluster. Will be ignored if "username" is already set on the access request.
userNamePrefix: test-use1-

# Optional. If this is set, along with useAtlasApi, all the kubernetes nodes on the cluster will be added to the Atlas firewall. The only available value right now is "rancher-annotation", which uses the rke.cattle.io/external-ip annotation.
atlasNodeIpAccessStrategy: rancher-annotation

---
apiVersion: v1
kind: Secret
Expand Down
139 changes: 139 additions & 0 deletions controllers/mongodbcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ package controllers
import (
"context"
"fmt"
"net/http"
"strings"
"time"

"github.com/go-logr/logr"
"go.mongodb.org/atlas/mongodbatlas"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
Expand Down Expand Up @@ -120,6 +123,23 @@ func (r *MongoDBClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque

return ctrl.Result{}, utilerrors.NewAggregate([]error{err, r.Status().Update(ctx, mongodbClusterCR)})
}

// Add nodes to Atlas firewall
if mongodbClusterCR.Spec.AtlasNodeIPAccessStrategy == "rancher-annotation" {
err = r.reconcileAtlasFirewall(ctx, mongodbClusterCR, secret)
if err != nil {
meta.SetStatusCondition(&mongodbClusterCR.Status.Conditions,
metav1.Condition{
Type: "Ready",
Status: metav1.ConditionFalse,
Reason: "AtlasFirewallFailed",
LastTransitionTime: metav1.NewTime(time.Now()),
Message: fmt.Sprintf("Failed to add nodes to atlas firewall: %s", err.Error()),
})

return ctrl.Result{}, utilerrors.NewAggregate([]error{err, r.Status().Update(ctx, mongodbClusterCR)})
}
}
} else {
err = testMongoConnection(ctx, mongodbClusterCR, secret)
if err != nil {
Expand Down Expand Up @@ -196,6 +216,35 @@ func (r *MongoDBClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSecret),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
).
Watches(
&source.Kind{Type: &corev1.Node{}},
handler.EnqueueRequestsFromMapFunc(func(node client.Object) []reconcile.Request {
mongodbClusterCR := &airlockv1alpha1.MongoDBClusterList{}
listOps := &client.ListOptions{
Namespace: "",
}

err := r.List(context.TODO(), mongodbClusterCR, listOps)
if err != nil {
return []reconcile.Request{}
}

requests := make([]reconcile.Request, 0)
for _, item := range mongodbClusterCR.Items {
if item.Spec.AtlasNodeIPAccessStrategy != "" {
requests = append(requests, reconcile.Request{
NamespacedName: types.NamespacedName{
Name: item.GetName(),
Namespace: item.GetNamespace(),
},
})
}
}

return requests
}),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
).
Complete(r)
}

Expand Down Expand Up @@ -332,3 +381,93 @@ func canCreateUsers(logger logr.Logger, roles primitive.A) bool {

return false
}

func (r *MongoDBClusterReconciler) reconcileAtlasFirewall(ctx context.Context, mongodbClusterCR *airlockv1alpha1.MongoDBCluster, secret *corev1.Secret) error {
logger := log.FromContext(ctx)

AIRLOCK_PREFIX := "Airlock-"
IP_ANNOTATION := "rke.cattle.io/external-ip"

logger.Info("Reconciling atlas firewall for " + mongodbClusterCR.Name)

client, atlasGroupID, err := getAtlasClientFromSecret(secret)
if err != nil {
logger.Error(err, "Couldn't get a client for Atlas")
return err
}

// Get all nodes in the cluster
nodeList := &corev1.NodeList{}

err = r.List(ctx, nodeList)
if err != nil {
logger.Error(err, "Couldn't get nodes in the cluster")
return err
}

// Get all nodes in the Atlas firewall
firewallList, _, err := client.ProjectIPAccessList.List(context.Background(), atlasGroupID, nil)
if err != nil {
logger.Error(err, "Couldn't get nodes in the Atlas firewall")
return err
}

// Look for nodes in atlas firewall that don't match the current nodes
for _, entry := range firewallList.Results {
found := false

for _, node := range nodeList.Items {
externalIP := node.Annotations[IP_ANNOTATION]

if externalIP == entry.IPAddress && AIRLOCK_PREFIX+node.Name == entry.Comment {
found = true
break
}
}

// If the node has the airlock prefix but wasn't found locally, remove it from the Atlas firewall
if strings.HasPrefix(entry.Comment, AIRLOCK_PREFIX) && !found {
logger.Info("Removing node " + entry.Comment + " from the Atlas firewall")

_, err := client.ProjectIPAccessList.Delete(context.Background(), atlasGroupID, entry.IPAddress)
if err != nil {
logger.Error(err, "Couldn't remove node "+entry.Comment+" from the Atlas firewall")
return err
}
}
}

// Add missing nodes to the Atlas firewall
entriesToAdd := []*mongodbatlas.ProjectIPAccessList{}

for _, node := range nodeList.Items {
externalIP := node.Annotations[IP_ANNOTATION]

// Check if node already exists in the firewall
found := false

for _, entry := range firewallList.Results {
if externalIP == entry.IPAddress && AIRLOCK_PREFIX+node.Name == entry.Comment {
found = true
break
}
}

// If not, add it
if !found && externalIP != "" {
entriesToAdd = append(entriesToAdd, &mongodbatlas.ProjectIPAccessList{
IPAddress: externalIP,
Comment: AIRLOCK_PREFIX + node.Name,
})
logger.Info("Adding node " + node.Name + " to the Atlas firewall")
}
}

_, response, err := client.ProjectIPAccessList.Create(context.Background(), atlasGroupID, entriesToAdd)
if err != nil || response.StatusCode != http.StatusCreated {
logger.Error(err, "Couldn't add nodes to the Atlas firewall")
return err
}

return nil
}

0 comments on commit 622629e

Please sign in to comment.