From 84f338426e958d0b4776c74a3e9d0e005057480b Mon Sep 17 00:00:00 2001 From: Tobias Trabelsi Date: Wed, 19 Mar 2025 22:36:42 +0100 Subject: [PATCH] first controller implementation --- go.mod | 1 + go.sum | 2 + .../controller/bitwardensecret_controller.go | 98 ++++++++++++++++++- 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 8db761a..1ff269c 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.0 godebug default=go1.23 require ( + github.com/bitwarden/sdk-go v1.0.2 github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 k8s.io/apimachinery v0.32.1 diff --git a/go.sum b/go.sum index b257e61..147ceae 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bitwarden/sdk-go v1.0.2 h1:krk5et4sfksLDDcrYHcs8f3jL/TGcQ1EShw4CG21JSI= +github.com/bitwarden/sdk-go v1.0.2/go.mod h1:RuYh+gqffp3h8wNUVWz1bvp2Pho10AFz+WIlI26iWY4= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= diff --git a/internal/controller/bitwardensecret_controller.go b/internal/controller/bitwardensecret_controller.go index c96591a..42f6472 100644 --- a/internal/controller/bitwardensecret_controller.go +++ b/internal/controller/bitwardensecret_controller.go @@ -21,13 +21,19 @@ package controller import ( "context" + "encoding/base64" + "os" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/bitwarden/sdk-go" lerentisuploadfilter24euv1 "github.com/lerentis/bitwarden-crd-operator/api/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // BitwardenSecretReconciler reconciles a BitwardenSecret object @@ -50,9 +56,97 @@ type BitwardenSecretReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.2/pkg/reconcile func (r *BitwardenSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + olog := log.FromContext(ctx) - // TODO(user): your logic here + // Fetch the BitwardenSecret instance + var bitwardenSecret lerentisuploadfilter24euv1.BitwardenSecret + if err := r.Get(ctx, req.NamespacedName, &bitwardenSecret); err != nil { + if errors.IsNotFound(err) { + olog.Info("BitwardenSecret resource not found. Ignoring since object must be deleted.") + return ctrl.Result{}, nil + } + olog.Error(err, "Failed to get BitwardenSecret.") + return ctrl.Result{}, err + } + + // Fetch secrets from Bitwarden + apiURL := os.Getenv("API_URL") + identityURL := os.Getenv("IDENTITY_URL") + + client, _ := sdk.NewBitwardenClient(&apiURL, &identityURL) + + accessToken := os.Getenv("ACCESS_TOKEN") + + stateFile := os.Getenv("STATE_FILE") + + err := client.AccessTokenLogin(accessToken, &stateFile) + if err != nil { + olog.Error(err, "Failed to authenticate with Bitwarden.") + return ctrl.Result{}, err + } + + secretData := make(map[string][]byte) + for _, element := range bitwardenSecret.Spec.Content { + resp, err := client.Secrets().Get(bitwardenSecret.Spec.ID) + if err != nil { + olog.Error(err, "Failed to fetch item from Bitwarden.") + return ctrl.Result{}, err + } + + item := resp.Value + + var secretValue string + switch element.SecretScope { + case "login": + if element.SecretName == "username" { + secretValue = item.Login.Username + } else if element.SecretName == "password" { + secretValue = item.Login.Password + } + case "fields": + for _, field := range item.Fields { + if field.Name == element.SecretName { + secretValue = field.Value + break + } + } + case "attachment": + for _, attachment := range item.Attachments { + if attachment.FileName == element.SecretName { + secretValue = attachment.File + break + } + } + } + + if secretValue != "" { + secretData[element.SecretRef] = []byte(base64.StdEncoding.EncodeToString([]byte(secretValue))) + } + } + + // Create or update Kubernetes secret + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: bitwardenSecret.Spec.Name, + Namespace: bitwardenSecret.Spec.Namespace, + Labels: bitwardenSecret.Spec.Labels, + Annotations: bitwardenSecret.Spec.Annotations, + }, + Data: secretData, + Type: corev1.SecretType(bitwardenSecret.Spec.SecretType), + } + + if err := r.Client.Create(ctx, secret); err != nil && !errors.IsAlreadyExists(err) { + olog.Error(err, "Failed to create Secret.") + return ctrl.Result{}, err + } + + if errors.IsAlreadyExists(err) { + if err := r.Client.Update(ctx, secret); err != nil { + olog.Error(err, "Failed to update Secret.") + return ctrl.Result{}, err + } + } return ctrl.Result{}, nil }