From 313cd1169b1465a73813d1620d25704d77781091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Antunes?= Date: Fri, 2 Jul 2021 10:23:28 +0100 Subject: [PATCH 01/22] Update README.md Minor update to the README. Got me debugging for a few hours --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2ec464..8cbb486 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ $ cat 1password-credentials.json | base64 | \ Create a Kubernetes secret from the op-session file: ```bash -$ kubectl create secret generic op-credentials --from-file=1password-credentials.json +$ kubectl create secret generic op-credentials --from-file=1password-credentials.json=op-session ``` Add the following environment variable to the onepassword-connect-operator container in `deploy/operator.yaml`: From 244771717c0d60c4964e2454097d0f336c435de1 Mon Sep 17 00:00:00 2001 From: "david.gunter" Date: Thu, 23 Sep 2021 11:18:53 -0700 Subject: [PATCH 02/22] Prepare release/1.1.0 --- .VERSION | 2 +- CHANGELOG.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.VERSION b/.VERSION index e6d5cb8..1cc5f65 100644 --- a/.VERSION +++ b/.VERSION @@ -1 +1 @@ -1.0.2 \ No newline at end of file +1.1.0 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 79cee80..a45b92c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ --- +[//]: # (START/v1.1.0) +# v1.1.0 + +## Fixes + * Fix normalization for keys in a Secret's `data` section to allow upper- and lower-case alphanumeric characters. {#66} + +--- + [//]: # (START/v1.0.2) # v1.0.2 From 5fab6624247e35faec68d61feeb407a99e138c8a Mon Sep 17 00:00:00 2001 From: Samuel Archambault Date: Fri, 24 Sep 2021 11:03:47 -0400 Subject: [PATCH 03/22] More logging if 1password item cant be read and continue processing others --- cmd/manager/main.go | 5 ++++- pkg/onepassword/secret_update_handler.go | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 2d7904c..05ca380 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -178,7 +178,10 @@ func main() { ticker.Stop() return case <-ticker.C: - updatedSecretsPoller.UpdateKubernetesSecretsTask() + err := updatedSecretsPoller.UpdateKubernetesSecretsTask() + if err != nil { + log.Error(err, "error running update kubernetes secret task") + } } } }() diff --git a/pkg/onepassword/secret_update_handler.go b/pkg/onepassword/secret_update_handler.go index ad2e8d6..d32f0b9 100644 --- a/pkg/onepassword/secret_update_handler.go +++ b/pkg/onepassword/secret_update_handler.go @@ -118,7 +118,8 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]* item, err := GetOnePasswordItemByPath(h.opConnectClient, secret.Annotations[ItemPathAnnotation]) if err != nil { - return nil, fmt.Errorf("Failed to retrieve item: %v", err) + log.Error(err, "failed to retrieve 1Password item at path \"%s\" for secret \"%s\"", secret.Annotations[ItemPathAnnotation], secret.Name) + continue } itemVersion := fmt.Sprint(item.Version) From b25f943b3af846377f1be3f6c8abf13824243c28 Mon Sep 17 00:00:00 2001 From: Samuel Archambault Date: Fri, 24 Sep 2021 13:51:05 -0400 Subject: [PATCH 04/22] Verify secrets and FromEnv in addition to Env --- pkg/onepassword/containers.go | 22 +++++++- pkg/onepassword/containers_test.go | 55 +++++++++++++++++-- pkg/onepassword/deployments_test.go | 2 +- pkg/onepassword/object_generators_for_test.go | 16 +++++- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/pkg/onepassword/containers.go b/pkg/onepassword/containers.go index 1f51bd9..c0910a8 100644 --- a/pkg/onepassword/containers.go +++ b/pkg/onepassword/containers.go @@ -1,6 +1,8 @@ package onepassword -import corev1 "k8s.io/api/core/v1" +import ( + corev1 "k8s.io/api/core/v1" +) func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string]*corev1.Secret) bool { for i := 0; i < len(containers); i++ { @@ -13,6 +15,15 @@ func AreContainersUsingSecrets(containers []corev1.Container, secrets map[string } } } + envFromVariables := containers[i].EnvFrom + for j := 0; j < len(envFromVariables); j++ { + if envFromVariables[j].SecretRef != nil { + _, ok := secrets[envFromVariables[j].SecretRef.Name] + if ok { + return true + } + } + } } return false } @@ -28,6 +39,15 @@ func AppendUpdatedContainerSecrets(containers []corev1.Container, secrets map[st } } } + envFromVariables := containers[i].EnvFrom + for j := 0; j < len(envFromVariables); j++ { + if envFromVariables[j].SecretRef != nil { + secret, ok := secrets[envFromVariables[j].SecretRef.LocalObjectReference.Name] + if ok { + updatedDeploymentSecrets[secret.Name] = secret + } + } + } } return updatedDeploymentSecrets } diff --git a/pkg/onepassword/containers_test.go b/pkg/onepassword/containers_test.go index 676c517..98dc427 100644 --- a/pkg/onepassword/containers_test.go +++ b/pkg/onepassword/containers_test.go @@ -4,9 +4,10 @@ import ( "testing" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func TestAreContainersUsingSecrets(t *testing.T) { +func TestAreContainersUsingSecretsFromEnv(t *testing.T) { secretNamesToSearch := map[string]*corev1.Secret{ "onepassword-database-secret": &corev1.Secret{}, "onepassword-api-key": &corev1.Secret{}, @@ -18,7 +19,26 @@ func TestAreContainersUsingSecrets(t *testing.T) { "some_other_key", } - containers := generateContainers(containerSecretNames) + containers := generateContainersWithSecretRefsFromEnv(containerSecretNames) + + if !AreContainersUsingSecrets(containers, secretNamesToSearch) { + t.Errorf("Expected that containers were using secrets but they were not detected.") + } +} + +func TestAreContainersUsingSecretsFromEnvFrom(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": {}, + "onepassword-api-key": {}, + } + + containerSecretNames := []string{ + "onepassword-database-secret", + "onepassword-api-key", + "some_other_key", + } + + containers := generateContainersWithSecretRefsFromEnvFrom(containerSecretNames) if !AreContainersUsingSecrets(containers, secretNamesToSearch) { t.Errorf("Expected that containers were using secrets but they were not detected.") @@ -27,17 +47,42 @@ func TestAreContainersUsingSecrets(t *testing.T) { func TestAreContainersNotUsingSecrets(t *testing.T) { secretNamesToSearch := map[string]*corev1.Secret{ - "onepassword-database-secret": &corev1.Secret{}, - "onepassword-api-key": &corev1.Secret{}, + "onepassword-database-secret": {}, + "onepassword-api-key": {}, } containerSecretNames := []string{ "some_other_key", } - containers := generateContainers(containerSecretNames) + containers := generateContainersWithSecretRefsFromEnv(containerSecretNames) if AreContainersUsingSecrets(containers, secretNamesToSearch) { t.Errorf("Expected that containers were not using secrets but they were detected.") } } + +func TestAppendUpdatedContainerSecretsParsesEnvFromEnv(t *testing.T) { + secretNamesToSearch := map[string]*corev1.Secret{ + "onepassword-database-secret": {}, + "onepassword-api-key": {ObjectMeta: metav1.ObjectMeta{Name: "onepassword-api-key"}}, + } + + containerSecretNames := []string{ + "onepassword-api-key", + } + + containers := generateContainersWithSecretRefsFromEnvFrom(containerSecretNames) + + //fmt.Println(containers) + updatedDeploymentSecrets := map[string]*corev1.Secret{} + updatedDeploymentSecrets = AppendUpdatedContainerSecrets(containers, secretNamesToSearch, updatedDeploymentSecrets) + + secretKeyName := "onepassword-api-key" + + //fmt.Println(updatedDeploymentSecrets) + //fmt.Println(secretNamesToSearch) + if updatedDeploymentSecrets[secretKeyName] != secretNamesToSearch[secretKeyName] { + t.Errorf("Expected that updated Secret from envfrom is found.") + } +} diff --git a/pkg/onepassword/deployments_test.go b/pkg/onepassword/deployments_test.go index d7445b1..45761fc 100644 --- a/pkg/onepassword/deployments_test.go +++ b/pkg/onepassword/deployments_test.go @@ -39,7 +39,7 @@ func TestIsDeploymentUsingSecretsUsingContainers(t *testing.T) { } deployment := &appsv1.Deployment{} - deployment.Spec.Template.Spec.Containers = generateContainers(containerSecretNames) + deployment.Spec.Template.Spec.Containers = generateContainersWithSecretRefsFromEnv(containerSecretNames) if !IsDeploymentUsingSecrets(deployment, secretNamesToSearch) { t.Errorf("Expected that deployment was using secrets but they were not detected.") } diff --git a/pkg/onepassword/object_generators_for_test.go b/pkg/onepassword/object_generators_for_test.go index 7e2f526..2392070 100644 --- a/pkg/onepassword/object_generators_for_test.go +++ b/pkg/onepassword/object_generators_for_test.go @@ -17,8 +17,7 @@ func generateVolumes(names []string) []corev1.Volume { } return volumes } - -func generateContainers(names []string) []corev1.Container { +func generateContainersWithSecretRefsFromEnv(names []string) []corev1.Container { containers := []corev1.Container{} for i := 0; i < len(names); i++ { container := corev1.Container{ @@ -40,3 +39,16 @@ func generateContainers(names []string) []corev1.Container { } return containers } + +func generateContainersWithSecretRefsFromEnvFrom(names []string) []corev1.Container { + containers := []corev1.Container{} + for i := 0; i < len(names); i++ { + container := corev1.Container{ + EnvFrom: []corev1.EnvFromSource{ + {SecretRef: &corev1.SecretEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: names[i]}}}, + }, + } + containers = append(containers, container) + } + return containers +} From d9e003bdb714f1d448f6dc26d40a145762179ad0 Mon Sep 17 00:00:00 2001 From: Samuel Archambault Date: Fri, 24 Sep 2021 14:02:46 -0400 Subject: [PATCH 05/22] cleanup comments --- pkg/onepassword/containers_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/onepassword/containers_test.go b/pkg/onepassword/containers_test.go index 98dc427..c6540f9 100644 --- a/pkg/onepassword/containers_test.go +++ b/pkg/onepassword/containers_test.go @@ -74,14 +74,11 @@ func TestAppendUpdatedContainerSecretsParsesEnvFromEnv(t *testing.T) { containers := generateContainersWithSecretRefsFromEnvFrom(containerSecretNames) - //fmt.Println(containers) updatedDeploymentSecrets := map[string]*corev1.Secret{} updatedDeploymentSecrets = AppendUpdatedContainerSecrets(containers, secretNamesToSearch, updatedDeploymentSecrets) secretKeyName := "onepassword-api-key" - //fmt.Println(updatedDeploymentSecrets) - //fmt.Println(secretNamesToSearch) if updatedDeploymentSecrets[secretKeyName] != secretNamesToSearch[secretKeyName] { t.Errorf("Expected that updated Secret from envfrom is found.") } From 0363ae1e4ebf865946238f0ecc7f3b056c0ca989 Mon Sep 17 00:00:00 2001 From: Claudio Canales Date: Wed, 29 Sep 2021 16:16:45 -0300 Subject: [PATCH 06/22] Removing $ from bash commands Using the copy button is bringing the commands with a $, which is giving the error `-bash: $: command not found` after pasting them to the console. --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ca2e7d3..3a4f8fc 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,14 @@ If 1Password Connect is already running, you can skip this step. This guide will Encode the 1password-credentials.json file you generated in the prerequisite steps and save it to a file named op-session: ```bash -$ cat 1password-credentials.json | base64 | \ +cat 1password-credentials.json | base64 | \ tr '/+' '_-' | tr -d '=' | tr -d '\n' > op-session ``` Create a Kubernetes secret from the op-session file: ```bash -$ kubectl create secret generic op-credentials --from-file=1password-credentials.json +kubectl create secret generic op-credentials --from-file=1password-credentials.json ``` Add the following environment variable to the onepassword-connect-operator container in `deploy/operator.yaml`: @@ -53,12 +53,12 @@ Adding this environment variable will have the operator automatically deploy a d "Create a Connect token for the operator and save it as a Kubernetes Secret: ```bash -$ kubectl create secret generic onepassword-token --from-literal=token=" +kubectl create secret generic onepassword-token --from-literal=token=" ``` If you do not have a token for the operator, you can generate a token and save it to kubernetes with the following command: ```bash -$ kubectl create secret generic onepassword-token --from-literal=token=$(op create connect token op-k8s-operator --vault ) +kubectl create secret generic onepassword-token --from-literal=token=$(op create connect token op-k8s-operator --vault ) ``` [More information on generating a token can be found here](https://support.1password.com/secrets-automation/#appendix-issue-additional-access-tokens) @@ -68,13 +68,13 @@ $ kubectl create secret generic onepassword-token --from-literal=token=$(op crea We must create a service account, role, and role binding and Kubernetes. Examples can be found in the `/deploy` folder. ```bash -$ kubectl apply -f deploy/permissions.yaml +kubectl apply -f deploy/permissions.yaml ``` **Create Custom One Password Secret Resource** ```bash -$ kubectl apply -f deploy/crds/onepassword.com_onepassworditems_crd.yaml +kubectl apply -f deploy/crds/onepassword.com_onepassworditems_crd.yaml ``` **Deploying the Operator** @@ -112,13 +112,13 @@ spec: Deploy the OnePasswordItem to Kubernetes: ```bash -$ kubectl apply -f .yaml +kubectl apply -f .yaml ``` To test that the Kubernetes Secret check that the following command returns a secret: ```bash -$ kubectl get secret +kubectl get secret ``` Note: Deleting the `OnePasswordItem` that you've created will automatically delete the created Kubernetes Secret. From 5d229c42d5b5078ce7af3f363dca8011084078ca Mon Sep 17 00:00:00 2001 From: Andres Montalban Date: Wed, 17 Nov 2021 18:47:12 -0300 Subject: [PATCH 07/22] feat: Allow configuration of the Kubernetes Secret type to be created --- .../onepassword.com_onepassworditems_crd.yaml | 3 ++ .../onepassword/v1/onepasswordsecret_types.go | 1 + .../deployment/deployment_controller.go | 4 +- .../onepassworditem_controller.go | 4 +- .../kubernetes_secrets_builder.go | 13 +++-- .../kubernetes_secrets_builder_test.go | 50 +++++++++++++++++-- .../kubernetes_secrets_types.go | 17 +++++++ pkg/onepassword/secret_update_handler.go | 2 +- 8 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 pkg/kubernetessecrets/kubernetes_secrets_types.go diff --git a/deploy/crds/onepassword.com_onepassworditems_crd.yaml b/deploy/crds/onepassword.com_onepassworditems_crd.yaml index 3a21918..2a8dc9e 100644 --- a/deploy/crds/onepassword.com_onepassworditems_crd.yaml +++ b/deploy/crds/onepassword.com_onepassworditems_crd.yaml @@ -39,4 +39,7 @@ spec: status: description: OnePasswordItemStatus defines the observed state of OnePasswordItem type: object + type: + description: 'Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' + type: string type: object diff --git a/pkg/apis/onepassword/v1/onepasswordsecret_types.go b/pkg/apis/onepassword/v1/onepasswordsecret_types.go index 7918539..a2e9ba3 100644 --- a/pkg/apis/onepassword/v1/onepasswordsecret_types.go +++ b/pkg/apis/onepassword/v1/onepasswordsecret_types.go @@ -26,6 +26,7 @@ type OnePasswordItemStatus struct { type OnePasswordItem struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` + Type string `json:"type,omitempty"` Spec OnePasswordItemSpec `json:"spec,omitempty"` Status OnePasswordItemStatus `json:"status,omitempty"` diff --git a/pkg/controller/deployment/deployment_controller.go b/pkg/controller/deployment/deployment_controller.go index 3b81fdf..fc2183d 100644 --- a/pkg/controller/deployment/deployment_controller.go +++ b/pkg/controller/deployment/deployment_controller.go @@ -192,6 +192,8 @@ func (r *ReconcileDeployment) HandleApplyingDeployment(namespace string, annotat secretName := annotations[op.NameAnnotation] secretLabels := map[string]string(nil) + secretType := "" + if len(secretName) == 0 { reqLog.Info("No 'item-name' annotation set. 'item-path' and 'item-name' must be set as annotations to add new secret.") return nil @@ -202,5 +204,5 @@ func (r *ReconcileDeployment) HandleApplyingDeployment(namespace string, annotat return fmt.Errorf("Failed to retrieve item: %v", err) } - return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, annotations) + return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, annotations) } diff --git a/pkg/controller/onepassworditem/onepassworditem_controller.go b/pkg/controller/onepassworditem/onepassworditem_controller.go index dc7fdff..0788f02 100644 --- a/pkg/controller/onepassworditem/onepassworditem_controller.go +++ b/pkg/controller/onepassworditem/onepassworditem_controller.go @@ -3,6 +3,7 @@ package onepassworditem import ( "context" "fmt" + onepasswordv1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1" kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" "github.com/1Password/onepassword-operator/pkg/onepassword" @@ -145,6 +146,7 @@ func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1 secretName := resource.GetName() labels := resource.Labels annotations := resource.Annotations + secretType := resource.Type autoRestart := annotations[op.RestartDeploymentsAnnotation] item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath) @@ -152,5 +154,5 @@ func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1 return fmt.Errorf("Failed to retrieve item: %v", err) } - return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, annotations) + return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, secretType, annotations) } diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go index 48e80fa..e800ad9 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -7,13 +7,14 @@ import ( "regexp" "strings" + "reflect" + "github.com/1Password/connect-sdk-go/onepassword" "github.com/1Password/onepassword-operator/pkg/utils" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "reflect" kubeValidate "k8s.io/apimachinery/pkg/util/validation" kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,7 +30,7 @@ const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart" var log = logf.Log -func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *onepassword.Item, autoRestart string, labels map[string]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, secretAnnotations map[string]string) error { itemVersion := fmt.Sprint(item.Version) @@ -49,7 +50,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa } secretAnnotations[RestartDeploymentsAnnotation] = autoRestart } - secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, *item) + secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item) currentSecret := &corev1.Secret{} err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret) @@ -60,11 +61,12 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa return err } - if ! reflect.DeepEqual(currentSecret.Annotations, secretAnnotations) || ! reflect.DeepEqual(currentSecret.Labels, labels) { + if !reflect.DeepEqual(currentSecret.Annotations, secretAnnotations) || !reflect.DeepEqual(currentSecret.Labels, labels) || !reflect.DeepEqual(string(currentSecret.Type), secretType) { log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace)) currentSecret.ObjectMeta.Annotations = secretAnnotations currentSecret.ObjectMeta.Labels = labels currentSecret.Data = secret.Data + currentSecret.Type = KubernetesSecretTypes[secretType] return kubeClient.Update(context.Background(), currentSecret) } @@ -72,7 +74,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa return nil } -func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, item onepassword.Item) *corev1.Secret { +func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotations map[string]string, labels map[string]string, secretType string, item onepassword.Item) *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: formatSecretName(name), @@ -81,6 +83,7 @@ func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotation Labels: labels, }, Data: BuildKubernetesSecretData(item.Fields), + Type: KubernetesSecretTypes[secretType], } } diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go b/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go index 63c0d0c..e0fd732 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go @@ -35,7 +35,9 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { secretAnnotations := map[string]string{ "testAnnotation": "exists", } - err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretAnnotations) + secretType := "" + + err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -66,7 +68,10 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { kubeClient := fake.NewFakeClient() secretLabels := map[string]string{} secretAnnotations := map[string]string{} - err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretAnnotations) + secretType := "" + + err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) + if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -77,7 +82,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { newItem.Version = 456 newItem.Vault.ID = "hfnjvi6aymbsnfc2xeeoheizda" newItem.ID = "h46bb3jddvay7nxopfhvlwg35q" - err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretAnnotations) + err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -111,8 +116,9 @@ func TestBuildKubernetesSecretFromOnePasswordItem(t *testing.T) { item := onepassword.Item{} item.Fields = generateFields(5) labels := map[string]string{} + secretType := "" - kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, item) + kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item) if kubeSecret.Name != strings.ToLower(name) { t.Errorf("Expected name value: %v but got: %v", name, kubeSecret.Name) } @@ -134,6 +140,7 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { } labels := map[string]string{} item := onepassword.Item{} + secretType := "" item.Fields = []*onepassword.ItemField{ { @@ -146,7 +153,7 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { }, } - kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, item) + kubeSecret := BuildKubernetesSecretFromOnePasswordItem(name, namespace, annotations, labels, secretType, item) // Assert Secret's meta.name was fixed if kubeSecret.Name != expectedName { @@ -164,6 +171,39 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { } } +func TestCreateKubernetesTLSSecretFromOnePasswordItem(t *testing.T) { + secretName := "tls-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{} + secretAnnotations := map[string]string{ + "testAnnotation": "exists", + } + secretType := "kubernetes.io/tls" + + err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, secretAnnotations) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + createdSecret := &corev1.Secret{} + err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) + + if err != nil { + t.Errorf("Secret was not created: %v", err) + } + + if createdSecret.Type != corev1.SecretTypeTLS { + t.Errorf("Expected secretType to be of tyype corev1.SecretTypeTLS, got %s", string(createdSecret.Type)) + } +} + func compareAnnotationsToItem(annotations map[string]string, item onepassword.Item, t *testing.T) { actualVaultId, actualItemId, err := ParseVaultIdAndItemIdFromPath(annotations[ItemPathAnnotation]) if err != nil { diff --git a/pkg/kubernetessecrets/kubernetes_secrets_types.go b/pkg/kubernetessecrets/kubernetes_secrets_types.go new file mode 100644 index 0000000..16cd8d4 --- /dev/null +++ b/pkg/kubernetessecrets/kubernetes_secrets_types.go @@ -0,0 +1,17 @@ +package kubernetessecrets + +import ( + corev1 "k8s.io/api/core/v1" +) + +// Default to Opaque as this is Kubernetes' default +var KubernetesSecretTypes = map[string]corev1.SecretType{ + "Opaque": corev1.SecretTypeOpaque, + "kubernetes.io/basic-auth": corev1.SecretTypeBasicAuth, + "kubernetes.io/service-account-token": corev1.SecretTypeServiceAccountToken, + "kubernetes.io/dockercfg": corev1.SecretTypeDockercfg, + "kubernetes.io/dockerconfigjson": corev1.SecretTypeDockerConfigJson, + "kubernetes.io/ssh-auth": corev1.SecretTypeSSHAuth, + "kubernetes.io/tls": corev1.SecretTypeTLS, + "bootstrap.kubernetes.io/token": corev1.SecretTypeBootstrapToken, +} diff --git a/pkg/onepassword/secret_update_handler.go b/pkg/onepassword/secret_update_handler.go index ad2e8d6..a261ac0 100644 --- a/pkg/onepassword/secret_update_handler.go +++ b/pkg/onepassword/secret_update_handler.go @@ -131,7 +131,7 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]* } log.Info(fmt.Sprintf("Updating kubernetes secret '%v'", secret.GetName())) secret.Annotations[VersionAnnotation] = itemVersion - updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, secret.Labels, *item) + updatedSecret := kubeSecrets.BuildKubernetesSecretFromOnePasswordItem(secret.Name, secret.Namespace, secret.Annotations, secret.Labels, string(secret.Type), *item) h.client.Update(context.Background(), updatedSecret) if updatedSecrets[secret.Namespace] == nil { updatedSecrets[secret.Namespace] = make(map[string]*corev1.Secret) From 302653832ea05c8e398086fe5fc9adb3d16f36ee Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 17 Feb 2022 19:18:33 +0100 Subject: [PATCH 08/22] Account for the fact that the '' type and Opaque are equivalent on secret comparison --- pkg/kubernetessecrets/kubernetes_secrets_builder.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go index e800ad9..8ac14aa 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -61,7 +61,16 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa return err } - if !reflect.DeepEqual(currentSecret.Annotations, secretAnnotations) || !reflect.DeepEqual(currentSecret.Labels, labels) || !reflect.DeepEqual(string(currentSecret.Type), secretType) { + currentAnnotations := currentSecret.Annotations + currentLabels := currentSecret.Labels + currentSecretType := string(currentSecret.Type) + if currentSecretType == "" { + currentSecretType = "Opaque" + } + if secretType == "" { + secretType = "Opaque" + } + if !reflect.DeepEqual(currentAnnotations, secretAnnotations) || !reflect.DeepEqual(currentLabels, labels) || !reflect.DeepEqual(currentSecretType, secretType) { log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace)) currentSecret.ObjectMeta.Annotations = secretAnnotations currentSecret.ObjectMeta.Labels = labels From bb7a0c8ca9687cd4e575e1b93fb56a9332e5e619 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 17 Feb 2022 19:36:49 +0100 Subject: [PATCH 09/22] Simplify secret type cast and default to Opaque --- .../kubernetes_secrets_builder.go | 12 +++++++----- .../kubernetes_secrets_types.go | 17 ----------------- 2 files changed, 7 insertions(+), 22 deletions(-) delete mode 100644 pkg/kubernetessecrets/kubernetes_secrets_types.go diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go index 8ac14aa..4548ceb 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -50,6 +50,11 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa } secretAnnotations[RestartDeploymentsAnnotation] = autoRestart } + + // Default to Opaque secrets + if secretType == "" { + secretType = "Opaque" + } secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item) currentSecret := &corev1.Secret{} @@ -67,15 +72,12 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa if currentSecretType == "" { currentSecretType = "Opaque" } - if secretType == "" { - secretType = "Opaque" - } if !reflect.DeepEqual(currentAnnotations, secretAnnotations) || !reflect.DeepEqual(currentLabels, labels) || !reflect.DeepEqual(currentSecretType, secretType) { log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace)) currentSecret.ObjectMeta.Annotations = secretAnnotations currentSecret.ObjectMeta.Labels = labels currentSecret.Data = secret.Data - currentSecret.Type = KubernetesSecretTypes[secretType] + currentSecret.Type = corev1.SecretType(secretType) return kubeClient.Update(context.Background(), currentSecret) } @@ -92,7 +94,7 @@ func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotation Labels: labels, }, Data: BuildKubernetesSecretData(item.Fields), - Type: KubernetesSecretTypes[secretType], + Type: corev1.SecretType(secretType), } } diff --git a/pkg/kubernetessecrets/kubernetes_secrets_types.go b/pkg/kubernetessecrets/kubernetes_secrets_types.go deleted file mode 100644 index 16cd8d4..0000000 --- a/pkg/kubernetessecrets/kubernetes_secrets_types.go +++ /dev/null @@ -1,17 +0,0 @@ -package kubernetessecrets - -import ( - corev1 "k8s.io/api/core/v1" -) - -// Default to Opaque as this is Kubernetes' default -var KubernetesSecretTypes = map[string]corev1.SecretType{ - "Opaque": corev1.SecretTypeOpaque, - "kubernetes.io/basic-auth": corev1.SecretTypeBasicAuth, - "kubernetes.io/service-account-token": corev1.SecretTypeServiceAccountToken, - "kubernetes.io/dockercfg": corev1.SecretTypeDockercfg, - "kubernetes.io/dockerconfigjson": corev1.SecretTypeDockerConfigJson, - "kubernetes.io/ssh-auth": corev1.SecretTypeSSHAuth, - "kubernetes.io/tls": corev1.SecretTypeTLS, - "bootstrap.kubernetes.io/token": corev1.SecretTypeBootstrapToken, -} From f38cf7e1c28f95ce9c4fa0a64a1927a783948104 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 17 Feb 2022 21:23:22 +0100 Subject: [PATCH 10/22] Fix tests and add new test --- .../deployment/deployment_controller_test.go | 7 +- .../onepassworditem/onepassworditem_test.go | 69 +++++++++++++++++-- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/pkg/controller/deployment/deployment_controller_test.go b/pkg/controller/deployment/deployment_controller_test.go index 25ae956..ee6ac97 100644 --- a/pkg/controller/deployment/deployment_controller_test.go +++ b/pkg/controller/deployment/deployment_controller_test.go @@ -279,7 +279,7 @@ var tests = []testReconcileItem{ Name: name, Namespace: namespace, Annotations: map[string]string{ - op.VersionAnnotation: fmt.Sprint(version), + op.VersionAnnotation: fmt.Sprint(version), op.ItemPathAnnotation: itemPath, op.NameAnnotation: name, }, @@ -292,7 +292,7 @@ var tests = []testReconcileItem{ Name: name, Namespace: namespace, Annotations: map[string]string{ - op.VersionAnnotation: fmt.Sprint(version), + op.VersionAnnotation: fmt.Sprint(version), op.ItemPathAnnotation: itemPath, op.NameAnnotation: name, }, @@ -329,6 +329,7 @@ var tests = []testReconcileItem{ op.VersionAnnotation: "456", }, }, + Type: corev1.SecretTypeOpaque, Data: expectedSecretData, }, expectedError: nil, @@ -340,6 +341,7 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, + Type: corev1.SecretTypeOpaque, Data: expectedSecretData, }, opItem: map[string]string{ @@ -373,6 +375,7 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, + Type: corev1.SecretTypeOpaque, Data: expectedSecretData, }, opItem: map[string]string{ diff --git a/pkg/controller/onepassworditem/onepassworditem_test.go b/pkg/controller/onepassworditem/onepassworditem_test.go index 381e336..f6bf8e2 100644 --- a/pkg/controller/onepassworditem/onepassworditem_test.go +++ b/pkg/controller/onepassworditem/onepassworditem_test.go @@ -119,7 +119,7 @@ var tests = []testReconcileItem{ Name: name, Namespace: namespace, Annotations: map[string]string{ - op.VersionAnnotation: fmt.Sprint(version), + op.VersionAnnotation: fmt.Sprint(version), op.ItemPathAnnotation: itemPath, }, }, @@ -131,7 +131,7 @@ var tests = []testReconcileItem{ Name: name, Namespace: namespace, Annotations: map[string]string{ - op.VersionAnnotation: fmt.Sprint(version), + op.VersionAnnotation: fmt.Sprint(version), op.ItemPathAnnotation: itemPath, }, }, @@ -153,7 +153,7 @@ var tests = []testReconcileItem{ Name: name, Namespace: namespace, Annotations: map[string]string{ - op.VersionAnnotation: fmt.Sprint(version), + op.VersionAnnotation: fmt.Sprint(version), op.ItemPathAnnotation: itemPath, }, Labels: map[string]string{}, @@ -167,7 +167,7 @@ var tests = []testReconcileItem{ Name: name, Namespace: namespace, Annotations: map[string]string{ - op.VersionAnnotation: "456", + op.VersionAnnotation: "456", op.ItemPathAnnotation: itemPath, }, Labels: map[string]string{}, @@ -180,11 +180,65 @@ var tests = []testReconcileItem{ Name: name, Namespace: namespace, Annotations: map[string]string{ - op.VersionAnnotation: fmt.Sprint(version), + op.VersionAnnotation: fmt.Sprint(version), op.ItemPathAnnotation: itemPath, }, Labels: map[string]string{}, }, + Type: corev1.SecretTypeOpaque, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + }, + { + testName: "Test Updating Type of Existing Kubernetes Secret using OnePasswordItem", + customResource: &onepasswordv1.OnePasswordItem{ + TypeMeta: metav1.TypeMeta{ + Kind: onePasswordItemKind, + APIVersion: onePasswordItemAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + op.VersionAnnotation: fmt.Sprint(version), + op.ItemPathAnnotation: itemPath, + }, + Labels: map[string]string{}, + }, + Spec: onepasswordv1.OnePasswordItemSpec{ + ItemPath: itemPath, + }, + Type: string(corev1.SecretTypeBasicAuth), + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + op.VersionAnnotation: fmt.Sprint(version), + op.ItemPathAnnotation: itemPath, + }, + Labels: map[string]string{}, + }, + Type: corev1.SecretTypeOpaque, + Data: expectedSecretData, + }, + expectedError: nil, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + op.VersionAnnotation: fmt.Sprint(version), + op.ItemPathAnnotation: itemPath, + }, + Labels: map[string]string{}, + }, + Type: corev1.SecretTypeBasicAuth, Data: expectedSecretData, }, opItem: map[string]string{ @@ -206,6 +260,7 @@ var tests = []testReconcileItem{ Spec: onepasswordv1.OnePasswordItemSpec{ ItemPath: itemPath, }, + Type: "custom", }, existingSecret: nil, expectedError: nil, @@ -217,6 +272,7 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, + Type: corev1.SecretType("custom"), Data: expectedSecretData, }, opItem: map[string]string{ @@ -249,6 +305,7 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, + Type: corev1.SecretTypeOpaque, Data: expectedSecretData, }, opItem: map[string]string{ @@ -281,6 +338,7 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, + Type: corev1.SecretTypeOpaque, Data: map[string][]byte{ "password": []byte(password), "username": []byte(username), @@ -322,6 +380,7 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, + Type: corev1.SecretTypeOpaque, Data: map[string][]byte{ "password": []byte(password), "username": []byte(username), From 285496dc7e869385b336c9974452e9ac6865eda7 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 18 Feb 2022 10:27:48 +0100 Subject: [PATCH 11/22] Error when secret type is changed --- .../kubernetes_secrets_builder.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go index 4548ceb..5845118 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -16,6 +16,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" kubeValidate "k8s.io/apimachinery/pkg/util/validation" + errs "errors" kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -51,10 +52,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa secretAnnotations[RestartDeploymentsAnnotation] = autoRestart } - // Default to Opaque secrets - if secretType == "" { - secretType = "Opaque" - } + // "Opaque" and "" secret types are treated the same by Kubernetes. secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item) currentSecret := &corev1.Secret{} @@ -69,15 +67,15 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa currentAnnotations := currentSecret.Annotations currentLabels := currentSecret.Labels currentSecretType := string(currentSecret.Type) - if currentSecretType == "" { - currentSecretType = "Opaque" + if !reflect.DeepEqual(currentSecretType, secretType) { + return errs.New("Cannot change secret type. Secret type is immutable") } - if !reflect.DeepEqual(currentAnnotations, secretAnnotations) || !reflect.DeepEqual(currentLabels, labels) || !reflect.DeepEqual(currentSecretType, secretType) { + + if !reflect.DeepEqual(currentAnnotations, secretAnnotations) || !reflect.DeepEqual(currentLabels, labels) { log.Info(fmt.Sprintf("Updating Secret %v at namespace '%v'", secret.Name, secret.Namespace)) currentSecret.ObjectMeta.Annotations = secretAnnotations currentSecret.ObjectMeta.Labels = labels currentSecret.Data = secret.Data - currentSecret.Type = corev1.SecretType(secretType) return kubeClient.Update(context.Background(), currentSecret) } From b16960057a082e89e4113c9a5fc8bf3ad707d202 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 18 Feb 2022 10:47:14 +0100 Subject: [PATCH 12/22] Update tests and add new test --- .../deployment/deployment_controller_test.go | 6 +-- .../onepassworditem/onepassworditem_test.go | 51 +++++++++++++++++-- .../kubernetes_secrets_builder.go | 7 ++- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/pkg/controller/deployment/deployment_controller_test.go b/pkg/controller/deployment/deployment_controller_test.go index ee6ac97..bd98231 100644 --- a/pkg/controller/deployment/deployment_controller_test.go +++ b/pkg/controller/deployment/deployment_controller_test.go @@ -329,7 +329,7 @@ var tests = []testReconcileItem{ op.VersionAnnotation: "456", }, }, - Type: corev1.SecretTypeOpaque, + Type: corev1.SecretType(""), Data: expectedSecretData, }, expectedError: nil, @@ -341,7 +341,7 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, - Type: corev1.SecretTypeOpaque, + Type: corev1.SecretType(""), Data: expectedSecretData, }, opItem: map[string]string{ @@ -375,7 +375,7 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, - Type: corev1.SecretTypeOpaque, + Type: corev1.SecretType(""), Data: expectedSecretData, }, opItem: map[string]string{ diff --git a/pkg/controller/onepassworditem/onepassworditem_test.go b/pkg/controller/onepassworditem/onepassworditem_test.go index f6bf8e2..9add483 100644 --- a/pkg/controller/onepassworditem/onepassworditem_test.go +++ b/pkg/controller/onepassworditem/onepassworditem_test.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + "github.com/1Password/onepassword-operator/pkg/kubernetessecrets" "github.com/1Password/onepassword-operator/pkg/mocks" op "github.com/1Password/onepassword-operator/pkg/onepassword" @@ -185,7 +186,6 @@ var tests = []testReconcileItem{ }, Labels: map[string]string{}, }, - Type: corev1.SecretTypeOpaque, Data: expectedSecretData, }, opItem: map[string]string{ @@ -224,7 +224,7 @@ var tests = []testReconcileItem{ }, Labels: map[string]string{}, }, - Type: corev1.SecretTypeOpaque, + Type: corev1.SecretTypeBasicAuth, Data: expectedSecretData, }, expectedError: nil, @@ -280,6 +280,50 @@ var tests = []testReconcileItem{ passKey: password, }, }, + { + testName: "Error if secret type is changed", + customResource: &onepasswordv1.OnePasswordItem{ + TypeMeta: metav1.TypeMeta{ + Kind: onePasswordItemKind, + APIVersion: onePasswordItemAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: onepasswordv1.OnePasswordItemSpec{ + ItemPath: itemPath, + }, + Type: "custom", + }, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + op.VersionAnnotation: fmt.Sprint(version), + }, + }, + Type: corev1.SecretTypeOpaque, + Data: expectedSecretData, + }, + expectedError: kubernetessecrets.ErrCannotUpdateSecretType, + expectedResultSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Annotations: map[string]string{ + op.VersionAnnotation: fmt.Sprint(version), + }, + }, + Type: corev1.SecretTypeOpaque, + Data: expectedSecretData, + }, + opItem: map[string]string{ + userKey: username, + passKey: password, + }, + }, { testName: "Secret from 1Password item with invalid K8s labels", customResource: &onepasswordv1.OnePasswordItem{ @@ -305,7 +349,6 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, - Type: corev1.SecretTypeOpaque, Data: expectedSecretData, }, opItem: map[string]string{ @@ -338,7 +381,6 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, - Type: corev1.SecretTypeOpaque, Data: map[string][]byte{ "password": []byte(password), "username": []byte(username), @@ -380,7 +422,6 @@ var tests = []testReconcileItem{ op.VersionAnnotation: fmt.Sprint(version), }, }, - Type: corev1.SecretTypeOpaque, Data: map[string][]byte{ "password": []byte(password), "username": []byte(username), diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go index 5845118..d3fa890 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -9,6 +9,8 @@ import ( "reflect" + errs "errors" + "github.com/1Password/connect-sdk-go/onepassword" "github.com/1Password/onepassword-operator/pkg/utils" corev1 "k8s.io/api/core/v1" @@ -16,7 +18,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" kubeValidate "k8s.io/apimachinery/pkg/util/validation" - errs "errors" kubernetesClient "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -29,6 +30,8 @@ const restartAnnotation = OnepasswordPrefix + "/last-restarted" const ItemPathAnnotation = OnepasswordPrefix + "/item-path" const RestartDeploymentsAnnotation = OnepasswordPrefix + "/auto-restart" +var ErrCannotUpdateSecretType = errs.New("Cannot change secret type. Secret type is immutable") + 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 { @@ -68,7 +71,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa currentLabels := currentSecret.Labels currentSecretType := string(currentSecret.Type) if !reflect.DeepEqual(currentSecretType, secretType) { - return errs.New("Cannot change secret type. Secret type is immutable") + return ErrCannotUpdateSecretType } if !reflect.DeepEqual(currentAnnotations, secretAnnotations) || !reflect.DeepEqual(currentLabels, labels) { From b24aa48bd6979deb4e4e37dd44b1697a53244c24 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 21 Feb 2022 11:03:14 +0100 Subject: [PATCH 13/22] Add release notes for v1.2.0 --- .VERSION | 2 +- CHANGELOG.md | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.VERSION b/.VERSION index 1cc5f65..867e524 100644 --- a/.VERSION +++ b/.VERSION @@ -1 +1 @@ -1.1.0 \ No newline at end of file +1.2.0 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a45b92c..5eabdc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ --- +[//]: # (START/v1.2.0) +# v1.2.0 + +## Features + * Support secrets provisioned through FromEnv. {#74} + * Support configuration of Kubernetes Secret type. {#87} + * Improved logging. (#2) +--- + [//]: # (START/v1.1.0) # v1.1.0 From befcaae45741330f14e5c461a2f5e1815647429f Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 21 Feb 2022 11:49:14 +0100 Subject: [PATCH 14/22] Fix typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eabdc1..f6102d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ ## Features * Support secrets provisioned through FromEnv. {#74} * Support configuration of Kubernetes Secret type. {#87} - * Improved logging. (#2) + * Improved logging. (#72) --- [//]: # (START/v1.1.0) From 098d504d2a409b23265318d24fbd073da27d64b1 Mon Sep 17 00:00:00 2001 From: Alex Vanderbist Date: Wed, 16 Mar 2022 18:21:00 +0100 Subject: [PATCH 15/22] Fix typos in readme (auto reset -> auto restart) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c641b78..8e128c9 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ metadata: annotations: operator.1password.io/auto-restart: "true" ``` -If the value is not set, the auto reset settings on the operator will be used. This value can be overwritten by deployment. +If the value is not set, the auto restart settings on the operator will be used. This value can be overwritten by deployment. **Per Deployment** This method allows for managing auto restarts on a given deployment. Auto restarts can by managed by setting the annotation `operator.1password.io/auto-restart` to either `true` or `false` on the desired deployment. An example of this is shown below: @@ -183,7 +183,7 @@ metadata: annotations: operator.1password.io/auto-restart: "true" ``` -If the value is not set, the auto reset settings on the namespace will be used. +If the value is not set, the auto restart settings on the namespace will be used. **Per OnePasswordItem Custom Resource** This method allows for managing auto restarts on a given OnePasswordItem custom resource. Auto restarts can by managed by setting the annotation `operator.1password.io/auto_restart` to either `true` or `false` on the desired OnePasswordItem. An example of this is shown below: @@ -196,7 +196,7 @@ metadata: annotations: operator.1password.io/auto-restart: "true" ``` -If the value is not set, the auto reset settings on the deployment will be used. +If the value is not set, the auto restart settings on the deployment will be used. ## Development From 0d9e07f543336c29a8fd309ae27e0b84132f60d9 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Mon, 21 Mar 2022 11:19:49 +0100 Subject: [PATCH 16/22] Update go sdk version to v1.2.0 --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 4327e11..920c69a 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,11 @@ module github.com/1Password/onepassword-operator go 1.13 require ( - github.com/1Password/connect-sdk-go v1.0.1 + github.com/1Password/connect-sdk-go v1.2.0 github.com/operator-framework/operator-sdk v0.19.0 github.com/prometheus/common v0.14.0 // indirect github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.7.0 k8s.io/api v0.18.2 k8s.io/apimachinery v0.18.2 k8s.io/client-go v12.0.0+incompatible diff --git a/go.sum b/go.sum index 470bc3f..bf98094 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeS dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/1Password/connect-sdk-go v1.0.1 h1:BOeMIxVk6/ISmLNWUkSxEbVI7tNr5+aNXIobMM0/I0U= github.com/1Password/connect-sdk-go v1.0.1/go.mod h1:br2BWk2sqgJFnOFK5WSDfBBmwQ6E7hV9LoPqrtHGRNY= +github.com/1Password/connect-sdk-go v1.2.0 h1:WbIvmbDUpA89nyH0l3LF2iRSFJAv86d2D7IjVNjw6iw= +github.com/1Password/connect-sdk-go v1.2.0/go.mod h1:qK2bF/GweAq812xj+HGfbauaE6cKX1MXfKhpAvoHEq8= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -885,6 +887,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/thanos-io/thanos v0.11.0/go.mod h1:N/Yes7J68KqvmY+xM6J5CJqEvWIvKSR5sqGtmuD6wDc= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= From 7e1b94fae7e36336719c457232b01b98b43822bd Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Wed, 23 Mar 2022 19:36:57 +0100 Subject: [PATCH 17/22] Fix wrong command in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e128c9..f03d1cd 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ cat 1password-credentials.json | base64 | \ Create a Kubernetes secret from the op-session file: ```bash -kubectl create secret generic op-credentials --from-file=1password-credentials.json=op-session +kubectl create secret generic op-credentials --from-file=op-session ``` Add the following environment variable to the onepassword-connect-operator container in `deploy/operator.yaml`: From a903f9b1afb91a2c0540fd9d840fb82793ef6861 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 24 Mar 2022 11:37:24 +0100 Subject: [PATCH 18/22] Also add file data to kubernetes secrets --- .../kubernetes_secrets_builder.go | 20 +- pkg/onepassword/items.go | 8 + .../1Password/connect-sdk-go/LICENSE | 21 ++ .../connect-sdk-go/connect/client.go | 219 +++++++++++------- .../connect-sdk-go/connect/version.go | 101 +++++++- .../connect-sdk-go/onepassword/errors.go | 21 ++ .../connect-sdk-go/onepassword/files.go | 49 ++++ .../connect-sdk-go/onepassword/items.go | 4 +- .../testify/assert/assertion_compare.go | 172 +++++++++++--- .../testify/assert/assertion_format.go | 97 ++++++++ .../testify/assert/assertion_forward.go | 194 ++++++++++++++++ .../testify/assert/assertion_order.go | 81 +++++++ .../stretchr/testify/assert/assertions.go | 83 ++++++- vendor/modules.txt | 4 +- 14 files changed, 959 insertions(+), 115 deletions(-) create mode 100644 vendor/github.com/1Password/connect-sdk-go/LICENSE create mode 100644 vendor/github.com/1Password/connect-sdk-go/onepassword/errors.go create mode 100644 vendor/github.com/1Password/connect-sdk-go/onepassword/files.go create mode 100644 vendor/github.com/stretchr/testify/assert/assertion_order.go diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go index d3fa890..a62d69c 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -12,6 +12,7 @@ import ( errs "errors" "github.com/1Password/connect-sdk-go/onepassword" + "github.com/1Password/onepassword-operator/pkg/utils" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -94,12 +95,12 @@ func BuildKubernetesSecretFromOnePasswordItem(name, namespace string, annotation Annotations: annotations, Labels: labels, }, - Data: BuildKubernetesSecretData(item.Fields), + Data: BuildKubernetesSecretData(item.Fields, item.Files), Type: corev1.SecretType(secretType), } } -func BuildKubernetesSecretData(fields []*onepassword.ItemField) map[string][]byte { +func BuildKubernetesSecretData(fields []*onepassword.ItemField, files []*onepassword.File) map[string][]byte { secretData := map[string][]byte{} for i := 0; i < len(fields); i++ { if fields[i].Value != "" { @@ -107,6 +108,21 @@ func BuildKubernetesSecretData(fields []*onepassword.ItemField) map[string][]byt secretData[key] = []byte(fields[i].Value) } } + + // populate unpopulated fields from files + for _, file := range files { + content, err := file.Content() + if err != nil { + log.Error(err, "Could not load contents of file %s", file.Name) + continue + } + if content != nil { + key := file.Name + if secretData[key] == nil { + secretData[key] = content + } + } + } return secretData } diff --git a/pkg/onepassword/items.go b/pkg/onepassword/items.go index 11c4914..c022f5d 100644 --- a/pkg/onepassword/items.go +++ b/pkg/onepassword/items.go @@ -30,6 +30,14 @@ func GetOnePasswordItemByPath(opConnectClient connect.Client, path string) (*one if err != nil { return nil, err } + + for _, file := range item.Files { + _, err := opConnectClient.GetFileContent(file) + if err != nil { + return nil, err + } + } + return item, nil } diff --git a/vendor/github.com/1Password/connect-sdk-go/LICENSE b/vendor/github.com/1Password/connect-sdk-go/LICENSE new file mode 100644 index 0000000..340215c --- /dev/null +++ b/vendor/github.com/1Password/connect-sdk-go/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 1Password + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/1Password/connect-sdk-go/connect/client.go b/vendor/github.com/1Password/connect-sdk-go/connect/client.go index f26a402..16b1832 100644 --- a/vendor/github.com/1Password/connect-sdk-go/connect/client.go +++ b/vendor/github.com/1Password/connect-sdk-go/connect/client.go @@ -3,6 +3,7 @@ package connect import ( "bytes" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -10,11 +11,12 @@ import ( "net/url" "os" - "github.com/1Password/connect-sdk-go/onepassword" opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" jaegerClientConfig "github.com/uber/jaeger-client-go/config" "github.com/uber/jaeger-client-go/zipkin" + + "github.com/1Password/connect-sdk-go/onepassword" ) const ( @@ -24,6 +26,7 @@ const ( // Client Represents an available 1Password Connect API to connect to type Client interface { GetVaults() ([]onepassword.Vault, error) + GetVault(uuid string) (*onepassword.Vault, error) GetVaultsByTitle(uuid string) ([]onepassword.Vault, error) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) GetItems(vaultUUID string) ([]onepassword.Item, error) @@ -32,6 +35,8 @@ type Client interface { CreateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) DeleteItem(item *onepassword.Item, vaultUUID string) error + GetFile(fileUUID string, itemUUID string, vaultUUID string) (*onepassword.File, error) + GetFileContent(file *onepassword.File) ([]byte, error) } type httpClient interface { @@ -112,23 +117,41 @@ func (rs *restClient) GetVaults() ([]onepassword.Vault, error) { return nil, err } - if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, vaultURL) - } - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - - vaults := []onepassword.Vault{} - if err := json.Unmarshal(body, &vaults); err != nil { + var vaults []onepassword.Vault + if err := parseResponse(response, http.StatusOK, &vaults); err != nil { return nil, err } return vaults, nil } +// GetVaults Get a list of all available vaults +func (rs *restClient) GetVault(uuid string) (*onepassword.Vault, error) { + if uuid == "" { + return nil, errors.New("no uuid provided") + } + + span := rs.tracer.StartSpan("GetVault") + defer span.Finish() + + vaultURL := fmt.Sprintf("/v1/vaults/%s", uuid) + request, err := rs.buildRequest(http.MethodGet, vaultURL, http.NoBody, span) + if err != nil { + return nil, err + } + + response, err := rs.client.Do(request) + if err != nil { + return nil, err + } + var vault onepassword.Vault + if err := parseResponse(response, http.StatusOK, &vault); err != nil { + return nil, err + } + + return &vault, nil +} + func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) { span := rs.tracer.StartSpan("GetVaultsByTitle") defer span.Finish() @@ -145,17 +168,8 @@ func (rs *restClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error return nil, err } - if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Unable to retrieve vaults. Receieved %q for %q", response.Status, itemURL) - } - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - - vaults := []onepassword.Vault{} - if err := json.Unmarshal(body, &vaults); err != nil { + var vaults []onepassword.Vault + if err := parseResponse(response, http.StatusOK, &vaults); err != nil { return nil, err } @@ -177,18 +191,8 @@ func (rs *restClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, if err != nil { return nil, err } - - if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL) - } - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - - item := onepassword.Item{} - if err := json.Unmarshal(body, &item); err != nil { + var item onepassword.Item + if err := parseResponse(response, http.StatusOK, &item); err != nil { return nil, err } @@ -226,17 +230,8 @@ func (rs *restClient) GetItemsByTitle(title string, vaultUUID string) ([]onepass return nil, err } - if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL) - } - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - - items := []onepassword.Item{} - if err := json.Unmarshal(body, &items); err != nil { + var items []onepassword.Item + if err := parseResponse(response, http.StatusOK, &items); err != nil { return nil, err } @@ -258,17 +253,8 @@ func (rs *restClient) GetItems(vaultUUID string) ([]onepassword.Item, error) { return nil, err } - if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Unable to retrieve items. Receieved %q for %q", response.Status, itemURL) - } - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - - items := []onepassword.Item{} - if err := json.Unmarshal(body, &items); err != nil { + var items []onepassword.Item + if err := parseResponse(response, http.StatusOK, &items); err != nil { return nil, err } @@ -296,17 +282,8 @@ func (rs *restClient) CreateItem(item *onepassword.Item, vaultUUID string) (*one return nil, err } - if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Unable to create item. Receieved %q for %q", response.Status, itemURL) - } - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - - newItem := onepassword.Item{} - if err := json.Unmarshal(body, &newItem); err != nil { + var newItem onepassword.Item + if err := parseResponse(response, http.StatusOK, &newItem); err != nil { return nil, err } @@ -334,17 +311,8 @@ func (rs *restClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*one return nil, err } - if response.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Unable to update item. Receieved %q for %q", response.Status, itemURL) - } - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, err - } - - newItem := onepassword.Item{} - if err := json.Unmarshal(body, &newItem); err != nil { + var newItem onepassword.Item + if err := parseResponse(response, http.StatusOK, &newItem); err != nil { return nil, err } @@ -367,13 +335,73 @@ func (rs *restClient) DeleteItem(item *onepassword.Item, vaultUUID string) error return err } - if response.StatusCode != http.StatusNoContent { - return fmt.Errorf("Unable to retrieve item. Receieved %q for %q", response.Status, itemURL) + if err := parseResponse(response, http.StatusNoContent, nil); err != nil { + return err } return nil } +// GetFile Get a specific File in a specified item. +// This does not include the file contents. Call GetFileContent() to load the file's content. +func (rs *restClient) GetFile(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) { + span := rs.tracer.StartSpan("GetFile") + defer span.Finish() + + itemURL := fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s", vaultUUID, itemUUID, uuid) + request, err := rs.buildRequest(http.MethodGet, itemURL, http.NoBody, span) + if err != nil { + return nil, err + } + + response, err := rs.client.Do(request) + if err != nil { + return nil, err + } + if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil { + return nil, err + } + + var file onepassword.File + if err := parseResponse(response, http.StatusOK, &file); err != nil { + return nil, err + } + + return &file, nil +} + +// GetFileContent retrieves the file's content. +// If the file's content have previously been fetched, those contents are returned without making another request. +func (rs *restClient) GetFileContent(file *onepassword.File) ([]byte, error) { + if content, err := file.Content(); err == nil { + return content, nil + } + + span := rs.tracer.StartSpan("GetFileContent") + defer span.Finish() + + request, err := rs.buildRequest(http.MethodGet, file.ContentPath, http.NoBody, span) + if err != nil { + return nil, err + } + + response, err := rs.client.Do(request) + if err != nil { + return nil, err + } + if err := expectMinimumConnectVersion(response, version{1, 3, 0}); err != nil { + return nil, err + } + + content, err := readResponseBody(response, http.StatusOK) + if err != nil { + return nil, err + } + + file.SetContent(content) + return content, nil +} + func (rs *restClient) buildRequest(method string, path string, body io.Reader, span opentracing.Span) (*http.Request, error) { url := fmt.Sprintf("%s%s", rs.URL, path) @@ -394,3 +422,32 @@ func (rs *restClient) buildRequest(method string, path string, body io.Reader, s return request, nil } + +func parseResponse(resp *http.Response, expectedStatusCode int, result interface{}) error { + body, err := readResponseBody(resp, expectedStatusCode) + if err != nil { + return err + } + if result != nil { + if err := json.Unmarshal(body, result); err != nil { + return fmt.Errorf("decoding response: %s", err) + } + } + return nil +} + +func readResponseBody(resp *http.Response, expectedStatusCode int) ([]byte, error) { + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode != expectedStatusCode { + var errResp *onepassword.Error + if err := json.Unmarshal(body, &errResp); err != nil { + return nil, fmt.Errorf("decoding error response: %s", err) + } + return nil, errResp + } + return body, nil +} diff --git a/vendor/github.com/1Password/connect-sdk-go/connect/version.go b/vendor/github.com/1Password/connect-sdk-go/connect/version.go index 61f9195..b654f8b 100644 --- a/vendor/github.com/1Password/connect-sdk-go/connect/version.go +++ b/vendor/github.com/1Password/connect-sdk-go/connect/version.go @@ -1,5 +1,104 @@ package connect +import ( + "errors" + "fmt" + "net/http" + "strconv" + "strings" +) + // SDKVersion is the latest Semantic Version of the library // Do not rename this variable without changing the regex in the Makefile -const SDKVersion = "1.0.1" +const SDKVersion = "1.2.0" + +const VersionHeaderKey = "1Password-Connect-Version" + +// expectMinimumConnectVersion returns an error if the provided minimum version for Connect is lower than the version +// reported in the response from Connect. +func expectMinimumConnectVersion(resp *http.Response, minimumVersion version) error { + serverVersion, err := getServerVersion(resp) + if err != nil { + // Return gracefully if server version cannot be determined reliably + return nil + } + if !serverVersion.IsGreaterOrEqualThan(minimumVersion) { + return fmt.Errorf("need at least version %s of Connect for this function, detected version %s. Please update your Connect server", minimumVersion, serverVersion) + } + return nil +} + +func getServerVersion(resp *http.Response) (serverVersion, error) { + versionHeader := resp.Header.Get(VersionHeaderKey) + if versionHeader == "" { + // The last version without the version header was v1.2.0 + return serverVersion{ + version: version{1, 2, 0}, + orEarlier: true, + }, nil + } + return parseServerVersion(versionHeader) +} + +type version struct { + major int + minor int + patch int +} + +// serverVersion describes the version reported by the server. +type serverVersion struct { + version + // orEarlier is true if the version is derived from the lack of a version header from the server. + orEarlier bool +} + +func (v version) String() string { + return fmt.Sprintf("%d.%d.%d", v.major, v.minor, v.patch) +} + +func (v serverVersion) String() string { + if v.orEarlier { + return v.version.String() + " (or earlier)" + } + return v.version.String() +} + +// IsGreaterOrEqualThan returns true if the lefthand-side version is equal to or or a higher version than the provided +// minimum according to the semantic versioning rules. +func (v version) IsGreaterOrEqualThan(min version) bool { + if v.major != min.major { + // Different major version + return v.major > min.major + } + + if v.minor != min.minor { + // Same major, but different minor version + return v.minor > min.minor + } + + // Same major and minor version + return v.patch >= min.patch +} + +func parseServerVersion(v string) (serverVersion, error) { + spl := strings.Split(v, ".") + if len(spl) != 3 { + return serverVersion{}, errors.New("wrong length") + } + var res [3]int + for i := range res { + tmp, err := strconv.Atoi(spl[i]) + if err != nil { + return serverVersion{}, err + } + res[i] = tmp + } + return serverVersion{ + version: version{ + major: res[0], + minor: res[1], + patch: res[2], + }, + }, nil +} diff --git a/vendor/github.com/1Password/connect-sdk-go/onepassword/errors.go b/vendor/github.com/1Password/connect-sdk-go/onepassword/errors.go new file mode 100644 index 0000000..329a0b3 --- /dev/null +++ b/vendor/github.com/1Password/connect-sdk-go/onepassword/errors.go @@ -0,0 +1,21 @@ +package onepassword + +import "fmt" + +// Error is an error returned by the Connect API. +type Error struct { + StatusCode int `json:"status"` + Message string `json:"message"` +} + +func (e *Error) Error() string { + return fmt.Sprintf("status %d: %s", e.StatusCode, e.Message) +} + +func (e *Error) Is(target error) bool { + t, ok := target.(*Error) + if !ok { + return false + } + return t.Message == e.Message && t.StatusCode == e.StatusCode +} diff --git a/vendor/github.com/1Password/connect-sdk-go/onepassword/files.go b/vendor/github.com/1Password/connect-sdk-go/onepassword/files.go new file mode 100644 index 0000000..a34692f --- /dev/null +++ b/vendor/github.com/1Password/connect-sdk-go/onepassword/files.go @@ -0,0 +1,49 @@ +package onepassword + +import ( + "encoding/json" + "errors" +) + +type File struct { + ID string `json:"id"` + Name string `json:"name"` + Section *ItemSection `json:"section,omitempty"` + Size int `json:"size"` + ContentPath string `json:"content_path"` + content []byte +} + +func (f *File) UnmarshalJSON(data []byte) error { + var jsonFile struct { + ID string `json:"id"` + Name string `json:"name"` + Section *ItemSection `json:"section,omitempty"` + Size int `json:"size"` + ContentPath string `json:"content_path"` + Content []byte `json:"content,omitempty"` + } + if err := json.Unmarshal(data, &jsonFile); err != nil { + return err + } + f.ID = jsonFile.ID + f.Name = jsonFile.Name + f.Section = jsonFile.Section + f.Size = jsonFile.Size + f.ContentPath = jsonFile.ContentPath + f.content = jsonFile.Content + return nil +} + +// Content returns the content of the file if they have been loaded and returns an error if they have not been loaded. +// Use `client.GetFileContent(file *File)` instead to make sure the content is fetched automatically if not present. +func (f *File) Content() ([]byte, error) { + if f.content == nil { + return nil, errors.New("file content not loaded") + } + return f.content, nil +} + +func (f *File) SetContent(content []byte) { + f.content = content +} diff --git a/vendor/github.com/1Password/connect-sdk-go/onepassword/items.go b/vendor/github.com/1Password/connect-sdk-go/onepassword/items.go index 71bc409..783ca8e 100644 --- a/vendor/github.com/1Password/connect-sdk-go/onepassword/items.go +++ b/vendor/github.com/1Password/connect-sdk-go/onepassword/items.go @@ -28,6 +28,7 @@ const ( Document ItemCategory = "DOCUMENT" EmailAccount ItemCategory = "EMAIL_ACCOUNT" SocialSecurityNumber ItemCategory = "SOCIAL_SECURITY_NUMBER" + ApiCredential ItemCategory = "API_CREDENTIAL" Custom ItemCategory = "CUSTOM" ) @@ -39,7 +40,7 @@ func (ic *ItemCategory) UnmarshalJSON(b []byte) error { switch category { case Login, Password, Server, Database, CreditCard, Membership, Passport, SoftwareLicense, OutdoorLicense, SecureNote, WirelessRouter, BankAccount, DriverLicense, Identity, RewardProgram, - Document, EmailAccount, SocialSecurityNumber: + Document, EmailAccount, SocialSecurityNumber, ApiCredential: *ic = category default: *ic = Custom @@ -64,6 +65,7 @@ type Item struct { Sections []*ItemSection `json:"sections,omitempty"` Fields []*ItemField `json:"fields,omitempty"` + Files []*File `json:"files,omitempty"` LastEditedBy string `json:"lastEditedBy,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"` diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/vendor/github.com/stretchr/testify/assert/assertion_compare.go index dc20039..41649d2 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_compare.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_compare.go @@ -13,12 +13,42 @@ const ( compareGreater ) +var ( + intType = reflect.TypeOf(int(1)) + int8Type = reflect.TypeOf(int8(1)) + int16Type = reflect.TypeOf(int16(1)) + int32Type = reflect.TypeOf(int32(1)) + int64Type = reflect.TypeOf(int64(1)) + + uintType = reflect.TypeOf(uint(1)) + uint8Type = reflect.TypeOf(uint8(1)) + uint16Type = reflect.TypeOf(uint16(1)) + uint32Type = reflect.TypeOf(uint32(1)) + uint64Type = reflect.TypeOf(uint64(1)) + + float32Type = reflect.TypeOf(float32(1)) + float64Type = reflect.TypeOf(float64(1)) + + stringType = reflect.TypeOf("") +) + func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { + obj1Value := reflect.ValueOf(obj1) + obj2Value := reflect.ValueOf(obj2) + + // throughout this switch we try and avoid calling .Convert() if possible, + // as this has a pretty big performance impact switch kind { case reflect.Int: { - intobj1 := obj1.(int) - intobj2 := obj2.(int) + intobj1, ok := obj1.(int) + if !ok { + intobj1 = obj1Value.Convert(intType).Interface().(int) + } + intobj2, ok := obj2.(int) + if !ok { + intobj2 = obj2Value.Convert(intType).Interface().(int) + } if intobj1 > intobj2 { return compareGreater, true } @@ -31,8 +61,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Int8: { - int8obj1 := obj1.(int8) - int8obj2 := obj2.(int8) + int8obj1, ok := obj1.(int8) + if !ok { + int8obj1 = obj1Value.Convert(int8Type).Interface().(int8) + } + int8obj2, ok := obj2.(int8) + if !ok { + int8obj2 = obj2Value.Convert(int8Type).Interface().(int8) + } if int8obj1 > int8obj2 { return compareGreater, true } @@ -45,8 +81,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Int16: { - int16obj1 := obj1.(int16) - int16obj2 := obj2.(int16) + int16obj1, ok := obj1.(int16) + if !ok { + int16obj1 = obj1Value.Convert(int16Type).Interface().(int16) + } + int16obj2, ok := obj2.(int16) + if !ok { + int16obj2 = obj2Value.Convert(int16Type).Interface().(int16) + } if int16obj1 > int16obj2 { return compareGreater, true } @@ -59,8 +101,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Int32: { - int32obj1 := obj1.(int32) - int32obj2 := obj2.(int32) + int32obj1, ok := obj1.(int32) + if !ok { + int32obj1 = obj1Value.Convert(int32Type).Interface().(int32) + } + int32obj2, ok := obj2.(int32) + if !ok { + int32obj2 = obj2Value.Convert(int32Type).Interface().(int32) + } if int32obj1 > int32obj2 { return compareGreater, true } @@ -73,8 +121,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Int64: { - int64obj1 := obj1.(int64) - int64obj2 := obj2.(int64) + int64obj1, ok := obj1.(int64) + if !ok { + int64obj1 = obj1Value.Convert(int64Type).Interface().(int64) + } + int64obj2, ok := obj2.(int64) + if !ok { + int64obj2 = obj2Value.Convert(int64Type).Interface().(int64) + } if int64obj1 > int64obj2 { return compareGreater, true } @@ -87,8 +141,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Uint: { - uintobj1 := obj1.(uint) - uintobj2 := obj2.(uint) + uintobj1, ok := obj1.(uint) + if !ok { + uintobj1 = obj1Value.Convert(uintType).Interface().(uint) + } + uintobj2, ok := obj2.(uint) + if !ok { + uintobj2 = obj2Value.Convert(uintType).Interface().(uint) + } if uintobj1 > uintobj2 { return compareGreater, true } @@ -101,8 +161,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Uint8: { - uint8obj1 := obj1.(uint8) - uint8obj2 := obj2.(uint8) + uint8obj1, ok := obj1.(uint8) + if !ok { + uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8) + } + uint8obj2, ok := obj2.(uint8) + if !ok { + uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8) + } if uint8obj1 > uint8obj2 { return compareGreater, true } @@ -115,8 +181,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Uint16: { - uint16obj1 := obj1.(uint16) - uint16obj2 := obj2.(uint16) + uint16obj1, ok := obj1.(uint16) + if !ok { + uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16) + } + uint16obj2, ok := obj2.(uint16) + if !ok { + uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16) + } if uint16obj1 > uint16obj2 { return compareGreater, true } @@ -129,8 +201,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Uint32: { - uint32obj1 := obj1.(uint32) - uint32obj2 := obj2.(uint32) + uint32obj1, ok := obj1.(uint32) + if !ok { + uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32) + } + uint32obj2, ok := obj2.(uint32) + if !ok { + uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32) + } if uint32obj1 > uint32obj2 { return compareGreater, true } @@ -143,8 +221,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Uint64: { - uint64obj1 := obj1.(uint64) - uint64obj2 := obj2.(uint64) + uint64obj1, ok := obj1.(uint64) + if !ok { + uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64) + } + uint64obj2, ok := obj2.(uint64) + if !ok { + uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64) + } if uint64obj1 > uint64obj2 { return compareGreater, true } @@ -157,8 +241,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Float32: { - float32obj1 := obj1.(float32) - float32obj2 := obj2.(float32) + float32obj1, ok := obj1.(float32) + if !ok { + float32obj1 = obj1Value.Convert(float32Type).Interface().(float32) + } + float32obj2, ok := obj2.(float32) + if !ok { + float32obj2 = obj2Value.Convert(float32Type).Interface().(float32) + } if float32obj1 > float32obj2 { return compareGreater, true } @@ -171,8 +261,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.Float64: { - float64obj1 := obj1.(float64) - float64obj2 := obj2.(float64) + float64obj1, ok := obj1.(float64) + if !ok { + float64obj1 = obj1Value.Convert(float64Type).Interface().(float64) + } + float64obj2, ok := obj2.(float64) + if !ok { + float64obj2 = obj2Value.Convert(float64Type).Interface().(float64) + } if float64obj1 > float64obj2 { return compareGreater, true } @@ -185,8 +281,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { } case reflect.String: { - stringobj1 := obj1.(string) - stringobj2 := obj2.(string) + stringobj1, ok := obj1.(string) + if !ok { + stringobj1 = obj1Value.Convert(stringType).Interface().(string) + } + stringobj2, ok := obj2.(string) + if !ok { + stringobj2 = obj2Value.Convert(stringType).Interface().(string) + } if stringobj1 > stringobj2 { return compareGreater, true } @@ -240,6 +342,24 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs) } +// Positive asserts that the specified element is positive +// +// assert.Positive(t, 1) +// assert.Positive(t, 1.23) +func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { + zero := reflect.Zero(reflect.TypeOf(e)) + return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs) +} + +// Negative asserts that the specified element is negative +// +// assert.Negative(t, -1) +// assert.Negative(t, -1.23) +func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool { + zero := reflect.Zero(reflect.TypeOf(e)) + return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs) +} + func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go index 49370eb..4dfd122 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_format.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -114,6 +114,24 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) bool { return Error(t, err, append([]interface{}{msg}, args...)...) } +// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...) +} + +// ErrorIsf asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...) +} + // Eventuallyf asserts that given condition will be met in waitFor time, // periodically checking target function each tick. // @@ -321,6 +339,54 @@ func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsil return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) } +// IsDecreasingf asserts that the collection is decreasing +// +// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return IsDecreasing(t, object, append([]interface{}{msg}, args...)...) +} + +// IsIncreasingf asserts that the collection is increasing +// +// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return IsIncreasing(t, object, append([]interface{}{msg}, args...)...) +} + +// IsNonDecreasingf asserts that the collection is not decreasing +// +// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...) +} + +// IsNonIncreasingf asserts that the collection is not increasing +// +// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...) +} + // IsTypef asserts that the specified objects are of the same type. func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { @@ -375,6 +441,17 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args . return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...) } +// Negativef asserts that the specified element is negative +// +// assert.Negativef(t, -1, "error message %s", "formatted") +// assert.Negativef(t, -1.23, "error message %s", "formatted") +func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Negative(t, e, append([]interface{}{msg}, args...)...) +} + // Neverf asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // @@ -476,6 +553,15 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...) } +// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...) +} + // NotNilf asserts that the specified object is not nil. // // assert.NotNilf(t, err, "error message %s", "formatted") @@ -572,6 +658,17 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...) } +// Positivef asserts that the specified element is positive +// +// assert.Positivef(t, 1, "error message %s", "formatted") +// assert.Positivef(t, 1.23, "error message %s", "formatted") +func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Positive(t, e, append([]interface{}{msg}, args...)...) +} + // Regexpf asserts that a specified regexp matches a string. // // assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index 9db8894..25337a6 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -204,6 +204,42 @@ func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool { return Error(a.t, err, msgAndArgs...) } +// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func (a *Assertions) ErrorAs(err error, target interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ErrorAs(a.t, err, target, msgAndArgs...) +} + +// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ErrorAsf(a.t, err, target, msg, args...) +} + +// ErrorIs asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ErrorIs(a.t, err, target, msgAndArgs...) +} + +// ErrorIsf asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return ErrorIsf(a.t, err, target, msg, args...) +} + // Errorf asserts that a function returned an error (i.e. not `nil`). // // actualObj, err := SomeFunction() @@ -631,6 +667,102 @@ func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilo return InEpsilonf(a.t, expected, actual, epsilon, msg, args...) } +// IsDecreasing asserts that the collection is decreasing +// +// a.IsDecreasing([]int{2, 1, 0}) +// a.IsDecreasing([]float{2, 1}) +// a.IsDecreasing([]string{"b", "a"}) +func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsDecreasing(a.t, object, msgAndArgs...) +} + +// IsDecreasingf asserts that the collection is decreasing +// +// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted") +// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted") +func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsDecreasingf(a.t, object, msg, args...) +} + +// IsIncreasing asserts that the collection is increasing +// +// a.IsIncreasing([]int{1, 2, 3}) +// a.IsIncreasing([]float{1, 2}) +// a.IsIncreasing([]string{"a", "b"}) +func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsIncreasing(a.t, object, msgAndArgs...) +} + +// IsIncreasingf asserts that the collection is increasing +// +// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted") +// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted") +func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsIncreasingf(a.t, object, msg, args...) +} + +// IsNonDecreasing asserts that the collection is not decreasing +// +// a.IsNonDecreasing([]int{1, 1, 2}) +// a.IsNonDecreasing([]float{1, 2}) +// a.IsNonDecreasing([]string{"a", "b"}) +func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsNonDecreasing(a.t, object, msgAndArgs...) +} + +// IsNonDecreasingf asserts that the collection is not decreasing +// +// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted") +func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsNonDecreasingf(a.t, object, msg, args...) +} + +// IsNonIncreasing asserts that the collection is not increasing +// +// a.IsNonIncreasing([]int{2, 1, 1}) +// a.IsNonIncreasing([]float{2, 1}) +// a.IsNonIncreasing([]string{"b", "a"}) +func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsNonIncreasing(a.t, object, msgAndArgs...) +} + +// IsNonIncreasingf asserts that the collection is not increasing +// +// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted") +func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return IsNonIncreasingf(a.t, object, msg, args...) +} + // IsType asserts that the specified objects are of the same type. func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { @@ -739,6 +871,28 @@ func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...i return Lessf(a.t, e1, e2, msg, args...) } +// Negative asserts that the specified element is negative +// +// a.Negative(-1) +// a.Negative(-1.23) +func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Negative(a.t, e, msgAndArgs...) +} + +// Negativef asserts that the specified element is negative +// +// a.Negativef(-1, "error message %s", "formatted") +// a.Negativef(-1.23, "error message %s", "formatted") +func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Negativef(a.t, e, msg, args...) +} + // Never asserts that the given condition doesn't satisfy in waitFor time, // periodically checking the target function each tick. // @@ -941,6 +1095,24 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str return NotEqualf(a.t, expected, actual, msg, args...) } +// NotErrorIs asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotErrorIs(a.t, err, target, msgAndArgs...) +} + +// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return NotErrorIsf(a.t, err, target, msg, args...) +} + // NotNil asserts that the specified object is not nil. // // a.NotNil(err) @@ -1133,6 +1305,28 @@ func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) b return Panicsf(a.t, f, msg, args...) } +// Positive asserts that the specified element is positive +// +// a.Positive(1) +// a.Positive(1.23) +func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Positive(a.t, e, msgAndArgs...) +} + +// Positivef asserts that the specified element is positive +// +// a.Positivef(1, "error message %s", "formatted") +// a.Positivef(1.23, "error message %s", "formatted") +func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Positivef(a.t, e, msg, args...) +} + // Regexp asserts that a specified regexp matches a string. // // a.Regexp(regexp.MustCompile("start"), "it's starting") diff --git a/vendor/github.com/stretchr/testify/assert/assertion_order.go b/vendor/github.com/stretchr/testify/assert/assertion_order.go new file mode 100644 index 0000000..1c3b471 --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_order.go @@ -0,0 +1,81 @@ +package assert + +import ( + "fmt" + "reflect" +) + +// isOrdered checks that collection contains orderable elements. +func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool { + objKind := reflect.TypeOf(object).Kind() + if objKind != reflect.Slice && objKind != reflect.Array { + return false + } + + objValue := reflect.ValueOf(object) + objLen := objValue.Len() + + if objLen <= 1 { + return true + } + + value := objValue.Index(0) + valueInterface := value.Interface() + firstValueKind := value.Kind() + + for i := 1; i < objLen; i++ { + prevValue := value + prevValueInterface := valueInterface + + value = objValue.Index(i) + valueInterface = value.Interface() + + compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind) + + if !isComparable { + return Fail(t, fmt.Sprintf("Can not compare type \"%s\" and \"%s\"", reflect.TypeOf(value), reflect.TypeOf(prevValue)), msgAndArgs...) + } + + if !containsValue(allowedComparesResults, compareResult) { + return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...) + } + } + + return true +} + +// IsIncreasing asserts that the collection is increasing +// +// assert.IsIncreasing(t, []int{1, 2, 3}) +// assert.IsIncreasing(t, []float{1, 2}) +// assert.IsIncreasing(t, []string{"a", "b"}) +func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs) +} + +// IsNonIncreasing asserts that the collection is not increasing +// +// assert.IsNonIncreasing(t, []int{2, 1, 1}) +// assert.IsNonIncreasing(t, []float{2, 1}) +// assert.IsNonIncreasing(t, []string{"b", "a"}) +func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs) +} + +// IsDecreasing asserts that the collection is decreasing +// +// assert.IsDecreasing(t, []int{2, 1, 0}) +// assert.IsDecreasing(t, []float{2, 1}) +// assert.IsDecreasing(t, []string{"b", "a"}) +func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs) +} + +// IsNonDecreasing asserts that the collection is not decreasing +// +// assert.IsNonDecreasing(t, []int{1, 1, 2}) +// assert.IsNonDecreasing(t, []float{1, 2}) +// assert.IsNonDecreasing(t, []string{"a", "b"}) +func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { + return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs) +} diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index 914a10d..bcac440 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -172,8 +172,8 @@ func isTest(name, prefix string) bool { if len(name) == len(prefix) { // "Test" is ok return true } - rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) - return !unicode.IsLower(rune) + r, _ := utf8.DecodeRuneInString(name[len(prefix):]) + return !unicode.IsLower(r) } func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { @@ -1622,6 +1622,7 @@ var spewConfig = spew.ConfigState{ DisableCapacities: true, SortKeys: true, DisableMethods: true, + MaxDepth: 10, } type tHelper interface { @@ -1693,3 +1694,81 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D } } } + +// ErrorIs asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if errors.Is(err, target) { + return true + } + + var expectedText string + if target != nil { + expectedText = target.Error() + } + + chain := buildErrorChainString(err) + + return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+ + "expected: %q\n"+ + "in chain: %s", expectedText, chain, + ), msgAndArgs...) +} + +// NotErrorIs asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if !errors.Is(err, target) { + return true + } + + var expectedText string + if target != nil { + expectedText = target.Error() + } + + chain := buildErrorChainString(err) + + return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+ + "found: %q\n"+ + "in chain: %s", expectedText, chain, + ), msgAndArgs...) +} + +// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if errors.As(err, target) { + return true + } + + chain := buildErrorChainString(err) + + return Fail(t, fmt.Sprintf("Should be in error chain:\n"+ + "expected: %q\n"+ + "in chain: %s", target, chain, + ), msgAndArgs...) +} + +func buildErrorChainString(err error) string { + if err == nil { + return "" + } + + e := errors.Unwrap(err) + chain := fmt.Sprintf("%q", err.Error()) + for e != nil { + chain += fmt.Sprintf("\n\t%q", e.Error()) + e = errors.Unwrap(e) + } + return chain +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 70f0694..fa10b85 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,6 +1,6 @@ # cloud.google.com/go v0.49.0 cloud.google.com/go/compute/metadata -# github.com/1Password/connect-sdk-go v1.0.1 +# github.com/1Password/connect-sdk-go v1.2.0 github.com/1Password/connect-sdk-go/connect github.com/1Password/connect-sdk-go/onepassword # github.com/Azure/go-autorest/autorest v0.9.3-0.20191028180845-3492b2aff503 @@ -110,7 +110,7 @@ github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util # github.com/spf13/pflag v1.0.5 github.com/spf13/pflag -# github.com/stretchr/testify v1.6.1 +# github.com/stretchr/testify v1.7.0 github.com/stretchr/testify/assert # github.com/uber/jaeger-client-go v2.25.0+incompatible github.com/uber/jaeger-client-go From d6f7b80c400678d83a16624616671d937f49dc6f Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 24 Mar 2022 11:56:33 +0100 Subject: [PATCH 19/22] Log a message if a file on an item is ignored due to a field with the same name --- pkg/kubernetessecrets/kubernetes_secrets_builder.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go index a62d69c..3415737 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -120,6 +120,8 @@ func BuildKubernetesSecretData(fields []*onepassword.ItemField, files []*onepass key := file.Name if secretData[key] == nil { secretData[key] = content + } else { + log.Info(fmt.Sprintf("File '%s' ignored because of a field with the same name", file.Name)) } } } From 62e55a3f1905bcfc278d7ce5c43f6090d4998cf0 Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 24 Mar 2022 12:13:34 +0100 Subject: [PATCH 20/22] Update tests and mock client --- .../kubernetes_secrets_builder_test.go | 2 +- pkg/mocks/mocksecretserver.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go b/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go index e0fd732..2bc9faf 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go @@ -98,7 +98,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { func TestBuildKubernetesSecretData(t *testing.T) { fields := generateFields(5) - secretData := BuildKubernetesSecretData(fields) + secretData := BuildKubernetesSecretData(fields, nil) if len(secretData) != len(fields) { t.Errorf("Unexpected number of secret fields returned. Expected 3, got %v", len(secretData)) } diff --git a/pkg/mocks/mocksecretserver.go b/pkg/mocks/mocksecretserver.go index 489182f..29c7664 100644 --- a/pkg/mocks/mocksecretserver.go +++ b/pkg/mocks/mocksecretserver.go @@ -7,6 +7,7 @@ import ( type TestClient struct { GetVaultsFunc func() ([]onepassword.Vault, error) GetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error) + GetVaultFunc func(uuid string) (*onepassword.Vault, error) GetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error) GetItemsFunc func(vaultUUID string) ([]onepassword.Item, error) GetItemsByTitleFunc func(title string, vaultUUID string) ([]onepassword.Item, error) @@ -14,11 +15,14 @@ type TestClient struct { CreateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) UpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) DeleteItemFunc func(item *onepassword.Item, vaultUUID string) error + GetFileFunc func(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) + GetFileContentFunc func(file *onepassword.File) ([]byte, error) } var ( GetGetVaultsFunc func() ([]onepassword.Vault, error) DoGetVaultsByTitleFunc func(title string) ([]onepassword.Vault, error) + DoGetVaultFunc func(uuid string) (*onepassword.Vault, error) GetGetItemFunc func(uuid string, vaultUUID string) (*onepassword.Item, error) DoGetItemsByTitleFunc func(title string, vaultUUID string) ([]onepassword.Item, error) DoGetItemByTitleFunc func(title string, vaultUUID string) (*onepassword.Item, error) @@ -26,6 +30,8 @@ var ( DoDeleteItemFunc func(item *onepassword.Item, vaultUUID string) error DoGetItemsFunc func(vaultUUID string) ([]onepassword.Item, error) DoUpdateItemFunc func(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) + DoGetFileFunc func(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) + DoGetFileContentFunc func(file *onepassword.File) ([]byte, error) ) // Do is the mock client's `Do` func @@ -37,6 +43,10 @@ func (m *TestClient) GetVaultsByTitle(title string) ([]onepassword.Vault, error) return DoGetVaultsByTitleFunc(title) } +func (m *TestClient) GetVault(uuid string) (*onepassword.Vault, error) { + return DoGetVaultFunc(uuid) +} + func (m *TestClient) GetItem(uuid string, vaultUUID string) (*onepassword.Item, error) { return GetGetItemFunc(uuid, vaultUUID) } @@ -64,3 +74,11 @@ func (m *TestClient) DeleteItem(item *onepassword.Item, vaultUUID string) error func (m *TestClient) UpdateItem(item *onepassword.Item, vaultUUID string) (*onepassword.Item, error) { return DoUpdateItemFunc(item, vaultUUID) } + +func (m *TestClient) GetFile(uuid string, itemUUID string, vaultUUID string) (*onepassword.File, error) { + return DoGetFileFunc(uuid, itemUUID, vaultUUID) +} + +func (m *TestClient) GetFileContent(file *onepassword.File) ([]byte, error) { + return DoGetFileContentFunc(file) +} From 8fa441388031868641ee016e2e188b98314b925d Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Thu, 24 Mar 2022 18:00:24 +0100 Subject: [PATCH 21/22] Update readme to mention that non-file fields take precedence over file-fields --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index c641b78..81efac3 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,11 @@ metadata: Applying this yaml file will create a Kubernetes Secret with the name `` and contents from the location specified at the specified Item Path. +The contents of the Kubernetes secret will be key-value pairs in which the keys are the fields of the 1Password item and the values are the corresponding values stored in 1Password. +In case of fields that store files, the file's contents will be used as the value. + +Within an item, if both a field storing a file and a field of another type have the same name, the file field will be ignored and the other field will take precedence. + Note: Deleting the Deployment that you've created will automatically delete the created Kubernetes Secret only if the deployment is still annotated with `operator.1password.io/item-path` and `operator.1password.io/item-name` and no other deployment is using the secret. If a 1Password Item that is linked to a Kubernetes Secret is updated within the POLLING_INTERVAL the associated Kubernetes Secret will be updated. However, if you do not want a specific secret to be updated you can add the tag `operator.1password.io:ignore-secret` to the item stored in 1Password. While this tag is in place, any updates made to an item will not trigger an update to the associated secret in Kubernetes. From a37bddbfd9b217132d52274925d45ab41b364dee Mon Sep 17 00:00:00 2001 From: Marton Soos Date: Fri, 25 Mar 2022 14:58:07 +0100 Subject: [PATCH 22/22] Update release notes --- .VERSION | 2 +- CHANGELOG.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.VERSION b/.VERSION index 867e524..589268e 100644 --- a/.VERSION +++ b/.VERSION @@ -1 +1 @@ -1.2.0 \ No newline at end of file +1.3.0 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f6102d6..6fac99a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ --- +[//]: # (START/v1.3.0) +# v1.3.0 + +## Features + * Added support for loading secrets from files stored in 1Password. {#47} + +--- + [//]: # (START/v1.2.0) # v1.2.0