diff --git a/.infisicalignore b/.infisicalignore index 8072078d42..b80ecaad50 100644 --- a/.infisicalignore +++ b/.infisicalignore @@ -12,3 +12,5 @@ docs/cli/commands/bootstrap.mdx:jwt:86 docs/documentation/platform/audit-log-streams/audit-log-streams.mdx:generic-api-key:102 docs/self-hosting/guides/automated-bootstrapping.mdx:jwt:74 frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx:generic-api-key:72 +k8-operator/config/samples/crd/pushsecret/source-secret-with-templating.yaml:private-key:11 +k8-operator/config/samples/crd/pushsecret/push-secret-with-template.yaml:private-key:52 diff --git a/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx b/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx index 67fc7f2a8a..0664f0cd87 100644 --- a/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx +++ b/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx @@ -401,6 +401,59 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y +## Using templating to push secrets + +Pushing secrets to Infisical from the operator may not always be enough. +Templating is a useful utility of the Infisical secrets operator that allows you to use Go Templating to template the secrets you want to push to Infisical. +Using Go templates, you can format, combine, and create new key-value pairs of secrets that you want to push to Infisical. + + + + This property controls what secrets are included in your push to Infisica. + When set to `true`, all secrets included in the `push.secret.secretName` Kubernetes secret will be pushed to Infisical. + **Use this option when you would like to push all secrets to Infisical from the secrets operator, but want to template a subset of them.** + + When set to `false`, only secrets defined in the `push.secret.template.data` field of the template will be pushed to Infisical. + Use this option when you would like to push **only** a subset of secrets from the Kubernetes secret to Infisical. + + + Define secret keys and their corresponding templates. + Each data value uses a Golang template with access to all secrets defined in the `push.secret.secretName` Kubernetes secret. + + Secrets are structured as follows: + + ```go + type TemplateSecret struct { + Value string `json:"value"` + SecretPath string `json:"secretPath"` + } + ``` + + #### Example template configuration: + + ```yaml + # This example assumes that the `push-secret-demo` Kubernetes secret contains the following secrets: + # SITE_URL = "https://example.com" + # REGION = "us-east-1" + # OTHER_SECRET = "other-secret" + + push: + secret: + secretName: push-secret-demo + secretNamespace: default + template: + includeAllSecrets: true # Includes all secrets from the `push-secret-demo` Kubernetes secret + data: + SITE_URL: "{{ .SITE_URL.Value }}" + API_URL: "https://api.{{.SITE_URL.Value}}.{{.REGION.Value}}.com" # Will create a new secret in Infisical with the key `API_URL` with the value of the `SITE_URL` and `REGION` secrets + ``` + + To help transform your config map data further, the operator provides a set of built-in functions that you can use in your templates. + + ### Available templating functions + Please refer to the [templating functions documentation](/integrations/platforms/kubernetes/overview#available-helper-functions) for more information. + + ## Applying the InfisicalPushSecret CRD to your cluster Once you have configured the `InfisicalPushSecret` CRD with the required fields, you can apply it to your cluster. diff --git a/docs/integrations/platforms/kubernetes/infisical-secret-crd.mdx b/docs/integrations/platforms/kubernetes/infisical-secret-crd.mdx index 3266fd2b06..f23eb010db 100644 --- a/docs/integrations/platforms/kubernetes/infisical-secret-crd.mdx +++ b/docs/integrations/platforms/kubernetes/infisical-secret-crd.mdx @@ -654,30 +654,7 @@ To help transform your secrets further, the operator provides a set of built-in ### Available templating functions - - **Function name**: decodeBase64ToBytes - -**Description**: -Given a base64 encoded string, this function will decodes the base64-encoded string. -This function is useful when your secrets are already stored as base64 encoded value in Infisical. - -**Returns**: The decoded base64 string as bytes. - -**Example**: -The example below assumes that the `BINARY_KEY_BASE64` secret is stored as a base64 encoded value in Infisical. -The resulting managed secret will contain the decoded value of `BINARY_KEY_BASE64`. - -```yaml -managedKubeSecretReferences: -secretName: managed-secret -secretNamespace: default -template: - includeAllSecrets: true - data: - BINARY_KEY: "{{ decodeBase64ToBytes .BINARY_KEY_BASE64.Value }}" -``` - - +Please refer to the [templating functions documentation](/integrations/platforms/kubernetes/overview#available-helper-functions) for more information. @@ -783,31 +760,8 @@ Using Go templates, you can format, combine, and create new key-value pairs from To help transform your config map data further, the operator provides a set of built-in functions that you can use in your templates. ### Available templating functions - - - **Function name**: decodeBase64ToBytes - - **Description**: - Given a base64 encoded string, this function will decodes the base64-encoded string. - This function is useful when your Infisical secrets are already stored as base64 encoded value in Infisical. - - **Returns**: The decoded base64 string as bytes. - - **Example**: - The example below assumes that the `BINARY_KEY_BASE64` secret is stored as a base64 encoded value in Infisical. - The resulting managed config map will contain the decoded value of `BINARY_KEY_BASE64`. - - ```yaml - managedKubeConfigMapReferences: - - configMapName: managed-configmap - configMapNamespace: default - template: - includeAllSecrets: true - data: - BINARY_KEY: "{{ decodeBase64ToBytes .BINARY_KEY_BASE64.Value }}" - ``` - - + + Please refer to the [templating functions documentation](/integrations/platforms/kubernetes/overview#available-helper-functions) for more information. ## Applying CRD @@ -854,39 +808,39 @@ Here, we will highlight three of the most common ways to utilize it. Learn more This will take all the secrets from your managed secret and expose them to your container -````yaml - envFrom: - - secretRef: - name: managed-secret # managed secret name - ``` + ````yaml + envFrom: + - secretRef: + name: managed-secret # managed secret name + ``` - Example usage in a deployment - ```yaml - apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 1 - selector: - matchLabels: + Example usage in a deployment + ```yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + labels: app: nginx - template: - metadata: - labels: + spec: + replicas: 1 + selector: + matchLabels: app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - envFrom: - - secretRef: - name: managed-secret # <- name of managed secret - ports: - - containerPort: 80 -```` + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + envFrom: + - secretRef: + name: managed-secret # <- name of managed secret + ports: + - containerPort: 80 + ```` @@ -902,91 +856,90 @@ spec: key: SOME_SECRET_KEY # The name of the key which exists in the managed secret ``` -Example usage in a deployment + Example usage in a deployment -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 1 - selector: - matchLabels: + ```yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + labels: app: nginx - template: - metadata: - labels: + spec: + replicas: 1 + selector: + matchLabels: app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - env: - - name: STRIPE_API_SECRET - valueFrom: - secretKeyRef: - name: managed-secret # <- name of managed secret - key: STRIPE_API_SECRET - ports: - - containerPort: 80 -``` - + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + env: + - name: STRIPE_API_SECRET + valueFrom: + secretKeyRef: + name: managed-secret # <- name of managed secret + key: STRIPE_API_SECRET + ports: + - containerPort: 80 + ``` -This will allow you to create a volume on your container which comprises of files holding the secrets in your managed kubernetes secret -```yaml -volumes: - - name: secrets-volume-name # The name of the volume under which secrets will be stored - secret: - secretName: managed-secret # managed secret name -```` + This will allow you to create a volume on your container which comprises of files holding the secrets in your managed kubernetes secret + ```yaml + volumes: + - name: secrets-volume-name # The name of the volume under which secrets will be stored + secret: + secretName: managed-secret # managed secret name + ```` -You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets + You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets -```yaml -volumeMounts: - - name: secrets-volume-name - mountPath: /etc/secrets - readOnly: true -``` + ```yaml + volumeMounts: + - name: secrets-volume-name + mountPath: /etc/secrets + readOnly: true + ``` -Example usage in a deployment + Example usage in a deployment -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 1 - selector: - matchLabels: + ```yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + labels: app: nginx - template: - metadata: - labels: + spec: + replicas: 1 + selector: + matchLabels: app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - volumeMounts: - - name: secrets-volume-name - mountPath: /etc/secrets - readOnly: true - ports: - - containerPort: 80 - volumes: - - name: secrets-volume-name - secret: - secretName: managed-secret # <- managed secrets -``` + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + volumeMounts: + - name: secrets-volume-name + mountPath: /etc/secrets + readOnly: true + ports: + - containerPort: 80 + volumes: + - name: secrets-volume-name + secret: + secretName: managed-secret # <- managed secrets + ``` @@ -1021,34 +974,34 @@ secrets.infisical.com/auto-reload: "true" ``` -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx - annotations: - secrets.infisical.com/auto-reload: "true" # <- redeployment annotation -spec: - replicas: 1 - selector: - matchLabels: + ```yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + labels: app: nginx - template: - metadata: - labels: + annotations: + secrets.infisical.com/auto-reload: "true" # <- redeployment annotation + spec: + replicas: 1 + selector: + matchLabels: app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - envFrom: - - secretRef: - name: managed-secret - ports: - - containerPort: 80 -``` + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + envFrom: + - secretRef: + name: managed-secret + ports: + - containerPort: 80 + ``` #### How it works @@ -1069,39 +1022,39 @@ Here, we will highlight three of the most common ways to utilize it. Learn more This will take all the secrets from your managed ConfigMap and expose them to your container -````yaml - envFrom: - - configMapRef: - name: managed-configmap # managed configmap name - ``` + ````yaml + envFrom: + - configMapRef: + name: managed-configmap # managed configmap name + ``` - Example usage in a deployment - ```yaml - apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 1 - selector: - matchLabels: + Example usage in a deployment + ```yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + labels: app: nginx - template: - metadata: - labels: + spec: + replicas: 1 + selector: + matchLabels: app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - envFrom: - - configMapRef: - name: managed-configmap # <- name of managed configmap - ports: - - containerPort: 80 -```` + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + envFrom: + - configMapRef: + name: managed-configmap # <- name of managed configmap + ports: + - containerPort: 80 + ```` @@ -1117,92 +1070,91 @@ spec: key: SOME_CONFIG_KEY # The name of the key which exists in the managed configmap ``` -Example usage in a deployment + Example usage in a deployment -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 1 - selector: - matchLabels: + ```yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + labels: app: nginx - template: - metadata: - labels: + spec: + replicas: 1 + selector: + matchLabels: app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - env: - - name: STRIPE_API_SECRET - valueFrom: - configMapKeyRef: - name: managed-configmap # <- name of managed configmap - key: STRIPE_API_SECRET - ports: - - containerPort: 80 -``` + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + env: + - name: STRIPE_API_SECRET + valueFrom: + configMapKeyRef: + name: managed-configmap # <- name of managed configmap + key: STRIPE_API_SECRET + ports: + - containerPort: 80 + ``` -This will allow you to create a volume on your container which comprises of files holding the secrets in your managed kubernetes secret -```yaml -volumes: - - name: configmaps-volume-name # The name of the volume under which configmaps will be stored - configMap: - name: managed-configmap # managed configmap name -```` + This will allow you to create a volume on your container which comprises of files holding the secrets in your managed kubernetes secret + ```yaml + volumes: + - name: configmaps-volume-name # The name of the volume under which configmaps will be stored + configMap: + name: managed-configmap # managed configmap name + ```` -You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets + You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets -```yaml -volumeMounts: - - name: configmaps-volume-name - mountPath: /etc/config - readOnly: true -``` + ```yaml + volumeMounts: + - name: configmaps-volume-name + mountPath: /etc/config + readOnly: true + ``` -Example usage in a deployment + Example usage in a deployment -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 1 - selector: - matchLabels: + ```yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment + labels: app: nginx - template: - metadata: - labels: + spec: + replicas: 1 + selector: + matchLabels: app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - volumeMounts: - - name: configmaps-volume-name - mountPath: /etc/config - readOnly: true - ports: - - containerPort: 80 - volumes: - - name: configmaps-volume-name - configMap: - name: managed-configmap # <- managed configmap -``` - + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + volumeMounts: + - name: configmaps-volume-name + mountPath: /etc/config + readOnly: true + ports: + - containerPort: 80 + volumes: + - name: configmaps-volume-name + configMap: + name: managed-configmap # <- managed configmap + ``` The definition file of the Kubernetes secret for the CA certificate can be structured like the following: @@ -1228,37 +1180,37 @@ The operator will transfer all labels & annotations present on the `InfisicalSec Thus, if a specific label is required on the resulting secret, it can be applied as demonstrated in the following example: -```yaml -apiVersion: secrets.infisical.com/v1alpha1 -kind: InfisicalSecret -metadata: - name: infisicalsecret-sample - labels: - label-to-be-passed-to-managed-secret: sample-value - annotations: - example.com/annotation-to-be-passed-to-managed-secret: "sample-value" -spec: - .. - authentication: - ... - managedKubeSecretReferences: - ... -``` + ```yaml + apiVersion: secrets.infisical.com/v1alpha1 + kind: InfisicalSecret + metadata: + name: infisicalsecret-sample + labels: + label-to-be-passed-to-managed-secret: sample-value + annotations: + example.com/annotation-to-be-passed-to-managed-secret: "sample-value" + spec: + .. + authentication: + ... + managedKubeSecretReferences: + ... + ``` -This would result in the following managed secret to be created: + This would result in the following managed secret to be created: -```yaml -apiVersion: v1 -data: ... -kind: Secret -metadata: - annotations: - example.com/annotation-to-be-passed-to-managed-secret: sample-value - secrets.infisical.com/version: W/"3f1-ZyOSsrCLGSkAhhCkY2USPu2ivRw" - labels: - label-to-be-passed-to-managed-secret: sample-value - name: managed-token - namespace: default -type: Opaque -``` + ```yaml + apiVersion: v1 + data: ... + kind: Secret + metadata: + annotations: + example.com/annotation-to-be-passed-to-managed-secret: sample-value + secrets.infisical.com/version: W/"3f1-ZyOSsrCLGSkAhhCkY2USPu2ivRw" + labels: + label-to-be-passed-to-managed-secret: sample-value + name: managed-token + namespace: default + type: Opaque + ``` diff --git a/docs/integrations/platforms/kubernetes/overview.mdx b/docs/integrations/platforms/kubernetes/overview.mdx index c4e3f7c642..ca700d7775 100644 --- a/docs/integrations/platforms/kubernetes/overview.mdx +++ b/docs/integrations/platforms/kubernetes/overview.mdx @@ -114,6 +114,48 @@ spec: ``` +## Advanced Templating + +With the Infisical Secrets Operator, you can use templating to dynamically generate secrets in Kubernetes. The templating is built on top of [Go templates](https://pkg.go.dev/text/template), which is a powerful and flexible template engine built into Go. + +Please be aware that trying to reference non-existing keys will result in an error. Additionally, each template field is processed individually, which means one template field cannot reference another template field. + + + Please note that templating is currently only supported for the `InfisicalPushSecret` and `InfisicalSecret` CRDs. + + +### Available helper functions + +The Infisical Secrets Operator exposes a wide range of helper functions to make it easier to work with secrets in Kubernetes. + +| Function | Description | Signature | +| -------- | ----------- | --------- | +| `decodeBase64ToBytes` | Given a base64 encoded string, this function will decode the base64-encoded string. | `decodeBase64ToBytes(encodedString string) string` | +| `encodeBase64` | Given a string, this function will encode the string to a base64 encoded string. | `encodeBase64(plainString string) string` | +| `pkcs12key`| Extracts all private keys from a PKCS#12 archive and encodes them in PKCS#8 PEM format. | `pkcs12key(input string) string` | +| `pkcs12keyPass`|Same as pkcs12key. Uses the provided password to decrypt the PKCS#12 archive. | `pkcs12keyPass(pass string, input string) string` | +| `pkcs12cert` | Extracts all certificates from a PKCS#12 archive and orders them if possible. If disjunct or multiple leaf certs are provided they are returned as-is. Sort order: `leaf / intermediate(s) / root`. | `pkcs12cert(input string) string` | +| `pkcs12certPass` | Same as `pkcs12cert`. Uses the provided password to decrypt the PKCS#12 archive. | `pkcs12certPass(pass string, input string) string` | +| `pemToPkcs12` | Takes a PEM encoded certificate and key and creates a base64 encoded PKCS#12 archive. | `pemToPkcs12(cert string, key string) string` | +| `pemToPkcs12Pass` | Same as `pemToPkcs12`. Uses the provided password to encrypt the PKCS#12 archive. | `pemToPkcs12Pass(cert string, key string, pass string) string` | +| `fullPemToPkcs12` | Takes a PEM encoded certificates chain and key and creates a base64 encoded PKCS#12 archive. | `fullPemToPkcs12(cert string, key string) string` | +| `fullPemToPkcs12Pass` | Same as `fullPemToPkcs12`. Uses the provided password to encrypt the PKCS#12 archive. | `fullPemToPkcs12Pass(cert string, key string, pass string) string` | +| `filterPEM` | Filters PEM blocks with a specific type from a list of PEM blocks.. | `filterPEM(pemType string, input string) string` | +| `filterCertChain` | Filters PEM block(s) with a specific certificate type (`leaf`, `intermediate` or `root`) from a certificate chain of PEM blocks (PEM blocks with type `CERTIFICATE`). | `filterCertChain(certType string, input string) string` | +| `jwkPublicKeyPem` | Takes an json-serialized JWK and returns an PEM block of type `PUBLIC KEY` that contains the public key. [See here](https://golang.org/pkg/crypto/x509/#MarshalPKIXPublicKey) for details. | `jwkPublicKeyPem(jwkjson string) string` | +| `jwkPrivateKeyPem` | Takes an json-serialized JWK and returns an PEM block of type `PRIVATE KEY` that contains the private key. [See here](https://pkg.go.dev/crypto/x509#MarshalPKCS8PrivateKey) for details. | `jwkPrivateKeyPem(jwkjson string) string` | +| `toYaml` | Takes an interface, marshals it to yaml. It returns a string, even on marshal error (empty string). | `toYaml(v any) string` | +| `fromYaml` | Function converts a YAML document into a `map[string]any`. | `fromYaml(str string) map[string]any` | + +### Sprig functions + +The Infisical Secrets Operator integrates with the [Sprig library](https://github.com/Masterminds/sprig) to provide additional helper functions. + + + We've removed `expandEnv` and `env` from the supported functions for security reasons. + + + ## Global configuration To configure global settings that will apply to all instances of `InfisicalSecret`, you can define these configurations in a Kubernetes ConfigMap. diff --git a/k8-operator/api/v1alpha1/common.go b/k8-operator/api/v1alpha1/common.go index 5631984d10..2489b0b95d 100644 --- a/k8-operator/api/v1alpha1/common.go +++ b/k8-operator/api/v1alpha1/common.go @@ -105,7 +105,7 @@ type ManagedKubeSecretConfig struct { // The template to transform the secret data // +kubebuilder:validation:Optional - Template *InfisicalSecretTemplate `json:"template,omitempty"` + Template *SecretTemplate `json:"template,omitempty"` } type ManagedKubeConfigMapConfig struct { @@ -127,5 +127,15 @@ type ManagedKubeConfigMapConfig struct { // The template to transform the secret data // +kubebuilder:validation:Optional - Template *InfisicalSecretTemplate `json:"template,omitempty"` + Template *SecretTemplate `json:"template,omitempty"` +} + +type SecretTemplate struct { + // This injects all retrieved secrets into the top level of your template. + // Secrets defined in the template will take precedence over the injected ones. + // +kubebuilder:validation:Optional + IncludeAllSecrets bool `json:"includeAllSecrets"` + // The template key values + // +kubebuilder:validation:Optional + Data map[string]string `json:"data,omitempty"` } diff --git a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go index 9a1040868c..5a74388816 100644 --- a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go @@ -16,9 +16,22 @@ type InfisicalPushSecretDestination struct { ProjectID string `json:"projectId"` } +type InfisicalPushSecretSecretSource struct { + // The name of the Kubernetes Secret + // +kubebuilder:validation:Required + SecretName string `json:"secretName"` + + // The name space where the Kubernetes Secret is located + // +kubebuilder:validation:Required + SecretNamespace string `json:"secretNamespace"` + + // +kubebuilder:validation:Optional + Template *SecretTemplate `json:"template,omitempty"` +} + type SecretPush struct { // +kubebuilder:validation:Required - Secret KubeSecretReference `json:"secret"` + Secret InfisicalPushSecretSecretSource `json:"secret"` } // InfisicalPushSecretSpec defines the desired state of InfisicalPushSecret diff --git a/k8-operator/api/v1alpha1/infisicalsecret_types.go b/k8-operator/api/v1alpha1/infisicalsecret_types.go index 8f871dfa6f..e90c06938d 100644 --- a/k8-operator/api/v1alpha1/infisicalsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalsecret_types.go @@ -116,16 +116,6 @@ type MachineIdentityScopeInWorkspace struct { Recursive bool `json:"recursive"` } -type InfisicalSecretTemplate struct { - // This injects all retrieved secrets into the top level of your template. - // Secrets defined in the template will take precedence over the injected ones. - // +kubebuilder:validation:Optional - IncludeAllSecrets bool `json:"includeAllSecrets"` - // The template key values - // +kubebuilder:validation:Optional - Data map[string]string `json:"data,omitempty"` -} - // InfisicalSecretSpec defines the desired state of InfisicalSecret type InfisicalSecretSpec struct { // +kubebuilder:validation:Optional diff --git a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go index 11d864ad4f..2ad97108d4 100644 --- a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -383,7 +383,7 @@ func (in *InfisicalPushSecret) DeepCopyInto(out *InfisicalPushSecret) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -452,12 +452,32 @@ func (in *InfisicalPushSecretList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InfisicalPushSecretSecretSource) DeepCopyInto(out *InfisicalPushSecretSecretSource) { + *out = *in + if in.Template != nil { + in, out := &in.Template, &out.Template + *out = new(SecretTemplate) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalPushSecretSecretSource. +func (in *InfisicalPushSecretSecretSource) DeepCopy() *InfisicalPushSecretSecretSource { + if in == nil { + return nil + } + out := new(InfisicalPushSecretSecretSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InfisicalPushSecretSpec) DeepCopyInto(out *InfisicalPushSecretSpec) { *out = *in out.Destination = in.Destination out.Authentication = in.Authentication - out.Push = in.Push + in.Push.DeepCopyInto(&out.Push) out.TLS = in.TLS } @@ -614,28 +634,6 @@ func (in *InfisicalSecretStatus) DeepCopy() *InfisicalSecretStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *InfisicalSecretTemplate) DeepCopyInto(out *InfisicalSecretTemplate) { - *out = *in - if in.Data != nil { - in, out := &in.Data, &out.Data - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalSecretTemplate. -func (in *InfisicalSecretTemplate) DeepCopy() *InfisicalSecretTemplate { - if in == nil { - return nil - } - out := new(InfisicalSecretTemplate) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KubeSecretReference) DeepCopyInto(out *KubeSecretReference) { *out = *in @@ -703,7 +701,7 @@ func (in *ManagedKubeConfigMapConfig) DeepCopyInto(out *ManagedKubeConfigMapConf *out = *in if in.Template != nil { in, out := &in.Template, &out.Template - *out = new(InfisicalSecretTemplate) + *out = new(SecretTemplate) (*in).DeepCopyInto(*out) } } @@ -723,7 +721,7 @@ func (in *ManagedKubeSecretConfig) DeepCopyInto(out *ManagedKubeSecretConfig) { *out = *in if in.Template != nil { in, out := &in.Template, &out.Template - *out = new(InfisicalSecretTemplate) + *out = new(SecretTemplate) (*in).DeepCopyInto(*out) } } @@ -741,7 +739,7 @@ func (in *ManagedKubeSecretConfig) DeepCopy() *ManagedKubeSecretConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretPush) DeepCopyInto(out *SecretPush) { *out = *in - out.Secret = in.Secret + in.Secret.DeepCopyInto(&out.Secret) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretPush. @@ -769,6 +767,28 @@ func (in *SecretScopeInWorkspace) DeepCopy() *SecretScopeInWorkspace { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretTemplate) DeepCopyInto(out *SecretTemplate) { + *out = *in + if in.Data != nil { + in, out := &in.Data, &out.Data + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretTemplate. +func (in *SecretTemplate) DeepCopy() *SecretTemplate { + if in == nil { + return nil + } + out := new(SecretTemplate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceAccountDetails) DeepCopyInto(out *ServiceAccountDetails) { *out = *in diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml index a12ec9dbe7..25fafd98a5 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml @@ -137,6 +137,19 @@ spec: description: The name space where the Kubernetes Secret is located type: string + template: + properties: + data: + additionalProperties: + type: string + description: The template key values + type: object + includeAllSecrets: + description: This injects all retrieved secrets into the + top level of your template. Secrets defined in the template + will take precedence over the injected ones. + type: boolean + type: object required: - secretName - secretNamespace diff --git a/k8-operator/config/samples/crd/infisicalsecret/infisical-secret-crd-with-template.yml b/k8-operator/config/samples/crd/infisicalsecret/infisical-secret-crd-with-template.yml index c1e0c86b9b..06086e93f8 100644 --- a/k8-operator/config/samples/crd/infisicalsecret/infisical-secret-crd-with-template.yml +++ b/k8-operator/config/samples/crd/infisicalsecret/infisical-secret-crd-with-template.yml @@ -41,6 +41,9 @@ spec: # Native Kubernetes Auth kubernetesAuth: + serviceAccountRef: + name: + namespace: identityId: serviceAccountTokenPath: "/path/to/your/service-account/token" # Optional, defaults to /var/run/secrets/kubernetes.io/serviceaccount/token @@ -97,18 +100,13 @@ spec: secretsPath: "/path" recursive: true - managedSecretReference: - secretName: managed-secret - secretNamespace: default - template: - includeAllSecrets: true - data: - SSH_KEY: "{{ .KEY.SecretPath }} {{ .KEY.Value }}" - BINARY_KEY: "{{ decodeBase64ToBytes .BINARY_KEY_BASE64.Value }}" - creationPolicy: "Orphan" ## Owner | Orphan - # secretType: kubernetes.io/dockerconfigjson - - # # To be depreciated soon - # tokenSecretReference: - # secretName: service-token - # secretNamespace: default + managedKubeSecretReferences: + - secretName: managed-secret + secretNamespace: default + creationPolicy: "Orphan" ## Owner | Orphan + # secretType: kubernetes.io/dockerconfigjson + template: + includeAllSecrets: true + data: + SSH_KEY: "{{ .KEY.SecretPath }} {{ .KEY.Value }}" + BINARY_KEY: "{{ decodeBase64ToBytes .BINARY_KEY_BASE64.Value }}" diff --git a/k8-operator/config/samples/crd/infisicalsecret/infisicalSecretCrd.yaml b/k8-operator/config/samples/crd/infisicalsecret/infisicalSecretCrd.yaml index 108b86e783..82bb8039c7 100644 --- a/k8-operator/config/samples/crd/infisicalsecret/infisicalSecretCrd.yaml +++ b/k8-operator/config/samples/crd/infisicalsecret/infisicalSecretCrd.yaml @@ -41,6 +41,9 @@ spec: # Native Kubernetes Auth kubernetesAuth: + serviceAccountRef: + name: + namespace: identityId: serviceAccountTokenPath: "/path/to/your/service-account/token" # Optional, defaults to /var/run/secrets/kubernetes.io/serviceaccount/token @@ -97,7 +100,7 @@ spec: secretsPath: "/path" recursive: true - managedSecretReferences: + managedKubeSecretReferences: - secretName: managed-secret secretNamespace: default creationPolicy: "Orphan" ## Owner | Orphan diff --git a/k8-operator/config/samples/crd/pushsecret/push-secret-with-template.yaml b/k8-operator/config/samples/crd/pushsecret/push-secret-with-template.yaml new file mode 100644 index 0000000000..a81c2189da --- /dev/null +++ b/k8-operator/config/samples/crd/pushsecret/push-secret-with-template.yaml @@ -0,0 +1,91 @@ +apiVersion: secrets.infisical.com/v1alpha1 +kind: InfisicalPushSecret +metadata: + name: infisical-api-secret-sample-push +spec: + resyncInterval: 1m + hostAPI: https://app.infisical.com/api # This is the default hostAPI for the Infisical API + + # Optional, defaults to replacement. + updatePolicy: Replace # If set to replace, existing secrets inside Infisical will be replaced by the value of the PushSecret on sync. + + # Optional, defaults to no deletion. + deletionPolicy: Delete # If set to delete, the secret(s) inside Infisical managed by the operator, will be deleted if the InfisicalPushSecret CRD is deleted. + + destination: + projectId: + environmentSlug: + secretsPath: + + push: + secret: + secretName: push-secret-demo-with-templating + secretNamespace: default + template: + includeAllSecrets: false + data: + PKCS12_CERT_NO_PASSWORD: "{{ .PKCS12_CONTENT_NO_PASSWORD.Value | decodeBase64ToBytes | pkcs12cert }}" + PKCS12_KEY_NO_PASSWORD: "{{ .PKCS12_CONTENT_NO_PASSWORD.Value | decodeBase64ToBytes | pkcs12key }}" + + PKCS12_CERT_WITH_PASSWORD: '{{ .PKCS12_CONTENT_WITH_PASSWORD.Value | decodeBase64ToBytes | pkcs12certPass "123456" }}' + PKCS12_KEY_WITH_PASSWORD: '{{ .PKCS12_CONTENT_WITH_PASSWORD.Value | decodeBase64ToBytes | pkcs12keyPass "123456" }}' + + PEM_TO_PKCS12_PASS: '{{ pemToPkcs12Pass + (.PKCS12_CONTENT_WITH_PASSWORD.Value | decodeBase64ToBytes | pkcs12certPass "123456") + (.PKCS12_CONTENT_WITH_PASSWORD.Value | decodeBase64ToBytes | pkcs12keyPass "123456") + "123456" }}' + PEM_TO_PKCS12_NO_PASSWORD: "{{ pemToPkcs12 + (.PKCS12_CONTENT_NO_PASSWORD.Value | decodeBase64ToBytes | pkcs12cert) + (.PKCS12_CONTENT_NO_PASSWORD.Value | decodeBase64ToBytes | pkcs12key) + }}" + + FULL_PEM_TO_PKCS12_PASS: '{{ fullPemToPkcs12Pass + (.PKCS12_CONTENT_WITH_PASSWORD.Value | decodeBase64ToBytes | pkcs12certPass "123456") + (.PKCS12_CONTENT_WITH_PASSWORD.Value | decodeBase64ToBytes | pkcs12keyPass "123456") + "123456" }}' + FULL_PEM_TO_PKCS12_NO_PASSWORD: "{{ fullPemToPkcs12 + (.PKCS12_CONTENT_NO_PASSWORD.Value | decodeBase64ToBytes | pkcs12cert) + (.PKCS12_CONTENT_NO_PASSWORD.Value | decodeBase64ToBytes | pkcs12key) + }}" + + FILTERED_PEM_CERT: '{{ filterPEM "CERTIFICATE" (printf "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" .JWK_PRIVATE_RSA_PKCS8.Value .PKCS12_CONTENT_NO_PASSWORD.Value) }}' + FILTERED_PEM_KEY: '{{ filterPEM "PRIVATE KEY" (printf "-----BEGIN PRIVATE KEY-----\n%s\n-----END PRIVATE KEY-----\n-----BEGIN PRIVATE KEY-----\n%s\n-----END PRIVATE KEY-----" .JWK_PRIVATE_RSA_PKCS8.Value .PKCS12_CONTENT_NO_PASSWORD.Value) }}' + + # Will be empty with our current test data as there is no chain + CERT_CHAIN: '{{ filterCertChain "CERTIFICATE" (.PKCS12_CONTENT_NO_PASSWORD.Value | decodeBase64ToBytes | pkcs12cert) }}' + + JWK_RSA_PUBLIC_PEM: "{{ jwkPublicKeyPem .JWK_PUB_RSA.Value }}" + JWK_ECDSA_PUBLIC_PEM: "{{ jwkPublicKeyPem .JWK_PUB_ECDSA.Value }}" + + JWK_ECDSA_PRIVATE_PEM: "{{ jwkPrivateKeyPem .JWK_PRIV_ECDSA.Value }}" + JWK_RSA_PRIVATE_PEM: "{{ jwkPrivateKeyPem .JWK_PRIV_RSA.Value }}" + + JSON_STR_TO_YAML: "{{ .TEST_JSON_DATA.Value | fromJsonStringToJson | toYaml }}" + + FROM_YAML_TO_JSON: "{{ .TEST_YAML_STRING.Value | fromYaml | toJson }}" + YAML_ROUNDTRIP: "{{ .TEST_YAML_STRING.Value | fromYaml | toYaml }}" + + TEST_LOWERCASE_STRING: "{{ .TEST_LOWERCASE_STRING.Value | upper }}" + + # Only have one authentication method defined or you are likely to run into authentication issues. + # Remove all except one authentication method. + authentication: + awsIamAuth: + identityId: + azureAuth: + identityId: + gcpIamAuth: + identityId: + serviceAccountKeyFilePath: + gcpIdTokenAuth: + identityId: + kubernetesAuth: + identityId: + serviceAccountRef: + name: + namespace: + universalAuth: + credentialsRef: + secretName: # universal-auth-credentials + secretNamespace: # default + diff --git a/k8-operator/config/samples/crd/pushsecret/pushSecret.yaml b/k8-operator/config/samples/crd/pushsecret/push-secret.yaml similarity index 96% rename from k8-operator/config/samples/crd/pushsecret/pushSecret.yaml rename to k8-operator/config/samples/crd/pushsecret/push-secret.yaml index f1738fc474..b6a132987e 100644 --- a/k8-operator/config/samples/crd/pushsecret/pushSecret.yaml +++ b/k8-operator/config/samples/crd/pushsecret/push-secret.yaml @@ -19,7 +19,7 @@ spec: push: secret: - secretName: push-secret-demo # Secret CRD + secretName: push-secret-source-secret # Secret CRD secretNamespace: default # Only have one authentication method defined or you are likely to run into authentication issues. diff --git a/k8-operator/config/samples/crd/pushsecret/source-secret-with-templating.yaml b/k8-operator/config/samples/crd/pushsecret/source-secret-with-templating.yaml new file mode 100644 index 0000000000..79a12a4d4d --- /dev/null +++ b/k8-operator/config/samples/crd/pushsecret/source-secret-with-templating.yaml @@ -0,0 +1,91 @@ +# This is the source secret that you can use to demo the advanced templating functionality seen in `push-secret-with-template.yaml` + +apiVersion: v1 +kind: Secret +metadata: + name: push-secret-demo-with-templating + namespace: default +stringData: + PKCS12_CONTENT_NO_PASSWORD: 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 + PKCS12_CONTENT_WITH_PASSWORD: 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 + JWK_PRIVATE_RSA_PKCS8: "-----BEGIN PRIVATE KEY----- + MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQCmN2yzxloN8Qfo + rpTsZ5bafEOpHgg/Tj1+TV8rSWd2KZswxUF0+/+FKmbxPwS0EPGtR2LU4dl8yFSL + EZq637edDgYb2czbj2jGEK3Gqo28ReuZBEapzPIvG6H58qf0WD76FL1SlrMel9UA + WcHloJ9eg2E+4jygHLIUowpo5WAc2o/k0ESppuIt+1kPdb+WwUI8a7OvhWnRhLvN + LaENhJwLag4y7isZTUtwxl/f2nfXncKrttLZeHpj6/DmnDMVhl2NDEOfzHwEbd8n + qPxMYtdCxsofXbXz8dxQlG8zB2ltRAbme8DYZdWoup3CnTngvOT38H9/WVWuY4q4 + eNM0erjzAgMBAAECggEBAJLA5rnHTCV5BRmcYqJjR566DmcXvAJgywxjtb4bPjzm + uT2TO5rVD6J8cI1ZrYZqW2c5WvpIOeThXzu2HF4YPh5tjlkysJu9/6y4dyWr2h47 + warFSrqK191d0WJEq6Oh8mCMxSdRJO7C8W4w0XAzo+Inr0l9KDfZfiWYWg2JT5XI + ubibKKq6P2KxND0UVlYbRsp3fv2loEL9WM5H2bjA/oSbQ4tSJtobpjlsQOHmaxbP + XhvsIV3Dr2ksDuLEhm0vfXnEGRzNk3HV3gLNT741YEP3Sp2ZRjd5U1qFn0D+eWe0 + 4LfDX9auGQCnfjZTHvu4qghX7JxcF40omjmtgkRmZ/kCgYEA4A5nU4ahEww7B65y + uzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ++wwf + pRwHvSxtNU9qXb8ewo+BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3In + KF4JvIlchyqs0RQ8wx7lULqwnn0CgYEAven83GM6SfrmO+TBHbjTk6JhP/3CMsIv + mSdo4KrbQNvp4vHO3w1/0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEB + pxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA+k4UoH/eQmGKGK44TRz + Yj5hZYGWIC8CgYEAlmmU/AG5SGxBhJqb8wxfNXDPJjf//i92BgJT2Vp4pskBbr5P + GoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ+m0/XSWx13v9t9DIbheA + tgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpECgYEA + mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe//EjuCBbwHfcT8OG3hWOv8vpzo + kQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p+AF2p6Yfahscjtq+GY9cB85Nx + Ly2IXCC0PF++Sq9LOrTE9QV988SJy/yUrAjcZ5MmECkCgYEAldHXIrEmMZVaNwGz + DF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uY + iqewXfCKw/UngrJt8Xwfq1Zruz0YY869zPN4GiE9+9rzdZB33RBw8kIOquY3MK74 + FMwCihYx/LiU2YTHkaoJ3ncvtvg= + -----END PRIVATE KEY-----" + JWK_PUB_RSA: '{"kid":"ex","kty":"RSA","key_ops":["sign","verify","wrapKey","unwrapKey","encrypt","decrypt"],"n":"p2VQo8qCfWAZmdWBVaYuYb-a-tWWm78K6Sr9poCvNcmv8rUPSLACxitQWR8gZaSH1DklVkqz-Ed8Cdlf8lkDg4Ex5tkB64jRdC1Uvn4CDpOH6cp-N2s8hTFLqy9_YaDmyQS7HiqthOi9oVjil1VMeWfaAbClGtFt6UnKD0Vb_DvLoWYQSqlhgBArFJi966b4E1pOq5Ad02K8pHBDThlIIx7unibLehhDU6q3DCwNH_OOLx6bgNtmvGYJDd1cywpkLQ3YzNCUPWnfMBJRP3iQP_WI21uP6cvo0DqBPBM4wvVzHbCT0vnIflwkbgEWkq1FprqAitZlop9KjLqzjp9vyQ","e":"AQAB"}' + JWK_PUB_ECDSA: '{"kid":"https://kv-test-mj.vault.azure.net/keys/ec-p-521/e3d0e9c179b54988860c69c6ae172c65","kty":"EC","key_ops":["sign","verify"],"crv":"P-521","x":"AedOAtb7H7Oz1C_cPKI_R4CN_eai5nteY6KFW07FOoaqgQfVCSkQDK22fCOiMT_28c8LZYJRsiIFz_IIbQUW7bXj","y":"AOnchHnmBphIWXvanmMAmcCDkaED6ycW8GsAl9fQ43BMVZTqcTkJYn6vGnhn7MObizmkNSmgZYTwG-vZkIg03HHs"}' + + JWK_PRIV_RSA: '{"kty" : "RSA","kid" : "cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df","use" : "sig","n" : "pjdss8ZaDfEH6K6U7GeW2nxDqR4IP049fk1fK0lndimbMMVBdPv_hSpm8T8EtBDxrUdi1OHZfMhUixGaut-3nQ4GG9nM249oxhCtxqqNvEXrmQRGqczyLxuh-fKn9Fg--hS9UpazHpfVAFnB5aCfXoNhPuI8oByyFKMKaOVgHNqP5NBEqabiLftZD3W_lsFCPGuzr4Vp0YS7zS2hDYScC2oOMu4rGU1LcMZf39p3153Cq7bS2Xh6Y-vw5pwzFYZdjQxDn8x8BG3fJ6j8TGLXQsbKH1218_HcUJRvMwdpbUQG5nvA2GXVqLqdwp054Lzk9_B_f1lVrmOKuHjTNHq48w","e" : "AQAB","d" : "ksDmucdMJXkFGZxiomNHnroOZxe8AmDLDGO1vhs-POa5PZM7mtUPonxwjVmthmpbZzla-kg55OFfO7YcXhg-Hm2OWTKwm73_rLh3JavaHjvBqsVKuorX3V3RYkSro6HyYIzFJ1Ek7sLxbjDRcDOj4ievSX0oN9l-JZhaDYlPlci5uJsoqro_YrE0PRRWVhtGynd-_aWgQv1YzkfZuMD-hJtDi1Im2humOWxA4eZrFs9eG-whXcOvaSwO4sSGbS99ecQZHM2TcdXeAs1PvjVgQ_dKnZlGN3lTWoWfQP55Z7Tgt8Nf1q4ZAKd-NlMe-7iqCFfsnFwXjSiaOa2CRGZn-Q","p" : "4A5nU4ahEww7B65yuzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ--wwfpRwHvSxtNU9qXb8ewo-BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3InKF4JvIlchyqs0RQ8wx7lULqwnn0","q" : "ven83GM6SfrmO-TBHbjTk6JhP_3CMsIvmSdo4KrbQNvp4vHO3w1_0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEBpxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA-k4UoH_eQmGKGK44TRzYj5hZYGWIC8","dp" : "lmmU_AG5SGxBhJqb8wxfNXDPJjf__i92BgJT2Vp4pskBbr5PGoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ-m0_XSWx13v9t9DIbheAtgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpE","dq" : "mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe__EjuCBbwHfcT8OG3hWOv8vpzokQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p-AF2p6Yfahscjtq-GY9cB85NxLy2IXCC0PF--Sq9LOrTE9QV988SJy_yUrAjcZ5MmECk","qi" : "ldHXIrEmMZVaNwGzDF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uYiqewXfCKw_UngrJt8Xwfq1Zruz0YY869zPN4GiE9-9rzdZB33RBw8kIOquY3MK74FMwCihYx_LiU2YTHkaoJ3ncvtvg"}' + JWK_PRIV_ECDSA: '{"kty": "EC","kid": "rie3pHe8u8gjSa0IaJfqk7_iEfHeYfDYx-Bqi7vQc0s","crv": "P-256","x": "fDjg3Nq4jPf8IOZ0277aPVal_8iXySnzLUJAZghUzZM","y": "d863PeyBOK_Q4duiSmWwgIRzi1RPlFZTR-vACMlPg-Q","d": "jJs5xsoHUetdMabtt8H2KyX5T92nGul1chFeMT5hlr0"}' + + TEST_LOWERCASE_STRING: i am a lowercase string + + TEST_YAML_STRING: | + name: test-object + type: example + properties: + key1: value1 + key2: value2 + numbers: + - 1 + - 2 + - 3 + + TEST_JSON_DATA: '{ + "id": "f1bc87fc-99cf-48d2-b289-7cf578152f9a", + "name": "test", + "description": "", + "type": "secret-manager", + "slug": "test-o-bto", + "autoCapitalization": false, + "orgId": "601815be-6884-4ee4-86c7-bfc6415f2123", + "createdAt": "2025-03-26T01:32:39.890Z", + "updatedAt": "2025-03-26T01:32:41.688Z", + "version": 3, + "upgradeStatus": null, + "pitVersionLimit": 10, + "kmsCertificateKeyId": null, + "auditLogsRetentionDays": null, + "_id": "f1bc87fc-99cf-48d2-b289-7cf578152f9a", + "environments": [ + { + "name": "Development", + "slug": "dev", + "id": "cbb62f88-44cb-4c29-975a-871f8d7d303b" + }, + { + "name": "Staging", + "slug": "staging", + "id": "c933a63d-418a-4d5c-a7d1-91b74d3ee2eb" + }, + { + "name": "Production", + "slug": "prod", + "id": "0b70125e-47d5-46e8-a03e-a3105df05d37" + } + ] + }' diff --git a/k8-operator/config/samples/crd/pushsecret/source-secret.yaml b/k8-operator/config/samples/crd/pushsecret/source-secret.yaml new file mode 100644 index 0000000000..88fd3e0d26 --- /dev/null +++ b/k8-operator/config/samples/crd/pushsecret/source-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: push-secret-source-secret + namespace: default +stringData: + ENCRYPTION_KEY: secret-encryption-key + API_URL: https://example.com/api + REGION: us-east-1 diff --git a/k8-operator/config/samples/crd/pushsecret/sourceSecret.yaml b/k8-operator/config/samples/crd/pushsecret/sourceSecret.yaml deleted file mode 100644 index 6a3703a956..0000000000 --- a/k8-operator/config/samples/crd/pushsecret/sourceSecret.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: push-secret-demo - namespace: default -stringData: # can also be "data", but needs to be base64 encoded - API_KEY: some-api-key - DATABASE_URL: postgres://127.0.0.1:5432 - ENCRYPTION_KEY: fabcc12-a22-facbaa4-11aa568aab diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go index b87c5bcc26..a5526bd6cf 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go @@ -65,6 +65,8 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. if err != nil { if errors.IsNotFound(err) { logger.Info("Infisical Push Secret CRD not found") + r.DeleteManagedSecrets(ctx, logger, infisicalPushSecretCRD) + return ctrl.Result{ Requeue: false, }, nil diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go index 47fc8e69b3..848df3c829 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go @@ -1,16 +1,21 @@ package controllers import ( + "bytes" "context" "errors" "fmt" "strings" + tpl "text/template" "github.com/Infisical/infisical/k8-operator/api/v1alpha1" "github.com/Infisical/infisical/k8-operator/packages/api" "github.com/Infisical/infisical/k8-operator/packages/constants" + "github.com/Infisical/infisical/k8-operator/packages/model" + "github.com/Infisical/infisical/k8-operator/packages/template" "github.com/Infisical/infisical/k8-operator/packages/util" "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -101,6 +106,48 @@ func (r *InfisicalPushSecretReconciler) updateResourceVariables(infisicalPushSec infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)] = resourceVariables } +func (r *InfisicalPushSecretReconciler) processTemplatedSecrets(infisicalPushSecret v1alpha1.InfisicalPushSecret, kubePushSecret *corev1.Secret, destination v1alpha1.InfisicalPushSecretDestination) (map[string]string, error) { + + processedSecrets := make(map[string]string) + + sourceSecrets := make(map[string]model.SecretTemplateOptions) + for key, value := range kubePushSecret.Data { + + sourceSecrets[key] = model.SecretTemplateOptions{ + Value: string(value), + SecretPath: destination.SecretsPath, + } + } + + if infisicalPushSecret.Spec.Push.Secret.Template == nil || (infisicalPushSecret.Spec.Push.Secret.Template != nil && infisicalPushSecret.Spec.Push.Secret.Template.IncludeAllSecrets) { + for key, value := range kubePushSecret.Data { + processedSecrets[key] = string(value) + } + } + + if infisicalPushSecret.Spec.Push.Secret.Template != nil && + len(infisicalPushSecret.Spec.Push.Secret.Template.Data) > 0 { + + for templateKey, userTemplate := range infisicalPushSecret.Spec.Push.Secret.Template.Data { + + tmpl, err := tpl.New("push-secret-templates").Funcs(template.GetTemplateFunctions()).Parse(userTemplate) + if err != nil { + return nil, fmt.Errorf("unable to compile template: %s [err=%v]", templateKey, err) + } + + buf := bytes.NewBuffer(nil) + err = tmpl.Execute(buf, sourceSecrets) + if err != nil { + return nil, fmt.Errorf("unable to execute template: %s [err=%v]", templateKey, err) + } + + processedSecrets[templateKey] = buf.String() + } + } + + return processedSecrets, nil +} + func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context.Context, logger logr.Logger, infisicalPushSecret v1alpha1.InfisicalPushSecret) error { resourceVariables := r.getResourceVariables(infisicalPushSecret) @@ -134,10 +181,9 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context return fmt.Errorf("unable to fetch kube secret [err=%s]", err) } - var kubeSecrets = make(map[string]string) - - for key, value := range kubePushSecret.Data { - kubeSecrets[key] = string(value) + processedSecrets, err := r.processTemplatedSecrets(infisicalPushSecret, kubePushSecret, infisicalPushSecret.Spec.Destination) + if err != nil { + return fmt.Errorf("unable to process templated secrets [err=%s]", err) } destination := infisicalPushSecret.Spec.Destination @@ -191,7 +237,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context infisicalPushSecret.Status.ManagedSecrets = make(map[string]string) // (string[id], string[key] ) - for secretKey, secretValue := range kubeSecrets { + for secretKey, secretValue := range processedSecrets { if exists := getExistingSecretByKey(secretKey); exists != nil { if updatePolicy == string(constants.PUSH_SECRET_REPLACE_POLICY_ENABLED) { @@ -280,7 +326,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context // We need to check if any of the secrets have been removed in the new kube secret for _, managedSecretKey := range infisicalPushSecret.Status.ManagedSecrets { - if _, ok := kubeSecrets[managedSecretKey]; !ok { + if _, ok := processedSecrets[managedSecretKey]; !ok { // Secret has been removed, verify that the secret is managed by the operator if getExistingSecretByKey(managedSecretKey) != nil { @@ -305,7 +351,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context } // We need to check if any new secrets have been added in the kube secret - for currentSecretKey := range kubeSecrets { + for currentSecretKey := range processedSecrets { if exists := getExistingSecretByKey(currentSecretKey); exists == nil { @@ -317,7 +363,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context createdSecret, err := infisicalClient.Secrets().Create(infisicalSdk.CreateSecretOptions{ SecretKey: currentSecretKey, - SecretValue: kubeSecrets[currentSecretKey], + SecretValue: processedSecrets[currentSecretKey], ProjectID: destination.ProjectID, Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, @@ -336,12 +382,12 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context existingSecret := getExistingSecretByKey(currentSecretKey) - if existingSecret != nil && existingSecret.SecretValue != kubeSecrets[currentSecretKey] { + if existingSecret != nil && existingSecret.SecretValue != processedSecrets[currentSecretKey] { logger.Info(fmt.Sprintf("Secret with key [key=%s] has changed value. Updating secret in Infisical", currentSecretKey)) updatedSecret, err := infisicalClient.Secrets().Update(infisicalSdk.UpdateSecretOptions{ SecretKey: currentSecretKey, - NewSecretValue: kubeSecrets[currentSecretKey], + NewSecretValue: processedSecrets[currentSecretKey], ProjectID: destination.ProjectID, Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, @@ -353,7 +399,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context continue } - updateExistingSecretByKey(currentSecretKey, kubeSecrets[currentSecretKey]) + updateExistingSecretByKey(currentSecretKey, processedSecrets[currentSecretKey]) infisicalPushSecret.Status.ManagedSecrets[updatedSecret.ID] = currentSecretKey } } @@ -361,7 +407,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context } // Check if any of the existing secrets values have changed - for secretKey, secretValue := range kubeSecrets { + for secretKey, secretValue := range processedSecrets { existingSecret := getExistingSecretByKey(secretKey) @@ -440,9 +486,28 @@ func (r *InfisicalPushSecretReconciler) DeleteManagedSecrets(ctx context.Context resourceVariables := r.getResourceVariables(infisicalPushSecret) infisicalClient := resourceVariables.InfisicalClient + cancelCtx := resourceVariables.CancelCtx + authDetails := resourceVariables.AuthDetails + var err error + + if authDetails.AuthStrategy == "" { + logger.Info("No authentication strategy found. Attempting to authenticate") + authDetails, err = r.handleAuthentication(ctx, infisicalPushSecret, infisicalClient) + r.SetAuthenticatedStatusCondition(ctx, &infisicalPushSecret, err) + + if err != nil { + return fmt.Errorf("unable to authenticate [err=%s]", err) + } + + r.updateResourceVariables(infisicalPushSecret, util.ResourceVariables{ + InfisicalClient: infisicalClient, + CancelCtx: cancelCtx, + AuthDetails: authDetails, + }) + } destination := infisicalPushSecret.Spec.Destination - existingSecrets, err := infisicalClient.Secrets().List(infisicalSdk.ListSecretsOptions{ + existingSecrets, err := resourceVariables.InfisicalClient.Secrets().List(infisicalSdk.ListSecretsOptions{ ProjectID: destination.ProjectID, Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, diff --git a/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go index eb41fe6e91..f2197b0ed1 100644 --- a/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go @@ -3,17 +3,17 @@ package controllers import ( "bytes" "context" - "encoding/base64" "errors" "fmt" "strings" - "text/template" + tpl "text/template" "github.com/Infisical/infisical/k8-operator/api/v1alpha1" "github.com/Infisical/infisical/k8-operator/packages/api" "github.com/Infisical/infisical/k8-operator/packages/constants" "github.com/Infisical/infisical/k8-operator/packages/crypto" "github.com/Infisical/infisical/k8-operator/packages/model" + "github.com/Infisical/infisical/k8-operator/packages/template" "github.com/Infisical/infisical/k8-operator/packages/util" "github.com/go-logr/logr" @@ -156,16 +156,6 @@ func (r *InfisicalSecretReconciler) getInfisicalServiceAccountCredentialsFromKub return model.ServiceAccountDetails{AccessKey: string(accessKeyFromSecret), PrivateKey: string(privateKeyFromSecret), PublicKey: string(publicKeyFromSecret)}, nil } -var infisicalSecretTemplateFunctions = template.FuncMap{ - "decodeBase64ToBytes": func(encodedString string) string { - decoded, err := base64.StdEncoding.DecodeString(encodedString) - if err != nil { - panic(fmt.Sprintf("Error: %v", err)) - } - return string(decoded) - }, -} - func convertBinaryToStringMap(binaryMap map[string][]byte) map[string]string { stringMap := make(map[string]string) for k, v := range binaryMap { @@ -177,7 +167,7 @@ func convertBinaryToStringMap(binaryMap map[string][]byte) map[string]string { func (r *InfisicalSecretReconciler) createInfisicalManagedKubeResource(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedSecretReferenceInterface interface{}, secretsFromAPI []model.SingleEnvironmentVariable, ETag string, resourceType constants.ManagedKubeResourceType) error { plainProcessedSecrets := make(map[string][]byte) - var managedTemplateData *v1alpha1.InfisicalSecretTemplate + var managedTemplateData *v1alpha1.SecretTemplate if resourceType == constants.MANAGED_KUBE_RESOURCE_TYPE_SECRET { managedTemplateData = managedSecretReferenceInterface.(v1alpha1.ManagedKubeSecretConfig).Template @@ -201,7 +191,7 @@ func (r *InfisicalSecretReconciler) createInfisicalManagedKubeResource(ctx conte } for templateKey, userTemplate := range managedTemplateData.Data { - tmpl, err := template.New("secret-templates").Funcs(infisicalSecretTemplateFunctions).Parse(userTemplate) + tmpl, err := tpl.New("secret-templates").Funcs(template.GetTemplateFunctions()).Parse(userTemplate) if err != nil { return fmt.Errorf("unable to compile template: %s [err=%v]", templateKey, err) } @@ -322,7 +312,7 @@ func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context } for templateKey, userTemplate := range managedTemplateData.Data { - tmpl, err := template.New("secret-templates").Funcs(infisicalSecretTemplateFunctions).Parse(userTemplate) + tmpl, err := tpl.New("secret-templates").Funcs(template.GetTemplateFunctions()).Parse(userTemplate) if err != nil { return fmt.Errorf("unable to compile template: %s [err=%v]", templateKey, err) } @@ -373,7 +363,7 @@ func (r *InfisicalSecretReconciler) updateInfisicalManagedConfigMap(ctx context. } for templateKey, userTemplate := range managedTemplateData.Data { - tmpl, err := template.New("secret-templates").Funcs(infisicalSecretTemplateFunctions).Parse(userTemplate) + tmpl, err := tpl.New("secret-templates").Funcs(template.GetTemplateFunctions()).Parse(userTemplate) if err != nil { return fmt.Errorf("unable to compile template: %s [err=%v]", templateKey, err) } diff --git a/k8-operator/go.mod b/k8-operator/go.mod index 0731666fab..6ce68ed7fc 100644 --- a/k8-operator/go.mod +++ b/k8-operator/go.mod @@ -3,12 +3,15 @@ module github.com/Infisical/infisical/k8-operator go 1.21 require ( + github.com/Masterminds/sprig/v3 v3.3.0 github.com/infisical/go-sdk v0.4.4 + github.com/lestrrat-go/jwx/v2 v2.1.4 github.com/onsi/ginkgo/v2 v2.6.0 github.com/onsi/gomega v1.24.1 k8s.io/apimachinery v0.26.1 k8s.io/client-go v0.26.1 sigs.k8s.io/controller-runtime v0.14.4 + software.sslmate.com/src/go-pkcs12 v0.5.0 ) require ( @@ -16,6 +19,9 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.4.0 // indirect cloud.google.com/go/iam v1.1.11 // indirect + dario.cat/mergo v1.0.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/aws/aws-sdk-go-v2 v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.24 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.24 // indirect @@ -29,18 +35,31 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.30.1 // indirect github.com/aws/smithy-go v1.20.3 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.5 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/segmentio/asm v1.2.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/spf13/cast v1.7.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sync v0.10.0 // indirect google.golang.org/api v0.188.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240708141625-4ad9e859172b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect @@ -84,18 +103,18 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.32.0 golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.26.1 k8s.io/apiextensions-apiserver v0.26.1 // indirect k8s.io/component-base v0.26.1 // indirect diff --git a/k8-operator/go.sum b/k8-operator/go.sum index 9037bc4a06..bcecfadc01 100644 --- a/k8-operator/go.sum +++ b/k8-operator/go.sum @@ -38,9 +38,17 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -92,6 +100,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= @@ -106,6 +116,8 @@ github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJ github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -138,6 +150,8 @@ github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/ github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g= github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -214,6 +228,8 @@ github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBY github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -239,10 +255,24 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.1.4 h1:uBCMmJX8oRZStmKuMMOFb0Yh9xmEMgNJLgjuKKt4/qc= +github.com/lestrrat-go/jwx/v2 v2.1.4/go.mod h1:nWRbDFR1ALG2Z6GJbBXzfQaYyvn751KuuyySN2yR6is= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= @@ -250,6 +280,10 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -300,9 +334,17 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -319,8 +361,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -362,8 +404,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -458,8 +500,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -506,16 +548,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -527,8 +569,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -728,3 +770,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kF sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +software.sslmate.com/src/go-pkcs12 v0.5.0 h1:EC6R394xgENTpZ4RltKydeDUjtlM5drOYIG9c6TVj2M= +software.sslmate.com/src/go-pkcs12 v0.5.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/k8-operator/main.go b/k8-operator/main.go index 79df0d4d8e..d2f1905953 100644 --- a/k8-operator/main.go +++ b/k8-operator/main.go @@ -22,6 +22,7 @@ import ( infisicalDynamicSecretController "github.com/Infisical/infisical/k8-operator/controllers/infisicaldynamicsecret" infisicalPushSecretController "github.com/Infisical/infisical/k8-operator/controllers/infisicalpushsecret" infisicalSecretController "github.com/Infisical/infisical/k8-operator/controllers/infisicalsecret" + "github.com/Infisical/infisical/k8-operator/packages/template" //+kubebuilder:scaffold:imports ) @@ -86,6 +87,8 @@ func main() { os.Exit(1) } + template.InitializeTemplateFunctions() + if err = (&infisicalSecretController.InfisicalSecretReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), diff --git a/k8-operator/packages/template/base64.go b/k8-operator/packages/template/base64.go new file mode 100644 index 0000000000..3fff06c867 --- /dev/null +++ b/k8-operator/packages/template/base64.go @@ -0,0 +1,18 @@ +package template + +import ( + "encoding/base64" + "fmt" +) + +func decodeBase64ToBytes(encodedString string) string { + decoded, err := base64.StdEncoding.DecodeString(encodedString) + if err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + return string(decoded) +} + +func encodeBase64(plainString string) string { + return base64.StdEncoding.EncodeToString([]byte(plainString)) +} diff --git a/k8-operator/packages/template/jwk.go b/k8-operator/packages/template/jwk.go new file mode 100644 index 0000000000..8dbc8f3792 --- /dev/null +++ b/k8-operator/packages/template/jwk.go @@ -0,0 +1,43 @@ +package template + +import ( + "crypto/x509" + "fmt" + + "github.com/lestrrat-go/jwx/v2/jwk" +) + +func jwkPublicKeyPem(jwkjson string) string { + k, err := jwk.ParseKey([]byte(jwkjson)) + if err != nil { + panic(fmt.Sprintf("[jwkPublicKeyPem] Error: %v", err)) + } + var rawkey any + err = k.Raw(&rawkey) + if err != nil { + panic(fmt.Sprintf("[jwkPublicKeyPem] Error: %v", err)) + } + mpk, err := x509.MarshalPKIXPublicKey(rawkey) + if err != nil { + panic(fmt.Sprintf("[jwkPublicKeyPem] Error: %v", err)) + } + return pemEncode(mpk, "PUBLIC KEY") +} + +func jwkPrivateKeyPem(jwkjson string) string { + k, err := jwk.ParseKey([]byte(jwkjson)) + if err != nil { + panic(fmt.Sprintf("[jwkPrivateKeyPem] Error: %v", err)) + } + var mpk []byte + var pk any + err = k.Raw(&pk) + if err != nil { + panic(fmt.Sprintf("[jwkPrivateKeyPem] Error: %v", err)) + } + mpk, err = x509.MarshalPKCS8PrivateKey(pk) + if err != nil { + panic(fmt.Sprintf("[jwkPrivateKeyPem] Error: %v", err)) + } + return pemEncode(mpk, "PRIVATE KEY") +} diff --git a/k8-operator/packages/template/pem.go b/k8-operator/packages/template/pem.go new file mode 100644 index 0000000000..f37a9d576f --- /dev/null +++ b/k8-operator/packages/template/pem.go @@ -0,0 +1,98 @@ +package template + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "fmt" + "strings" +) + +const ( + errJunk = "error filtering pem: found junk" + + certTypeLeaf = "leaf" + certTypeIntermediate = "intermediate" + certTypeRoot = "root" +) + +func filterPEM(pemType, input string) string { + data := []byte(input) + var blocks []byte + var block *pem.Block + var rest []byte + for { + block, rest = pem.Decode(data) + data = rest + + if block == nil { + break + } + if !strings.EqualFold(block.Type, pemType) { + continue + } + + var buf bytes.Buffer + err := pem.Encode(&buf, block) + if err != nil { + panic(fmt.Sprintf("[filterPEM] Error: %v", err)) + } + blocks = append(blocks, buf.Bytes()...) + } + + if len(blocks) == 0 && len(rest) != 0 { + panic(fmt.Sprintf("[filterPEM] Error: %v", errJunk)) + } + + return string(blocks) +} + +func filterCertChain(certType, input string) string { + ordered := fetchX509CertChains([]byte(input)) + + switch certType { + case certTypeLeaf: + cert := ordered[0] + if cert.AuthorityKeyId != nil && !bytes.Equal(cert.AuthorityKeyId, cert.SubjectKeyId) { + return pemEncode(ordered[0].Raw, pemTypeCertificate) + } + case certTypeIntermediate: + if len(ordered) < 2 { + return "" + } + var pemData []byte + for _, cert := range ordered[1:] { + if isRootCertificate(cert) { + break + } + b := &pem.Block{ + Type: pemTypeCertificate, + Bytes: cert.Raw, + } + pemData = append(pemData, pem.EncodeToMemory(b)...) + } + return string(pemData) + case certTypeRoot: + cert := ordered[len(ordered)-1] + if isRootCertificate(cert) { + return pemEncode(cert.Raw, pemTypeCertificate) + } + } + + return "" +} + +func isRootCertificate(cert *x509.Certificate) bool { + return cert.AuthorityKeyId == nil || bytes.Equal(cert.AuthorityKeyId, cert.SubjectKeyId) +} + +func pemEncode(thing []byte, kind string) string { + buf := bytes.NewBuffer(nil) + err := pem.Encode(buf, &pem.Block{Type: kind, Bytes: thing}) + + if err != nil { + panic(fmt.Sprintf("[pemEncode] Error: %v", err)) + } + + return buf.String() +} diff --git a/k8-operator/packages/template/pem_chain.go b/k8-operator/packages/template/pem_chain.go new file mode 100644 index 0000000000..00c4f5d3f7 --- /dev/null +++ b/k8-operator/packages/template/pem_chain.go @@ -0,0 +1,117 @@ +package template + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "fmt" +) + +const ( + errNilCert = "certificate is nil" + errFoundDisjunctCert = "found multiple leaf or disjunct certificates" + errNoLeafFound = "no leaf certificate found" + errChainCycle = "constructing chain resulted in cycle" +) + +type node struct { + cert *x509.Certificate + parent *node + isParent bool +} + +func fetchX509CertChains(data []byte) []*x509.Certificate { + var newCertChain []*x509.Certificate + nodes := pemToNodes(data) + + // at the end of this computation, the output will be a single linked list + // the tail of the list will be the root node (which has no parents) + // the head of the list will be the leaf node (whose parent will be intermediate certs) + // (head) leaf -> intermediates -> root (tail) + for i := range nodes { + for j := range nodes { + // ignore same node to prevent generating a cycle + if i == j { + continue + } + // if ith node AuthorityKeyId is same as jth node SubjectKeyId, jth node was used + // to sign the ith certificate + if bytes.Equal(nodes[i].cert.AuthorityKeyId, nodes[j].cert.SubjectKeyId) { + nodes[j].isParent = true + nodes[i].parent = nodes[j] + break + } + } + } + + var foundLeaf bool + var leaf *node + for i := range nodes { + if !nodes[i].isParent { + if foundLeaf { + panic(fmt.Sprintf("[fetchX509CertChains] Error: %v", errFoundDisjunctCert)) + } + // this is the leaf node as it's not a parent for any other node + leaf = nodes[i] + foundLeaf = true + } + } + + if leaf == nil { + panic(fmt.Sprintf("[fetchX509CertChains] Error: %v", errNoLeafFound)) + } + + processedNodes := 0 + // iterate through the directed list and append the nodes to new cert chain + for leaf != nil { + processedNodes++ + // ensure we aren't stuck in a cyclic loop + if processedNodes > len(nodes) { + panic(fmt.Sprintf("[fetchX509CertChains] Error: %v", errChainCycle)) + } + newCertChain = append(newCertChain, leaf.cert) + leaf = leaf.parent + } + return newCertChain +} + +func fetchCertChains(data []byte) []byte { + var pemData []byte + newCertChain := fetchX509CertChains(data) + + for _, cert := range newCertChain { + b := &pem.Block{ + Type: pemTypeCertificate, + Bytes: cert.Raw, + } + pemData = append(pemData, pem.EncodeToMemory(b)...) + } + return pemData +} + +func pemToNodes(data []byte) []*node { + nodes := make([]*node, 0) + for { + // decode pem to der first + block, rest := pem.Decode(data) + data = rest + + if block == nil { + break + } + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + panic(fmt.Sprintf("[pemToNodes] Error: %v", err)) + } + + if cert == nil { + panic(fmt.Sprintf("[pemToNodes] Error: %v", errNilCert)) + } + nodes = append(nodes, &node{ + cert: cert, + parent: nil, + isParent: false, + }) + } + return nodes +} diff --git a/k8-operator/packages/template/pkcs12.go b/k8-operator/packages/template/pkcs12.go new file mode 100644 index 0000000000..e6763fc46e --- /dev/null +++ b/k8-operator/packages/template/pkcs12.go @@ -0,0 +1,144 @@ +package template + +import ( + "bytes" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + + gopkcs12 "software.sslmate.com/src/go-pkcs12" +) + +func pkcs12keyPass(pass, input string) string { + privateKey, _, _, err := gopkcs12.DecodeChain([]byte(input), pass) + if err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + + marshalPrivateKey, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + + var buf bytes.Buffer + if err := pem.Encode(&buf, &pem.Block{ + Type: pemTypeKey, + Bytes: marshalPrivateKey, + }); err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + return buf.String() +} + +func parsePrivateKey(block []byte) any { + if k, err := x509.ParsePKCS1PrivateKey(block); err == nil { + return k + } + if k, err := x509.ParsePKCS8PrivateKey(block); err == nil { + return k + } + if k, err := x509.ParseECPrivateKey(block); err == nil { + return k + } + panic("Error: unable to parse private key") +} + +func pkcs12key(input string) string { + return pkcs12keyPass("", input) +} + +func pkcs12certPass(pass, input string) string { + _, certificate, caCerts, err := gopkcs12.DecodeChain([]byte(input), pass) + if err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + + var pemData []byte + var buf bytes.Buffer + if err := pem.Encode(&buf, &pem.Block{ + Type: pemTypeCertificate, + Bytes: certificate.Raw, + }); err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + + pemData = append(pemData, buf.Bytes()...) + + for _, ca := range caCerts { + var buf bytes.Buffer + if err := pem.Encode(&buf, &pem.Block{ + Type: pemTypeCertificate, + Bytes: ca.Raw, + }); err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + pemData = append(pemData, buf.Bytes()...) + } + + // try to order certificate chain. If it fails we return + // the unordered raw pem data. + // This fails if multiple leaf or disjunct certs are provided. + ordered := fetchCertChains(pemData) + + return string(ordered) +} + +func pkcs12cert(input string) string { + return pkcs12certPass("", input) +} + +func pemToPkcs12(cert, key string) string { + return pemToPkcs12Pass(cert, key, "") +} + +func pemToPkcs12Pass(cert, key, pass string) string { + certPem, _ := pem.Decode([]byte(cert)) + + parsedCert, err := x509.ParseCertificate(certPem.Bytes) + if err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + + return certsToPkcs12(parsedCert, key, nil, pass) +} + +func fullPemToPkcs12(cert, key string) string { + return fullPemToPkcs12Pass(cert, key, "") +} + +func fullPemToPkcs12Pass(cert, key, pass string) string { + certPem, rest := pem.Decode([]byte(cert)) + + parsedCert, err := x509.ParseCertificate(certPem.Bytes) + if err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + + caCerts := make([]*x509.Certificate, 0) + for len(rest) > 0 { + caPem, restBytes := pem.Decode(rest) + rest = restBytes + + caCert, err := x509.ParseCertificate(caPem.Bytes) + if err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + + caCerts = append(caCerts, caCert) + } + + return certsToPkcs12(parsedCert, key, caCerts, pass) +} + +func certsToPkcs12(cert *x509.Certificate, key string, caCerts []*x509.Certificate, password string) string { + keyPem, _ := pem.Decode([]byte(key)) + parsedKey := parsePrivateKey(keyPem.Bytes) + + pfx, err := gopkcs12.Modern.Encode(parsedKey, cert, caCerts, password) + if err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + + return base64.StdEncoding.EncodeToString(pfx) +} diff --git a/k8-operator/packages/template/template.go b/k8-operator/packages/template/template.go new file mode 100644 index 0000000000..d56b3da5d4 --- /dev/null +++ b/k8-operator/packages/template/template.go @@ -0,0 +1,67 @@ +package template + +import ( + tpl "text/template" + + "github.com/Masterminds/sprig/v3" +) + +var customInfisicalSecretTemplateFunctions = tpl.FuncMap{ + "pkcs12key": pkcs12key, + "pkcs12keyPass": pkcs12keyPass, + "pkcs12cert": pkcs12cert, + "pkcs12certPass": pkcs12certPass, + + "pemToPkcs12": pemToPkcs12, + "pemToPkcs12Pass": pemToPkcs12Pass, + "fullPemToPkcs12": fullPemToPkcs12, + "fullPemToPkcs12Pass": fullPemToPkcs12Pass, + + "filterPEM": filterPEM, + "filterCertChain": filterCertChain, + + "jwkPublicKeyPem": jwkPublicKeyPem, + "jwkPrivateKeyPem": jwkPrivateKeyPem, + + "toYaml": toYAML, + "fromYaml": fromYAML, + + "decodeBase64ToBytes": decodeBase64ToBytes, + "encodeBase64": encodeBase64, +} + +const ( + errParse = "unable to parse template at key %s: %s" + errExecute = "unable to execute template at key %s: %s" + errDecodePKCS12WithPass = "unable to decode pkcs12 with password: %s" + errDecodeCertWithPass = "unable to decode pkcs12 certificate with password: %s" + errParsePrivKey = "unable to parse private key type" + errUnmarshalJSON = "unable to unmarshal json: %s" + errMarshalJSON = "unable to marshal json: %s" + + pemTypeCertificate = "CERTIFICATE" + pemTypeKey = "PRIVATE KEY" +) + +func InitializeTemplateFunctions() { + templates := customInfisicalSecretTemplateFunctions + + sprigFuncs := sprig.TxtFuncMap() + // removed for security reasons + delete(sprigFuncs, "env") + delete(sprigFuncs, "expandenv") + + for k, v := range sprigFuncs { + // make sure we aren't overwriting any of our own functions + _, exists := templates[k] + if !exists { + templates[k] = v + } + } + + customInfisicalSecretTemplateFunctions = templates +} + +func GetTemplateFunctions() tpl.FuncMap { + return customInfisicalSecretTemplateFunctions +} diff --git a/k8-operator/packages/template/yaml.go b/k8-operator/packages/template/yaml.go new file mode 100644 index 0000000000..5352d5a023 --- /dev/null +++ b/k8-operator/packages/template/yaml.go @@ -0,0 +1,30 @@ +package template + +import ( + "fmt" + "strings" + + "gopkg.in/yaml.v3" +) + +func toYAML(v any) string { + data, err := yaml.Marshal(v) + if err != nil { + panic(fmt.Sprintf("Error: %v", err)) + + } + return strings.TrimSuffix(string(data), "\n") +} + +// fromYAML converts a YAML document into a map[string]any. +// +// This is not a general-purpose YAML parser, and will not parse all valid +// YAML documents. +func fromYAML(str string) map[string]any { + mapData := map[string]any{} + + if err := yaml.Unmarshal([]byte(str), &mapData); err != nil { + panic(fmt.Sprintf("Error: %v", err)) + } + return mapData +}