mirror of
				https://github.com/1Password/onepassword-operator.git
				synced 2025-10-31 03:39:39 +00:00 
			
		
		
		
	Compare commits
	
		
			22 Commits
		
	
	
		
			release/v1
			...
			v1.4.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0796b9c5e2 | ||
|   | 37a0f4b51e | ||
|   | 004e0101ff | ||
|   | 6326a856ae | ||
|   | 1ddf92c5a0 | ||
|   | f5c6fa5860 | ||
|   | afa076d321 | ||
|   | d4b04c233c | ||
|   | ea68cfc2b4 | ||
|   | 58b4ff8ecf | ||
|   | d93fecdc76 | ||
|   | 486465247d | ||
|   | 79868ae374 | ||
|   | 6286f7e306 | ||
|   | 0b5efc8690 | ||
|   | c00baeedcb | ||
|   | a760e524ea | ||
|   | 19f774bb2d | ||
|   | 32643651d9 | ||
|   | ba8d3fa698 | ||
|   | c57aa22a9c | ||
|   | 48944b0d56 | 
							
								
								
									
										103
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -12,85 +12,124 @@ | |||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| [//]: # (START/v1.3.0) | [//]: # "START/v1.4.1" | ||||||
|  |  | ||||||
|  | # v1.4.1 | ||||||
|  |  | ||||||
|  | ## Fixes | ||||||
|  |  | ||||||
|  | - OwnerReferences on secrets are now persisted after an item is updated. {#101} | ||||||
|  | - Annotations from a Deployment or OnePasswordItem are no longer applied to Secrets that are created for it. {#102} | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | [//]: # "START/v1.4.0" | ||||||
|  |  | ||||||
|  | # v1.4.0 | ||||||
|  |  | ||||||
|  | ## Features | ||||||
|  |  | ||||||
|  | - The operator now declares the an OwnerReference for the secrets it manages. This should stop secrets from getting pruned by tools like Argo CD. {#51,#84,#96} | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | [//]: # "START/v1.3.0" | ||||||
|  |  | ||||||
| # v1.3.0 | # v1.3.0 | ||||||
|  |  | ||||||
| ## Features | ## Features | ||||||
|   * Added support for loading secrets from files stored in 1Password. {#47} |  | ||||||
|  | - Added support for loading secrets from files stored in 1Password. {#47} | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| [//]: # (START/v1.2.0) | [//]: # "START/v1.2.0" | ||||||
|  |  | ||||||
| # v1.2.0 | # v1.2.0 | ||||||
|  |  | ||||||
| ## Features | ## Features | ||||||
|   * Support secrets provisioned through FromEnv. {#74} |  | ||||||
|   * Support configuration of Kubernetes Secret type. {#87} | - Support secrets provisioned through FromEnv. {#74} | ||||||
|   * Improved logging. (#72) | - Support configuration of Kubernetes Secret type. {#87} | ||||||
|  | - Improved logging. (#72) | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| [//]: # (START/v1.1.0) | [//]: # "START/v1.1.0" | ||||||
|  |  | ||||||
| # v1.1.0 | # v1.1.0 | ||||||
|  |  | ||||||
| ## Fixes | ## Fixes | ||||||
|  * Fix normalization for keys in a Secret's `data` section to allow upper- and lower-case alphanumeric characters. {#66} |  | ||||||
|  | - Fix normalization for keys in a Secret's `data` section to allow upper- and lower-case alphanumeric characters. {#66} | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| [//]: # (START/v1.0.2) | [//]: # "START/v1.0.2" | ||||||
|  |  | ||||||
| # v1.0.2 | # v1.0.2 | ||||||
|  |  | ||||||
| ## Fixes | ## Fixes | ||||||
|  * Name normalizer added to handle non-conforming item names. |  | ||||||
|  | - Name normalizer added to handle non-conforming item names. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| [//]: # (START/v1.0.1) | [//]: # "START/v1.0.1" | ||||||
|  |  | ||||||
| # v1.0.1 | # v1.0.1 | ||||||
|  |  | ||||||
| ## Features | ## Features | ||||||
| * This release also contains an arm64 Docker image. {#20} |  | ||||||
| * Docker images are also pushed to the :latest and :<major>.<minor> tags. | - This release also contains an arm64 Docker image. {#20} | ||||||
|  | - Docker images are also pushed to the :latest and :<major>.<minor> tags. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| [//]: # (START/v1.0.0) | [//]: # "START/v1.0.0" | ||||||
|  |  | ||||||
| # v1.0.0 | # v1.0.0 | ||||||
|  |  | ||||||
| ## Features: | ## Features: | ||||||
| * Option to automatically deploy 1Password Connect via the operator |  | ||||||
| * Ignore restart annotation when looking for 1Password annotations | - Option to automatically deploy 1Password Connect via the operator | ||||||
| * Release Automation | - Ignore restart annotation when looking for 1Password annotations | ||||||
| * Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource | - Release Automation | ||||||
| * Adding configuration for auto rolling restart on deployments | - Upgrading apiextensions.k8s.io/v1beta apiversion from the operator custom resource | ||||||
| * Configure Auto Restarts for a OnePasswordItem Custom Resource | - Adding configuration for auto rolling restart on deployments | ||||||
| * Update Connect Dependencies to latest | - Configure Auto Restarts for a OnePasswordItem Custom Resource | ||||||
| * Add Github action for building and testing operator | - Update Connect Dependencies to latest | ||||||
|  | - Add Github action for building and testing operator | ||||||
|  |  | ||||||
| ## Fixes: | ## Fixes: | ||||||
| * Fix spec field example for OnePasswordItem in readme |  | ||||||
| * Casing of annotations are now consistent | - Fix spec field example for OnePasswordItem in readme | ||||||
|  | - Casing of annotations are now consistent | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| [//]: # (START/v0.0.2) | [//]: # "START/v0.0.2" | ||||||
|  |  | ||||||
| # v0.0.2 | # v0.0.2 | ||||||
|  |  | ||||||
| ## Features: | ## Features: | ||||||
| * Items can now be accessed by either `vaults/<vault_id>/items/<item_id>` or `vaults/<vault_title>/items/<item_title>` |  | ||||||
|  | - Items can now be accessed by either `vaults/<vault_id>/items/<item_id>` or `vaults/<vault_title>/items/<item_title>` | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| [//]: # (START/v0.0.1) | [//]: # "START/v0.0.1" | ||||||
|  |  | ||||||
| # v0.0.1 | # v0.0.1 | ||||||
|  |  | ||||||
| Initial 1Password Operator release | Initial 1Password Operator release | ||||||
|  |  | ||||||
| ## Features | ## Features | ||||||
| * watches for deployment creations with `onepassword` annotations and creates an associated kubernetes secret |  | ||||||
| * watches for `onepasswordsecret` crd creations and creates an associated kubernetes secrets | - watches for deployment creations with `onepassword` annotations and creates an associated kubernetes secret | ||||||
| * watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes | - watches for `onepasswordsecret` crd creations and creates an associated kubernetes secrets | ||||||
| * restart pods when secret has been updated | - watches for changes to 1Password secrets associated with kubernetes secrets and updates the kubernetes secret with changes | ||||||
| * cleanup of kubernetes secrets when deployment or `onepasswordsecret` is deleted | - restart pods when secret has been updated | ||||||
|  | - cleanup of kubernetes secrets when deployment or `onepasswordsecret` is deleted | ||||||
|  |  | ||||||
| --- | --- | ||||||
|   | |||||||
| @@ -14,9 +14,11 @@ import ( | |||||||
| 	appsv1 "k8s.io/api/apps/v1" | 	appsv1 "k8s.io/api/apps/v1" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/api/errors" | 	"k8s.io/apimachinery/pkg/api/errors" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| 	ctrl "sigs.k8s.io/controller-runtime" | 	ctrl "sigs.k8s.io/controller-runtime" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
|  | 	"sigs.k8s.io/controller-runtime/pkg/client/apiutil" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/controller" | 	"sigs.k8s.io/controller-runtime/pkg/controller" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/handler" | 	"sigs.k8s.io/controller-runtime/pkg/handler" | ||||||
| 	logf "sigs.k8s.io/controller-runtime/pkg/log" | 	logf "sigs.k8s.io/controller-runtime/pkg/log" | ||||||
| @@ -114,7 +116,7 @@ func (r *ReconcileDeployment) Reconcile(request reconcile.Request) (reconcile.Re | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		// Handles creation or updating secrets for deployment if needed | 		// Handles creation or updating secrets for deployment if needed | ||||||
| 		if err := r.HandleApplyingDeployment(deployment.Namespace, annotations, request); err != nil { | 		if err := r.HandleApplyingDeployment(deployment, deployment.Namespace, annotations, request); err != nil { | ||||||
| 			return reconcile.Result{}, err | 			return reconcile.Result{}, err | ||||||
| 		} | 		} | ||||||
| 		return reconcile.Result{}, nil | 		return reconcile.Result{}, nil | ||||||
| @@ -187,7 +189,7 @@ func (r *ReconcileDeployment) removeOnePasswordFinalizerFromDeployment(deploymen | |||||||
| 	return r.kubeClient.Update(context.Background(), deployment) | 	return r.kubeClient.Update(context.Background(), deployment) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (r *ReconcileDeployment) HandleApplyingDeployment(namespace string, annotations map[string]string, request reconcile.Request) error { | func (r *ReconcileDeployment) HandleApplyingDeployment(deployment *appsv1.Deployment, namespace string, annotations map[string]string, request reconcile.Request) error { | ||||||
| 	reqLog := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) | 	reqLog := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) | ||||||
|  |  | ||||||
| 	secretName := annotations[op.NameAnnotation] | 	secretName := annotations[op.NameAnnotation] | ||||||
| @@ -204,5 +206,17 @@ func (r *ReconcileDeployment) HandleApplyingDeployment(namespace string, annotat | |||||||
| 		return fmt.Errorf("Failed to retrieve item: %v", err) | 		return fmt.Errorf("Failed to retrieve item: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, annotations) | 	// Create owner reference. | ||||||
|  | 	gvk, err := apiutil.GVKForObject(deployment, r.scheme) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("could not to retrieve group version kind: %v", err) | ||||||
|  | 	} | ||||||
|  | 	ownerRef := &metav1.OwnerReference{ | ||||||
|  | 		APIVersion: gvk.GroupVersion().String(), | ||||||
|  | 		Kind:       gvk.Kind, | ||||||
|  | 		Name:       deployment.GetName(), | ||||||
|  | 		UID:        deployment.GetUID(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, ownerRef) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -281,7 +281,6 @@ var tests = []testReconcileItem{ | |||||||
| 				Annotations: map[string]string{ | 				Annotations: map[string]string{ | ||||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | 					op.VersionAnnotation:  fmt.Sprint(version), | ||||||
| 					op.ItemPathAnnotation: itemPath, | 					op.ItemPathAnnotation: itemPath, | ||||||
| 					op.NameAnnotation:     name, |  | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			Data: expectedSecretData, | 			Data: expectedSecretData, | ||||||
| @@ -294,7 +293,6 @@ var tests = []testReconcileItem{ | |||||||
| 				Annotations: map[string]string{ | 				Annotations: map[string]string{ | ||||||
| 					op.VersionAnnotation:  fmt.Sprint(version), | 					op.VersionAnnotation:  fmt.Sprint(version), | ||||||
| 					op.ItemPathAnnotation: itemPath, | 					op.ItemPathAnnotation: itemPath, | ||||||
| 					op.NameAnnotation:     name, |  | ||||||
| 				}, | 				}, | ||||||
| 				Labels: map[string]string(nil), | 				Labels: map[string]string(nil), | ||||||
| 			}, | 			}, | ||||||
| @@ -385,7 +383,7 @@ var tests = []testReconcileItem{ | |||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestReconcileDepoyment(t *testing.T) { | func TestReconcileDeployment(t *testing.T) { | ||||||
| 	for _, testData := range tests { | 	for _, testData := range tests { | ||||||
| 		t.Run(testData.testName, func(t *testing.T) { | 		t.Run(testData.testName, func(t *testing.T) { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,9 +14,11 @@ import ( | |||||||
|  |  | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/api/errors" | 	"k8s.io/apimachinery/pkg/api/errors" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| 	ctrl "sigs.k8s.io/controller-runtime" | 	ctrl "sigs.k8s.io/controller-runtime" | ||||||
| 	kubeClient "sigs.k8s.io/controller-runtime/pkg/client" | 	kubeClient "sigs.k8s.io/controller-runtime/pkg/client" | ||||||
|  | 	"sigs.k8s.io/controller-runtime/pkg/client/apiutil" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/controller" | 	"sigs.k8s.io/controller-runtime/pkg/controller" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/handler" | 	"sigs.k8s.io/controller-runtime/pkg/handler" | ||||||
| 	logf "sigs.k8s.io/controller-runtime/pkg/log" | 	logf "sigs.k8s.io/controller-runtime/pkg/log" | ||||||
| @@ -145,14 +147,25 @@ func (r *ReconcileOnePasswordItem) removeOnePasswordFinalizerFromOnePasswordItem | |||||||
| func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error { | func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error { | ||||||
| 	secretName := resource.GetName() | 	secretName := resource.GetName() | ||||||
| 	labels := resource.Labels | 	labels := resource.Labels | ||||||
| 	annotations := resource.Annotations |  | ||||||
| 	secretType := resource.Type | 	secretType := resource.Type | ||||||
| 	autoRestart := annotations[op.RestartDeploymentsAnnotation] | 	autoRestart := resource.Annotations[op.RestartDeploymentsAnnotation] | ||||||
|  |  | ||||||
| 	item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath) | 	item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("Failed to retrieve item: %v", err) | 		return fmt.Errorf("Failed to retrieve item: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, secretType, annotations) | 	// Create owner reference. | ||||||
|  | 	gvk, err := apiutil.GVKForObject(resource, r.scheme) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("could not to retrieve group version kind: %v", err) | ||||||
|  | 	} | ||||||
|  | 	ownerRef := &metav1.OwnerReference{ | ||||||
|  | 		APIVersion: gvk.GroupVersion().String(), | ||||||
|  | 		Kind:       gvk.Kind, | ||||||
|  | 		Name:       resource.GetName(), | ||||||
|  | 		UID:        resource.GetUID(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, secretType, ownerRef) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -101,7 +101,7 @@ var tests = []testReconcileItem{ | |||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		testName: "Test Do not update if OnePassword Version has not changed", | 		testName: "Test Do not update if OnePassword Version or VaultPath has not changed", | ||||||
| 		customResource: &onepasswordv1.OnePasswordItem{ | 		customResource: &onepasswordv1.OnePasswordItem{ | ||||||
| 			TypeMeta: metav1.TypeMeta{ | 			TypeMeta: metav1.TypeMeta{ | ||||||
| 				Kind:       onePasswordItemKind, | 				Kind:       onePasswordItemKind, | ||||||
|   | |||||||
| @@ -35,18 +35,13 @@ var ErrCannotUpdateSecretType = errs.New("Cannot change secret type. Secret type | |||||||
|  |  | ||||||
| var log = logf.Log | var log = logf.Log | ||||||
|  |  | ||||||
| func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]string, secretType string, secretAnnotations map[string]string) error { | func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]string, secretType string, ownerRef *metav1.OwnerReference) error { | ||||||
|  |  | ||||||
| 	itemVersion := fmt.Sprint(item.Version) | 	itemVersion := fmt.Sprint(item.Version) | ||||||
|  | 	secretAnnotations := map[string]string{ | ||||||
| 	// If secretAnnotations is nil we create an empty map so we can later assign values for the OP Annotations in the map | 		VersionAnnotation:  itemVersion, | ||||||
| 	if secretAnnotations == nil { | 		ItemPathAnnotation: fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID), | ||||||
| 		secretAnnotations = map[string]string{} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	secretAnnotations[VersionAnnotation] = itemVersion |  | ||||||
| 	secretAnnotations[ItemPathAnnotation] = fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID) |  | ||||||
|  |  | ||||||
| 	if autoRestart != "" { | 	if autoRestart != "" { | ||||||
| 		_, err := utils.StringToBool(autoRestart) | 		_, err := utils.StringToBool(autoRestart) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -57,7 +52,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// "Opaque" and "" secret types are treated the same by Kubernetes. | 	// "Opaque" and "" secret types are treated the same by Kubernetes. | ||||||
| 	secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item) | 	secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item, ownerRef) | ||||||
|  |  | ||||||
| 	currentSecret := &corev1.Secret{} | 	currentSecret := &corev1.Secret{} | ||||||
| 	err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret) | 	err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret) | ||||||
| @@ -87,13 +82,19 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, secretType string, item onepassword.Item) *corev1.Secret { | func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, secretType string, item onepassword.Item, ownerRef *metav1.OwnerReference) *corev1.Secret { | ||||||
|  | 	var ownerRefs []metav1.OwnerReference | ||||||
|  | 	if ownerRef != nil { | ||||||
|  | 		ownerRefs = []metav1.OwnerReference{*ownerRef} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return &corev1.Secret{ | 	return &corev1.Secret{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name:            formatSecretName(name), | 			Name:            formatSecretName(name), | ||||||
| 			Namespace:       namespace, | 			Namespace:       namespace, | ||||||
| 			Annotations:     annotations, | 			Annotations:     annotations, | ||||||
| 			Labels:          labels, | 			Labels:          labels, | ||||||
|  | 			OwnerReferences: ownerRefs, | ||||||
| 		}, | 		}, | ||||||
| 		Data: BuildKubernetesSecretData(item.Fields, item.Files), | 		Data: BuildKubernetesSecretData(item.Fields, item.Files), | ||||||
| 		Type: corev1.SecretType(secretType), | 		Type: corev1.SecretType(secretType), | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/1Password/connect-sdk-go/onepassword" | 	"github.com/1Password/connect-sdk-go/onepassword" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
| 	kubeValidate "k8s.io/apimachinery/pkg/util/validation" | 	kubeValidate "k8s.io/apimachinery/pkg/util/validation" | ||||||
| 	"k8s.io/client-go/kubernetes" | 	"k8s.io/client-go/kubernetes" | ||||||
| @@ -32,12 +33,9 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
|  |  | ||||||
| 	kubeClient := fake.NewFakeClient() | 	kubeClient := fake.NewFakeClient() | ||||||
| 	secretLabels := map[string]string{} | 	secretLabels := map[string]string{} | ||||||
| 	secretAnnotations := map[string]string{ |  | ||||||
| 		"testAnnotation": "exists", |  | ||||||
| 	} |  | ||||||
| 	secretType := "" | 	secretType := "" | ||||||
|  |  | ||||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) | 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Unexpected error: %v", err) | 		t.Errorf("Unexpected error: %v", err) | ||||||
| 	} | 	} | ||||||
| @@ -49,9 +47,50 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| 	compareFields(item.Fields, createdSecret.Data, t) | 	compareFields(item.Fields, createdSecret.Data, t) | ||||||
| 	compareAnnotationsToItem(createdSecret.Annotations, item, t) | 	compareAnnotationsToItem(createdSecret.Annotations, item, t) | ||||||
|  | } | ||||||
|  |  | ||||||
| 	if createdSecret.Annotations["testAnnotation"] != "exists" { | func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) { | ||||||
| 		t.Errorf("Expected testAnnotation to be merged with existing annotations, but wasn't.") | 	secretName := "test-secret-name" | ||||||
|  | 	namespace := "test" | ||||||
|  |  | ||||||
|  | 	item := onepassword.Item{} | ||||||
|  | 	item.Fields = generateFields(5) | ||||||
|  | 	item.Version = 123 | ||||||
|  | 	item.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" | ||||||
|  | 	item.ID = "h46bb3jddvay7nxopfhvlwg35q" | ||||||
|  |  | ||||||
|  | 	kubeClient := fake.NewFakeClient() | ||||||
|  | 	secretLabels := map[string]string{} | ||||||
|  | 	secretType := "" | ||||||
|  |  | ||||||
|  | 	ownerRef := &metav1.OwnerReference{ | ||||||
|  | 		Kind:       "Deployment", | ||||||
|  | 		APIVersion: "apps/v1", | ||||||
|  | 		Name:       "test-deployment", | ||||||
|  | 		UID:        types.UID("test-uid"), | ||||||
|  | 	} | ||||||
|  | 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, ownerRef) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("Unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  | 	createdSecret := &corev1.Secret{} | ||||||
|  | 	err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) | ||||||
|  |  | ||||||
|  | 	// Check owner references. | ||||||
|  | 	gotOwnerRefs := createdSecret.ObjectMeta.OwnerReferences | ||||||
|  | 	if len(gotOwnerRefs) != 1 { | ||||||
|  | 		t.Errorf("Expected owner references length: 1 but got: %d", len(gotOwnerRefs)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	expOwnerRef := metav1.OwnerReference{ | ||||||
|  | 		Kind:       "Deployment", | ||||||
|  | 		APIVersion: "apps/v1", | ||||||
|  | 		Name:       "test-deployment", | ||||||
|  | 		UID:        types.UID("test-uid"), | ||||||
|  | 	} | ||||||
|  | 	gotOwnerRef := gotOwnerRefs[0] | ||||||
|  | 	if gotOwnerRef != expOwnerRef { | ||||||
|  | 		t.Errorf("Expected owner reference value: %v but got: %v", expOwnerRef, gotOwnerRef) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -67,10 +106,9 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
|  |  | ||||||
| 	kubeClient := fake.NewFakeClient() | 	kubeClient := fake.NewFakeClient() | ||||||
| 	secretLabels := map[string]string{} | 	secretLabels := map[string]string{} | ||||||
| 	secretAnnotations := map[string]string{} |  | ||||||
| 	secretType := "" | 	secretType := "" | ||||||
|  |  | ||||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) | 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||||
|  |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Unexpected error: %v", err) | 		t.Errorf("Unexpected error: %v", err) | ||||||
| @@ -82,7 +120,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
| 	newItem.Version = 456 | 	newItem.Version = 456 | ||||||
| 	newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" | 	newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" | ||||||
| 	newItem.ID = "h46bb3jddvay7nxopfhvlwg35q" | 	newItem.ID = "h46bb3jddvay7nxopfhvlwg35q" | ||||||
| 	err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) | 	err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Unexpected error: %v", err) | 		t.Errorf("Unexpected error: %v", err) | ||||||
| 	} | 	} | ||||||
| @@ -118,7 +156,7 @@ func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) { | |||||||
| 	labels := map[string]string{} | 	labels := map[string]string{} | ||||||
| 	secretType := "" | 	secretType := "" | ||||||
|  |  | ||||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item) | 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) | ||||||
| 	if kubeSecret.Name != strings.ToLower(name) { | 	if kubeSecret.Name != strings.ToLower(name) { | ||||||
| 		t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name) | 		t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name) | ||||||
| 	} | 	} | ||||||
| @@ -153,7 +191,7 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item) | 	kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item, nil) | ||||||
|  |  | ||||||
| 	// Assert Secret's meta.name was fixed | 	// Assert Secret's meta.name was fixed | ||||||
| 	if kubeSecret.Name != expectedName { | 	if kubeSecret.Name != expectedName { | ||||||
| @@ -183,12 +221,9 @@ func TestCreateKubernetesTLSSecretFromOnePasswordItem(t *testing.T) { | |||||||
|  |  | ||||||
| 	kubeClient := fake.NewFakeClient() | 	kubeClient := fake.NewFakeClient() | ||||||
| 	secretLabels := map[string]string{} | 	secretLabels := map[string]string{} | ||||||
| 	secretAnnotations := map[string]string{ |  | ||||||
| 		"testAnnotation": "exists", |  | ||||||
| 	} |  | ||||||
| 	secretType := "kubernetes.io/tls" | 	secretType := "kubernetes.io/tls" | ||||||
|  |  | ||||||
| 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) | 	err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Unexpected error: %v", err) | 		t.Errorf("Unexpected error: %v", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	v1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1" | ||||||
|  |  | ||||||
| 	kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" | 	kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" | ||||||
| 	"github.com/1Password/onepassword-operator/pkg/utils" | 	"github.com/1Password/onepassword-operator/pkg/utils" | ||||||
|  |  | ||||||
| @@ -116,24 +118,37 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]* | |||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		item, err := GetOnePasswordItemByPath(h.opConnectClient, secret.Annotations[ItemPathAnnotation]) | 		OnePasswordItemPath := h.getPathFromOnePasswordItem(secret) | ||||||
|  |  | ||||||
|  | 		item, err := GetOnePasswordItemByPath(h.opConnectClient, OnePasswordItemPath) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Error(err, "failed to retrieve 1Password item at path \"%s\" for secret \"%s\"", secret.Annotations[ItemPathAnnotation], secret.Name) | 			log.Error(err, "failed to retrieve 1Password item at path \"%s\" for secret \"%s\"", secret.Annotations[ItemPathAnnotation], secret.Name) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		itemVersion := fmt.Sprint(item.Version) | 		itemVersion := fmt.Sprint(item.Version) | ||||||
| 		if currentVersion != itemVersion { | 		itemPathString := fmt.Sprintf("vaults/%v/items/%v", item.Vault.ID, item.ID) | ||||||
|  |  | ||||||
|  | 		if currentVersion != itemVersion || secret.Annotations[ItemPathAnnotation] != itemPathString { | ||||||
| 			if isItemLockedForForcedRestarts(item) { | 			if isItemLockedForForcedRestarts(item) { | ||||||
| 				log.Info(fmt.Sprintf("Secret '%v' has been updated in 1Password but is set to be ignored. Updates to an ignored secret will not trigger an update to a kubernetes secret or a rolling restart.", secret.GetName())) | 				log.Info(fmt.Sprintf("Secret '%v' has been updated in 1Password but is set to be ignored. Updates to an ignored secret will not trigger an update to a kubernetes secret or a rolling restart.", secret.GetName())) | ||||||
| 				secret.Annotations[VersionAnnotation] = itemVersion | 				secret.Annotations[VersionAnnotation] = itemVersion | ||||||
| 				h.client.Update(context.Background(), &secret) | 				secret.Annotations[ItemPathAnnotation] = itemPathString | ||||||
|  | 				if err := h.client.Update(context.Background(), &secret); err != nil { | ||||||
|  | 					log.Error(err, "failed to update secret %s annotations to version %d: %s", secret.Name, itemVersion, err) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 			log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName())) | 			log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName())) | ||||||
| 			secret.Annotations[VersionAnnotation] = itemVersion | 			secret.Annotations[VersionAnnotation] = itemVersion | ||||||
| 			updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, secret.Labels, string(secret.Type), *item) | 			secret.Annotations[ItemPathAnnotation] = itemPathString | ||||||
| 			h.client.Update(context.Background(), updatedSecret) | 			secret.Data = kubeSecrets.BuildKubernetesSecretData(item.Fields, item.Files) | ||||||
|  | 			log.Info(fmt.Sprintf("New secret path: %v and version: %v", secret.Annotations[ItemPathAnnotation], secret.Annotations[VersionAnnotation])) | ||||||
|  | 			if err := h.client.Update(context.Background(), &secret); err != nil { | ||||||
|  | 				log.Error(err, "failed to update secret %s to version %d: %s", secret.Name, itemVersion, err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
| 			if updatedSecrets[secret.Namespace] == nil { | 			if updatedSecrets[secret.Namespace] == nil { | ||||||
| 				updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret) | 				updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret) | ||||||
| 			} | 			} | ||||||
| @@ -177,6 +192,22 @@ func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap() (map[string | |||||||
| 	return namespacesMap, nil | 	return namespacesMap, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (h *SecretUpdateHandler) getPathFromOnePasswordItem(secret corev1.Secret) string { | ||||||
|  | 	onePasswordItem := &v1.OnePasswordItem{} | ||||||
|  |  | ||||||
|  | 	// Search for our original OnePasswordItem if it exists | ||||||
|  | 	err := h.client.Get(context.TODO(), client.ObjectKey{ | ||||||
|  | 		Namespace: secret.Namespace, | ||||||
|  | 		Name:      secret.Name}, onePasswordItem) | ||||||
|  |  | ||||||
|  | 	if err == nil { | ||||||
|  | 		return onePasswordItem.Spec.ItemPath | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If we can't find the OnePassword Item we'll just return the annotation from the secret item. | ||||||
|  | 	return secret.Annotations[ItemPathAnnotation] | ||||||
|  | } | ||||||
|  |  | ||||||
| func isSecretSetForAutoRestart(secret *corev1.Secret, deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool { | func isSecretSetForAutoRestart(secret *corev1.Secret, deployment *appsv1.Deployment, setForAutoRestartByNamespace map[string]bool) bool { | ||||||
| 	restartDeployment := secret.Annotations[RestartDeploymentsAnnotation] | 	restartDeployment := secret.Annotations[RestartDeploymentsAnnotation] | ||||||
| 	//If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace | 	//If annotation for auto restarts for deployment is not set. Check for the annotation on its namepsace | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user