Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding namespace based reconcilation, fixing secret issue #9

Merged
merged 1 commit into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified Dockerfile
100755 → 100644
Empty file.
4 changes: 2 additions & 2 deletions Makefile
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# VERSION defines the project version for the bundle.
# Update this value when you upgrade the version of your project.
# To re-generate a bundle for another specific version without changing the standard setup, you can:
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.3)
# - use environment variables to overwrite this value (e.g export VERSION=0.0.3)
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=1.0.0)
# - use environment variables to overwrite this value (e.g export VERSION=1.0.0)
VERSION ?= 0.0.1
PROJECT_DIR = $(CURDIR)
SCRIPTS_DIR = ${PROJECT_DIR}/scripts
Expand Down
Empty file modified PROJECT
100755 → 100644
Empty file.
Empty file modified README.md
100755 → 100644
Empty file.
Empty file modified api/v1alpha1/groupversion_info.go
100755 → 100644
Empty file.
1 change: 1 addition & 0 deletions api/v1alpha1/secretrotator_types.go
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ type SecretRotatorStatus struct {
}

// ExternalSecretCreationPolicy defines rules on how to create the resulting Secret.

// ExternalSecretTemplate defines a blueprint for the created Secret resource.
// we can not use native corev1.Secret, it will have empty ObjectMeta values: https://github.com/kubernetes-sigs/controller-tools/issues/448

Expand Down
Empty file modified api/v1alpha1/zz_generated.deepcopy.go
100755 → 100644
Empty file.
Empty file modified charts/jfrog-registry-operator/.helmignore
100755 → 100644
Empty file.
Empty file modified charts/jfrog-registry-operator/CHANGELOG.md
100755 → 100644
Empty file.
Empty file modified charts/jfrog-registry-operator/Chart.lock
100755 → 100644
Empty file.
Empty file modified charts/jfrog-registry-operator/Chart.yaml
100755 → 100644
Empty file.
Empty file modified charts/jfrog-registry-operator/charts/jfrog-common-0.0.7.tgz
100755 → 100644
Empty file.
Empty file.
Empty file modified charts/jfrog-registry-operator/examples/secretrotator.yaml
100755 → 100644
Empty file.
2 changes: 1 addition & 1 deletion charts/jfrog-registry-operator/full-values.yaml
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ persistence:
##
mountPath: /var/opt/jfrog/jfrog-registry-operator
## @param persistence.subPath The subdirectory of the volume to mount to
## Useful in main environments and one PV for multiple services
## Useful in dev environments and one PV for multiple services
##
subPath: ""
## @param persistence.size PVC Storage Request for metadata data volume
Expand Down
Empty file modified charts/jfrog-registry-operator/logo/jfrog-logo.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified charts/jfrog-registry-operator/templates/NOTES.txt
100755 → 100644
Empty file.
Empty file modified charts/jfrog-registry-operator/templates/_helpers.tpl
100755 → 100644
Empty file.
Empty file modified charts/jfrog-registry-operator/templates/deployment.yaml
100755 → 100644
Empty file.
Empty file modified charts/jfrog-registry-operator/templates/role.yaml
100755 → 100644
Empty file.
Empty file modified charts/jfrog-registry-operator/templates/rolebinding.yaml
100755 → 100644
Empty file.
Empty file modified charts/jfrog-registry-operator/templates/serviceaccount.yaml
100755 → 100644
Empty file.
4 changes: 2 additions & 2 deletions charts/jfrog-registry-operator/values.yaml
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ global:
image:
registry: releases-docker.jfrog.io
repository: jfrog/jfrog-registry-operator
tag: 0.0.3
tag: 1.0.0

pullPolicy: IfNotPresent
# pullSecrets:
Expand Down Expand Up @@ -189,7 +189,7 @@ persistence:
##
mountPath: /var/opt/jfrog/jfrog-registry-operator
## @param persistence.subPath The subdirectory of the volume to mount to
## Useful in main environments and one PV for multiple services
## Useful in dev environments and one PV for multiple services
##
subPath: ""
## @param persistence.size PVC Storage Request for metadata data volume
Expand Down
Empty file modified config/crd/bases/apps.jfrog.com_secretrotators.yaml
100755 → 100644
Empty file.
Empty file modified config/deploy/operator.yaml
100755 → 100644
Empty file.
Binary file removed config/images/frogbot-badge.png
Binary file not shown.
Binary file removed config/images/frogbot-intro.png
Binary file not shown.
Empty file modified config/monitoring/README.md
100755 → 100644
Empty file.
Empty file modified config/monitoring/graph.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified config/monitoring/operator-service.yaml
100755 → 100644
Empty file.
Empty file modified config/monitoring/prometheus/01-prometheus-rbac.yaml
100755 → 100644
Empty file.
Empty file modified config/monitoring/prometheus/02-prometheus-configMap.yaml
100755 → 100644
Empty file.
Empty file modified config/monitoring/prometheus/prometheus-alert-rules.yaml
100755 → 100644
Empty file.
Empty file modified config/monitoring/prometheus/prometheus-deployment.yaml
100755 → 100644
Empty file.
Empty file modified config/monitoring/prometheus/prometheus-service.yaml
100755 → 100644
Empty file.
62 changes: 0 additions & 62 deletions config/rbac/role.yaml

This file was deleted.

4 changes: 2 additions & 2 deletions config/samples/jfrog_v1alpha1_secretrotator.yaml
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ metadata:

app.kubernetes.io/created-by: artifactory-secrets-rotator
name: secretrotator-sample
namespace: oumk
namespace: jfrog-operator
spec:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: oumk
kubernetes.io/metadata.name: jfrog-operator
secretName: token-secret
artifactoryUrl: ""
refreshTime: 30m
Expand Down
3 changes: 1 addition & 2 deletions config/samples/jfrog_v1alpha1_secretrotator2.yaml
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ metadata:
labels:
app.kubernetes.io/name: secretrotators.apps.jfrog.com
app.kubernetes.io/instance: secretrotator-sample

app.kubernetes.io/created-by: artifactory-secrets-rotator
name: secretrotator-sample2
namespace: jfrog-operator
spec:
namespaceSelector:
matchLabels:
registry: rt2
kubernetes.io/metadata.name: jfrog-operator
secretName: token-secret
artifactoryUrl: http://10.100.23.21
refreshTime: 10m
Expand Down
Empty file modified config/samples/secret_example/echo_pod_example.yaml
100755 → 100644
Empty file.
Empty file modified config/samples/secret_example/secret_namespace.yaml
100755 → 100644
Empty file.
Empty file modified config/samples/secret_example/secret_namespace2.yaml
100755 → 100644
Empty file.
Empty file modified config/samples/secret_example/secret_namespace_not_labeled.yaml
100755 → 100644
Empty file.
Empty file modified config/samples/secret_example/secret_namespace_other_label.yaml
100755 → 100644
Empty file.
65 changes: 43 additions & 22 deletions controllers/secretrotator_controller.go
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ package controllers
import (
jfrogv1alpha1 "artifactory-secrets-rotator/api/v1alpha1"
"artifactory-secrets-rotator/internal/operations"
"errors"
"reflect"

corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"

"context"
"fmt"
Expand Down Expand Up @@ -86,16 +88,19 @@ func (r *SecretRotatorReconciler) Reconcile(ctx context.Context, req ctrl.Reques

// InitializeResource initializes the secret rotator object and validates specs
if err := r.InitializeResource(ctx, &tokenDetails, secretRotator, req); err != nil {
r.Recorder.Eventf(secretRotator, "Warning", "Failed in initializing resource", "%s", err)
return r.handleError(err)
}

// ManagingSecrets is validating the desired state versus the actual state of secrets and creating or updating secrets.
if err := r.ManagingSecrets(ctx, &tokenDetails, secretRotator, req); err != nil {
r.Recorder.Eventf(secretRotator, "Warning", "Failed in managing secret", "%s", err)
return r.handleError(err)
}

// UpdateStatus, update the custom resource status
if err := r.UpdateStatus(ctx, &tokenDetails, secretRotator); err != nil {
r.Recorder.Eventf(secretRotator, "Warning", "Failed in updating status", "%s", err)
return r.handleError(err)
}

Expand All @@ -105,36 +110,52 @@ func (r *SecretRotatorReconciler) Reconcile(ctx context.Context, req ctrl.Reques
} else {
r.RequeueInterval = secretRotator.Spec.RefreshInterval.Duration
}

r.Log.Info("Reconcile completed, see you in", "next iteration", r.RequeueInterval)
return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
}

// handleError converts an error into reconcile result
func (r *SecretRotatorReconciler) handleError(err error) (ctrl.Result, error) {
var status *operations.ReconcileError
if !errors.As(err, &status) {
r.Log.Error(err, "Reconcile terminated")
return ctrl.Result{}, err
}
if status.Cause == nil {
r.Log.Error(status, status.Message)
} else {
r.Log.Error(status.Cause, status.Message)
}
if status.RetryIn == 0*time.Minute {
r.Log.Info("Reconcile stopped")
return ctrl.Result{}, nil
}
r.Log.Info("Reconcile stopped, will retry in", "next iteration", status.RetryIn)
return ctrl.Result{RequeueAfter: status.RetryIn}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *SecretRotatorReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&jfrogv1alpha1.SecretRotator{}).
WithEventFilter(WatchNsChanges(r)).
WithOptions(controller.Options{MaxConcurrentReconciles: 1}).
Owns(&corev1.Namespace{}).
Complete(r)
}

// WatchNsChanges uses predicates for Event Filtering (namespace creation changes)
func WatchNsChanges(r *SecretRotatorReconciler) predicate.Predicate {
return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
if _, ok := e.Object.(*corev1.Namespace); ok {
secretRotators := operations.ListSecretRotatorObjects(r.Client)
for i := range secretRotators.Items {
if operations.IsExist(e.Object.GetLabels(), secretRotators.Items[i].Spec.NamespaceSelector.MatchLabels) {
r.Log.Info("Created new namespace with matching labels to secret rotator object, ", "Namespace name :", e.Object.GetName(), "Secret rotator name :", secretRotators.Items[i].Name)
if flag := operations.HandlingNamespaceEvents(r.Client, r.Log, &secretRotators.Items[i]); !flag {
return false
}
}
}
}
return true
},
UpdateFunc: func(e event.UpdateEvent) bool {
if _, ok := e.ObjectOld.(*corev1.Namespace); ok {
if !reflect.DeepEqual(e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels()) {
secretRotators := operations.ListSecretRotatorObjects(r.Client)
for i := range secretRotators.Items {
if operations.IsExist(e.ObjectNew.GetLabels(), secretRotators.Items[i].Spec.NamespaceSelector.MatchLabels) || operations.IsExist(e.ObjectOld.GetLabels(), secretRotators.Items[i].Spec.NamespaceSelector.MatchLabels) {
r.Log.Info("Namespace lebels has been changes, ", "Namespace name :", e.ObjectNew.GetName(), "Secret rotator name :", secretRotators.Items[i].Name)
if flag := operations.HandlingNamespaceEvents(r.Client, r.Log, &secretRotators.Items[i]); !flag {
return false
}
}
}
}
}
return true
},
}
}
23 changes: 23 additions & 0 deletions controllers/secretrotator_operations.go
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"artifactory-secrets-rotator/internal/handler"
operations "artifactory-secrets-rotator/internal/operations"
resource "artifactory-secrets-rotator/internal/resource"
"errors"
"fmt"
"sort"
"time"
Expand Down Expand Up @@ -110,6 +111,8 @@ func (r *SecretRotatorReconciler) UpdateStatus(ctx context.Context, tokenDetails
if err := r.Status().Update(ctx, secretRotator); err != nil {
return &operations.ReconcileError{Message: "Failed to update SecretRotator status", Cause: err, RetryIn: 1 * time.Minute}
}
r.Recorder.Eventf(secretRotator, "Normal", "Secret rotated successfully", "")

return nil
}

Expand Down Expand Up @@ -217,3 +220,23 @@ func (r *SecretRotatorReconciler) DeferPatch(ctx context.Context, secretRotator
func (r *SecretRotatorReconciler) DoFinalizerOperationsForSecretRotator(secretRotator *jfrogv1alpha1.SecretRotator) {
r.Recorder.Event(secretRotator, "Warning", "Deleting", fmt.Sprintf("Custom Resource %s is being deleted from the namespace %s", secretRotator.Name, secretRotator.Namespace))
}

// handleError converts an error into reconcile result
func (r *SecretRotatorReconciler) handleError(err error) (ctrl.Result, error) {
var status *operations.ReconcileError
if !errors.As(err, &status) {
r.Log.Error(err, "Reconcile terminated")
return ctrl.Result{}, err
}
if status.Cause == nil {
r.Log.Error(status, status.Message)
} else {
r.Log.Error(status.Cause, status.Message)
}
if status.RetryIn == 0*time.Minute {
r.Log.Info("Reconcile stopped")
return ctrl.Result{}, nil
}
r.Log.Info("Reconcile stopped, will retry in", "next iteration", status.RetryIn)
return ctrl.Result{RequeueAfter: status.RetryIn}, nil
}
Empty file modified controllers/suite_test.go
100755 → 100644
Empty file.
Empty file modified go.mod
100755 → 100644
Empty file.
Empty file modified go.sum
100755 → 100644
Empty file.
Empty file modified hack/boilerplate.go.txt
100755 → 100644
Empty file.
Empty file modified internal/handler/aws.go
100755 → 100644
Empty file.
11 changes: 6 additions & 5 deletions internal/handler/token.go
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"io"
"net/http"
"os"
"time"

"github.com/aws/aws-sdk-go-v2/aws"

"k8s.io/client-go/tools/record"

"sigs.k8s.io/controller-runtime/pkg/log"
Expand All @@ -32,7 +33,7 @@ func HandlingToken(ctx context.Context, tokenDetails *operations.TokenDetails, s
awsTokenFile := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")
//getting AWS_ROLE_ARN from env
if awsRoleName == "" {
recorder.Event(secretRotator, "Warning", "Misconfiguration", "missing aws Role Name")
recorder.Eventf(secretRotator, "Warning", "Misconfiguration", "missing aws Role Name")
return &operations.ReconcileError{Message: "AWS_ROLE_ARN is empty", RetryIn: 1 * time.Minute}
}
//getting AWS_WEB_IDENTITY_TOKEN_FILE from env
Expand All @@ -43,7 +44,7 @@ func HandlingToken(ctx context.Context, tokenDetails *operations.TokenDetails, s
//getting signed request headers for AWS STS GetCallerIdentity call
request, err := GetSignedRequest(ctx, awsRoleName, awsTokenFile)
if err != nil {
recorder.Event(secretRotator, "Warning", "TokenGenerationFailure",
recorder.Eventf(secretRotator, "Warning", "TokenGenerationFailure",
fmt.Sprintf("Error getting signed AWS credentials, error was %s", err.Error()))
return err
}
Expand All @@ -61,15 +62,15 @@ func HandlingToken(ctx context.Context, tokenDetails *operations.TokenDetails, s
err = errors.New("the token TTL taken from Role max session value, is shorter then reconciliation duration set through operator refreshTime, which is a misconfiguration causing token expire events")
logger.Error(err, "CRITICAL MIS CONFIGURATION")
//reflect this mis misconfiguration through the operator events
recorder.Event(secretRotator, "Warning", "TokenGenerationFailure",
recorder.Eventf(secretRotator, "Warning", "TokenGenerationFailure",
fmt.Sprintf("The token TTL taken from Role max session value (%d), is shorter then reconciliation duration set through operator refreshTime (%s), which is a misconfiguration causing token expire events",
*maxTTL,
secretRotator.Spec.RefreshInterval))
}
logger.Info("Generating artifactory token")
tokenDetails.Username, tokenDetails.Token, err = createArtifactoryToken(ctx, request, tokenDetails.ArtifactoryUrl, maxTTL)
if err != nil {
recorder.Event(secretRotator, "Warning", "Misconfiguration",
recorder.Eventf(secretRotator, "Warning", "Misconfiguration",
fmt.Sprintf("could not get artifactory Token, notice we might ran into expired tokens if this persists, error was %s", err.Error()))
return err
}
Expand Down
47 changes: 47 additions & 0 deletions internal/operations/operations.go
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package operations
import (
jfrogv1alpha1 "artifactory-secrets-rotator/api/v1alpha1"
"context"
"math/rand"
"time"

"github.com/go-logr/logr"
"sigs.k8s.io/controller-runtime/pkg/log"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -48,3 +52,46 @@ func ValidateObjectSpec(ctx context.Context, tokenDetails *TokenDetails, secretR
logger.Info("Artifactory host", "host", tokenDetails.ArtifactoryUrl)
return nil
}

// IsExist checks the labels from namespaces and secret rotator objects
func IsExist(namespaceLabels, objectLabels map[string]string) bool {

for val, _ := range objectLabels {
if namespaceLabels[val] != objectLabels[val] {
return false
}
}
return true
}

// GetRandomString generates random string with size 10
func GetRandomString() string {
const charset = "abcdefghijklmnopqrstuvwxyz"
var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, 10)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}

// ListSecretRotatorObjects return list of secret rotator objects
func ListSecretRotatorObjects(cli client.Client) *jfrogv1alpha1.SecretRotatorList {
secretRotators := &jfrogv1alpha1.SecretRotatorList{}
err := cli.List(context.Background(), secretRotators, &client.ListOptions{})
if err != nil {
return &jfrogv1alpha1.SecretRotatorList{}
}
return secretRotators
}

func HandlingNamespaceEvents(cli client.Client, log logr.Logger, object *jfrogv1alpha1.SecretRotator) bool {
if object.Annotations == nil {
object.Annotations = make(map[string]string)
}
object.Annotations["uid"] = GetRandomString()
if err := cli.Update(context.Background(), object, &client.UpdateOptions{}); err != nil {
return false
}
return true
}
Empty file modified internal/operations/types.go
100755 → 100644
Empty file.
Empty file modified internal/resource/namespace.go
100755 → 100644
Empty file.
Empty file modified internal/resource/secret.go
100755 → 100644
Empty file.
Empty file modified internal/sign/signer.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/crypto/compare.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/crypto/compare_test.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/crypto/ecc.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/crypto/ecc_test.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/error.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/v4/const.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/v4/header_rules.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/v4/headers.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/v4/hmac.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/v4/host.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/v4/time.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/v4/util.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/v4/util_test.go
100755 → 100644
Empty file.
Empty file modified internal/sign/v4a/v4a.go
100755 → 100644
Empty file.
Empty file modified main.go
100755 → 100644
Empty file.
Empty file modified pom.xml
100755 → 100644
Empty file.
Loading