diff --git a/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx b/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx index 50f07bb76..936a37303 100644 --- a/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx +++ b/docs/integrations/platforms/kubernetes/infisical-push-secret-crd.mdx @@ -34,7 +34,7 @@ Before applying the InfisicalPushSecret CRD, you need to create a Kubernetes sec metadata: name: infisical-push-secret-demo spec: - resyncInterval: 1m + resyncInterval: 1m # Remove this field to disable automatic reconciliation of the InfisicalPushSecret CRD. hostAPI: https://app.infisical.com/api # Optional, defaults to no replacement. @@ -124,7 +124,9 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y - The `resyncInterval` is a string-formatted duration that defines the time between each resync. + The `resyncInterval` is a string-formatted duration that defines the time between each resync. The field is optional, and will default to no automatic resync if not defined. + + If you don't want to automatically reconcile the InfisicalPushSecret CRD on an interval, you can remove the `resyncInterval` field entirely from your InfisicalPushSecret CRD. The format of the field is `[duration][unit]` where `duration` is a number and `unit` is a string representing the unit of time. @@ -459,6 +461,126 @@ Using Go templates, you can format, combine, and create new key-value pairs of s Please refer to the [templating functions documentation](/integrations/platforms/kubernetes/overview#available-helper-functions) for more information. +## Using generators to push secrets + +Generators are a feature of the Infisical secrets operator that allows you to generate secrets on-reconcile and push them to Infisical. This is useful for secret rotation purposes, and fully operator-managed secrets. +A generator is a custom resource that is installed on the cluster that defines the logic for generating a secret. + +Generators don't keep track of the secrets they generate, which means that on each reconciliation, a new value will be created and pushed. +For this reason you may want to disable automatic reconciliation of the InfisicalPushSecret CRD. You can do this by removing `resyncInterval` from the InfisicalPushSecret CRD. + +**Supported generators**: +- `Password`: Generates a random password of string format. +- `UUID`: Generates a random v4 UUID. + +To use a generator, you must specify at least one generator in the `push.generators[]` field. An example of a generator usage can be seen here: + + + Define a generator in the `push.generators[]` field. + + + The name of the secret that will be created in Infisical. + + + + The reference to the generator resource. + + Valid fields: + - `kind`: The kind of the generator resource, must match the generator kind. + - `name`: The name of the generator resource. + + + +```yaml + push: + secret: + secretName: push-secret-source-secret + secretNamespace: dev + generators: + - destinationSecretName: password-generator # Name of the secret that will be created in Infisical + generatorRef: + kind: Password|UUID # Kind of the resource, must match the generator kind. + name: custom-generator # Name of the generator resource +``` + + + + + The Password generator is a custom resource that is installed on the cluster that defines the logic for generating a password. + + + - `kind`: The kind of the generator resource, must match the generator kind. For the Password generator, the kind is `Password`. + - `generator.passwordSpec`: The spec of the password generator. + + + - `length`: The length of the password. + - `digits`: The number of digits in the password. + - `symbols`: The number of symbols in the password. + - `symbolCharacters`: The characters to use for the symbols in the password. + - `noUpper`: Whether to include uppercase letters in the password. + - `allowRepeat`: Whether to allow repeating characters in the password. + + + + ```yaml password-cluster-generator.yaml + apiVersion: secrets.infisical.com/v1alpha1 + kind: ClusterGenerator + metadata: + name: password-generator + spec: + kind: Password + generator: + passwordSpec: + length: 10 + digits: 5 + symbols: 5 + symbolCharacters: "-_$@" + noUpper: false + allowRepeat: true + ``` + + Example InfisicalPushSecret CRD using the Password generator: + ```yaml infisical-push-secret-crd.yaml + push: + generators: + - destinationSecretName: password-generator-test + generatorRef: + kind: Password + name: password-generator + ``` + + + The UUID generator is a custom resource that is installed on the cluster that defines the logic for generating a UUID. + + - `kind`: The kind of the generator resource, must match the generator kind. For the UUID generator, the kind is `UUID`. + - `generator.uuidSpec`: The spec of the UUID generator. For UUID's, this can be left empty. + + + ```yaml uuid-cluster-generator.yaml + apiVersion: secrets.infisical.com/v1alpha1 + kind: ClusterGenerator + metadata: + name: uuid-generator + spec: + kind: UUID + generator: + uuidSpec: + ``` + + Example InfisicalPushSecret CRD using the UUID generator: + ```yaml infisical-push-secret-crd.yaml + push: + generators: + - destinationSecretName: uuid-generator-test + generatorRef: + kind: UUID + name: uuid-generator + ``` + + + + + ## 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/k8-operator/api/v1alpha1/generators.go b/k8-operator/api/v1alpha1/generators.go new file mode 100644 index 000000000..0f6d86c2d --- /dev/null +++ b/k8-operator/api/v1alpha1/generators.go @@ -0,0 +1,152 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GeneratorKind represents a kind of generator. +// +kubebuilder:validation:Enum=Password;UUID +type GeneratorKind string + +const ( + GeneratorKindPassword GeneratorKind = "Password" + GeneratorKindUUID GeneratorKind = "UUID" +) + +type ClusterGeneratorSpec struct { + // Kind the kind of this generator. + Kind GeneratorKind `json:"kind"` + + // Generator the spec for this generator, must match the kind. + Generator GeneratorSpec `json:"generator,omitempty"` +} + +type GeneratorSpec struct { + // +kubebuilder:validation:Optional + PasswordSpec *PasswordSpec `json:"passwordSpec,omitempty"` + // +kubebuilder:validation:Optional + UUIDSpec *UUIDSpec `json:"uuidSpec,omitempty"` +} + +// ClusterGenerator represents a cluster-wide generator +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster +type ClusterGenerator struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ClusterGeneratorSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// ClusterGeneratorList contains a list of ClusterGenerator resources. +type ClusterGeneratorList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ClusterGenerator `json:"items"` +} + +// ! UUID Generator + +// UUIDSpec controls the behavior of the uuid generator. +type UUIDSpec struct{} + +// UUID generates a version 4 UUID (e56657e3-764f-11ef-a397-65231a88c216). +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type UUID struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec UUIDSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// UUIDList contains a list of UUID resources. +type UUIDList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []UUID `json:"items"` +} + +// ! Password Generator + +// PasswordSpec controls the behavior of the password generator. +type PasswordSpec struct { + // Length of the password to be generated. + // Defaults to 24 + // +kubebuilder:validation:Optional + // +kubebuilder:default=24 + Length int `json:"length"` + + // digits specifies the number of digits in the generated + // password. If omitted it defaults to 25% of the length of the password + Digits *int `json:"digits,omitempty"` + + // symbols specifies the number of symbol characters in the generated + // password. If omitted it defaults to 25% of the length of the password + Symbols *int `json:"symbols,omitempty"` + + // symbolCharacters specifies the special characters that should be used + // in the generated password. + SymbolCharacters *string `json:"symbolCharacters,omitempty"` + + // Set noUpper to disable uppercase characters + // +kubebuilder:validation:Optional + // +kubebuilder:default=false + NoUpper bool `json:"noUpper"` + + // set allowRepeat to true to allow repeating characters. + // +kubebuilder:validation:Optional + // +kubebuilder:default=false + AllowRepeat bool `json:"allowRepeat"` +} + +// Password generates a random password based on the +// configuration parameters in spec. +// You can specify the length, characterset and other attributes. +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Namespaced +type Password struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PasswordSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// PasswordList contains a list of Password resources. +type PasswordList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Password `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Password{}, &PasswordList{}) + SchemeBuilder.Register(&UUID{}, &UUIDList{}) + SchemeBuilder.Register(&ClusterGenerator{}, &ClusterGeneratorList{}) +} diff --git a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go index 5a7438881..8958c714d 100644 --- a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go @@ -29,9 +29,28 @@ type InfisicalPushSecretSecretSource struct { Template *SecretTemplate `json:"template,omitempty"` } -type SecretPush struct { +type GeneratorRef struct { + // Specify the Kind of the generator resource + // +kubebuilder:validation:Enum=Password;UUID // +kubebuilder:validation:Required - Secret InfisicalPushSecretSecretSource `json:"secret"` + Kind GeneratorKind `json:"kind"` + + // +kubebuilder:validation:Required + Name string `json:"name"` +} + +type SecretPushGenerator struct { + // +kubebuilder:validation:Required + DestinationSecretName string `json:"destinationSecretName"` + // +kubebuilder:validation:Required + GeneratorRef GeneratorRef `json:"generatorRef"` +} + +type SecretPush struct { + // +kubebuilder:validation:Optional + Secret *InfisicalPushSecretSecretSource `json:"secret,omitempty"` + // +kubebuilder:validation:Optional + Generators []SecretPushGenerator `json:"generators,omitempty"` } // InfisicalPushSecretSpec defines the desired state of InfisicalPushSecret @@ -52,7 +71,8 @@ type InfisicalPushSecretSpec struct { // +kubebuilder:validation:Required Push SecretPush `json:"push"` - ResyncInterval string `json:"resyncInterval"` + // +kubebuilder:validation:Optional + ResyncInterval *string `json:"resyncInterval,omitempty"` // Infisical host to pull secrets from // +kubebuilder:validation:Optional diff --git a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go index 4958e9c76..382b0e8cd 100644 --- a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -96,6 +96,80 @@ func (in *CaReference) DeepCopy() *CaReference { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterGenerator) DeepCopyInto(out *ClusterGenerator) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGenerator. +func (in *ClusterGenerator) DeepCopy() *ClusterGenerator { + if in == nil { + return nil + } + out := new(ClusterGenerator) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterGenerator) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterGeneratorList) DeepCopyInto(out *ClusterGeneratorList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterGenerator, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGeneratorList. +func (in *ClusterGeneratorList) DeepCopy() *ClusterGeneratorList { + if in == nil { + return nil + } + out := new(ClusterGeneratorList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterGeneratorList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterGeneratorSpec) DeepCopyInto(out *ClusterGeneratorSpec) { + *out = *in + in.Generator.DeepCopyInto(&out.Generator) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterGeneratorSpec. +func (in *ClusterGeneratorSpec) DeepCopy() *ClusterGeneratorSpec { + if in == nil { + return nil + } + out := new(ClusterGeneratorSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DynamicSecretDetails) DeepCopyInto(out *DynamicSecretDetails) { *out = *in @@ -143,6 +217,46 @@ func (in *GcpIamAuthDetails) DeepCopy() *GcpIamAuthDetails { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GeneratorRef) DeepCopyInto(out *GeneratorRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratorRef. +func (in *GeneratorRef) DeepCopy() *GeneratorRef { + if in == nil { + return nil + } + out := new(GeneratorRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GeneratorSpec) DeepCopyInto(out *GeneratorSpec) { + *out = *in + if in.PasswordSpec != nil { + in, out := &in.PasswordSpec, &out.PasswordSpec + *out = new(PasswordSpec) + (*in).DeepCopyInto(*out) + } + if in.UUIDSpec != nil { + in, out := &in.UUIDSpec, &out.UUIDSpec + *out = new(UUIDSpec) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GeneratorSpec. +func (in *GeneratorSpec) DeepCopy() *GeneratorSpec { + if in == nil { + return nil + } + out := new(GeneratorSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GenericAwsIamAuth) DeepCopyInto(out *GenericAwsIamAuth) { *out = *in @@ -483,6 +597,11 @@ func (in *InfisicalPushSecretSpec) DeepCopyInto(out *InfisicalPushSecretSpec) { out.Destination = in.Destination in.Authentication.DeepCopyInto(&out.Authentication) in.Push.DeepCopyInto(&out.Push) + if in.ResyncInterval != nil { + in, out := &in.ResyncInterval, &out.ResyncInterval + *out = new(string) + **out = **in + } out.TLS = in.TLS } @@ -746,10 +865,107 @@ func (in *ManagedKubeSecretConfig) DeepCopy() *ManagedKubeSecretConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Password) DeepCopyInto(out *Password) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Password. +func (in *Password) DeepCopy() *Password { + if in == nil { + return nil + } + out := new(Password) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Password) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PasswordList) DeepCopyInto(out *PasswordList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Password, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswordList. +func (in *PasswordList) DeepCopy() *PasswordList { + if in == nil { + return nil + } + out := new(PasswordList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PasswordList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PasswordSpec) DeepCopyInto(out *PasswordSpec) { + *out = *in + if in.Digits != nil { + in, out := &in.Digits, &out.Digits + *out = new(int) + **out = **in + } + if in.Symbols != nil { + in, out := &in.Symbols, &out.Symbols + *out = new(int) + **out = **in + } + if in.SymbolCharacters != nil { + in, out := &in.SymbolCharacters, &out.SymbolCharacters + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswordSpec. +func (in *PasswordSpec) DeepCopy() *PasswordSpec { + if in == nil { + return nil + } + out := new(PasswordSpec) + in.DeepCopyInto(out) + return out +} + // 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 - in.Secret.DeepCopyInto(&out.Secret) + if in.Secret != nil { + in, out := &in.Secret, &out.Secret + *out = new(InfisicalPushSecretSecretSource) + (*in).DeepCopyInto(*out) + } + if in.Generators != nil { + in, out := &in.Generators, &out.Generators + *out = make([]SecretPushGenerator, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretPush. @@ -762,6 +978,22 @@ func (in *SecretPush) DeepCopy() *SecretPush { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretPushGenerator) DeepCopyInto(out *SecretPushGenerator) { + *out = *in + out.GeneratorRef = in.GeneratorRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretPushGenerator. +func (in *SecretPushGenerator) DeepCopy() *SecretPushGenerator { + if in == nil { + return nil + } + out := new(SecretPushGenerator) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretScopeInWorkspace) DeepCopyInto(out *SecretScopeInWorkspace) { *out = *in @@ -848,6 +1080,79 @@ func (in *TLSConfig) DeepCopy() *TLSConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UUID) DeepCopyInto(out *UUID) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UUID. +func (in *UUID) DeepCopy() *UUID { + if in == nil { + return nil + } + out := new(UUID) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UUID) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UUIDList) DeepCopyInto(out *UUIDList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]UUID, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UUIDList. +func (in *UUIDList) DeepCopy() *UUIDList { + if in == nil { + return nil + } + out := new(UUIDList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *UUIDList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UUIDSpec) DeepCopyInto(out *UUIDSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UUIDSpec. +func (in *UUIDSpec) DeepCopy() *UUIDSpec { + if in == nil { + return nil + } + out := new(UUIDSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UniversalAuthDetails) DeepCopyInto(out *UniversalAuthDetails) { *out = *in diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_clustergenerators.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_clustergenerators.yaml new file mode 100644 index 000000000..c4a9eb168 --- /dev/null +++ b/k8-operator/config/crd/bases/secrets.infisical.com_clustergenerators.yaml @@ -0,0 +1,90 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: clustergenerators.secrets.infisical.com +spec: + group: secrets.infisical.com + names: + kind: ClusterGenerator + listKind: ClusterGeneratorList + plural: clustergenerators + singular: clustergenerator + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: ClusterGenerator represents a cluster-wide generator + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + generator: + description: Generator the spec for this generator, must match the + kind. + properties: + passwordSpec: + description: PasswordSpec controls the behavior of the password + generator. + properties: + allowRepeat: + default: false + description: set allowRepeat to true to allow repeating characters. + type: boolean + digits: + description: digits specifies the number of digits in the + generated password. If omitted it defaults to 25% of the + length of the password + type: integer + length: + default: 24 + description: Length of the password to be generated. Defaults + to 24 + type: integer + noUpper: + default: false + description: Set noUpper to disable uppercase characters + type: boolean + symbolCharacters: + description: symbolCharacters specifies the special characters + that should be used in the generated password. + type: string + symbols: + description: symbols specifies the number of symbol characters + in the generated password. If omitted it defaults to 25% + of the length of the password + type: integer + type: object + uuidSpec: + description: UUIDSpec controls the behavior of the uuid generator. + type: object + type: object + kind: + description: Kind the kind of this generator. + enum: + - Password + - UUID + type: string + required: + - kind + type: object + type: object + served: true + storage: true + subresources: + status: {} 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 37df72854..9d7e2adbd 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml @@ -142,6 +142,34 @@ spec: type: string push: properties: + generators: + items: + properties: + destinationSecretName: + type: string + generatorRef: + properties: + kind: + allOf: + - enum: + - Password + - UUID + - enum: + - Password + - UUID + description: Specify the Kind of the generator resource + type: string + name: + type: string + required: + - kind + - name + type: object + required: + - destinationSecretName + - generatorRef + type: object + type: array secret: properties: secretName: @@ -168,8 +196,6 @@ spec: - secretName - secretNamespace type: object - required: - - secret type: object resyncInterval: type: string @@ -200,7 +226,6 @@ spec: required: - destination - push - - resyncInterval type: object status: description: InfisicalPushSecretStatus defines the observed state of InfisicalPushSecret diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_passwords.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_passwords.yaml new file mode 100644 index 000000000..dc14f2bf0 --- /dev/null +++ b/k8-operator/config/crd/bases/secrets.infisical.com_passwords.yaml @@ -0,0 +1,69 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: passwords.secrets.infisical.com +spec: + group: secrets.infisical.com + names: + kind: Password + listKind: PasswordList + plural: passwords + singular: password + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Password generates a random password based on the configuration + parameters in spec. You can specify the length, characterset and other attributes. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: PasswordSpec controls the behavior of the password generator. + properties: + allowRepeat: + default: false + description: set allowRepeat to true to allow repeating characters. + type: boolean + digits: + description: digits specifies the number of digits in the generated + password. If omitted it defaults to 25% of the length of the password + type: integer + length: + default: 24 + description: Length of the password to be generated. Defaults to 24 + type: integer + noUpper: + default: false + description: Set noUpper to disable uppercase characters + type: boolean + symbolCharacters: + description: symbolCharacters specifies the special characters that + should be used in the generated password. + type: string + symbols: + description: symbols specifies the number of symbol characters in + the generated password. If omitted it defaults to 25% of the length + of the password + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_uuids.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_uuids.yaml new file mode 100644 index 000000000..495b5b276 --- /dev/null +++ b/k8-operator/config/crd/bases/secrets.infisical.com_uuids.yaml @@ -0,0 +1,42 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: uuids.secrets.infisical.com +spec: + group: secrets.infisical.com + names: + kind: UUID + listKind: UUIDList + plural: uuids + singular: uuid + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: UUID generates a version 4 UUID (e56657e3-764f-11ef-a397-65231a88c216). + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: UUIDSpec controls the behavior of the uuid generator. + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/k8-operator/config/crd/kustomization.yaml b/k8-operator/config/crd/kustomization.yaml index ea6db574a..02e3fad98 100644 --- a/k8-operator/config/crd/kustomization.yaml +++ b/k8-operator/config/crd/kustomization.yaml @@ -5,6 +5,7 @@ resources: - bases/secrets.infisical.com_infisicalsecrets.yaml - bases/secrets.infisical.com_infisicalpushsecrets.yaml - bases/secrets.infisical.com_infisicaldynamicsecrets.yaml + - bases/secrets.infisical.com_clustergenerators.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/k8-operator/config/rbac/role.yaml b/k8-operator/config/rbac/role.yaml index 542face87..ea2fbada3 100644 --- a/k8-operator/config/rbac/role.yaml +++ b/k8-operator/config/rbac/role.yaml @@ -74,6 +74,18 @@ rules: - tokenreviews verbs: - create +- apiGroups: + - secrets.infisical.com + resources: + - clustergenerators + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - secrets.infisical.com resources: diff --git a/k8-operator/config/samples/crd/pushsecret/cluster-password-generator.yml b/k8-operator/config/samples/crd/pushsecret/cluster-password-generator.yml new file mode 100644 index 000000000..ce3c8c087 --- /dev/null +++ b/k8-operator/config/samples/crd/pushsecret/cluster-password-generator.yml @@ -0,0 +1,14 @@ +apiVersion: secrets.infisical.com/v1alpha1 +kind: ClusterGenerator +metadata: + name: password-generator +spec: + kind: Password + generator: + passwordSpec: + length: 10 + digits: 5 + symbols: 5 + symbolCharacters: "-_$@" + noUpper: false + allowRepeat: true diff --git a/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_helper.go b/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_helper.go index 861880d95..6e3b135e4 100644 --- a/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_helper.go +++ b/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_helper.go @@ -393,7 +393,7 @@ func (r *InfisicalDynamicSecretReconciler) ReconcileInfisicalDynamicSecret(ctx c // Max TTL if infisicalDynamicSecret.Status.MaxTTL != "" { - maxTTLDuration, err := util.ConvertIntervalToDuration(infisicalDynamicSecret.Status.MaxTTL) + maxTTLDuration, err := util.ConvertIntervalToDuration(&infisicalDynamicSecret.Status.MaxTTL) if err != nil { return defaultNextReconcile, fmt.Errorf("unable to parse MaxTTL duration: %w", err) } diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go index ebf537a63..47c55d698 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go @@ -51,7 +51,7 @@ func (r *InfisicalPushSecretReconciler) GetLogger(req ctrl.Request) logr.Logger //+kubebuilder:rbac:groups="",resources=pods,verbs=get;list //+kubebuilder:rbac:groups="authentication.k8s.io",resources=tokenreviews,verbs=create //+kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create - +// +kubebuilder:rbac:groups=secrets.infisical.com,resources=clustergenerators,verbs=get;list;watch;create;update;patch;delete // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // For more details, check Reconcile and its Result here: @@ -108,23 +108,30 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. return ctrl.Result{}, nil } - if infisicalPushSecretCRD.Spec.ResyncInterval != "" { + if infisicalPushSecretCRD.Spec.Push.Secret == nil && infisicalPushSecretCRD.Spec.Push.Generators == nil { + logger.Info("No secret or generators found, skipping reconciliation. Please define ") + return ctrl.Result{}, nil + } - duration, err := util.ConvertIntervalToDuration(infisicalPushSecretCRD.Spec.ResyncInterval) + duration, err := util.ConvertIntervalToDuration(infisicalPushSecretCRD.Spec.ResyncInterval) - if err != nil { + if err != nil { + // if resyncInterval is nil, we don't want to reconcile automatically + if infisicalPushSecretCRD.Spec.ResyncInterval != nil { logger.Error(err, fmt.Sprintf("unable to convert resync interval to duration. Will requeue after [requeueTime=%v]", requeueTime)) return ctrl.Result{ RequeueAfter: requeueTime, }, nil + } else { + logger.Error(err, "unable to convert resync interval to duration") + return ctrl.Result{}, err } + } - requeueTime = duration + requeueTime = duration + if requeueTime != 0 { logger.Info(fmt.Sprintf("Manual re-sync interval set. Interval: %v", requeueTime)) - - } else { - logger.Info(fmt.Sprintf("Re-sync interval set. Interval: %v", requeueTime)) } // Check if the resource is already marked for deletion @@ -137,10 +144,15 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. // Get modified/default config infisicalConfig, err := controllerhelpers.GetInfisicalConfigMap(ctx, r.Client) if err != nil { - logger.Error(err, fmt.Sprintf("unable to fetch infisical-config. Will requeue after [requeueTime=%v]", requeueTime)) - return ctrl.Result{ - RequeueAfter: requeueTime, - }, nil + if requeueTime != 0 { + logger.Error(err, fmt.Sprintf("unable to fetch infisical-config. Will requeue after [requeueTime=%v]", requeueTime)) + return ctrl.Result{ + RequeueAfter: requeueTime, + }, nil + } else { + logger.Error(err, "unable to fetch infisical-config") + return ctrl.Result{}, err + } } if infisicalPushSecretCRD.Spec.HostAPI == "" { @@ -152,10 +164,15 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. if infisicalPushSecretCRD.Spec.TLS.CaRef.SecretName != "" { api.API_CA_CERTIFICATE, err = r.getInfisicalCaCertificateFromKubeSecret(ctx, infisicalPushSecretCRD) if err != nil { - logger.Error(err, fmt.Sprintf("unable to fetch CA certificate. Will requeue after [requeueTime=%v]", requeueTime)) - return ctrl.Result{ - RequeueAfter: requeueTime, - }, nil + if requeueTime != 0 { + logger.Error(err, fmt.Sprintf("unable to fetch CA certificate. Will requeue after [requeueTime=%v]", requeueTime)) + return ctrl.Result{ + RequeueAfter: requeueTime, + }, nil + } else { + logger.Error(err, "unable to fetch CA certificate") + return ctrl.Result{}, err + } } logger.Info("Using custom CA certificate...") @@ -167,17 +184,27 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. r.SetReconcileStatusCondition(ctx, &infisicalPushSecretCRD, err) if err != nil { - logger.Error(err, fmt.Sprintf("unable to reconcile Infisical Push Secret. Will requeue after [requeueTime=%v]", requeueTime)) - return ctrl.Result{ - RequeueAfter: requeueTime, - }, nil + if requeueTime != 0 { + logger.Error(err, fmt.Sprintf("unable to reconcile Infisical Push Secret. Will requeue after [requeueTime=%v]", requeueTime)) + return ctrl.Result{ + RequeueAfter: requeueTime, + }, nil + } else { + logger.Error(err, "unable to reconcile Infisical Push Secret") + return ctrl.Result{}, err + } } // Sync again after the specified time - logger.Info(fmt.Sprintf("Operator will requeue after [%v]", requeueTime)) - return ctrl.Result{ - RequeueAfter: requeueTime, - }, nil + if requeueTime != 0 { + logger.Info(fmt.Sprintf("Operator will requeue after [%v]", requeueTime)) + return ctrl.Result{ + RequeueAfter: requeueTime, + }, nil + } else { + logger.Info("Operator will reconcile on next spec change") + return ctrl.Result{}, nil + } } func (r *InfisicalPushSecretReconciler) SetupWithManager(mgr ctrl.Manager) error { @@ -228,27 +255,67 @@ func (r *InfisicalPushSecretReconciler) SetupWithManager(mgr ctrl.Manager) error )). Watches( &source.Kind{Type: &corev1.Secret{}}, - handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request { - ctx := context.Background() - pushSecrets := &secretsv1alpha1.InfisicalPushSecretList{} - if err := r.List(ctx, pushSecrets); err != nil { - return []reconcile.Request{} - } - - requests := []reconcile.Request{} - for _, pushSecret := range pushSecrets.Items { - if pushSecret.Spec.Push.Secret.SecretName == o.GetName() && - pushSecret.Spec.Push.Secret.SecretNamespace == o.GetNamespace() { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: pushSecret.GetName(), - Namespace: pushSecret.GetNamespace(), - }, - }) - } - } - return requests - }), + handler.EnqueueRequestsFromMapFunc(r.findPushSecretsForSecret), + ). + Watches( + &source.Kind{Type: &secretsv1alpha1.ClusterGenerator{}}, + handler.EnqueueRequestsFromMapFunc(r.findPushSecretsForClusterGenerator), ). Complete(r) } + +func (r *InfisicalPushSecretReconciler) findPushSecretsForClusterGenerator(o client.Object) []reconcile.Request { + ctx := context.Background() + pushSecrets := &secretsv1alpha1.InfisicalPushSecretList{} + if err := r.List(ctx, pushSecrets); err != nil { + return []reconcile.Request{} + } + + clusterGenerator, ok := o.(*secretsv1alpha1.ClusterGenerator) + if !ok { + return []reconcile.Request{} + } + + requests := []reconcile.Request{} + for _, pushSecret := range pushSecrets.Items { + if pushSecret.Spec.Push.Generators != nil { + for _, generator := range pushSecret.Spec.Push.Generators { + if generator.GeneratorRef.Name == clusterGenerator.GetName() { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: pushSecret.GetName(), + Namespace: pushSecret.GetNamespace(), + }, + }) + break + } + } + } + } + return requests +} + +func (r *InfisicalPushSecretReconciler) findPushSecretsForSecret(o client.Object) []reconcile.Request { + ctx := context.Background() + pushSecrets := &secretsv1alpha1.InfisicalPushSecretList{} + if err := r.List(ctx, pushSecrets); err != nil { + return []reconcile.Request{} + } + + requests := []reconcile.Request{} + for _, pushSecret := range pushSecrets.Items { + if pushSecret.Spec.Push.Secret != nil && + pushSecret.Spec.Push.Secret.SecretName == o.GetName() && + pushSecret.Spec.Push.Secret.SecretNamespace == o.GetNamespace() { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: pushSecret.GetName(), + Namespace: pushSecret.GetNamespace(), + }, + }) + } + + } + + return requests +} diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go index 848df3c82..1400b163b 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + generatorUtil "github.com/Infisical/infisical/k8-operator/packages/generator" infisicalSdk "github.com/infisical/go-sdk" k8Errors "k8s.io/apimachinery/pkg/api/errors" ) @@ -106,6 +107,52 @@ func (r *InfisicalPushSecretReconciler) updateResourceVariables(infisicalPushSec infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)] = resourceVariables } +func (r *InfisicalPushSecretReconciler) processGenerators(ctx context.Context, infisicalPushSecret v1alpha1.InfisicalPushSecret) (map[string]string, error) { + + processedSecrets := make(map[string]string) + + if len(infisicalPushSecret.Spec.Push.Generators) == 0 { + return processedSecrets, nil + } + + for _, generator := range infisicalPushSecret.Spec.Push.Generators { + generatorRef := generator.GeneratorRef + + clusterGenerator := &v1alpha1.ClusterGenerator{} + err := r.Client.Get(ctx, types.NamespacedName{Name: generatorRef.Name}, clusterGenerator) + if err != nil { + return nil, fmt.Errorf("unable to get ClusterGenerator resource [err=%s]", err) + } + if generatorRef.Kind == v1alpha1.GeneratorKindPassword { + // get the custom ClusterGenerator resource from the cluster + + if clusterGenerator.Spec.Generator.PasswordSpec == nil { + return nil, fmt.Errorf("password spec is not defined in the ClusterGenerator resource") + } + + password, err := generatorUtil.GeneratorPassword(*clusterGenerator.Spec.Generator.PasswordSpec) + if err != nil { + return nil, fmt.Errorf("unable to generate password [err=%s]", err) + } + + processedSecrets[generator.DestinationSecretName] = password + } + + if generatorRef.Kind == v1alpha1.GeneratorKindUUID { + + uuid, err := generatorUtil.GeneratorUUID() + if err != nil { + return nil, fmt.Errorf("unable to generate UUID [err=%s]", err) + } + + processedSecrets[generator.DestinationSecretName] = uuid + } + } + + return processedSecrets, nil + +} + func (r *InfisicalPushSecretReconciler) processTemplatedSecrets(infisicalPushSecret v1alpha1.InfisicalPushSecret, kubePushSecret *corev1.Secret, destination v1alpha1.InfisicalPushSecretDestination) (map[string]string, error) { processedSecrets := make(map[string]string) @@ -172,18 +219,31 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context }) } - kubePushSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{ - Namespace: infisicalPushSecret.Spec.Push.Secret.SecretNamespace, - Name: infisicalPushSecret.Spec.Push.Secret.SecretName, - }) + processedSecrets := make(map[string]string) - if err != nil { - return fmt.Errorf("unable to fetch kube secret [err=%s]", err) + if infisicalPushSecret.Spec.Push.Secret != nil { + kubePushSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{ + Namespace: infisicalPushSecret.Spec.Push.Secret.SecretNamespace, + Name: infisicalPushSecret.Spec.Push.Secret.SecretName, + }) + + if err != nil { + return fmt.Errorf("unable to fetch kube secret [err=%s]", err) + } + + processedSecrets, err = r.processTemplatedSecrets(infisicalPushSecret, kubePushSecret, infisicalPushSecret.Spec.Destination) + if err != nil { + return fmt.Errorf("unable to process templated secrets [err=%s]", err) + } } - processedSecrets, err := r.processTemplatedSecrets(infisicalPushSecret, kubePushSecret, infisicalPushSecret.Spec.Destination) + generatorSecrets, err := r.processGenerators(ctx, infisicalPushSecret) if err != nil { - return fmt.Errorf("unable to process templated secrets [err=%s]", err) + return fmt.Errorf("unable to process generators [err=%s]", err) + } + + for key, value := range generatorSecrets { + processedSecrets[key] = value } destination := infisicalPushSecret.Spec.Destination diff --git a/k8-operator/go.mod b/k8-operator/go.mod index 6ce68ed7f..c9b868b00 100644 --- a/k8-operator/go.mod +++ b/k8-operator/go.mod @@ -4,10 +4,12 @@ go 1.21 require ( github.com/Masterminds/sprig/v3 v3.3.0 + github.com/aws/smithy-go v1.20.3 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 + github.com/sethvargo/go-password v0.3.1 k8s.io/apimachinery v0.26.1 k8s.io/client-go v0.26.1 sigs.k8s.io/controller-runtime v0.14.4 @@ -34,7 +36,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.22.1 // indirect 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 @@ -85,7 +86,7 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/google/uuid v1.6.0 github.com/imdario/mergo v0.3.12 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/k8-operator/go.sum b/k8-operator/go.sum index bcecfadc0..2e151b0a4 100644 --- a/k8-operator/go.sum +++ b/k8-operator/go.sum @@ -338,6 +338,8 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV 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/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU= +github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs= 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= diff --git a/k8-operator/packages/generator/generator.go b/k8-operator/packages/generator/generator.go new file mode 100644 index 000000000..cc1b290c7 --- /dev/null +++ b/k8-operator/packages/generator/generator.go @@ -0,0 +1 @@ +package generator diff --git a/k8-operator/packages/generator/password.go b/k8-operator/packages/generator/password.go new file mode 100644 index 000000000..d322f1014 --- /dev/null +++ b/k8-operator/packages/generator/password.go @@ -0,0 +1,76 @@ +package generator + +import ( + "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + "github.com/sethvargo/go-password/password" +) + +const ( + defaultLength = 24 + defaultSymbolChars = "~!@#$%^&*()_+`-={}|[]\\:\"<>?,./" + digitFactor = 0.25 + symbolFactor = 0.25 +) + +func generateSafePassword( + passLen int, + symbols int, + symbolCharacters string, + digits int, + noUpper bool, + allowRepeat bool, +) (string, error) { + gen, err := password.NewGenerator(&password.GeneratorInput{ + Symbols: symbolCharacters, + }) + if err != nil { + return "", err + } + return gen.Generate( + passLen, + digits, + symbols, + noUpper, + allowRepeat, + ) +} + +func GeneratorPassword(spec v1alpha1.PasswordSpec) (string, error) { + + symbolCharacters := defaultSymbolChars + + if spec.SymbolCharacters != nil && *spec.SymbolCharacters != "" { + symbolCharacters = *spec.SymbolCharacters + } + + passwordLength := defaultLength + + if spec.Length != 0 { + passwordLength = spec.Length + } + + digits := int(float32(passwordLength) * digitFactor) + if spec.Digits != nil { + digits = *spec.Digits + } + + symbols := int(float32(passwordLength) * symbolFactor) + if spec.Symbols != nil { + symbols = *spec.Symbols + } + + pass, err := generateSafePassword( + passwordLength, + symbols, + symbolCharacters, + digits, + spec.NoUpper, + spec.AllowRepeat, + ) + + if err != nil { + return "", err + } + + return pass, nil +} diff --git a/k8-operator/packages/generator/uuid.go b/k8-operator/packages/generator/uuid.go new file mode 100644 index 000000000..b9249f783 --- /dev/null +++ b/k8-operator/packages/generator/uuid.go @@ -0,0 +1,10 @@ +package generator + +import ( + "github.com/google/uuid" +) + +func GeneratorUUID() (string, error) { + uuid := uuid.New().String() + return uuid, nil +} diff --git a/k8-operator/packages/util/helpers.go b/k8-operator/packages/util/helpers.go index 02621dcfd..ef3712715 100644 --- a/k8-operator/packages/util/helpers.go +++ b/k8-operator/packages/util/helpers.go @@ -7,14 +7,19 @@ import ( "time" ) -func ConvertIntervalToDuration(resyncInterval string) (time.Duration, error) { - length := len(resyncInterval) +func ConvertIntervalToDuration(resyncInterval *string) (time.Duration, error) { + + if resyncInterval == nil || *resyncInterval == "" { + return 0, nil + } + + length := len(*resyncInterval) if length < 2 { return 0, fmt.Errorf("invalid format") } - unit := resyncInterval[length-1:] - numberPart := resyncInterval[:length-1] + unit := (*resyncInterval)[length-1:] + numberPart := (*resyncInterval)[:length-1] number, err := strconv.Atoi(numberPart) if err != nil { @@ -40,16 +45,6 @@ func ConvertIntervalToDuration(resyncInterval string) (time.Duration, error) { } } -func ConvertIntervalToTime(resyncInterval string) (time.Time, error) { - duration, err := ConvertIntervalToDuration(resyncInterval) - if err != nil { - return time.Time{}, err - } - - // Add duration to current time - return time.Now().Add(duration), nil -} - func AppendAPIEndpoint(address string) string { if strings.HasSuffix(address, "/api") { return address