Add secret name normalizer to the operator.

The operator will now reformat 1Password item names to become valid names K8s Secret objects. Secret names must be a valid DNS subdomain name. See more: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
This commit is contained in:
david.gunter
2021-08-05 16:34:50 -07:00
parent b50d864b50
commit 579b5848da
2 changed files with 62 additions and 3 deletions

View File

@@ -3,6 +3,8 @@ package onepassworditem
import (
"context"
"fmt"
"regexp"
"strings"
onepasswordv1 "github.com/1Password/onepassword-operator/pkg/apis/onepassword/v1"
kubeSecrets "github.com/1Password/onepassword-operator/pkg/kubernetessecrets"
@@ -15,6 +17,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
kubeValidate "k8s.io/apimachinery/pkg/util/validation"
ctrl "sigs.k8s.io/controller-runtime"
kubeClient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
@@ -143,7 +146,7 @@ func (r *ReconcileOnePasswordItem) removeOnePasswordFinalizerFromOnePasswordItem
}
func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, request reconcile.Request) error {
secretName := resource.GetName()
secretName := formatK8sSecretName(resource.GetName())
autoRestart := resource.Annotations[op.RestartDeploymentsAnnotation]
item, err := onepassword.GetOnePasswordItemByPath(r.opConnectClient, resource.Spec.ItemPath)
@@ -153,3 +156,27 @@ func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1
return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart)
}
// formatK8sSecretName replaces any characters not supported by K8s secret names
//
// K8s Secrets must be valid DNS subdomain names (https://kubernetes.io/docs/concepts/configuration/secret/#overview-of-secrets)
func formatK8sSecretName(value string) string {
if errs := kubeValidate.IsDNS1123Subdomain(value); len(errs) == 0 {
return value
}
return createValidSecretName(value)
}
var invalidDNS1123Chars = regexp.MustCompile("[^a-z0-9-]+")
func createValidSecretName(value string) string {
result := strings.ToLower(value)
result = invalidDNS1123Chars.ReplaceAllString(result, "-")
if len(result) > kubeValidate.DNS1123SubdomainMaxLength {
result = result[0:kubeValidate.DNS1123SubdomainMaxLength]
}
// only alphanumeric characters allowed at beginning and end of value
return strings.Trim(result, "-")
}

View File

@@ -210,6 +210,38 @@ var tests = []testReconcileItem{
passKey: password,
},
},
{
testName: "Secret from 1Password item with invalid K8s secret name",
customResource: &onepasswordv1.OnePasswordItem{
TypeMeta: metav1.TypeMeta{
Kind: onePasswordItemKind,
APIVersion: onePasswordItemAPIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: "!my sECReT it3m%",
Namespace: namespace,
},
Spec: onepasswordv1.OnePasswordItemSpec{
ItemPath: itemPath,
},
},
existingSecret: nil,
expectedError: nil,
expectedResultSecret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "my-secret-it3m",
Namespace: namespace,
Annotations: map[string]string{
op.VersionAnnotation: fmt.Sprint(version),
},
},
Data: expectedSecretData,
},
opItem: map[string]string{
userKey: username,
passKey: password,
},
},
}
func TestReconcileOnePasswordItem(t *testing.T) {
@@ -257,8 +289,8 @@ func TestReconcileOnePasswordItem(t *testing.T) {
// watched resource .
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: name,
Namespace: namespace,
Name: testData.customResource.ObjectMeta.Name,
Namespace: testData.customResource.ObjectMeta.Namespace,
},
}
_, err := r.Reconcile(req)