From e761b4996458f87922c4fc02d29bd25c60c099b8 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 29 Nov 2024 03:05:50 +0400 Subject: [PATCH 01/14] feat(k8-operator): push secrets --- .../api/v1alpha1/infisicalpushsecret_types.go | 141 ++++++ .../api/v1alpha1/zz_generated.deepcopy.go | 267 ++++++++++ ...ts.infisical.com_infisicalpushsecrets.yaml | 265 ++++++++++ k8-operator/config/crd/kustomization.yaml | 5 +- k8-operator/config/rbac/role.yaml | 26 + .../samples/crd/pushsecret/pushSecret.yaml | 45 ++ .../samples/crd/pushsecret/sourceSecret.yaml | 9 + .../infisicalpushsecret/conditions.go | 155 ++++++ .../infisicalpushsecret_controller.go | 233 +++++++++ .../infisicalpushsecret_helper.go | 473 ++++++++++++++++++ .../auto_redeployment.go | 16 +- .../{ => infisicalsecret}/conditions.go | 10 +- .../infisicalsecret_controller.go | 76 +-- .../infisicalsecret_helper.go | 223 +++------ .../{ => infisicalsecret}/suite_test.go | 0 .../controllers/infisicalsecret_auth.go | 150 ------ .../install-secrets-operator.yaml | 209 ++++++++ k8-operator/main.go | 20 +- k8-operator/packages/api/api.go | 4 - k8-operator/packages/constants/constants.go | 24 + k8-operator/packages/controllerutil/util.go | 45 ++ k8-operator/packages/util/auth.go | 326 ++++++++++++ k8-operator/packages/util/kubernetes.go | 50 ++ k8-operator/packages/util/models.go | 13 + k8-operator/packages/util/time.go | 40 ++ 25 files changed, 2477 insertions(+), 348 deletions(-) create mode 100644 k8-operator/api/v1alpha1/infisicalpushsecret_types.go create mode 100644 k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml create mode 100644 k8-operator/config/samples/crd/pushsecret/pushSecret.yaml create mode 100644 k8-operator/config/samples/crd/pushsecret/sourceSecret.yaml create mode 100644 k8-operator/controllers/infisicalpushsecret/conditions.go create mode 100644 k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go create mode 100644 k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go rename k8-operator/controllers/{ => infisicalsecret}/auto_redeployment.go (81%) rename k8-operator/controllers/{ => infisicalsecret}/conditions.go (86%) rename k8-operator/controllers/{ => infisicalsecret}/infisicalsecret_controller.go (64%) rename k8-operator/controllers/{ => infisicalsecret}/infisicalsecret_helper.go (57%) rename k8-operator/controllers/{ => infisicalsecret}/suite_test.go (100%) delete mode 100644 k8-operator/controllers/infisicalsecret_auth.go create mode 100644 k8-operator/packages/constants/constants.go create mode 100644 k8-operator/packages/controllerutil/util.go create mode 100644 k8-operator/packages/util/kubernetes.go create mode 100644 k8-operator/packages/util/models.go create mode 100644 k8-operator/packages/util/time.go diff --git a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go new file mode 100644 index 000000000..336534ed2 --- /dev/null +++ b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go @@ -0,0 +1,141 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type InfisicalPushSecretDestination struct { + // +kubebuilder:validation:Required + // +kubebuilder:validation:Immutable + SecretsPath string `json:"secretsPath"` + // +kubebuilder:validation:Required + // +kubebuilder:validation:Immutable + EnvSlug string `json:"envSlug"` + // +kubebuilder:validation:Required + // +kubebuilder:validation:Immutable + ProjectID string `json:"projectId"` +} + +type PushSecretTlsConfig struct { + // Reference to secret containing CA cert + // +kubebuilder:validation:Optional + CaRef CaReference `json:"caRef,omitempty"` +} + +// PushSecretUniversalAuth defines universal authentication +type PushSecretUniversalAuth struct { + // +kubebuilder:validation:Required + CredentialsRef KubeSecretReference `json:"credentialsRef"` +} + +type PushSecretAwsIamAuth struct { + // +kubebuilder:validation:Required + IdentityID string `json:"identityId"` +} + +type PushSecretAzureAuth struct { + // +kubebuilder:validation:Required + IdentityID string `json:"identityId"` + // +kubebuilder:validation:Optional + Resource string `json:"resource,omitempty"` +} + +type PushSecretGcpIdTokenAuth struct { + // +kubebuilder:validation:Required + IdentityID string `json:"identityId"` +} + +type PushSecretGcpIamAuth struct { + // +kubebuilder:validation:Required + IdentityID string `json:"identityId"` + // +kubebuilder:validation:Required + ServiceAccountKeyFilePath string `json:"serviceAccountKeyFilePath"` +} + +// Rest of your types should be defined similarly... +type PushSecretKubernetesAuth struct { + // +kubebuilder:validation:Required + IdentityID string `json:"identityId"` + // +kubebuilder:validation:Required + ServiceAccountRef KubernetesServiceAccountRef `json:"serviceAccountRef"` +} + +type PushSecretAuthentication struct { + // +kubebuilder:validation:Optional + UniversalAuth PushSecretUniversalAuth `json:"universalAuth,omitempty"` + // +kubebuilder:validation:Optional + KubernetesAuth PushSecretKubernetesAuth `json:"kubernetesAuth,omitempty"` + // +kubebuilder:validation:Optional + AwsIamAuth PushSecretAwsIamAuth `json:"awsIamAuth,omitempty"` + // +kubebuilder:validation:Optional + AzureAuth PushSecretAzureAuth `json:"azureAuth,omitempty"` + // +kubebuilder:validation:Optional + GcpIdTokenAuth PushSecretGcpIdTokenAuth `json:"gcpIdTokenAuth,omitempty"` + // +kubebuilder:validation:Optional + GcpIamAuth PushSecretGcpIamAuth `json:"gcpIamAuth,omitempty"` +} + +type SecretPush struct { + // +kubebuilder:validation:Required + Secret KubeSecretReference `json:"secret"` +} + +// InfisicalPushSecretSpec defines the desired state of InfisicalPushSecret +type InfisicalPushSecretSpec struct { + // +kubebuilder:validation:Optional + UpdatePolicy string `json:"updatePolicy"` + + // +kubebuilder:validation:Optional + DeletionPolicy string `json:"deletionPolicy"` + + // +kubebuilder:validation:Required + // +kubebuilder:validation:Immutable + Destination InfisicalPushSecretDestination `json:"destination"` + + // +kubebuilder:validation:Optional + Authentication PushSecretAuthentication `json:"authentication"` + + // +kubebuilder:validation:Required + Push SecretPush `json:"push"` + + ResyncInterval string `json:"resyncInterval"` + + // Infisical host to pull secrets from + // +kubebuilder:validation:Optional + HostAPI string `json:"hostAPI"` + + // +kubebuilder:validation:Optional + TLS PushSecretTlsConfig `json:"tls"` +} + +// InfisicalPushSecretStatus defines the observed state of InfisicalPushSecret +type InfisicalPushSecretStatus struct { + Conditions []metav1.Condition `json:"conditions"` + + // managed secrets is a map where the key is the ID, and the value is the secret key (string[id], string[key] ) + ManagedSecrets map[string]string `json:"managedSecrets"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// InfisicalPushSecret is the Schema for the infisicalpushsecrets API +type InfisicalPushSecret struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec InfisicalPushSecretSpec `json:"spec,omitempty"` + Status InfisicalPushSecretStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// InfisicalPushSecretList contains a list of InfisicalPushSecret +type InfisicalPushSecretList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []InfisicalPushSecret `json:"items"` +} + +func init() { + SchemeBuilder.Register(&InfisicalPushSecret{}, &InfisicalPushSecretList{}) +} diff --git a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go index 41e4d3f20..fa54d4130 100644 --- a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -128,6 +128,128 @@ 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 *InfisicalPushSecret) DeepCopyInto(out *InfisicalPushSecret) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalPushSecret. +func (in *InfisicalPushSecret) DeepCopy() *InfisicalPushSecret { + if in == nil { + return nil + } + out := new(InfisicalPushSecret) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InfisicalPushSecret) 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 *InfisicalPushSecretDestination) DeepCopyInto(out *InfisicalPushSecretDestination) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalPushSecretDestination. +func (in *InfisicalPushSecretDestination) DeepCopy() *InfisicalPushSecretDestination { + if in == nil { + return nil + } + out := new(InfisicalPushSecretDestination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InfisicalPushSecretList) DeepCopyInto(out *InfisicalPushSecretList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]InfisicalPushSecret, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalPushSecretList. +func (in *InfisicalPushSecretList) DeepCopy() *InfisicalPushSecretList { + if in == nil { + return nil + } + out := new(InfisicalPushSecretList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *InfisicalPushSecretList) 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 *InfisicalPushSecretSpec) DeepCopyInto(out *InfisicalPushSecretSpec) { + *out = *in + out.Destination = in.Destination + out.Authentication = in.Authentication + out.Push = in.Push + out.TLS = in.TLS +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalPushSecretSpec. +func (in *InfisicalPushSecretSpec) DeepCopy() *InfisicalPushSecretSpec { + if in == nil { + return nil + } + out := new(InfisicalPushSecretSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InfisicalPushSecretStatus) DeepCopyInto(out *InfisicalPushSecretStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ManagedSecrets != nil { + in, out := &in.ManagedSecrets, &out.ManagedSecrets + *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 InfisicalPushSecretStatus. +func (in *InfisicalPushSecretStatus) DeepCopy() *InfisicalPushSecretStatus { + if in == nil { + return nil + } + out := new(InfisicalPushSecretStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InfisicalSecret) DeepCopyInto(out *InfisicalSecret) { *out = *in @@ -332,6 +454,151 @@ func (in *MangedKubeSecretConfig) DeepCopy() *MangedKubeSecretConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PushSecretAuthentication) DeepCopyInto(out *PushSecretAuthentication) { + *out = *in + out.UniversalAuth = in.UniversalAuth + out.KubernetesAuth = in.KubernetesAuth + out.AwsIamAuth = in.AwsIamAuth + out.AzureAuth = in.AzureAuth + out.GcpIdTokenAuth = in.GcpIdTokenAuth + out.GcpIamAuth = in.GcpIamAuth +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretAuthentication. +func (in *PushSecretAuthentication) DeepCopy() *PushSecretAuthentication { + if in == nil { + return nil + } + out := new(PushSecretAuthentication) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PushSecretAwsIamAuth) DeepCopyInto(out *PushSecretAwsIamAuth) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretAwsIamAuth. +func (in *PushSecretAwsIamAuth) DeepCopy() *PushSecretAwsIamAuth { + if in == nil { + return nil + } + out := new(PushSecretAwsIamAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PushSecretAzureAuth) DeepCopyInto(out *PushSecretAzureAuth) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretAzureAuth. +func (in *PushSecretAzureAuth) DeepCopy() *PushSecretAzureAuth { + if in == nil { + return nil + } + out := new(PushSecretAzureAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PushSecretGcpIamAuth) DeepCopyInto(out *PushSecretGcpIamAuth) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretGcpIamAuth. +func (in *PushSecretGcpIamAuth) DeepCopy() *PushSecretGcpIamAuth { + if in == nil { + return nil + } + out := new(PushSecretGcpIamAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PushSecretGcpIdTokenAuth) DeepCopyInto(out *PushSecretGcpIdTokenAuth) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretGcpIdTokenAuth. +func (in *PushSecretGcpIdTokenAuth) DeepCopy() *PushSecretGcpIdTokenAuth { + if in == nil { + return nil + } + out := new(PushSecretGcpIdTokenAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PushSecretKubernetesAuth) DeepCopyInto(out *PushSecretKubernetesAuth) { + *out = *in + out.ServiceAccountRef = in.ServiceAccountRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretKubernetesAuth. +func (in *PushSecretKubernetesAuth) DeepCopy() *PushSecretKubernetesAuth { + if in == nil { + return nil + } + out := new(PushSecretKubernetesAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PushSecretTlsConfig) DeepCopyInto(out *PushSecretTlsConfig) { + *out = *in + out.CaRef = in.CaRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretTlsConfig. +func (in *PushSecretTlsConfig) DeepCopy() *PushSecretTlsConfig { + if in == nil { + return nil + } + out := new(PushSecretTlsConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PushSecretUniversalAuth) DeepCopyInto(out *PushSecretUniversalAuth) { + *out = *in + out.CredentialsRef = in.CredentialsRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretUniversalAuth. +func (in *PushSecretUniversalAuth) DeepCopy() *PushSecretUniversalAuth { + if in == nil { + return nil + } + out := new(PushSecretUniversalAuth) + 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 + out.Secret = in.Secret +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretPush. +func (in *SecretPush) DeepCopy() *SecretPush { + if in == nil { + return nil + } + out := new(SecretPush) + 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 diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml new file mode 100644 index 000000000..a4ee1e21f --- /dev/null +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml @@ -0,0 +1,265 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: infisicalpushsecrets.secrets.infisical.com +spec: + group: secrets.infisical.com + names: + kind: InfisicalPushSecret + listKind: InfisicalPushSecretList + plural: infisicalpushsecrets + singular: infisicalpushsecret + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: InfisicalPushSecret is the Schema for the infisicalpushsecrets + API + 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: InfisicalPushSecretSpec defines the desired state of InfisicalPushSecret + properties: + authentication: + properties: + awsIamAuth: + properties: + identityId: + type: string + required: + - identityId + type: object + azureAuth: + properties: + identityId: + type: string + resource: + type: string + required: + - identityId + type: object + gcpIamAuth: + properties: + identityId: + type: string + serviceAccountKeyFilePath: + type: string + required: + - identityId + - serviceAccountKeyFilePath + type: object + gcpIdTokenAuth: + properties: + identityId: + type: string + required: + - identityId + type: object + kubernetesAuth: + description: Rest of your types should be defined similarly... + properties: + identityId: + type: string + serviceAccountRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + required: + - identityId + - serviceAccountRef + type: object + universalAuth: + description: PushSecretUniversalAuth defines universal authentication + properties: + credentialsRef: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret + is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - credentialsRef + type: object + type: object + deletionPolicy: + type: string + destination: + properties: + envSlug: + type: string + projectId: + type: string + secretsPath: + type: string + required: + - envSlug + - projectId + - secretsPath + type: object + hostAPI: + description: Infisical host to pull secrets from + type: string + push: + properties: + secret: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is + located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - secret + type: object + resyncInterval: + type: string + tls: + properties: + caRef: + description: Reference to secret containing CA cert + properties: + key: + description: The name of the secret property with the CA certificate + value + type: string + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The namespace where the Kubernetes Secret is + located + type: string + required: + - key + - secretName + - secretNamespace + type: object + type: object + updatePolicy: + type: string + required: + - destination + - push + - resyncInterval + type: object + status: + description: InfisicalPushSecretStatus defines the observed state of InfisicalPushSecret + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + managedSecrets: + additionalProperties: + type: string + description: managed secrets is a map where the key is the ID, and + the value is the secret key (string[id], string[key] ) + type: object + required: + - conditions + - managedSecrets + 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 ab2a736e1..bb7b81e6b 100644 --- a/k8-operator/config/crd/kustomization.yaml +++ b/k8-operator/config/crd/kustomization.yaml @@ -2,7 +2,8 @@ # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default resources: -- bases/secrets.infisical.com_infisicalsecrets.yaml + - bases/secrets.infisical.com_infisicalsecrets.yaml + - bases/secrets.infisical.com_infisicalpushsecrets.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -18,4 +19,4 @@ patchesStrategicMerge: # the following config is for teaching kustomize how to do kustomization for CRDs. configurations: -- kustomizeconfig.yaml + - kustomizeconfig.yaml diff --git a/k8-operator/config/rbac/role.yaml b/k8-operator/config/rbac/role.yaml index 10c2af414..00fab4a89 100644 --- a/k8-operator/config/rbac/role.yaml +++ b/k8-operator/config/rbac/role.yaml @@ -44,6 +44,32 @@ rules: - list - update - watch +- apiGroups: + - secrets.infisical.com + resources: + - infisicalpushsecrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - secrets.infisical.com + resources: + - infisicalpushsecrets/finalizers + verbs: + - update +- apiGroups: + - secrets.infisical.com + resources: + - infisicalpushsecrets/status + verbs: + - get + - patch + - update - apiGroups: - secrets.infisical.com resources: diff --git a/k8-operator/config/samples/crd/pushsecret/pushSecret.yaml b/k8-operator/config/samples/crd/pushsecret/pushSecret.yaml new file mode 100644 index 000000000..8df2d3da5 --- /dev/null +++ b/k8-operator/config/samples/crd/pushsecret/pushSecret.yaml @@ -0,0 +1,45 @@ +apiVersion: secrets.infisical.com/v1alpha1 +kind: InfisicalPushSecret +metadata: + name: infisical-push-secret-demo +spec: + resyncInterval: 1m + hostAPI: https://app.infisical.com/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: + envSlug: + secretsPath: + + push: + secret: + secretName: push-secret-demo # Secret CRD + secretNamespace: default + + # 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/sourceSecret.yaml b/k8-operator/config/samples/crd/pushsecret/sourceSecret.yaml new file mode 100644 index 000000000..6a3703a95 --- /dev/null +++ b/k8-operator/config/samples/crd/pushsecret/sourceSecret.yaml @@ -0,0 +1,9 @@ +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/conditions.go b/k8-operator/controllers/infisicalpushsecret/conditions.go new file mode 100644 index 000000000..bd1851eb2 --- /dev/null +++ b/k8-operator/controllers/infisicalpushsecret/conditions.go @@ -0,0 +1,155 @@ +package controllers + +import ( + "context" + + "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (r *InfisicalPushSecretReconciler) SetSuccessfullyReconciledConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, err error) error { + + if infisicalPushSecret.Status.Conditions == nil { + infisicalPushSecret.Status.Conditions = []metav1.Condition{} + } + + if err != nil { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/SuccessfullyReconciled", + Status: metav1.ConditionTrue, + Reason: "Error", + Message: "Reconcile failed, secrets were not pushed to Infisical. Check operator logs for more info", + }) + } else { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/SuccessfullyReconciled", + Status: metav1.ConditionFalse, + Reason: "OK", + Message: "Reconcile succeeded, secrets were pushed to Infisical", + }) + } + + return r.Client.Status().Update(ctx, infisicalPushSecret) + +} + +func (r *InfisicalPushSecretReconciler) SetFailedToReplaceSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { + if infisicalPushSecret.Status.Conditions == nil { + infisicalPushSecret.Status.Conditions = []metav1.Condition{} + } + + if failMessage != "" { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/FailedToReplaceSecrets", + Status: metav1.ConditionTrue, + Reason: "Error", + Message: failMessage, + }) + } else { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/FailedToReplaceSecrets", + Status: metav1.ConditionFalse, + Reason: "OK", + Message: "No errors, no secrets failed to be replaced", + }) + } + + return r.Client.Status().Update(ctx, infisicalPushSecret) +} + +func (r *InfisicalPushSecretReconciler) SetFailedToCreateSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { + if infisicalPushSecret.Status.Conditions == nil { + infisicalPushSecret.Status.Conditions = []metav1.Condition{} + } + + if failMessage != "" { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/FailedToCreateSecrets", + Status: metav1.ConditionTrue, + Reason: "Error", + Message: failMessage, + }) + } else { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/FailedToCreateSecrets", + Status: metav1.ConditionFalse, + Reason: "OK", + Message: "No errors, no secrets failed to be created", + }) + } + + return r.Client.Status().Update(ctx, infisicalPushSecret) +} + +func (r *InfisicalPushSecretReconciler) SetFailedToUpdateSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { + if infisicalPushSecret.Status.Conditions == nil { + infisicalPushSecret.Status.Conditions = []metav1.Condition{} + } + + if failMessage != "" { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/FailedToUpdateSecrets", + Status: metav1.ConditionTrue, + Reason: "Error", + Message: failMessage, + }) + } else { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/FailedToUpdateSecrets", + Status: metav1.ConditionFalse, + Reason: "OK", + Message: "No errors, no secrets failed to be updated", + }) + } + + return r.Client.Status().Update(ctx, infisicalPushSecret) +} + +func (r *InfisicalPushSecretReconciler) SetFailedToDeleteSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { + if infisicalPushSecret.Status.Conditions == nil { + infisicalPushSecret.Status.Conditions = []metav1.Condition{} + } + + if failMessage != "" { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/FailedToDeleteSecrets", + Status: metav1.ConditionTrue, + Reason: "Error", + Message: failMessage, + }) + } else { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/FailedToDeleteSecrets", + Status: metav1.ConditionFalse, + Reason: "OK", + Message: "No errors, no secrets failed to be deleted", + }) + } + + return r.Client.Status().Update(ctx, infisicalPushSecret) +} + +func (r *InfisicalPushSecretReconciler) SetAuthenticatedConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, errorToConditionOn error) error { + if infisicalPushSecret.Status.Conditions == nil { + infisicalPushSecret.Status.Conditions = []metav1.Condition{} + } + + if errorToConditionOn != nil { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/Authenticated", + Status: metav1.ConditionFalse, + Reason: "Error", + Message: "Failed to authenticate with Infisical API. This can be caused by invalid service token or an invalid API host that is set. Check operator logs for more info", + }) + } else { + meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/Authenticated", + Status: metav1.ConditionTrue, + Reason: "OK", + Message: "Successfully authenticated with Infisical API", + }) + } + + return r.Client.Status().Update(ctx, infisicalPushSecret) +} diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go new file mode 100644 index 000000000..f52f27881 --- /dev/null +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go @@ -0,0 +1,233 @@ +package controllers + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + "github.com/Infisical/infisical/k8-operator/packages/api" + "github.com/Infisical/infisical/k8-operator/packages/constants" + controllerhelpers "github.com/Infisical/infisical/k8-operator/packages/controllerutil" + "github.com/Infisical/infisical/k8-operator/packages/util" + "github.com/go-logr/logr" +) + +// InfisicalSecretReconciler reconciles a InfisicalSecret object +type InfisicalPushSecretReconciler struct { + client.Client + + BaseLogger logr.Logger + Scheme *runtime.Scheme +} + +var resourceVariablesMap map[string]util.ResourceVariables + +func (r *InfisicalPushSecretReconciler) GetLogger(req ctrl.Request) logr.Logger { + return r.BaseLogger.WithValues("infisicalpushsecret", req.NamespacedName) +} + +//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalpushsecrets,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalpushsecrets/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalpushsecrets/finalizers,verbs=update +//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;delete +//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;delete +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;get;update +//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch + +// 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: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile + +func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + + logger := r.GetLogger(req) + + var infisicalPushSecretCR secretsv1alpha1.InfisicalPushSecret + requeueTime := time.Minute // seconds + + if resourceVariablesMap == nil { + resourceVariablesMap = make(map[string]util.ResourceVariables) + } + + err := r.Get(ctx, req.NamespacedName, &infisicalPushSecretCR) + if err != nil { + if errors.IsNotFound(err) { + logger.Info("Infisical Push Secret CRD not found") + return ctrl.Result{ + Requeue: false, + }, nil + } else { + logger.Error(err, "Unable to fetch Infisical Secret CRD from cluster") + return ctrl.Result{ + RequeueAfter: requeueTime, + }, nil + } + } + + // Add finalizer if it doesn't exist + if !controllerutil.ContainsFinalizer(&infisicalPushSecretCR, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) { + controllerutil.AddFinalizer(&infisicalPushSecretCR, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) + if err := r.Update(ctx, &infisicalPushSecretCR); err != nil { + return ctrl.Result{}, err + } + } + + // Check if it's being deleted + if !infisicalPushSecretCR.DeletionTimestamp.IsZero() { + logger.Info("Handling deletion of InfisicalPushSecret") + if controllerutil.ContainsFinalizer(&infisicalPushSecretCR, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) { + // We remove finalizers before running deletion logic to be completely safe from stuck resources + infisicalPushSecretCR.ObjectMeta.Finalizers = []string{} + if err := r.Update(ctx, &infisicalPushSecretCR); err != nil { + logger.Error(err, fmt.Sprintf("Error removing finalizers from InfisicalPushSecret %s", infisicalPushSecretCR.Name)) + return ctrl.Result{}, err + } + + if err := r.DeleteManagedSecrets(ctx, logger, infisicalPushSecretCR); err != nil { + return ctrl.Result{}, err // Even if this fails, we still want to delete the CRD + } + + } + return ctrl.Result{}, nil + } + + if infisicalPushSecretCR.Spec.ResyncInterval != "" { + + duration, err := util.ConvertResyncIntervalToDuration(infisicalPushSecretCR.Spec.ResyncInterval) + + if err != 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 + } + + requeueTime = duration + + 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 + if infisicalPushSecretCR.GetDeletionTimestamp() != nil { + return ctrl.Result{ + Requeue: false, + }, nil + } + + // 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 infisicalPushSecretCR.Spec.HostAPI == "" { + api.API_HOST_URL = infisicalConfig["hostAPI"] + } else { + api.API_HOST_URL = infisicalPushSecretCR.Spec.HostAPI + } + + if infisicalPushSecretCR.Spec.TLS.CaRef.SecretName != "" { + api.API_CA_CERTIFICATE, err = r.getInfisicalCaCertificateFromKubeSecret(ctx, infisicalPushSecretCR) + 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 + } + + fmt.Println("Using custom CA certificate...") + } else { + api.API_CA_CERTIFICATE = "" + } + + err = r.ReconcileInfisicalPushSecret(ctx, logger, infisicalPushSecretCR) + r.SetSuccessfullyReconciledConditions(ctx, &infisicalPushSecretCR, 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 + } + + // Sync again after the specified time + logger.Info(fmt.Sprintf("Operator will requeue after [%v]", requeueTime)) + return ctrl.Result{ + RequeueAfter: requeueTime, + }, nil +} + +func (r *InfisicalPushSecretReconciler) SetupWithManager(mgr ctrl.Manager) error { + + // Custom predicate that allows both spec changes and deletions + specChangeOrDelete := predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + // Only reconcile if spec/generation changed + return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() + }, + DeleteFunc: func(e event.DeleteEvent) bool { + // Always reconcile on deletion + return true + }, + CreateFunc: func(e event.CreateEvent) bool { + // Reconcile on creation + return true + }, + GenericFunc: func(e event.GenericEvent) bool { + // Ignore generic events + return false + }, + } + + return ctrl.NewControllerManagedBy(mgr). + For(&secretsv1alpha1.InfisicalPushSecret{}, builder.WithPredicates( + specChangeOrDelete, + )). + 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 + }), + ). + Complete(r) +} diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go new file mode 100644 index 000000000..a13452b69 --- /dev/null +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go @@ -0,0 +1,473 @@ +package controllers + +import ( + "context" + "errors" + "fmt" + "strings" + + "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/util" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + infisicalSdk "github.com/infisical/go-sdk" + k8Errors "k8s.io/apimachinery/pkg/api/errors" +) + +func (r *InfisicalPushSecretReconciler) handleAuthentication(ctx context.Context, infisicalSecret v1alpha1.InfisicalPushSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (util.AuthenticationDetails, error) { + authStrategies := map[util.AuthStrategyType]func(ctx context.Context, reconcilerClient client.Client, secretCrd util.SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (util.AuthenticationDetails, error){ + util.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY: util.HandleUniversalAuth, + util.AuthStrategy.KUBERNETES_MACHINE_IDENTITY: util.HandleKubernetesAuth, + util.AuthStrategy.AWS_IAM_MACHINE_IDENTITY: util.HandleAwsIamAuth, + util.AuthStrategy.AZURE_MACHINE_IDENTITY: util.HandleAzureAuth, + util.AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY: util.HandleGcpIdTokenAuth, + util.AuthStrategy.GCP_IAM_MACHINE_IDENTITY: util.HandleGcpIamAuth, + } + + for authStrategy, authHandler := range authStrategies { + authDetails, err := authHandler(ctx, r.Client, util.SecretAuthInput{ + Secret: infisicalSecret, + Type: util.SecretCrd.INFISICAL_PUSH_SECRET, + }, infisicalClient) + + if err == nil { + return authDetails, nil + } + + if !errors.Is(err, util.ErrAuthNotApplicable) { + return util.AuthenticationDetails{}, fmt.Errorf("authentication failed for strategy [%s] [err=%w]", authStrategy, err) + } + } + + return util.AuthenticationDetails{}, fmt.Errorf("no authentication method provided") + +} + +func (r *InfisicalPushSecretReconciler) getInfisicalCaCertificateFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalPushSecret) (caCertificate string, err error) { + + caCertificateFromKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{ + Namespace: infisicalSecret.Spec.TLS.CaRef.SecretNamespace, + Name: infisicalSecret.Spec.TLS.CaRef.SecretName, + }) + + if k8Errors.IsNotFound(err) { + return "", fmt.Errorf("kubernetes secret containing custom CA certificate cannot be found. [err=%s]", err) + } + + if err != nil { + return "", fmt.Errorf("something went wrong when fetching your CA certificate [err=%s]", err) + } + + caCertificateFromSecret := string(caCertificateFromKubeSecret.Data[infisicalSecret.Spec.TLS.CaRef.SecretKey]) + + return caCertificateFromSecret, nil +} + +func (r *InfisicalPushSecretReconciler) getResourceVariables(infisicalPushSecret v1alpha1.InfisicalPushSecret) util.ResourceVariables { + + var resourceVariables util.ResourceVariables + + if _, ok := resourceVariablesMap[string(infisicalPushSecret.UID)]; !ok { + + ctx, cancel := context.WithCancel(context.Background()) + + client := infisicalSdk.NewInfisicalClient(ctx, infisicalSdk.Config{ + SiteUrl: api.API_HOST_URL, + CaCertificate: api.API_CA_CERTIFICATE, + UserAgent: api.USER_AGENT_NAME, + }) + + resourceVariablesMap[string(infisicalPushSecret.UID)] = util.ResourceVariables{ + InfisicalClient: client, + CancelCtx: cancel, + AuthDetails: util.AuthenticationDetails{}, + } + + resourceVariables = resourceVariablesMap[string(infisicalPushSecret.UID)] + + } else { + resourceVariables = resourceVariablesMap[string(infisicalPushSecret.UID)] + } + + return resourceVariables + +} + +func (r *InfisicalPushSecretReconciler) updateResourceVariables(infisicalPushSecret v1alpha1.InfisicalPushSecret, resourceVariables util.ResourceVariables) { + resourceVariablesMap[string(infisicalPushSecret.UID)] = resourceVariables +} + +func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context.Context, logger logr.Logger, infisicalPushSecret v1alpha1.InfisicalPushSecret) error { + + 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.SetAuthenticatedConditions(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, + }) + } + + 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) + } + + var kubeSecrets = make(map[string]string) + + for key, value := range kubePushSecret.Data { + kubeSecrets[key] = string(value) + } + + destination := infisicalPushSecret.Spec.Destination + existingSecrets, err := infisicalClient.Secrets().List(infisicalSdk.ListSecretsOptions{ + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + IncludeImports: false, + }) + + existingSecretsContainsKey := func(key string) bool { + for _, secret := range existingSecrets { + if secret.SecretKey == key { + return true + } + } + return false + } + + getExistingSecretByKey := func(key string) *infisicalSdk.Secret { + for _, secret := range existingSecrets { + if secret.SecretKey == key { + return &secret + } + } + return nil + } + + getExistingSecretById := func(id string) *infisicalSdk.Secret { + for _, secret := range existingSecrets { + if secret.ID == id { + return &secret + } + } + return nil + } + + if err != nil { + return fmt.Errorf("unable to list secrets [err=%s]", err) + } + + updatePolicy := infisicalPushSecret.Spec.UpdatePolicy + + var secretsFailedToCreate []string + var secretsFailedToUpdate []string + var secretsFailedToDelete []string + var secretsFailedToReplaceById []string + + // If the ManagedSecrets are nil, we know this is the first time the InfisicalPushSecret is being reconciled. + if infisicalPushSecret.Status.ManagedSecrets == nil { + + infisicalPushSecret.Status.ManagedSecrets = make(map[string]string) // (string[id], string[key] ) + + for secretKey, secretValue := range kubeSecrets { + if existingSecretsContainsKey(secretKey) { + if updatePolicy == string(constants.PUSH_SECRET_REPLACE_POLICY_ENABLED) { + updatedSecret, err := infisicalClient.Secrets().Update(infisicalSdk.UpdateSecretOptions{ + SecretKey: secretKey, + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + NewSecretValue: secretValue, + }) + + if err != nil { + secretsFailedToUpdate = append(secretsFailedToUpdate, secretKey) + logger.Info(fmt.Sprintf("unable to update secret [key=%s] [err=%s]", secretKey, err)) + continue + } + + infisicalPushSecret.Status.ManagedSecrets[updatedSecret.ID] = secretKey + } + } else { + createdSecret, err := infisicalClient.Secrets().Create(infisicalSdk.CreateSecretOptions{ + SecretKey: secretKey, + SecretValue: secretValue, + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + }) + + if err != nil { + secretsFailedToCreate = append(secretsFailedToCreate, secretKey) + logger.Info(fmt.Sprintf("unable to create secret [key=%s] [err=%s]", secretKey, err)) + continue + } + + infisicalPushSecret.Status.ManagedSecrets[createdSecret.ID] = secretKey + } + } + } else { + + // Loop over all the managed secrets, and find the corresponding existingSecret that has the same ID. If the key doesn't match, delete the secret, and re-create it with the correct key/value + for managedSecretId, managedSecretKey := range infisicalPushSecret.Status.ManagedSecrets { + + existingSecret := getExistingSecretById(managedSecretId) + + if existingSecret != nil { + + if existingSecret.SecretKey != managedSecretKey { + // Secret key has changed, lets delete the secret and re-create it with the correct key + + logger.Info(fmt.Sprintf("Secret with ID [id=%s] has changed key from [%s] to [%s]. Deleting and re-creating secret", managedSecretId, managedSecretKey, existingSecret.SecretKey)) + + deletedSecret, err := infisicalClient.Secrets().Delete(infisicalSdk.DeleteSecretOptions{ + SecretKey: existingSecret.SecretKey, + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + }) + + if err != nil { + secretsFailedToReplaceById = append(secretsFailedToReplaceById, managedSecretKey) + logger.Info(fmt.Sprintf("unable to delete secret [key=%s] [err=%s]", managedSecretKey, err)) + continue + } + + createdSecret, err := infisicalClient.Secrets().Create(infisicalSdk.CreateSecretOptions{ + SecretKey: managedSecretKey, + SecretValue: existingSecret.SecretValue, + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + }) + + if err != nil { + secretsFailedToReplaceById = append(secretsFailedToReplaceById, managedSecretKey) + logger.Info(fmt.Sprintf("unable to create secret [key=%s] [err=%s]", managedSecretKey, err)) + continue + } + + delete(infisicalPushSecret.Status.ManagedSecrets, deletedSecret.ID) + infisicalPushSecret.Status.ManagedSecrets[createdSecret.ID] = managedSecretKey + } + + } + } + + // 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 { + + // Secret has been removed, verify that the secret is managed by the operator + if getExistingSecretByKey(managedSecretKey) != nil { + logger.Info(fmt.Sprintf("Secret with key [key=%s] has been removed from the kube secret. Deleting secret from Infisical", managedSecretKey)) + + deletedSecret, err := infisicalClient.Secrets().Delete(infisicalSdk.DeleteSecretOptions{ + SecretKey: managedSecretKey, + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + }) + + if err != nil { + secretsFailedToDelete = append(secretsFailedToDelete, managedSecretKey) + logger.Info(fmt.Sprintf("unable to delete secret [key=%s] [err=%s]", managedSecretKey, err)) + continue + } + + delete(infisicalPushSecret.Status.ManagedSecrets, deletedSecret.ID) + } + } + } + + // We need to check if any new secrets have been added in the kube secret + for currentSecretKey := range kubeSecrets { + + if !existingSecretsContainsKey(currentSecretKey) { + + // Some secrets has been added, verify that the secret that has been added is not already managed by the operator + if _, ok := infisicalPushSecret.Status.ManagedSecrets[currentSecretKey]; !ok { + + // Secret was not managed by the operator, lets add it + logger.Info(fmt.Sprintf("Secret with key [key=%s] has been added to the kube secret. Creating secret in Infisical", currentSecretKey)) + + createdSecret, err := infisicalClient.Secrets().Create(infisicalSdk.CreateSecretOptions{ + SecretKey: currentSecretKey, + SecretValue: kubeSecrets[currentSecretKey], + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + }) + + if err != nil { + secretsFailedToCreate = append(secretsFailedToCreate, currentSecretKey) + logger.Info(fmt.Sprintf("unable to create secret [key=%s] [err=%s]", currentSecretKey, err)) + continue + } + + infisicalPushSecret.Status.ManagedSecrets[createdSecret.ID] = currentSecretKey + } + } else { + if updatePolicy == string(constants.PUSH_SECRET_REPLACE_POLICY_ENABLED) { + updatedSecret, err := infisicalClient.Secrets().Update(infisicalSdk.UpdateSecretOptions{ + SecretKey: currentSecretKey, + NewSecretValue: kubeSecrets[currentSecretKey], + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + }) + + if err != nil { + secretsFailedToUpdate = append(secretsFailedToUpdate, currentSecretKey) + logger.Info(fmt.Sprintf("unable to update secret [key=%s] [err=%s]", currentSecretKey, err)) + continue + } + + infisicalPushSecret.Status.ManagedSecrets[updatedSecret.ID] = currentSecretKey + } + } + } + + // Check if any of the existing secrets values have changed + for secretKey, secretValue := range kubeSecrets { + + existingSecret := getExistingSecretByKey(secretKey) + + if existingSecret != nil { + + _, managedByOperator := infisicalPushSecret.Status.ManagedSecrets[existingSecret.ID] + + if secretValue != existingSecret.SecretValue { + + if managedByOperator || updatePolicy == string(constants.PUSH_SECRET_REPLACE_POLICY_ENABLED) { + logger.Info(fmt.Sprintf("Secret with key [key=%s] has changed value. Updating secret in Infisical", secretKey)) + + updatedSecret, err := infisicalClient.Secrets().Update(infisicalSdk.UpdateSecretOptions{ + SecretKey: secretKey, + NewSecretValue: secretValue, + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + }) + + if err != nil { + secretsFailedToUpdate = append(secretsFailedToUpdate, secretKey) + logger.Info(fmt.Sprintf("unable to update secret [key=%s] [err=%s]", secretKey, err)) + continue + } + + infisicalPushSecret.Status.ManagedSecrets[updatedSecret.ID] = secretKey + } + } + } + } + } + + var errorMessage string + if len(secretsFailedToCreate) > 0 { + errorMessage = fmt.Sprintf("Failed to create secrets: [%s]", strings.Join(secretsFailedToCreate, ", ")) + } else { + errorMessage = "" + } + r.SetFailedToCreateSecretsConditions(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to create secrets: [%s]", errorMessage)) + + if len(secretsFailedToUpdate) > 0 { + errorMessage = fmt.Sprintf("Failed to update secrets: [%s]", strings.Join(secretsFailedToUpdate, ", ")) + } else { + errorMessage = "" + } + r.SetFailedToUpdateSecretsConditions(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to update secrets: [%s]", errorMessage)) + + if len(secretsFailedToDelete) > 0 { + errorMessage = fmt.Sprintf("Failed to delete secrets: [%s]", strings.Join(secretsFailedToDelete, ", ")) + } else { + errorMessage = "" + } + r.SetFailedToDeleteSecretsConditions(ctx, &infisicalPushSecret, errorMessage) + + if len(secretsFailedToReplaceById) > 0 { + errorMessage = fmt.Sprintf("Failed to replace secrets: [%s]", strings.Join(secretsFailedToReplaceById, ", ")) + } else { + errorMessage = "" + } + r.SetFailedToReplaceSecretsConditions(ctx, &infisicalPushSecret, errorMessage) + + // Update the status of the InfisicalPushSecret + if err := r.Client.Status().Update(ctx, &infisicalPushSecret); err != nil { + return fmt.Errorf("unable to update status of InfisicalPushSecret [err=%s]", err) + } + + return nil + +} + +func (r *InfisicalPushSecretReconciler) DeleteManagedSecrets(ctx context.Context, logger logr.Logger, infisicalPushSecret v1alpha1.InfisicalPushSecret) error { + if infisicalPushSecret.Spec.DeletionPolicy != string(constants.PUSH_SECRET_DELETE_POLICY_ENABLED) { + return nil + } + + resourceVariables := r.getResourceVariables(infisicalPushSecret) + infisicalClient := resourceVariables.InfisicalClient + + destination := infisicalPushSecret.Spec.Destination + existingSecrets, err := infisicalClient.Secrets().List(infisicalSdk.ListSecretsOptions{ + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + IncludeImports: false, + }) + + if err != nil { + return fmt.Errorf("unable to list secrets [err=%s]", err) + } + + existingSecretsMappedById := make(map[string]infisicalSdk.Secret) + for _, secret := range existingSecrets { + existingSecretsMappedById[secret.ID] = secret + } + + for managedSecretId, managedSecretKey := range infisicalPushSecret.Status.ManagedSecrets { + + if _, ok := existingSecretsMappedById[managedSecretId]; ok { + logger.Info(fmt.Sprintf("Deleting secret with key [key=%s]", managedSecretKey)) + + _, err := infisicalClient.Secrets().Delete(infisicalSdk.DeleteSecretOptions{ + SecretKey: managedSecretKey, + ProjectID: destination.ProjectID, + Environment: destination.EnvSlug, + SecretPath: destination.SecretsPath, + }) + + if err != nil { + logger.Info(fmt.Sprintf("unable to delete secret [key=%s] [err=%s]", managedSecretKey, err)) + continue + } + } + + } + + return nil +} diff --git a/k8-operator/controllers/auto_redeployment.go b/k8-operator/controllers/infisicalsecret/auto_redeployment.go similarity index 81% rename from k8-operator/controllers/auto_redeployment.go rename to k8-operator/controllers/infisicalsecret/auto_redeployment.go index cd5bf193b..599e126b5 100644 --- a/k8-operator/controllers/auto_redeployment.go +++ b/k8-operator/controllers/infisicalsecret/auto_redeployment.go @@ -6,6 +6,8 @@ import ( "sync" "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + "github.com/Infisical/infisical/k8-operator/packages/constants" + "github.com/go-logr/logr" v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -15,7 +17,7 @@ import ( const DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX = "secrets.infisical.com/managed-secret" const AUTO_RELOAD_DEPLOYMENT_ANNOTATION = "secrets.infisical.com/auto-reload" // needs to be set to true for a deployment to start auto redeploying -func (r *InfisicalSecretReconciler) ReconcileDeploymentsWithManagedSecrets(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (int, error) { +func (r *InfisicalSecretReconciler) ReconcileDeploymentsWithManagedSecrets(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret) (int, error) { listOfDeployments := &v1.DeploymentList{} err := r.Client.List(ctx, listOfDeployments, &client.ListOptions{Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace}) if err != nil { @@ -42,8 +44,8 @@ func (r *InfisicalSecretReconciler) ReconcileDeploymentsWithManagedSecrets(ctx c wg.Add(1) go func(d v1.Deployment, s corev1.Secret) { defer wg.Done() - if err := r.ReconcileDeployment(ctx, d, s); err != nil { - fmt.Printf("unable to reconcile deployment with [name=%v]. Will try next requeue", deployment.ObjectMeta.Name) + if err := r.ReconcileDeployment(ctx, logger, d, s); err != nil { + logger.Error(err, fmt.Sprintf("unable to reconcile deployment with [name=%v]. Will try next requeue", deployment.ObjectMeta.Name)) } }(deployment, *managedKubeSecret) } @@ -80,17 +82,17 @@ func (r *InfisicalSecretReconciler) IsDeploymentUsingManagedSecret(deployment v1 // This function ensures that a deployment is in sync with a Kubernetes secret by comparing their versions. // If the version of the secret is different from the version annotation on the deployment, the annotation is updated to trigger a restart of the deployment. -func (r *InfisicalSecretReconciler) ReconcileDeployment(ctx context.Context, deployment v1.Deployment, secret corev1.Secret) error { +func (r *InfisicalSecretReconciler) ReconcileDeployment(ctx context.Context, logger logr.Logger, deployment v1.Deployment, secret corev1.Secret) error { annotationKey := fmt.Sprintf("%s.%s", DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX, secret.Name) - annotationValue := secret.Annotations[SECRET_VERSION_ANNOTATION] + annotationValue := secret.Annotations[constants.SECRET_VERSION_ANNOTATION] if deployment.Annotations[annotationKey] == annotationValue && deployment.Spec.Template.Annotations[annotationKey] == annotationValue { - fmt.Printf("The [deploymentName=%v] is already using the most up to date managed secrets. No action required.\n", deployment.ObjectMeta.Name) + logger.Info(fmt.Sprintf("The [deploymentName=%v] is already using the most up to date managed secrets. No action required.", deployment.ObjectMeta.Name)) return nil } - fmt.Printf("deployment is using outdated managed secret. Starting re-deployment [deploymentName=%v]\n", deployment.ObjectMeta.Name) + logger.Info(fmt.Sprintf("Deployment is using outdated managed secret. Starting re-deployment [deploymentName=%v]", deployment.ObjectMeta.Name)) if deployment.Spec.Template.Annotations == nil { deployment.Spec.Template.Annotations = make(map[string]string) diff --git a/k8-operator/controllers/conditions.go b/k8-operator/controllers/infisicalsecret/conditions.go similarity index 86% rename from k8-operator/controllers/conditions.go rename to k8-operator/controllers/infisicalsecret/conditions.go index 312b1d699..7ac3a9e5f 100644 --- a/k8-operator/controllers/conditions.go +++ b/k8-operator/controllers/infisicalsecret/conditions.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + "github.com/Infisical/infisical/k8-operator/packages/util" + "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -40,7 +42,7 @@ func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context. return r.Client.Status().Update(ctx, infisicalSecret) } -func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, authStrategy AuthStrategyType, errorToConditionOn error) { +func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, logger logr.Logger, infisicalSecret *v1alpha1.InfisicalSecret, authStrategy util.AuthStrategyType, errorToConditionOn error) { if infisicalSecret.Status.Conditions == nil { infisicalSecret.Status.Conditions = []metav1.Condition{} } @@ -63,11 +65,11 @@ func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.C err := r.Client.Status().Update(ctx, infisicalSecret) if err != nil { - fmt.Println("Could not set condition for LoadedInfisicalToken") + logger.Error(err, "Could not set condition for LoadedInfisicalToken") } } -func (r *InfisicalSecretReconciler) SetInfisicalAutoRedeploymentReady(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, numDeployments int, errorToConditionOn error) { +func (r *InfisicalSecretReconciler) SetInfisicalAutoRedeploymentReady(ctx context.Context, logger logr.Logger, infisicalSecret *v1alpha1.InfisicalSecret, numDeployments int, errorToConditionOn error) { if infisicalSecret.Status.Conditions == nil { infisicalSecret.Status.Conditions = []metav1.Condition{} } @@ -90,6 +92,6 @@ func (r *InfisicalSecretReconciler) SetInfisicalAutoRedeploymentReady(ctx contex err := r.Client.Status().Update(ctx, infisicalSecret) if err != nil { - fmt.Println("Could not set condition for AutoRedeployReady") + logger.Error(err, "Could not set condition for AutoRedeployReady") } } diff --git a/k8-operator/controllers/infisicalsecret_controller.go b/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go similarity index 64% rename from k8-operator/controllers/infisicalsecret_controller.go rename to k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go index 90baf2396..8b5508858 100644 --- a/k8-operator/controllers/infisicalsecret_controller.go +++ b/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go @@ -15,15 +15,20 @@ import ( secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1" "github.com/Infisical/infisical/k8-operator/packages/api" - infisicalSdk "github.com/infisical/go-sdk" + controllerhelpers "github.com/Infisical/infisical/k8-operator/packages/controllerutil" + "github.com/Infisical/infisical/k8-operator/packages/util" + "github.com/go-logr/logr" ) // InfisicalSecretReconciler reconciles a InfisicalSecret object type InfisicalSecretReconciler struct { client.Client - Scheme *runtime.Scheme + BaseLogger logr.Logger + Scheme *runtime.Scheme } +var resourceVariablesMap map[string]util.ResourceVariables = make(map[string]util.ResourceVariables) + //+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets/status,verbs=get;update;patch //+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets/finalizers,verbs=update @@ -37,21 +42,26 @@ type InfisicalSecretReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile -type ResourceVariables struct { - infisicalClient infisicalSdk.InfisicalClientInterface - cancelCtx context.CancelFunc - authDetails AuthenticationDetails -} - const FINALIZER_NAME = "secrets.finalizers.infisical.com" // Maps the infisicalSecretCR.UID to a infisicalSdk.InfisicalClientInterface and AuthenticationDetails. -var resourceVariablesMap = make(map[string]ResourceVariables) +// var resourceVariablesMap = make(map[string]ResourceVariables) + +func (r *InfisicalSecretReconciler) GetLogger(req ctrl.Request) logr.Logger { + return r.BaseLogger.WithValues("infisicalsecret", req.NamespacedName) +} func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + + logger := r.GetLogger(req) + var infisicalSecretCR secretsv1alpha1.InfisicalSecret requeueTime := time.Minute // seconds + if resourceVariablesMap == nil { + resourceVariablesMap = make(map[string]util.ResourceVariables) + } + err := r.Get(ctx, req.NamespacedName, &infisicalSecretCR) if err != nil { if errors.IsNotFound(err) { @@ -59,7 +69,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ Requeue: false, }, nil } else { - fmt.Printf("\nUnable to fetch Infisical Secret CRD from cluster because [err=%v]", err) + logger.Error(err, "unable to fetch Infisical Secret CRD from cluster") return ctrl.Result{ RequeueAfter: requeueTime, }, nil @@ -71,7 +81,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ if !infisicalSecretCR.ObjectMeta.DeletionTimestamp.IsZero() && len(infisicalSecretCR.ObjectMeta.Finalizers) > 0 { infisicalSecretCR.ObjectMeta.Finalizers = []string{} if err := r.Update(ctx, &infisicalSecretCR); err != nil { - fmt.Printf("Error removing finalizers from Infisical Secret %s: %v\n", infisicalSecretCR.Name, err) + logger.Error(err, fmt.Sprintf("Error removing finalizers from Infisical Secret %s", infisicalSecretCR.Name)) return ctrl.Result{}, err } // Our finalizers have been removed, so the reconciler can do nothing. @@ -80,9 +90,10 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ if infisicalSecretCR.Spec.ResyncInterval != 0 { requeueTime = time.Second * time.Duration(infisicalSecretCR.Spec.ResyncInterval) - fmt.Printf("\nManual re-sync interval set. Interval: %v\n", requeueTime) + logger.Info(fmt.Sprintf("Manual re-sync interval set. Interval: %v", requeueTime)) + } else { - fmt.Printf("\nRe-sync interval set. Interval: %v\n", requeueTime) + logger.Info(fmt.Sprintf("Re-sync interval set. Interval: %v", requeueTime)) } // Check if the resource is already marked for deletion @@ -93,9 +104,9 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ } // Get modified/default config - infisicalConfig, err := r.GetInfisicalConfigMap(ctx) + infisicalConfig, err := controllerhelpers.GetInfisicalConfigMap(ctx, r.Client) if err != nil { - fmt.Printf("unable to fetch infisical-config [err=%s]. Will requeue after [requeueTime=%v]\n", err, requeueTime) + logger.Error(err, fmt.Sprintf("unable to fetch infisical-config. Will requeue after [requeueTime=%v]", requeueTime)) return ctrl.Result{ RequeueAfter: requeueTime, }, nil @@ -108,40 +119,41 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ } if infisicalSecretCR.Spec.TLS.CaRef.SecretName != "" { - api.API_CA_CERTIFICATE, err = r.GetInfisicalCaCertificateFromKubeSecret(ctx, infisicalSecretCR) + api.API_CA_CERTIFICATE, err = r.getInfisicalCaCertificateFromKubeSecret(ctx, infisicalSecretCR) if err != nil { - fmt.Printf("unable to fetch CA certificate [err=%s]. Will requeue after [requeueTime=%v]\n", err, requeueTime) + logger.Error(err, fmt.Sprintf("unable to fetch CA certificate. Will requeue after [requeueTime=%v]", requeueTime)) return ctrl.Result{ RequeueAfter: requeueTime, }, nil } - fmt.Println("Using custom CA certificate...") + logger.Info("Using custom CA certificate...") } else { api.API_CA_CERTIFICATE = "" } - err = r.ReconcileInfisicalSecret(ctx, infisicalSecretCR) + err = r.ReconcileInfisicalSecret(ctx, logger, infisicalSecretCR) r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCR, err) if err != nil { - fmt.Printf("unable to reconcile Infisical Secret because [err=%v]. Will requeue after [requeueTime=%v]\n", err, requeueTime) + + logger.Error(err, fmt.Sprintf("unable to reconcile InfisicalSecret. Will requeue after [requeueTime=%v]", requeueTime)) return ctrl.Result{ RequeueAfter: requeueTime, }, nil } - numDeployments, err := r.ReconcileDeploymentsWithManagedSecrets(ctx, infisicalSecretCR) - r.SetInfisicalAutoRedeploymentReady(ctx, &infisicalSecretCR, numDeployments, err) + numDeployments, err := r.ReconcileDeploymentsWithManagedSecrets(ctx, logger, infisicalSecretCR) + r.SetInfisicalAutoRedeploymentReady(ctx, logger, &infisicalSecretCR, numDeployments, err) if err != nil { - fmt.Printf("unable to reconcile auto redeployment because [err=%v]", err) + logger.Error(err, fmt.Sprintf("unable to reconcile auto redeployment. Will requeue after [requeueTime=%v]", requeueTime)) return ctrl.Result{ RequeueAfter: requeueTime, }, nil } // Sync again after the specified time - fmt.Printf("Operator will requeue after [%v] \n", requeueTime) + logger.Info(fmt.Sprintf("Operator will requeue after [%v]", requeueTime)) return ctrl.Result{ RequeueAfter: requeueTime, }, nil @@ -151,16 +163,20 @@ func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&secretsv1alpha1.InfisicalSecret{}, builder.WithPredicates(predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { - if rv, ok := resourceVariablesMap[string(e.ObjectNew.GetUID())]; ok { - rv.cancelCtx() - delete(resourceVariablesMap, string(e.ObjectNew.GetUID())) + if resourceVariablesMap != nil { + if rv, ok := resourceVariablesMap[string(e.ObjectNew.GetUID())]; ok { + rv.CancelCtx() + delete(resourceVariablesMap, string(e.ObjectNew.GetUID())) + } } return true }, DeleteFunc: func(e event.DeleteEvent) bool { - if rv, ok := resourceVariablesMap[string(e.Object.GetUID())]; ok { - rv.cancelCtx() - delete(resourceVariablesMap, string(e.Object.GetUID())) + if resourceVariablesMap != nil { + if rv, ok := resourceVariablesMap[string(e.Object.GetUID())]; ok { + rv.CancelCtx() + delete(resourceVariablesMap, string(e.Object.GetUID())) + } } return true }, diff --git a/k8-operator/controllers/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go similarity index 57% rename from k8-operator/controllers/infisicalsecret_helper.go rename to k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go index cdf2a4a26..7b9f8be93 100644 --- a/k8-operator/controllers/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go @@ -10,8 +10,10 @@ import ( "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/util" + "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/types" @@ -20,113 +22,61 @@ import ( k8Errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" ) -const SERVICE_ACCOUNT_ACCESS_KEY = "serviceAccountAccessKey" -const SERVICE_ACCOUNT_PUBLIC_KEY = "serviceAccountPublicKey" -const SERVICE_ACCOUNT_PRIVATE_KEY = "serviceAccountPrivateKey" - -const INFISICAL_MACHINE_IDENTITY_CLIENT_ID = "clientId" -const INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET = "clientSecret" - -const INFISICAL_TOKEN_SECRET_KEY_NAME = "infisicalToken" -const SECRET_VERSION_ANNOTATION = "secrets.infisical.com/version" // used to set the version of secrets via Etag -const OPERATOR_SETTINGS_CONFIGMAP_NAME = "infisical-config" -const OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE = "infisical-operator-system" -const INFISICAL_DOMAIN = "https://app.infisical.com/api" - -func (r *InfisicalSecretReconciler) HandleAuthentication(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { +func (r *InfisicalSecretReconciler) handleAuthentication(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (util.AuthenticationDetails, error) { // ? Legacy support, service token auth - infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret) + infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret) if err != nil { - return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err) + return util.AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err) } if infisicalToken != "" { infisicalClient.Auth().SetAccessToken(infisicalToken) - return AuthenticationDetails{authStrategy: AuthStrategy.SERVICE_TOKEN}, nil + return util.AuthenticationDetails{AuthStrategy: util.AuthStrategy.SERVICE_TOKEN}, nil } // ? Legacy support, service account auth - serviceAccountCreds, err := r.GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret) + serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret) if err != nil { - return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err) + return util.AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err) } if serviceAccountCreds.AccessKey != "" || serviceAccountCreds.PrivateKey != "" || serviceAccountCreds.PublicKey != "" { infisicalClient.Auth().SetAccessToken(serviceAccountCreds.AccessKey) - return AuthenticationDetails{authStrategy: AuthStrategy.SERVICE_ACCOUNT}, nil + return util.AuthenticationDetails{AuthStrategy: util.AuthStrategy.SERVICE_ACCOUNT}, nil } - authStrategies := map[AuthStrategyType]func(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error){ - AuthStrategy.UNIVERSAL_MACHINE_IDENTITY: r.handleUniversalAuth, - AuthStrategy.KUBERNETES_MACHINE_IDENTITY: r.handleKubernetesAuth, - AuthStrategy.AWS_IAM_MACHINE_IDENTITY: r.handleAwsIamAuth, - AuthStrategy.AZURE_MACHINE_IDENTITY: r.handleAzureAuth, - AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY: r.handleGcpIdTokenAuth, - AuthStrategy.GCP_IAM_MACHINE_IDENTITY: r.handleGcpIamAuth, + authStrategies := map[util.AuthStrategyType]func(ctx context.Context, reconcilerClient client.Client, secretCrd util.SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (util.AuthenticationDetails, error){ + util.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY: util.HandleUniversalAuth, + util.AuthStrategy.KUBERNETES_MACHINE_IDENTITY: util.HandleKubernetesAuth, + util.AuthStrategy.AWS_IAM_MACHINE_IDENTITY: util.HandleAwsIamAuth, + util.AuthStrategy.AZURE_MACHINE_IDENTITY: util.HandleAzureAuth, + util.AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY: util.HandleGcpIdTokenAuth, + util.AuthStrategy.GCP_IAM_MACHINE_IDENTITY: util.HandleGcpIamAuth, } for authStrategy, authHandler := range authStrategies { - authDetails, err := authHandler(ctx, infisicalSecret, infisicalClient) + authDetails, err := authHandler(ctx, r.Client, util.SecretAuthInput{ + Secret: infisicalSecret, + Type: util.SecretCrd.INFISICAL_SECRET, + }, infisicalClient) if err == nil { return authDetails, nil } - if !errors.Is(err, ErrAuthNotApplicable) { - return AuthenticationDetails{}, fmt.Errorf("authentication failed for strategy [%s] [err=%w]", authStrategy, err) + if !errors.Is(err, util.ErrAuthNotApplicable) { + return util.AuthenticationDetails{}, fmt.Errorf("authentication failed for strategy [%s] [err=%w]", authStrategy, err) } } - return AuthenticationDetails{}, fmt.Errorf("no authentication method provided") + return util.AuthenticationDetails{}, fmt.Errorf("no authentication method provided") } -func (r *InfisicalSecretReconciler) GetInfisicalConfigMap(ctx context.Context) (configMap map[string]string, errToReturn error) { - // default key values - defaultConfigMapData := make(map[string]string) - defaultConfigMapData["hostAPI"] = INFISICAL_DOMAIN - - kubeConfigMap := &corev1.ConfigMap{} - err := r.Client.Get(ctx, types.NamespacedName{ - Namespace: OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE, - Name: OPERATOR_SETTINGS_CONFIGMAP_NAME, - }, kubeConfigMap) - - if err != nil { - if k8Errors.IsNotFound(err) { - kubeConfigMap = nil - } else { - return nil, fmt.Errorf("GetConfigMapByNamespacedName: unable to fetch config map in [namespacedName=%s] [err=%s]", OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE, err) - } - } - - if kubeConfigMap == nil { - return defaultConfigMapData, nil - } else { - for key, value := range defaultConfigMapData { - _, exists := kubeConfigMap.Data[key] - if !exists { - kubeConfigMap.Data[key] = value - } - } - - return kubeConfigMap.Data, nil - } -} - -func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Context, namespacedName types.NamespacedName) (*corev1.Secret, error) { - kubeSecret := &corev1.Secret{} - err := r.Client.Get(ctx, namespacedName, kubeSecret) - if err != nil { - kubeSecret = nil - } - - return kubeSecret, err -} - -func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) { +func (r *InfisicalSecretReconciler) getInfisicalTokenFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) { // default to new secret ref structure secretName := infisicalSecret.Spec.Authentication.ServiceToken.ServiceTokenSecretReference.SecretName secretNamespace := infisicalSecret.Spec.Authentication.ServiceToken.ServiceTokenSecretReference.SecretNamespace @@ -139,7 +89,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context. secretNamespace = infisicalSecret.Spec.TokenSecretReference.SecretNamespace } - tokenSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{ + tokenSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{ Namespace: secretNamespace, Name: secretName, }) @@ -152,36 +102,14 @@ func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context. return "", fmt.Errorf("failed to read Infisical token secret from secret named [%s] in namespace [%s]: with error [%w]", infisicalSecret.Spec.TokenSecretReference.SecretName, infisicalSecret.Spec.TokenSecretReference.SecretNamespace, err) } - infisicalServiceToken := tokenSecret.Data[INFISICAL_TOKEN_SECRET_KEY_NAME] + infisicalServiceToken := tokenSecret.Data[constants.INFISICAL_TOKEN_SECRET_KEY_NAME] return strings.Replace(string(infisicalServiceToken), " ", "", -1), nil } -func (r *InfisicalSecretReconciler) GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (machineIdentityDetails model.MachineIdentityDetails, err error) { +func (r *InfisicalSecretReconciler) getInfisicalCaCertificateFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (caCertificate string, err error) { - universalAuthCredsFromKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{ - Namespace: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretNamespace, - Name: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretName, - }) - - if k8Errors.IsNotFound(err) { - return model.MachineIdentityDetails{}, nil - } - - if err != nil { - return model.MachineIdentityDetails{}, fmt.Errorf("something went wrong when fetching your machine identity credentials [err=%s]", err) - } - - clientIdFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_ID] - clientSecretFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET] - - return model.MachineIdentityDetails{ClientId: string(clientIdFromSecret), ClientSecret: string(clientSecretFromSecret)}, nil - -} - -func (r *InfisicalSecretReconciler) GetInfisicalCaCertificateFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (caCertificate string, err error) { - - caCertificateFromKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{ + caCertificateFromKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{ Namespace: infisicalSecret.Spec.TLS.CaRef.SecretNamespace, Name: infisicalSecret.Spec.TLS.CaRef.SecretName, }) @@ -197,13 +125,12 @@ func (r *InfisicalSecretReconciler) GetInfisicalCaCertificateFromKubeSecret(ctx caCertificateFromSecret := string(caCertificateFromKubeSecret.Data[infisicalSecret.Spec.TLS.CaRef.SecretKey]) return caCertificateFromSecret, nil - } // Fetches service account credentials from a Kubernetes secret specified in the infisicalSecret object, extracts the access key, public key, and private key from the secret, and returns them as a ServiceAccountCredentials object. // If any keys are missing or an error occurs, returns an empty object or an error object, respectively. -func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (serviceAccountDetails model.ServiceAccountDetails, err error) { - serviceAccountCredsFromKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{ +func (r *InfisicalSecretReconciler) getInfisicalServiceAccountCredentialsFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (serviceAccountDetails model.ServiceAccountDetails, err error) { + serviceAccountCredsFromKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{ Namespace: infisicalSecret.Spec.Authentication.ServiceAccount.ServiceAccountSecretReference.SecretNamespace, Name: infisicalSecret.Spec.Authentication.ServiceAccount.ServiceAccountSecretReference.SecretName, }) @@ -216,9 +143,9 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub return model.ServiceAccountDetails{}, fmt.Errorf("something went wrong when fetching your service account credentials [err=%s]", err) } - accessKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[SERVICE_ACCOUNT_ACCESS_KEY] - publicKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[SERVICE_ACCOUNT_PUBLIC_KEY] - privateKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[SERVICE_ACCOUNT_PRIVATE_KEY] + accessKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[constants.SERVICE_ACCOUNT_ACCESS_KEY] + publicKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[constants.SERVICE_ACCOUNT_PUBLIC_KEY] + privateKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[constants.SERVICE_ACCOUNT_PRIVATE_KEY] if accessKeyFromSecret == nil || publicKeyFromSecret == nil || privateKeyFromSecret == nil { return model.ServiceAccountDetails{}, nil @@ -227,7 +154,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub return model.ServiceAccountDetails{AccessKey: string(accessKeyFromSecret), PrivateKey: string(privateKeyFromSecret), PublicKey: string(publicKeyFromSecret)}, nil } -func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { +func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { plainProcessedSecrets := make(map[string][]byte) secretType := infisicalSecret.Spec.ManagedSecretReference.SecretType managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template @@ -283,7 +210,7 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context } } - annotations[SECRET_VERSION_ANNOTATION] = ETag + annotations[constants.SECRET_VERSION_ANNOTATION] = ETag // create a new secret as specified by the managed secret spec of CRD newKubeSecretInstance := &corev1.Secret{ @@ -310,11 +237,11 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context return fmt.Errorf("unable to create the managed Kubernetes secret : %w", err) } - fmt.Printf("Successfully created a managed Kubernetes secret with your Infisical secrets. Type: %s\n", secretType) + logger.Info(fmt.Sprintf("Successfully created a managed Kubernetes secret with your Infisical secrets. Type: %s", secretType)) return nil } -func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { +func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error { managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template plainProcessedSecrets := make(map[string][]byte) @@ -354,20 +281,20 @@ func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context } managedKubeSecret.Data = plainProcessedSecrets - managedKubeSecret.ObjectMeta.Annotations[SECRET_VERSION_ANNOTATION] = ETag + managedKubeSecret.ObjectMeta.Annotations[constants.SECRET_VERSION_ANNOTATION] = ETag err := r.Client.Update(ctx, &managedKubeSecret) if err != nil { return fmt.Errorf("unable to update Kubernetes secret because [%w]", err) } - fmt.Println("successfully updated managed Kubernetes secret") + logger.Info("successfully updated managed Kubernetes secret") return nil } -func (r *InfisicalSecretReconciler) GetResourceVariables(infisicalSecret v1alpha1.InfisicalSecret) ResourceVariables { +func (r *InfisicalSecretReconciler) getResourceVariables(infisicalSecret v1alpha1.InfisicalSecret) util.ResourceVariables { - var resourceVariables ResourceVariables + var resourceVariables util.ResourceVariables if _, ok := resourceVariablesMap[string(infisicalSecret.UID)]; !ok { @@ -379,10 +306,10 @@ func (r *InfisicalSecretReconciler) GetResourceVariables(infisicalSecret v1alpha UserAgent: api.USER_AGENT_NAME, }) - resourceVariablesMap[string(infisicalSecret.UID)] = ResourceVariables{ - infisicalClient: client, - cancelCtx: cancel, - authDetails: AuthenticationDetails{}, + resourceVariablesMap[string(infisicalSecret.UID)] = util.ResourceVariables{ + InfisicalClient: client, + CancelCtx: cancel, + AuthDetails: util.AuthenticationDetails{}, } resourceVariables = resourceVariablesMap[string(infisicalSecret.UID)] @@ -395,36 +322,36 @@ func (r *InfisicalSecretReconciler) GetResourceVariables(infisicalSecret v1alpha } -func (r *InfisicalSecretReconciler) UpdateResourceVariables(infisicalSecret v1alpha1.InfisicalSecret, resourceVariables ResourceVariables) { +func (r *InfisicalSecretReconciler) updateResourceVariables(infisicalSecret v1alpha1.InfisicalSecret, resourceVariables util.ResourceVariables) { resourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables } -func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error { +func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret) error { - resourceVariables := r.GetResourceVariables(infisicalSecret) - infisicalClient := resourceVariables.infisicalClient - cancelCtx := resourceVariables.cancelCtx - authDetails := resourceVariables.authDetails + resourceVariables := r.getResourceVariables(infisicalSecret) + infisicalClient := resourceVariables.InfisicalClient + cancelCtx := resourceVariables.CancelCtx + authDetails := resourceVariables.AuthDetails var err error - if authDetails.authStrategy == "" { - fmt.Println("ReconcileInfisicalSecret: No authentication strategy found. Attempting to authenticate") - authDetails, err = r.HandleAuthentication(ctx, infisicalSecret, infisicalClient) - r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, authDetails.authStrategy, err) + if authDetails.AuthStrategy == "" { + logger.Info("No authentication strategy found. Attempting to authenticate") + authDetails, err = r.handleAuthentication(ctx, infisicalSecret, infisicalClient) + r.SetInfisicalTokenLoadCondition(ctx, logger, &infisicalSecret, authDetails.AuthStrategy, err) if err != nil { return fmt.Errorf("unable to authenticate [err=%s]", err) } - r.UpdateResourceVariables(infisicalSecret, ResourceVariables{ - infisicalClient: infisicalClient, - cancelCtx: cancelCtx, - authDetails: authDetails, + r.updateResourceVariables(infisicalSecret, util.ResourceVariables{ + InfisicalClient: infisicalClient, + CancelCtx: cancelCtx, + AuthDetails: authDetails, }) } // Look for managed secret by name and namespace - managedKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{ + managedKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{ Name: infisicalSecret.Spec.ManagedSecretReference.SecretName, Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace, }) @@ -436,14 +363,14 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context // Get exiting Etag if exists secretVersionBasedOnETag := "" if managedKubeSecret != nil { - secretVersionBasedOnETag = managedKubeSecret.Annotations[SECRET_VERSION_ANNOTATION] + secretVersionBasedOnETag = managedKubeSecret.Annotations[constants.SECRET_VERSION_ANNOTATION] } var plainTextSecretsFromApi []model.SingleEnvironmentVariable var updateDetails model.RequestUpdateUpdateDetails - if authDetails.authStrategy == AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method - serviceAccountCreds, err := r.GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret) + if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method + serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret) if err != nil { return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err) } @@ -453,10 +380,10 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context return fmt.Errorf("\nfailed to get secrets because [err=%v]", err) } - fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service account") + logger.Info("ReconcileInfisicalSecret: Fetched secrets via service account") - } else if authDetails.authStrategy == AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method - infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret) + } else if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method + infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret) if err != nil { return fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err) } @@ -470,28 +397,30 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context return fmt.Errorf("\nfailed to get secrets because [err=%v]", err) } - fmt.Println("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]") - } else if authDetails.isMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point - plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.machineIdentityScope) + logger.Info("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]") + + } else if authDetails.IsMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point + plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.MachineIdentityScope) if err != nil { return fmt.Errorf("\nfailed to get secrets because [err=%v]", err) } - fmt.Printf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]\n", authDetails.authStrategy) + + logger.Info(fmt.Sprintf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]", authDetails.AuthStrategy)) } else { return errors.New("no authentication method provided yet. Please configure a authentication method then try again") } if !updateDetails.Modified { - fmt.Println("No secrets modified so reconcile not needed") + logger.Info("ReconcileInfisicalSecret: No secrets modified so reconcile not needed") return nil } if managedKubeSecret == nil { - return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, updateDetails.ETag) + return r.createInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, plainTextSecretsFromApi, updateDetails.ETag) } else { - return r.UpdateInfisicalManagedKubeSecret(ctx, infisicalSecret, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag) + return r.updateInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag) } } diff --git a/k8-operator/controllers/suite_test.go b/k8-operator/controllers/infisicalsecret/suite_test.go similarity index 100% rename from k8-operator/controllers/suite_test.go rename to k8-operator/controllers/infisicalsecret/suite_test.go diff --git a/k8-operator/controllers/infisicalsecret_auth.go b/k8-operator/controllers/infisicalsecret_auth.go deleted file mode 100644 index 06e1c659a..000000000 --- a/k8-operator/controllers/infisicalsecret_auth.go +++ /dev/null @@ -1,150 +0,0 @@ -package controllers - -import ( - "context" - "errors" - "fmt" - - "github.com/Infisical/infisical/k8-operator/api/v1alpha1" - "github.com/Infisical/infisical/k8-operator/packages/util" - infisicalSdk "github.com/infisical/go-sdk" -) - -type AuthStrategyType string - -var AuthStrategy = struct { - SERVICE_TOKEN AuthStrategyType - SERVICE_ACCOUNT AuthStrategyType - UNIVERSAL_MACHINE_IDENTITY AuthStrategyType - KUBERNETES_MACHINE_IDENTITY AuthStrategyType - AWS_IAM_MACHINE_IDENTITY AuthStrategyType - AZURE_MACHINE_IDENTITY AuthStrategyType - GCP_ID_TOKEN_MACHINE_IDENTITY AuthStrategyType - GCP_IAM_MACHINE_IDENTITY AuthStrategyType -}{ - SERVICE_TOKEN: "SERVICE_TOKEN", - SERVICE_ACCOUNT: "SERVICE_ACCOUNT", - UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY", - KUBERNETES_MACHINE_IDENTITY: "KUBERNETES_AUTH_MACHINE_IDENTITY", - AWS_IAM_MACHINE_IDENTITY: "AWS_IAM_MACHINE_IDENTITY", - AZURE_MACHINE_IDENTITY: "AZURE_MACHINE_IDENTITY", - GCP_ID_TOKEN_MACHINE_IDENTITY: "GCP_ID_TOKEN_MACHINE_IDENTITY", - GCP_IAM_MACHINE_IDENTITY: "GCP_IAM_MACHINE_IDENTITY", -} - -type AuthenticationDetails struct { - authStrategy AuthStrategyType - machineIdentityScope v1alpha1.MachineIdentityScopeInWorkspace // This will only be set if a machine identity auth method is used (e.g. UniversalAuth or KubernetesAuth, etc.) - isMachineIdentityAuth bool -} - -var ErrAuthNotApplicable = errors.New("authentication not applicable") - -func (r *InfisicalSecretReconciler) handleUniversalAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { - - // Machine Identities: - universalAuthKubeSecret, err := r.GetInfisicalUniversalAuthFromKubeSecret(ctx, infisicalSecret) - universalAuthSpec := infisicalSecret.Spec.Authentication.UniversalAuth - - if err != nil { - return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get machine identity creds from kube secret [err=%s]", err) - } - - if universalAuthKubeSecret.ClientId == "" && universalAuthKubeSecret.ClientSecret == "" { - return AuthenticationDetails{}, ErrAuthNotApplicable - } - - _, err = infisicalClient.Auth().UniversalAuthLogin(universalAuthKubeSecret.ClientId, universalAuthKubeSecret.ClientSecret) - if err != nil { - return AuthenticationDetails{}, fmt.Errorf("unable to login with machine identity credentials [err=%s]", err) - } - - fmt.Println("Successfully authenticated with machine identity credentials") - - return AuthenticationDetails{authStrategy: AuthStrategy.UNIVERSAL_MACHINE_IDENTITY, machineIdentityScope: universalAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil - -} - -func (r *InfisicalSecretReconciler) handleKubernetesAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { - kubernetesAuthSpec := infisicalSecret.Spec.Authentication.KubernetesAuth - - if kubernetesAuthSpec.IdentityID == "" { - return AuthenticationDetails{}, ErrAuthNotApplicable - } - - serviceAccountToken, err := util.GetServiceAccountToken(r.Client, kubernetesAuthSpec.ServiceAccountRef.Namespace, kubernetesAuthSpec.ServiceAccountRef.Name) - if err != nil { - return AuthenticationDetails{}, fmt.Errorf("unable to get service account token [err=%s]", err) - } - - _, err = infisicalClient.Auth().KubernetesRawServiceAccountTokenLogin(kubernetesAuthSpec.IdentityID, serviceAccountToken) - if err != nil { - return AuthenticationDetails{}, fmt.Errorf("unable to login with Kubernetes native auth [err=%s]", err) - } - - return AuthenticationDetails{authStrategy: AuthStrategy.KUBERNETES_MACHINE_IDENTITY, machineIdentityScope: kubernetesAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil - -} - -func (r *InfisicalSecretReconciler) handleAwsIamAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { - awsIamAuthSpec := infisicalSecret.Spec.Authentication.AwsIamAuth - - if awsIamAuthSpec.IdentityID == "" { - return AuthenticationDetails{}, ErrAuthNotApplicable - } - - _, err := infisicalClient.Auth().AwsIamAuthLogin(awsIamAuthSpec.IdentityID) - if err != nil { - return AuthenticationDetails{}, fmt.Errorf("unable to login with AWS IAM auth [err=%s]", err) - } - - return AuthenticationDetails{authStrategy: AuthStrategy.AWS_IAM_MACHINE_IDENTITY, machineIdentityScope: awsIamAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil - -} - -func (r *InfisicalSecretReconciler) handleAzureAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { - azureAuthSpec := infisicalSecret.Spec.Authentication.AzureAuth - - if azureAuthSpec.IdentityID == "" { - return AuthenticationDetails{}, ErrAuthNotApplicable - } - - _, err := infisicalClient.Auth().AzureAuthLogin(azureAuthSpec.IdentityID, azureAuthSpec.Resource) // If resource is empty(""), it will default to "https://management.azure.com/" in the SDK. - if err != nil { - return AuthenticationDetails{}, fmt.Errorf("unable to login with Azure auth [err=%s]", err) - } - - return AuthenticationDetails{authStrategy: AuthStrategy.AZURE_MACHINE_IDENTITY, machineIdentityScope: azureAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil - -} - -func (r *InfisicalSecretReconciler) handleGcpIdTokenAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { - gcpIdTokenSpec := infisicalSecret.Spec.Authentication.GcpIdTokenAuth - - if gcpIdTokenSpec.IdentityID == "" { - return AuthenticationDetails{}, ErrAuthNotApplicable - } - - _, err := infisicalClient.Auth().GcpIdTokenAuthLogin(gcpIdTokenSpec.IdentityID) - if err != nil { - return AuthenticationDetails{}, fmt.Errorf("unable to login with GCP Id Token auth [err=%s]", err) - } - - return AuthenticationDetails{authStrategy: AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY, machineIdentityScope: gcpIdTokenSpec.SecretsScope, isMachineIdentityAuth: true}, nil - -} - -func (r *InfisicalSecretReconciler) handleGcpIamAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { - gcpIamSpec := infisicalSecret.Spec.Authentication.GcpIamAuth - - if gcpIamSpec.IdentityID == "" && gcpIamSpec.ServiceAccountKeyFilePath == "" { - return AuthenticationDetails{}, ErrAuthNotApplicable - } - - _, err := infisicalClient.Auth().GcpIamAuthLogin(gcpIamSpec.IdentityID, gcpIamSpec.ServiceAccountKeyFilePath) - if err != nil { - return AuthenticationDetails{}, fmt.Errorf("unable to login with GCP IAM auth [err=%s]", err) - } - - return AuthenticationDetails{authStrategy: AuthStrategy.GCP_IAM_MACHINE_IDENTITY, machineIdentityScope: gcpIamSpec.SecretsScope, isMachineIdentityAuth: true}, nil -} diff --git a/k8-operator/kubectl-install/install-secrets-operator.yaml b/k8-operator/kubectl-install/install-secrets-operator.yaml index 79a926805..26d924c76 100644 --- a/k8-operator/kubectl-install/install-secrets-operator.yaml +++ b/k8-operator/kubectl-install/install-secrets-operator.yaml @@ -13,6 +13,215 @@ metadata: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null + name: infisicalpushsecrets.secrets.infisical.com +spec: + group: secrets.infisical.com + names: + kind: InfisicalPushSecret + listKind: InfisicalPushSecretList + plural: infisicalpushsecrets + singular: infisicalpushsecret + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: InfisicalPushSecret is the Schema for the infisicalpushsecrets API + 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: InfisicalPushSecretSpec defines the desired state of InfisicalPushSecret + properties: + authentication: + properties: + awsIamAuth: + properties: + identityId: + type: string + required: + - identityId + type: object + azureAuth: + properties: + identityId: + type: string + resource: + type: string + required: + - identityId + type: object + gcpIamAuth: + properties: + identityId: + type: string + serviceAccountKeyFilePath: + type: string + required: + - identityId + - serviceAccountKeyFilePath + type: object + gcpIdTokenAuth: + properties: + identityId: + type: string + required: + - identityId + type: object + kubernetesAuth: + description: Rest of your types should be defined similarly... + properties: + identityId: + type: string + serviceAccountRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + required: + - identityId + - serviceAccountRef + type: object + universalAuth: + description: PushSecretUniversalAuth defines universal authentication + properties: + credentialsRef: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - credentialsRef + type: object + type: object + deletionPolicy: + type: string + destination: + properties: + envSlug: + type: string + projectId: + type: string + secretsPath: + type: string + required: + - envSlug + - projectId + - secretsPath + type: object + hostAPI: + type: string + push: + properties: + secret: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - secret + type: object + resyncInterval: + type: string + updatePolicy: + type: string + required: + - destination + - push + - resyncInterval + type: object + status: + description: InfisicalPushSecretStatus defines the observed state of InfisicalPushSecret + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + managedSecrets: + additionalProperties: + type: string + description: managed secrets is a map where the key is the ID, and the value is the secret key (string[id], string[key] ) + type: object + required: + - conditions + - managedSecrets + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.10.0 diff --git a/k8-operator/main.go b/k8-operator/main.go index 4fb64c7f4..4184c8218 100644 --- a/k8-operator/main.go +++ b/k8-operator/main.go @@ -16,7 +16,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1" - "github.com/Infisical/infisical/k8-operator/controllers" + infisicalPushSecretController "github.com/Infisical/infisical/k8-operator/controllers/infisicalpushsecret" + infisicalSecretController "github.com/Infisical/infisical/k8-operator/controllers/infisicalsecret" //+kubebuilder:scaffold:imports ) @@ -81,13 +82,24 @@ func main() { os.Exit(1) } - if err = (&controllers.InfisicalSecretReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + if err = (&infisicalSecretController.InfisicalSecretReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + BaseLogger: ctrl.Log, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "InfisicalSecret") os.Exit(1) } + + if err = (&infisicalPushSecretController.InfisicalPushSecretReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + BaseLogger: ctrl.Log, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "InfisicalPushSecret") + os.Exit(1) + } + //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/k8-operator/packages/api/api.go b/k8-operator/packages/api/api.go index dd2af3353..de12b4bc5 100644 --- a/k8-operator/packages/api/api.go +++ b/k8-operator/packages/api/api.go @@ -24,10 +24,6 @@ func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDeta return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unsuccessful response: [response=%s]", response) } - // logging for better debugging and user experience - fmt.Printf("Workspace ID: %v\n", tokenDetailsResponse.Workspace) - fmt.Printf("TokenName: %v\n", tokenDetailsResponse.Name) - return tokenDetailsResponse, nil } diff --git a/k8-operator/packages/constants/constants.go b/k8-operator/packages/constants/constants.go new file mode 100644 index 000000000..ff28e408d --- /dev/null +++ b/k8-operator/packages/constants/constants.go @@ -0,0 +1,24 @@ +package constants + +const SERVICE_ACCOUNT_ACCESS_KEY = "serviceAccountAccessKey" +const SERVICE_ACCOUNT_PUBLIC_KEY = "serviceAccountPublicKey" +const SERVICE_ACCOUNT_PRIVATE_KEY = "serviceAccountPrivateKey" + +const INFISICAL_MACHINE_IDENTITY_CLIENT_ID = "clientId" +const INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET = "clientSecret" + +const INFISICAL_TOKEN_SECRET_KEY_NAME = "infisicalToken" +const SECRET_VERSION_ANNOTATION = "secrets.infisical.com/version" // used to set the version of secrets via Etag +const OPERATOR_SETTINGS_CONFIGMAP_NAME = "infisical-config" +const OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE = "infisical-operator-system" +const INFISICAL_DOMAIN = "https://app.infisical.com/api" + +const INFISICAL_PUSH_SECRET_FINALIZER_NAME = "infisical.secrets.infisical.com/finalizer" + +type PushSecretReplacePolicy string +type PushSecretDeletionPolicy string + +const ( + PUSH_SECRET_REPLACE_POLICY_ENABLED PushSecretReplacePolicy = "Replace" + PUSH_SECRET_DELETE_POLICY_ENABLED PushSecretDeletionPolicy = "Delete" +) diff --git a/k8-operator/packages/controllerutil/util.go b/k8-operator/packages/controllerutil/util.go new file mode 100644 index 000000000..8c610e2e5 --- /dev/null +++ b/k8-operator/packages/controllerutil/util.go @@ -0,0 +1,45 @@ +package controllerhelpers + +import ( + "context" + "fmt" + + "github.com/Infisical/infisical/k8-operator/packages/constants" + corev1 "k8s.io/api/core/v1" + k8Errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func GetInfisicalConfigMap(ctx context.Context, client client.Client) (configMap map[string]string, errToReturn error) { + // default key values + defaultConfigMapData := make(map[string]string) + defaultConfigMapData["hostAPI"] = constants.INFISICAL_DOMAIN + + kubeConfigMap := &corev1.ConfigMap{} + err := client.Get(ctx, types.NamespacedName{ + Namespace: constants.OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE, + Name: constants.OPERATOR_SETTINGS_CONFIGMAP_NAME, + }, kubeConfigMap) + + if err != nil { + if k8Errors.IsNotFound(err) { + kubeConfigMap = nil + } else { + return nil, fmt.Errorf("GetConfigMapByNamespacedName: unable to fetch config map in [namespacedName=%s] [err=%s]", constants.OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE, err) + } + } + + if kubeConfigMap == nil { + return defaultConfigMapData, nil + } else { + for key, value := range defaultConfigMapData { + _, exists := kubeConfigMap.Data[key] + if !exists { + kubeConfigMap.Data[key] = value + } + } + + return kubeConfigMap.Data, nil + } +} diff --git a/k8-operator/packages/util/auth.go b/k8-operator/packages/util/auth.go index d3ee0ce3b..d01174277 100644 --- a/k8-operator/packages/util/auth.go +++ b/k8-operator/packages/util/auth.go @@ -4,7 +4,12 @@ import ( "context" "fmt" + "errors" + corev1 "k8s.io/api/core/v1" + + "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + infisicalSdk "github.com/infisical/go-sdk" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -32,3 +37,324 @@ func GetServiceAccountToken(k8sClient client.Client, namespace string, serviceAc return string(token), nil } + +type AuthStrategyType string + +var AuthStrategy = struct { + SERVICE_TOKEN AuthStrategyType + SERVICE_ACCOUNT AuthStrategyType + UNIVERSAL_MACHINE_IDENTITY AuthStrategyType + KUBERNETES_MACHINE_IDENTITY AuthStrategyType + AWS_IAM_MACHINE_IDENTITY AuthStrategyType + AZURE_MACHINE_IDENTITY AuthStrategyType + GCP_ID_TOKEN_MACHINE_IDENTITY AuthStrategyType + GCP_IAM_MACHINE_IDENTITY AuthStrategyType +}{ + SERVICE_TOKEN: "SERVICE_TOKEN", + SERVICE_ACCOUNT: "SERVICE_ACCOUNT", + UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY", + KUBERNETES_MACHINE_IDENTITY: "KUBERNETES_AUTH_MACHINE_IDENTITY", + AWS_IAM_MACHINE_IDENTITY: "AWS_IAM_MACHINE_IDENTITY", + AZURE_MACHINE_IDENTITY: "AZURE_MACHINE_IDENTITY", + GCP_ID_TOKEN_MACHINE_IDENTITY: "GCP_ID_TOKEN_MACHINE_IDENTITY", + GCP_IAM_MACHINE_IDENTITY: "GCP_IAM_MACHINE_IDENTITY", +} + +type SecretCrdType string + +var SecretCrd = struct { + INFISICAL_SECRET SecretCrdType + INFISICAL_PUSH_SECRET SecretCrdType +}{ + INFISICAL_SECRET: "INFISICAL_SECRET", + INFISICAL_PUSH_SECRET: "INFISICAL_PUSH_SECRET", +} + +type SecretAuthInput struct { + Secret interface{} + Type SecretCrdType +} + +type AuthenticationDetails struct { + AuthStrategy AuthStrategyType + MachineIdentityScope v1alpha1.MachineIdentityScopeInWorkspace // This will only be set if a machine identity auth method is used (e.g. UniversalAuth or KubernetesAuth, etc.) + IsMachineIdentityAuth bool + SecretType SecretCrdType +} + +var ErrAuthNotApplicable = errors.New("authentication not applicable") + +func HandleUniversalAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { + + var universalAuthSpec v1alpha1.UniversalAuthDetails + + switch secretCrd.Type { + case SecretCrd.INFISICAL_SECRET: + infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret") + } + universalAuthSpec = infisicalSecret.Spec.Authentication.UniversalAuth + case SecretCrd.INFISICAL_PUSH_SECRET: + infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret") + } + + universalAuthSpec = v1alpha1.UniversalAuthDetails{ + CredentialsRef: infisicalPushSecret.Spec.Authentication.UniversalAuth.CredentialsRef, + SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{}, + } + } + + universalAuthKubeSecret, err := GetInfisicalUniversalAuthFromKubeSecret(ctx, reconcilerClient, v1alpha1.KubeSecretReference{ + SecretNamespace: universalAuthSpec.CredentialsRef.SecretNamespace, + SecretName: universalAuthSpec.CredentialsRef.SecretName, + }) + + if err != nil { + return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get machine identity creds from kube secret [err=%s]", err) + } + + if universalAuthKubeSecret.ClientId == "" && universalAuthKubeSecret.ClientSecret == "" { + return AuthenticationDetails{}, ErrAuthNotApplicable + } + + _, err = infisicalClient.Auth().UniversalAuthLogin(universalAuthKubeSecret.ClientId, universalAuthKubeSecret.ClientSecret) + if err != nil { + return AuthenticationDetails{}, fmt.Errorf("unable to login with machine identity credentials [err=%s]", err) + } + + return AuthenticationDetails{ + AuthStrategy: AuthStrategy.UNIVERSAL_MACHINE_IDENTITY, + MachineIdentityScope: universalAuthSpec.SecretsScope, + IsMachineIdentityAuth: true, + SecretType: secretCrd.Type, + }, nil +} + +func HandleKubernetesAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { + var kubernetesAuthSpec v1alpha1.KubernetesAuthDetails + + switch secretCrd.Type { + case SecretCrd.INFISICAL_SECRET: + infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret") + } + kubernetesAuthSpec = infisicalSecret.Spec.Authentication.KubernetesAuth + case SecretCrd.INFISICAL_PUSH_SECRET: + infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret") + } + kubernetesAuthSpec = v1alpha1.KubernetesAuthDetails{ + IdentityID: infisicalPushSecret.Spec.Authentication.KubernetesAuth.IdentityID, + ServiceAccountRef: v1alpha1.KubernetesServiceAccountRef{ + Namespace: infisicalPushSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Namespace, + Name: infisicalPushSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Name, + }, + SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{}, + } + } + + if kubernetesAuthSpec.IdentityID == "" { + return AuthenticationDetails{}, ErrAuthNotApplicable + } + + serviceAccountToken, err := GetServiceAccountToken(reconcilerClient, kubernetesAuthSpec.ServiceAccountRef.Namespace, kubernetesAuthSpec.ServiceAccountRef.Name) + if err != nil { + return AuthenticationDetails{}, fmt.Errorf("unable to get service account token [err=%s]", err) + } + + _, err = infisicalClient.Auth().KubernetesRawServiceAccountTokenLogin(kubernetesAuthSpec.IdentityID, serviceAccountToken) + if err != nil { + return AuthenticationDetails{}, fmt.Errorf("unable to login with Kubernetes native auth [err=%s]", err) + } + + return AuthenticationDetails{ + AuthStrategy: AuthStrategy.KUBERNETES_MACHINE_IDENTITY, + MachineIdentityScope: kubernetesAuthSpec.SecretsScope, + IsMachineIdentityAuth: true, + SecretType: secretCrd.Type, + }, nil + +} + +func HandleAwsIamAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { + awsIamAuthSpec := v1alpha1.AWSIamAuthDetails{} + + switch secretCrd.Type { + case SecretCrd.INFISICAL_SECRET: + infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret") + } + + awsIamAuthSpec = infisicalSecret.Spec.Authentication.AwsIamAuth + case SecretCrd.INFISICAL_PUSH_SECRET: + infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret") + } + + awsIamAuthSpec = v1alpha1.AWSIamAuthDetails{ + IdentityID: infisicalPushSecret.Spec.Authentication.AwsIamAuth.IdentityID, + SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{}, + } + } + + if awsIamAuthSpec.IdentityID == "" { + return AuthenticationDetails{}, ErrAuthNotApplicable + } + + _, err := infisicalClient.Auth().AwsIamAuthLogin(awsIamAuthSpec.IdentityID) + if err != nil { + return AuthenticationDetails{}, fmt.Errorf("unable to login with AWS IAM auth [err=%s]", err) + } + + return AuthenticationDetails{ + AuthStrategy: AuthStrategy.AWS_IAM_MACHINE_IDENTITY, + MachineIdentityScope: awsIamAuthSpec.SecretsScope, + IsMachineIdentityAuth: true, + SecretType: secretCrd.Type, + }, nil + +} + +func HandleAzureAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { + azureAuthSpec := v1alpha1.AzureAuthDetails{} + + switch secretCrd.Type { + case SecretCrd.INFISICAL_SECRET: + infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret") + } + + azureAuthSpec = infisicalSecret.Spec.Authentication.AzureAuth + + case SecretCrd.INFISICAL_PUSH_SECRET: + infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret") + } + + azureAuthSpec = v1alpha1.AzureAuthDetails{ + IdentityID: infisicalPushSecret.Spec.Authentication.AzureAuth.IdentityID, + Resource: infisicalPushSecret.Spec.Authentication.AzureAuth.Resource, + SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{}, + } + } + + if azureAuthSpec.IdentityID == "" { + return AuthenticationDetails{}, ErrAuthNotApplicable + } + + _, err := infisicalClient.Auth().AzureAuthLogin(azureAuthSpec.IdentityID, azureAuthSpec.Resource) // If resource is empty(""), it will default to "https://management.azure.com/" in the SDK. + if err != nil { + return AuthenticationDetails{}, fmt.Errorf("unable to login with Azure auth [err=%s]", err) + } + + return AuthenticationDetails{ + AuthStrategy: AuthStrategy.AZURE_MACHINE_IDENTITY, + MachineIdentityScope: azureAuthSpec.SecretsScope, + IsMachineIdentityAuth: true, + SecretType: secretCrd.Type, + }, nil + +} + +func HandleGcpIdTokenAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { + gcpIdTokenSpec := v1alpha1.GCPIdTokenAuthDetails{} + + switch secretCrd.Type { + case SecretCrd.INFISICAL_SECRET: + infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret") + } + + gcpIdTokenSpec = infisicalSecret.Spec.Authentication.GcpIdTokenAuth + case SecretCrd.INFISICAL_PUSH_SECRET: + infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret") + } + + gcpIdTokenSpec = v1alpha1.GCPIdTokenAuthDetails{ + IdentityID: infisicalPushSecret.Spec.Authentication.GcpIdTokenAuth.IdentityID, + SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{}, + } + } + + if gcpIdTokenSpec.IdentityID == "" { + return AuthenticationDetails{}, ErrAuthNotApplicable + } + + _, err := infisicalClient.Auth().GcpIdTokenAuthLogin(gcpIdTokenSpec.IdentityID) + if err != nil { + return AuthenticationDetails{}, fmt.Errorf("unable to login with GCP Id Token auth [err=%s]", err) + } + + return AuthenticationDetails{ + AuthStrategy: AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY, + MachineIdentityScope: gcpIdTokenSpec.SecretsScope, + IsMachineIdentityAuth: true, + SecretType: secretCrd.Type, + }, nil + +} + +func HandleGcpIamAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) { + gcpIamSpec := v1alpha1.GcpIamAuthDetails{} + + switch secretCrd.Type { + case SecretCrd.INFISICAL_SECRET: + infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret") + } + + gcpIamSpec = infisicalSecret.Spec.Authentication.GcpIamAuth + case SecretCrd.INFISICAL_PUSH_SECRET: + infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret) + + if !ok { + return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret") + } + + gcpIamSpec = v1alpha1.GcpIamAuthDetails{ + IdentityID: infisicalPushSecret.Spec.Authentication.GcpIamAuth.IdentityID, + ServiceAccountKeyFilePath: infisicalPushSecret.Spec.Authentication.GcpIamAuth.ServiceAccountKeyFilePath, + SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{}, + } + } + + if gcpIamSpec.IdentityID == "" && gcpIamSpec.ServiceAccountKeyFilePath == "" { + return AuthenticationDetails{}, ErrAuthNotApplicable + } + + _, err := infisicalClient.Auth().GcpIamAuthLogin(gcpIamSpec.IdentityID, gcpIamSpec.ServiceAccountKeyFilePath) + if err != nil { + return AuthenticationDetails{}, fmt.Errorf("unable to login with GCP IAM auth [err=%s]", err) + } + + return AuthenticationDetails{ + AuthStrategy: AuthStrategy.GCP_IAM_MACHINE_IDENTITY, + MachineIdentityScope: gcpIamSpec.SecretsScope, + IsMachineIdentityAuth: true, + SecretType: secretCrd.Type, + }, nil +} diff --git a/k8-operator/packages/util/kubernetes.go b/k8-operator/packages/util/kubernetes.go new file mode 100644 index 000000000..6397f4036 --- /dev/null +++ b/k8-operator/packages/util/kubernetes.go @@ -0,0 +1,50 @@ +package util + +import ( + "context" + "fmt" + + "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + "github.com/Infisical/infisical/k8-operator/packages/model" + corev1 "k8s.io/api/core/v1" + k8Errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const INFISICAL_MACHINE_IDENTITY_CLIENT_ID = "clientId" +const INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET = "clientSecret" + +func GetKubeSecretByNamespacedName(ctx context.Context, reconcilerClient client.Client, namespacedName types.NamespacedName) (*corev1.Secret, error) { + kubeSecret := &corev1.Secret{} + err := reconcilerClient.Get(ctx, namespacedName, kubeSecret) + if err != nil { + kubeSecret = nil + } + + return kubeSecret, err +} + +func GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, reconcilerClient client.Client, universalAuthRef v1alpha1.KubeSecretReference) (machineIdentityDetails model.MachineIdentityDetails, err error) { + + universalAuthCredsFromKubeSecret, err := GetKubeSecretByNamespacedName(ctx, reconcilerClient, types.NamespacedName{ + Namespace: universalAuthRef.SecretNamespace, + Name: universalAuthRef.SecretName, + // Namespace: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretNamespace, + // Name: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretName, + }) + + if k8Errors.IsNotFound(err) { + return model.MachineIdentityDetails{}, nil + } + + if err != nil { + return model.MachineIdentityDetails{}, fmt.Errorf("something went wrong when fetching your machine identity credentials [err=%s]", err) + } + + clientIdFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_ID] + clientSecretFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET] + + return model.MachineIdentityDetails{ClientId: string(clientIdFromSecret), ClientSecret: string(clientSecretFromSecret)}, nil + +} diff --git a/k8-operator/packages/util/models.go b/k8-operator/packages/util/models.go new file mode 100644 index 000000000..8030731c2 --- /dev/null +++ b/k8-operator/packages/util/models.go @@ -0,0 +1,13 @@ +package util + +import ( + "context" + + infisicalSdk "github.com/infisical/go-sdk" +) + +type ResourceVariables struct { + InfisicalClient infisicalSdk.InfisicalClientInterface + CancelCtx context.CancelFunc + AuthDetails AuthenticationDetails +} diff --git a/k8-operator/packages/util/time.go b/k8-operator/packages/util/time.go new file mode 100644 index 000000000..0b78a16a6 --- /dev/null +++ b/k8-operator/packages/util/time.go @@ -0,0 +1,40 @@ +package util + +import ( + "fmt" + "strconv" + "time" +) + +func ConvertResyncIntervalToDuration(resyncInterval string) (time.Duration, error) { + length := len(resyncInterval) + if length < 2 { + return 0, fmt.Errorf("invalid format") + } + + unit := resyncInterval[length-1:] + numberPart := resyncInterval[:length-1] + + number, err := strconv.Atoi(numberPart) + if err != nil { + return 0, err + } + + switch unit { + case "s": + if number < 5 { + return 0, fmt.Errorf("resync interval must be at least 5 seconds") + } + return time.Duration(number) * time.Second, nil + case "m": + return time.Duration(number) * time.Minute, nil + case "h": + return time.Duration(number) * time.Hour, nil + case "d": + return time.Duration(number) * 24 * time.Hour, nil + case "w": + return time.Duration(number) * 7 * 24 * time.Hour, nil + default: + return 0, fmt.Errorf("invalid time unit") + } +} From e094844601e3e64a326e9b997d56e35d21bd2312 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 29 Nov 2024 05:23:42 +0400 Subject: [PATCH 02/14] docs(k8-operator): push secrets --- docs/integrations/platforms/kubernetes.mdx | 421 ++++++++++++++++++++- 1 file changed, 417 insertions(+), 4 deletions(-) diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index ae4ee8071..e62fd2535 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -831,9 +831,9 @@ type: Opaque -### Apply the Infisical CRD to your cluster +### Apply the InfisicalSecret CRD to your cluster -Once you have configured the Infisical CRD with the required fields, you can apply it to your cluster. +Once you have configured the InfisicalSecret CRD with the required fields, you can apply it to your cluster. After applying, you should notice that the managed secret has been created in the desired namespace your specified. ``` @@ -1032,12 +1032,12 @@ stringData: -----END CERTIFICATE----- ``` -## Auto redeployment +### Auto redeployment Deployments using managed secrets don't reload automatically on updates, so they may use outdated secrets unless manually redeployed. To address this, we added functionality to automatically redeploy your deployment when its managed secret updates. -### Enabling auto redeploy +#### Enabling auto redeploy To enable auto redeployment you simply have to add the following annotation to the deployment that consumes a managed secret @@ -1080,6 +1080,419 @@ spec: When a secret change occurs, the operator will check to see which deployments are using the operator-managed Kubernetes secret that received the update. Then, for each deployment that has this annotation present, a rolling update will be triggered. + +## Push Secrets to Infisical + + +### Example usage + +Below is a sample InfisicalPushSecret CRD that pushes secrets defined in a Kubernetes secret to Infisical. + +After filling out the fields in the InfisicalPushSecret CRD, you can apply it directly to your cluster. + +Before applying the InfisicalPushSecret CRD, you need to create a Kubernetes secret containing the secrets you want to push to Infisical. An example can be seen below the InfisicalPushSecret CRD. + +```bash + kubectl apply -f source-secret.yaml +``` + +After applying the soruce-secret.yaml file, you are ready to apply the InfisicalPushSecret CRD. + +```bash + kubectl apply -f infisical-push-secret.yaml +``` + +After applying the InfisicalPushSecret CRD, you should notice that the secrets you have defined in your source-secret.yaml file have been pushed to your specified destination in Infisical. + +```yaml infisical-push-secret.yaml + apiVersion: secrets.infisical.com/v1alpha1 + kind: InfisicalPushSecret + metadata: + name: infisical-push-secret-demo + spec: + resyncInterval: 1m + hostAPI: https://app.infisical.com/api + + # Optional, defaults to no 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: + envSlug: + secretsPath: + + push: + secret: + secretName: push-secret-demo # Secret CRD + secretNamespace: default + + # 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 +``` + +```yaml source-secret.yaml + 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 +``` + +### InfisicalPushSecret CRD properties + + + If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to + ` https://your-self-hosted-instace.com/api` + + When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud. + + + If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet. + To achieve this, use the following address for the hostAPI field: + + ``` bash + http://..svc.cluster.local:4000/api + ``` + + Make sure to replace `` and `` with the appropriate values for your backend service and namespace. + + + + + + + The `resyncInterval` is a string-formatted duration that defines the time between each resync. + + The format of the field is `[duration][unit]` where `duration` is a number and `unit` is a string representing the unit of time. + + The following units are supported: + - `s` for seconds (must be at least 5 seconds) + - `m` for minutes + - `h` for hours + - `d` for days + - `w` for weeks + + The default value is `1m` (1 minute). + + Valid intervals examples: + ```yaml + resyncInterval: 5s # 10 seconds + resyncInterval: 10s # 10 seconds + resyncInterval: 5m # 5 minutes + resyncInterval: 1h # 1 hour + resyncInterval: 1d # 1 day + ``` + + + + + The field is optional and will default to `None` if not defined. + + The update policy defines how the operator should handle conflicting secrets when pushing secrets to Infisical. + + Valid values are `None` and `Replace`. + + Behavior of each policy: + - `None`: The operator will not override existing secrets in Infisical. If a secret with the same key already exists, the operator will skip pushing that secret, and the secret will not be managed by the operator. + - `Replace`: The operator will replace existing secrets in Infisical with the new secrets. If a secret with the same key already exists, the operator will update the secret with the new value. + + ```yaml + spec: + updatePolicy: Replace + ``` + + + + + This field is optional and will default to `None` if not defined. + + The deletion policy defines what the operator should do in case the InfisicalPushSecret CRD is deleted. + + Valid values are `None` and `Delete`. + + Behavior of each policy: + - `None`: The operator will not delete the secrets in Infisical when the InfisicalPushSecret CRD is deleted. + - `Delete`: The operator will delete the secrets in Infisical that are managed by the operator when the InfisicalPushSecret CRD is deleted. + + ```yaml + spec: + deletionPolicy: Delete + ``` + + + + The `destination` field is used to specify where you want to create the secrets in Infisical. The required fields are `projectId`, `envSlug`, and `secretsPath`. + + ```yaml + spec: + destination: + projectId: + envSlug: + secretsPath: + ``` + + + The project ID where you want to create the secrets in Infisical. + + + + The environment slug where you want to create the secrets in Infisical. + + + + The path where you want to create the secrets in Infisical. The root path is `/`. + + + + + + The `push` field is used to define what you want to push to Infisical. Currently the operator only supports pushing Kubernetes secrets to Infisical. An example of the `push` field is shown below. + + + + + The `secret` field is used to define the Kubernetes secret you want to push to Infisical. The required fields are `secretName` and `secretNamespace`. + + + + Example usage of the `push.secret` field: + + ```yaml infisical-push-secret.yaml + push: + secret: + secretName: push-secret-demo + secretNamespace: default + ``` + + ```yaml push-secret-demo.yaml + apiVersion: v1 + kind: Secret + metadata: + name: push-secret-demo + namespace: default + # Pass in the secrets you wish to push to Infisical + stringData: + API_KEY: some-api-key + DATABASE_URL: postgres://127.0.0.1:5432 + ENCRYPTION_KEY: fabcc12-a22-facbaa4-11aa568aab + ``` + + + + + + + The `authentication` field dictates which authentication method to use when pushing secrets to Infisical. + The available authentication methods are `universalAuth`, `kubernetesAuth`, `awsIamAuth`, `azureAuth`, `gcpIdTokenAuth`, and `gcpIamAuth`. + + + + The universal authentication method is one of the easiest ways to get started with Infisical. Universal Auth works anywhere and is not tied to any specific cloud provider. + [Read more about Universal Auth](/documentation/platform/identities/universal-auth). + + Valid fields: + - `identityId`: The identity ID of the machine identity you created. + - `credentialsRef`: The name and namespace of the Kubernetes secret that stores the service token. + - `credentialsRef.secretName`: The name of the Kubernetes secret. + - `credentialsRef.secretNamespace`: The namespace of the Kubernetes secret. + + Example: + + ```yaml + # infisical-push-secret.yaml + spec: + universalAuth: + credentialsRef: + secretName: + secretNamespace: + ``` + + ```yaml + # machine-identity-credentials.yaml + apiVersion: v1 + kind: Secret + metadata: + name: universal-auth-credentials + type: Opaque + stringData: + clientId: + clientSecret: + ``` + + + + The Kubernetes machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within a Kubernetes environment. + [Read more about Kubernetes Auth](/documentation/platform/identities/kubernetes-auth). + Valid fields: + - `identityId`: The identity ID of the machine identity you created. + - `serviceAccountRef`: The name and namespace of the service account that will be used to authenticate with Infisical. + - `serviceAccountRef.name`: The name of the service account. + - `serviceAccountRef.namespace`: The namespace of the service account. + + Example: + + ```yaml + spec: + kubernetesAuth: + identityId: + serviceAccountRef: + name: + namespace: + ``` + + + + The AWS IAM machine identity authentication method is used to authenticate with Infisical. + [Read more about AWS IAM Auth](/documentation/platform/identities/aws-auth). + + Valid fields: + - `identityId`: The identity ID of the machine identity you created. + + Example: + + ```yaml + spec: + authentication: + awsIamAuth: + identityId: + ``` + + + + The AWS IAM machine identity authentication method is used to authenticate with Infisical. Azure Auth can only be used from within an Azure environment. + [Read more about Azure Auth](/documentation/platform/identities/azure-auth). + + Valid fields: + - `identityId`: The identity ID of the machine identity you created. + + Example: + + ```yaml + spec: + authentication: + azureAuth: + identityId: + ``` + + + The GCP IAM machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used both within and outside GCP environments. + [Read more about Azure Auth](/documentation/platform/identities/gcp-auth). + + + Valid fields: + - `identityId`: The identity ID of the machine identity you created. + - `serviceAccountKeyFilePath`: The path to the GCP service account key file. + + Example: + + ```yaml + spec: + gcpIamAuth: + identityId: + serviceAccountKeyFilePath: + ``` + + + The GCP ID Token machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within GCP environments. + [Read more about Azure Auth](/documentation/platform/identities/gcp-auth). + + Valid fields: + - `identityId`: The identity ID of the machine identity you created. + + Example: + + ```yaml + spec: + gcpIdTokenAuth: + identityId: + ``` + + + + + + + This block defines the TLS settings to use for connecting to the Infisical + instance. + + Fields: + + This block defines the reference to the CA certificate to use for connecting to the Infisical instance with SSL/TLS. + + Valid fields: + - `secretName`: The name of the Kubernetes secret containing the CA certificate to use for connecting to the Infisical instance with SSL/TLS. + - `secretNamespace`: The namespace of the Kubernetes secret containing the CA certificate to use for connecting to the Infisical instance with SSL/TLS. + - `key`: The name of the key in the Kubernetes secret which contains the value of the CA certificate to use for connecting to the Infisical instance with SSL/TLS. + + Example: + + ```yaml + tls: + caRef: + secretName: custom-ca-certificate + secretNamespace: default + key: ca.crt + ``` + + + + + +### 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. +After applying, you should notice that the secrets have been pushed to Infisical. + +```bash + kubectl apply -f source-push-secret.yaml # The secret that you're referencing in the InfisicalPushSecret CRD push.secret field + kubectl apply -f example-infisical-push-secret-crd.yaml # The InfisicalPushSecret CRD itself +``` + +### Connecting to instances with private/self-signed certificate + +To connect to Infisical instances behind a private/self-signed certificate, you can configure the TLS settings in the `InfisicalPushSecret` CRD +to point to a CA certificate stored in a Kubernetes secret resource. + +```yaml +spec: + hostAPI: https://app.infisical.com/api + resyncInterval: 30s + tls: + caRef: + secretName: custom-ca-certificate + secretNamespace: default + key: ca.crt + authentication: + # ... +``` + + ## Global configuration To configure global settings that will apply to all instances of `InfisicalSecret`, you can define these configurations in a Kubernetes ConfigMap. From ed9472efc8ec4b541ad27d435e6091d0ee08801b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 29 Nov 2024 05:23:48 +0400 Subject: [PATCH 03/14] remove print --- .../infisicalpushsecret/infisicalpushsecret_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go index f52f27881..526b1059c 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go @@ -157,7 +157,7 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. }, nil } - fmt.Println("Using custom CA certificate...") + logger.Info("Using custom CA certificate...") } else { api.API_CA_CERTIFICATE = "" } From 0ab3ae442e88219521f2433275e983d2ce68cfd0 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 29 Nov 2024 06:01:18 +0400 Subject: [PATCH 04/14] cleanup and resource seperation --- .../infisicalpushsecret_controller.go | 28 ++++++++++++---- .../infisicalpushsecret_helper.go | 10 +++--- .../infisicalsecret_controller.go | 33 ++++++++----------- .../infisicalsecret/infisicalsecret_helper.go | 10 +++--- 4 files changed, 45 insertions(+), 36 deletions(-) diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go index 526b1059c..2b6a64ca1 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go @@ -35,7 +35,7 @@ type InfisicalPushSecretReconciler struct { Scheme *runtime.Scheme } -var resourceVariablesMap map[string]util.ResourceVariables +var infisicalPushSecretResourceVariablesMap map[string]util.ResourceVariables = make(map[string]util.ResourceVariables) func (r *InfisicalPushSecretReconciler) GetLogger(req ctrl.Request) logr.Logger { return r.BaseLogger.WithValues("infisicalpushsecret", req.NamespacedName) @@ -61,10 +61,6 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. var infisicalPushSecretCR secretsv1alpha1.InfisicalPushSecret requeueTime := time.Minute // seconds - if resourceVariablesMap == nil { - resourceVariablesMap = make(map[string]util.ResourceVariables) - } - err := r.Get(ctx, req.NamespacedName, &infisicalPushSecretCR) if err != nil { if errors.IsNotFound(err) { @@ -185,10 +181,30 @@ func (r *InfisicalPushSecretReconciler) SetupWithManager(mgr ctrl.Manager) error specChangeOrDelete := predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { // Only reconcile if spec/generation changed - return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() + + isSpecOrGenerationChange := e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() + + if isSpecOrGenerationChange { + if infisicalPushSecretResourceVariablesMap != nil { + if rv, ok := infisicalPushSecretResourceVariablesMap[string(e.ObjectNew.GetUID())]; ok { + rv.CancelCtx() + delete(infisicalPushSecretResourceVariablesMap, string(e.ObjectNew.GetUID())) + } + } + } + + return isSpecOrGenerationChange }, DeleteFunc: func(e event.DeleteEvent) bool { // Always reconcile on deletion + + if infisicalPushSecretResourceVariablesMap != nil { + if rv, ok := infisicalPushSecretResourceVariablesMap[string(e.Object.GetUID())]; ok { + rv.CancelCtx() + delete(infisicalPushSecretResourceVariablesMap, string(e.Object.GetUID())) + } + } + return true }, CreateFunc: func(e event.CreateEvent) bool { diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go index a13452b69..0ab0ab876 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go @@ -71,7 +71,7 @@ func (r *InfisicalPushSecretReconciler) getResourceVariables(infisicalPushSecret var resourceVariables util.ResourceVariables - if _, ok := resourceVariablesMap[string(infisicalPushSecret.UID)]; !ok { + if _, ok := infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)]; !ok { ctx, cancel := context.WithCancel(context.Background()) @@ -81,16 +81,16 @@ func (r *InfisicalPushSecretReconciler) getResourceVariables(infisicalPushSecret UserAgent: api.USER_AGENT_NAME, }) - resourceVariablesMap[string(infisicalPushSecret.UID)] = util.ResourceVariables{ + infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)] = util.ResourceVariables{ InfisicalClient: client, CancelCtx: cancel, AuthDetails: util.AuthenticationDetails{}, } - resourceVariables = resourceVariablesMap[string(infisicalPushSecret.UID)] + resourceVariables = infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)] } else { - resourceVariables = resourceVariablesMap[string(infisicalPushSecret.UID)] + resourceVariables = infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)] } return resourceVariables @@ -98,7 +98,7 @@ func (r *InfisicalPushSecretReconciler) getResourceVariables(infisicalPushSecret } func (r *InfisicalPushSecretReconciler) updateResourceVariables(infisicalPushSecret v1alpha1.InfisicalPushSecret, resourceVariables util.ResourceVariables) { - resourceVariablesMap[string(infisicalPushSecret.UID)] = resourceVariables + infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)] = resourceVariables } func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context.Context, logger logr.Logger, infisicalPushSecret v1alpha1.InfisicalPushSecret) error { diff --git a/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go b/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go index 8b5508858..97d13c689 100644 --- a/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go +++ b/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go @@ -27,7 +27,13 @@ type InfisicalSecretReconciler struct { Scheme *runtime.Scheme } -var resourceVariablesMap map[string]util.ResourceVariables = make(map[string]util.ResourceVariables) +const FINALIZER_NAME = "secrets.finalizers.infisical.com" + +var infisicalSecretResourceVariablesMap map[string]util.ResourceVariables = make(map[string]util.ResourceVariables) + +func (r *InfisicalSecretReconciler) GetLogger(req ctrl.Request) logr.Logger { + return r.BaseLogger.WithValues("infisicalsecret", req.NamespacedName) +} //+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets/status,verbs=get;update;patch @@ -42,15 +48,6 @@ var resourceVariablesMap map[string]util.ResourceVariables = make(map[string]uti // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile -const FINALIZER_NAME = "secrets.finalizers.infisical.com" - -// Maps the infisicalSecretCR.UID to a infisicalSdk.InfisicalClientInterface and AuthenticationDetails. -// var resourceVariablesMap = make(map[string]ResourceVariables) - -func (r *InfisicalSecretReconciler) GetLogger(req ctrl.Request) logr.Logger { - return r.BaseLogger.WithValues("infisicalsecret", req.NamespacedName) -} - func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := r.GetLogger(req) @@ -58,10 +55,6 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ var infisicalSecretCR secretsv1alpha1.InfisicalSecret requeueTime := time.Minute // seconds - if resourceVariablesMap == nil { - resourceVariablesMap = make(map[string]util.ResourceVariables) - } - err := r.Get(ctx, req.NamespacedName, &infisicalSecretCR) if err != nil { if errors.IsNotFound(err) { @@ -163,19 +156,19 @@ func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&secretsv1alpha1.InfisicalSecret{}, builder.WithPredicates(predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { - if resourceVariablesMap != nil { - if rv, ok := resourceVariablesMap[string(e.ObjectNew.GetUID())]; ok { + if infisicalSecretResourceVariablesMap != nil { + if rv, ok := infisicalSecretResourceVariablesMap[string(e.ObjectNew.GetUID())]; ok { rv.CancelCtx() - delete(resourceVariablesMap, string(e.ObjectNew.GetUID())) + delete(infisicalSecretResourceVariablesMap, string(e.ObjectNew.GetUID())) } } return true }, DeleteFunc: func(e event.DeleteEvent) bool { - if resourceVariablesMap != nil { - if rv, ok := resourceVariablesMap[string(e.Object.GetUID())]; ok { + if infisicalSecretResourceVariablesMap != nil { + if rv, ok := infisicalSecretResourceVariablesMap[string(e.Object.GetUID())]; ok { rv.CancelCtx() - delete(resourceVariablesMap, string(e.Object.GetUID())) + delete(infisicalSecretResourceVariablesMap, string(e.Object.GetUID())) } } return true diff --git a/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go b/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go index 7b9f8be93..28a9843c9 100644 --- a/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go +++ b/k8-operator/controllers/infisicalsecret/infisicalsecret_helper.go @@ -296,7 +296,7 @@ func (r *InfisicalSecretReconciler) getResourceVariables(infisicalSecret v1alpha var resourceVariables util.ResourceVariables - if _, ok := resourceVariablesMap[string(infisicalSecret.UID)]; !ok { + if _, ok := infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)]; !ok { ctx, cancel := context.WithCancel(context.Background()) @@ -306,16 +306,16 @@ func (r *InfisicalSecretReconciler) getResourceVariables(infisicalSecret v1alpha UserAgent: api.USER_AGENT_NAME, }) - resourceVariablesMap[string(infisicalSecret.UID)] = util.ResourceVariables{ + infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] = util.ResourceVariables{ InfisicalClient: client, CancelCtx: cancel, AuthDetails: util.AuthenticationDetails{}, } - resourceVariables = resourceVariablesMap[string(infisicalSecret.UID)] + resourceVariables = infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] } else { - resourceVariables = resourceVariablesMap[string(infisicalSecret.UID)] + resourceVariables = infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] } return resourceVariables @@ -323,7 +323,7 @@ func (r *InfisicalSecretReconciler) getResourceVariables(infisicalSecret v1alpha } func (r *InfisicalSecretReconciler) updateResourceVariables(infisicalSecret v1alpha1.InfisicalSecret, resourceVariables util.ResourceVariables) { - resourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables + infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables } func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret) error { From 7db1e6265417de8682ae1296d394ee509a76ba26 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 6 Dec 2024 01:43:41 +0400 Subject: [PATCH 05/14] fix: requested changes --- .../api/v1alpha1/infisicalpushsecret_types.go | 2 +- ...ts.infisical.com_infisicalpushsecrets.yaml | 4 +- .../infisicalpushsecret/conditions.go | 6 +-- .../infisicalpushsecret_controller.go | 40 +++++++++---------- .../infisicalpushsecret_helper.go | 22 +++++----- .../infisicalsecret_controller.go | 34 ++++++++-------- 6 files changed, 54 insertions(+), 54 deletions(-) diff --git a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go index 336534ed2..334b5a847 100644 --- a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go @@ -10,7 +10,7 @@ type InfisicalPushSecretDestination struct { SecretsPath string `json:"secretsPath"` // +kubebuilder:validation:Required // +kubebuilder:validation:Immutable - EnvSlug string `json:"envSlug"` + EnvironmentSlug string `json:"EnvironmentSlug"` // +kubebuilder:validation:Required // +kubebuilder:validation:Immutable ProjectID string `json:"projectId"` 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 a4ee1e21f..de0cd0b16 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml @@ -114,14 +114,14 @@ spec: type: string destination: properties: - envSlug: + EnvironmentSlug: type: string projectId: type: string secretsPath: type: string required: - - envSlug + - EnvironmentSlug - projectId - secretsPath type: object diff --git a/k8-operator/controllers/infisicalpushsecret/conditions.go b/k8-operator/controllers/infisicalpushsecret/conditions.go index bd1851eb2..335ea294d 100644 --- a/k8-operator/controllers/infisicalpushsecret/conditions.go +++ b/k8-operator/controllers/infisicalpushsecret/conditions.go @@ -51,7 +51,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToReplaceSecretsConditions(ctx Type: "secrets.infisical.com/FailedToReplaceSecrets", Status: metav1.ConditionFalse, Reason: "OK", - Message: "No errors, no secrets failed to be replaced", + Message: "No errors, no secrets failed to be replaced in Infisical", }) } @@ -75,7 +75,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToCreateSecretsConditions(ctx c Type: "secrets.infisical.com/FailedToCreateSecrets", Status: metav1.ConditionFalse, Reason: "OK", - Message: "No errors, no secrets failed to be created", + Message: "No errors, no secrets failed to be created in Infisical", }) } @@ -99,7 +99,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToUpdateSecretsConditions(ctx c Type: "secrets.infisical.com/FailedToUpdateSecrets", Status: metav1.ConditionFalse, Reason: "OK", - Message: "No errors, no secrets failed to be updated", + Message: "No errors, no secrets failed to be updated in Infisical", }) } diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go index 2b6a64ca1..af5428ee7 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go @@ -58,10 +58,10 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. logger := r.GetLogger(req) - var infisicalPushSecretCR secretsv1alpha1.InfisicalPushSecret + var infisicalPushSecretCRD secretsv1alpha1.InfisicalPushSecret requeueTime := time.Minute // seconds - err := r.Get(ctx, req.NamespacedName, &infisicalPushSecretCR) + err := r.Get(ctx, req.NamespacedName, &infisicalPushSecretCRD) if err != nil { if errors.IsNotFound(err) { logger.Info("Infisical Push Secret CRD not found") @@ -77,25 +77,25 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. } // Add finalizer if it doesn't exist - if !controllerutil.ContainsFinalizer(&infisicalPushSecretCR, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) { - controllerutil.AddFinalizer(&infisicalPushSecretCR, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) - if err := r.Update(ctx, &infisicalPushSecretCR); err != nil { + if !controllerutil.ContainsFinalizer(&infisicalPushSecretCRD, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) { + controllerutil.AddFinalizer(&infisicalPushSecretCRD, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) + if err := r.Update(ctx, &infisicalPushSecretCRD); err != nil { return ctrl.Result{}, err } } // Check if it's being deleted - if !infisicalPushSecretCR.DeletionTimestamp.IsZero() { + if !infisicalPushSecretCRD.DeletionTimestamp.IsZero() { logger.Info("Handling deletion of InfisicalPushSecret") - if controllerutil.ContainsFinalizer(&infisicalPushSecretCR, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) { + if controllerutil.ContainsFinalizer(&infisicalPushSecretCRD, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) { // We remove finalizers before running deletion logic to be completely safe from stuck resources - infisicalPushSecretCR.ObjectMeta.Finalizers = []string{} - if err := r.Update(ctx, &infisicalPushSecretCR); err != nil { - logger.Error(err, fmt.Sprintf("Error removing finalizers from InfisicalPushSecret %s", infisicalPushSecretCR.Name)) + infisicalPushSecretCRD.ObjectMeta.Finalizers = []string{} + if err := r.Update(ctx, &infisicalPushSecretCRD); err != nil { + logger.Error(err, fmt.Sprintf("Error removing finalizers from InfisicalPushSecret %s", infisicalPushSecretCRD.Name)) return ctrl.Result{}, err } - if err := r.DeleteManagedSecrets(ctx, logger, infisicalPushSecretCR); err != nil { + if err := r.DeleteManagedSecrets(ctx, logger, infisicalPushSecretCRD); err != nil { return ctrl.Result{}, err // Even if this fails, we still want to delete the CRD } @@ -103,9 +103,9 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. return ctrl.Result{}, nil } - if infisicalPushSecretCR.Spec.ResyncInterval != "" { + if infisicalPushSecretCRD.Spec.ResyncInterval != "" { - duration, err := util.ConvertResyncIntervalToDuration(infisicalPushSecretCR.Spec.ResyncInterval) + duration, err := util.ConvertResyncIntervalToDuration(infisicalPushSecretCRD.Spec.ResyncInterval) if err != nil { logger.Error(err, fmt.Sprintf("unable to convert resync interval to duration. Will requeue after [requeueTime=%v]", requeueTime)) @@ -123,7 +123,7 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. } // Check if the resource is already marked for deletion - if infisicalPushSecretCR.GetDeletionTimestamp() != nil { + if infisicalPushSecretCRD.GetDeletionTimestamp() != nil { return ctrl.Result{ Requeue: false, }, nil @@ -138,14 +138,14 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. }, nil } - if infisicalPushSecretCR.Spec.HostAPI == "" { + if infisicalPushSecretCRD.Spec.HostAPI == "" { api.API_HOST_URL = infisicalConfig["hostAPI"] } else { - api.API_HOST_URL = infisicalPushSecretCR.Spec.HostAPI + api.API_HOST_URL = infisicalPushSecretCRD.Spec.HostAPI } - if infisicalPushSecretCR.Spec.TLS.CaRef.SecretName != "" { - api.API_CA_CERTIFICATE, err = r.getInfisicalCaCertificateFromKubeSecret(ctx, infisicalPushSecretCR) + 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{ @@ -158,8 +158,8 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. api.API_CA_CERTIFICATE = "" } - err = r.ReconcileInfisicalPushSecret(ctx, logger, infisicalPushSecretCR) - r.SetSuccessfullyReconciledConditions(ctx, &infisicalPushSecretCR, err) + err = r.ReconcileInfisicalPushSecret(ctx, logger, infisicalPushSecretCRD) + r.SetSuccessfullyReconciledConditions(ctx, &infisicalPushSecretCRD, err) if err != nil { logger.Error(err, fmt.Sprintf("unable to reconcile Infisical Push Secret. Will requeue after [requeueTime=%v]", requeueTime)) diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go index 0ab0ab876..ebe125a49 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go @@ -143,7 +143,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context destination := infisicalPushSecret.Spec.Destination existingSecrets, err := infisicalClient.Secrets().List(infisicalSdk.ListSecretsOptions{ ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, IncludeImports: false, }) @@ -197,7 +197,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context updatedSecret, err := infisicalClient.Secrets().Update(infisicalSdk.UpdateSecretOptions{ SecretKey: secretKey, ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, NewSecretValue: secretValue, }) @@ -215,7 +215,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context SecretKey: secretKey, SecretValue: secretValue, ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, }) @@ -245,7 +245,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context deletedSecret, err := infisicalClient.Secrets().Delete(infisicalSdk.DeleteSecretOptions{ SecretKey: existingSecret.SecretKey, ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, }) @@ -259,7 +259,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context SecretKey: managedSecretKey, SecretValue: existingSecret.SecretValue, ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, }) @@ -288,7 +288,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context deletedSecret, err := infisicalClient.Secrets().Delete(infisicalSdk.DeleteSecretOptions{ SecretKey: managedSecretKey, ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, }) @@ -318,7 +318,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context SecretKey: currentSecretKey, SecretValue: kubeSecrets[currentSecretKey], ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, }) @@ -336,7 +336,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context SecretKey: currentSecretKey, NewSecretValue: kubeSecrets[currentSecretKey], ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, }) @@ -369,7 +369,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context SecretKey: secretKey, NewSecretValue: secretValue, ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, }) @@ -435,7 +435,7 @@ func (r *InfisicalPushSecretReconciler) DeleteManagedSecrets(ctx context.Context destination := infisicalPushSecret.Spec.Destination existingSecrets, err := infisicalClient.Secrets().List(infisicalSdk.ListSecretsOptions{ ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, IncludeImports: false, }) @@ -457,7 +457,7 @@ func (r *InfisicalPushSecretReconciler) DeleteManagedSecrets(ctx context.Context _, err := infisicalClient.Secrets().Delete(infisicalSdk.DeleteSecretOptions{ SecretKey: managedSecretKey, ProjectID: destination.ProjectID, - Environment: destination.EnvSlug, + Environment: destination.EnvironmentSlug, SecretPath: destination.SecretsPath, }) diff --git a/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go b/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go index 97d13c689..f5a974a67 100644 --- a/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go +++ b/k8-operator/controllers/infisicalsecret/infisicalsecret_controller.go @@ -52,10 +52,10 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ logger := r.GetLogger(req) - var infisicalSecretCR secretsv1alpha1.InfisicalSecret + var infisicalSecretCRD secretsv1alpha1.InfisicalSecret requeueTime := time.Minute // seconds - err := r.Get(ctx, req.NamespacedName, &infisicalSecretCR) + err := r.Get(ctx, req.NamespacedName, &infisicalSecretCRD) if err != nil { if errors.IsNotFound(err) { return ctrl.Result{ @@ -71,18 +71,18 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ // Remove finalizers if they exist. This is to support previous InfisicalSecret CRD's that have finalizers on them. // In order to delete secrets with finalizers, we first remove the finalizers so we can use the simplified and improved deletion process - if !infisicalSecretCR.ObjectMeta.DeletionTimestamp.IsZero() && len(infisicalSecretCR.ObjectMeta.Finalizers) > 0 { - infisicalSecretCR.ObjectMeta.Finalizers = []string{} - if err := r.Update(ctx, &infisicalSecretCR); err != nil { - logger.Error(err, fmt.Sprintf("Error removing finalizers from Infisical Secret %s", infisicalSecretCR.Name)) + if !infisicalSecretCRD.ObjectMeta.DeletionTimestamp.IsZero() && len(infisicalSecretCRD.ObjectMeta.Finalizers) > 0 { + infisicalSecretCRD.ObjectMeta.Finalizers = []string{} + if err := r.Update(ctx, &infisicalSecretCRD); err != nil { + logger.Error(err, fmt.Sprintf("Error removing finalizers from Infisical Secret %s", infisicalSecretCRD.Name)) return ctrl.Result{}, err } // Our finalizers have been removed, so the reconciler can do nothing. return ctrl.Result{}, nil } - if infisicalSecretCR.Spec.ResyncInterval != 0 { - requeueTime = time.Second * time.Duration(infisicalSecretCR.Spec.ResyncInterval) + if infisicalSecretCRD.Spec.ResyncInterval != 0 { + requeueTime = time.Second * time.Duration(infisicalSecretCRD.Spec.ResyncInterval) logger.Info(fmt.Sprintf("Manual re-sync interval set. Interval: %v", requeueTime)) } else { @@ -90,7 +90,7 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ } // Check if the resource is already marked for deletion - if infisicalSecretCR.GetDeletionTimestamp() != nil { + if infisicalSecretCRD.GetDeletionTimestamp() != nil { return ctrl.Result{ Requeue: false, }, nil @@ -105,14 +105,14 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ }, nil } - if infisicalSecretCR.Spec.HostAPI == "" { + if infisicalSecretCRD.Spec.HostAPI == "" { api.API_HOST_URL = infisicalConfig["hostAPI"] } else { - api.API_HOST_URL = infisicalSecretCR.Spec.HostAPI + api.API_HOST_URL = infisicalSecretCRD.Spec.HostAPI } - if infisicalSecretCR.Spec.TLS.CaRef.SecretName != "" { - api.API_CA_CERTIFICATE, err = r.getInfisicalCaCertificateFromKubeSecret(ctx, infisicalSecretCR) + if infisicalSecretCRD.Spec.TLS.CaRef.SecretName != "" { + api.API_CA_CERTIFICATE, err = r.getInfisicalCaCertificateFromKubeSecret(ctx, infisicalSecretCRD) if err != nil { logger.Error(err, fmt.Sprintf("unable to fetch CA certificate. Will requeue after [requeueTime=%v]", requeueTime)) return ctrl.Result{ @@ -125,8 +125,8 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ api.API_CA_CERTIFICATE = "" } - err = r.ReconcileInfisicalSecret(ctx, logger, infisicalSecretCR) - r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCR, err) + err = r.ReconcileInfisicalSecret(ctx, logger, infisicalSecretCRD) + r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCRD, err) if err != nil { @@ -136,8 +136,8 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ }, nil } - numDeployments, err := r.ReconcileDeploymentsWithManagedSecrets(ctx, logger, infisicalSecretCR) - r.SetInfisicalAutoRedeploymentReady(ctx, logger, &infisicalSecretCR, numDeployments, err) + numDeployments, err := r.ReconcileDeploymentsWithManagedSecrets(ctx, logger, infisicalSecretCRD) + r.SetInfisicalAutoRedeploymentReady(ctx, logger, &infisicalSecretCRD, numDeployments, err) if err != nil { logger.Error(err, fmt.Sprintf("unable to reconcile auto redeployment. Will requeue after [requeueTime=%v]", requeueTime)) return ctrl.Result{ From ced12baf5d50bf1941e3e3468489af149f2f92ce Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 6 Dec 2024 01:44:57 +0400 Subject: [PATCH 06/14] Update conditions.go --- k8-operator/controllers/infisicalpushsecret/conditions.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/k8-operator/controllers/infisicalpushsecret/conditions.go b/k8-operator/controllers/infisicalpushsecret/conditions.go index 335ea294d..442cdaaf2 100644 --- a/k8-operator/controllers/infisicalpushsecret/conditions.go +++ b/k8-operator/controllers/infisicalpushsecret/conditions.go @@ -75,7 +75,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToCreateSecretsConditions(ctx c Type: "secrets.infisical.com/FailedToCreateSecrets", Status: metav1.ConditionFalse, Reason: "OK", - Message: "No errors, no secrets failed to be created in Infisical", + Message: "No errors encountered, no secrets failed to be created in Infisical", }) } @@ -99,7 +99,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToUpdateSecretsConditions(ctx c Type: "secrets.infisical.com/FailedToUpdateSecrets", Status: metav1.ConditionFalse, Reason: "OK", - Message: "No errors, no secrets failed to be updated in Infisical", + Message: "No errors encountered, no secrets failed to be updated in Infisical", }) } @@ -123,7 +123,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToDeleteSecretsConditions(ctx c Type: "secrets.infisical.com/FailedToDeleteSecrets", Status: metav1.ConditionFalse, Reason: "OK", - Message: "No errors, no secrets failed to be deleted", + Message: "No errors encountered, no secrets failed to be deleted", }) } From dd5aab973f2e5f928cd20269b434641ff53bd50b Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 6 Dec 2024 02:00:52 +0400 Subject: [PATCH 07/14] Update PROJECT --- k8-operator/PROJECT | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/k8-operator/PROJECT b/k8-operator/PROJECT index 968a9a3d1..e57b8bee5 100644 --- a/k8-operator/PROJECT +++ b/k8-operator/PROJECT @@ -1,16 +1,29 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html domain: infisical.com layout: -- go.kubebuilder.io/v3 + - go.kubebuilder.io/v3 projectName: k8-operator repo: github.com/Infisical/infisical/k8-operator resources: -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: infisical.com - group: secrets - kind: InfisicalSecret - path: github.com/Infisical/infisical/k8-operator/api/v1alpha1 - version: v1alpha1 + - api: + crdVersion: v1 + namespaced: true + controller: true + domain: infisical.com + group: secrets + kind: InfisicalSecret + path: github.com/Infisical/infisical/k8-operator/api/v1alpha1 + version: v1alpha1 + - api: + crdVersion: v1 + namespaced: true + controller: true + domain: infisical.com + group: secrets + kind: InfisicalPushSecretSecret + path: github.com/Infisical/infisical/k8-operator/api/v1alpha1 + version: v1alpha1 version: "3" From 5532844ee7cd46c900c5758094ed5afa427391f5 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 6 Dec 2024 02:06:35 +0400 Subject: [PATCH 08/14] Added RBAC --- .../rbac/infisicalpushsecret_editor_role.yaml | 27 +++++++++++++++++++ .../rbac/infisicalpushsecret_viewer_role.yaml | 23 ++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 k8-operator/config/rbac/infisicalpushsecret_editor_role.yaml create mode 100644 k8-operator/config/rbac/infisicalpushsecret_viewer_role.yaml diff --git a/k8-operator/config/rbac/infisicalpushsecret_editor_role.yaml b/k8-operator/config/rbac/infisicalpushsecret_editor_role.yaml new file mode 100644 index 000000000..9344e17c5 --- /dev/null +++ b/k8-operator/config/rbac/infisicalpushsecret_editor_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to edit infisicalpushsecrets. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: k8-operator + app.kubernetes.io/managed-by: kustomize + name: infisicalpushsecret-editor-role +rules: + - apiGroups: + - secrets.infisical.com + resources: + - infisicalpushsecrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - secrets.infisical.com + resources: + - infisicalpushsecrets/status + verbs: + - get diff --git a/k8-operator/config/rbac/infisicalpushsecret_viewer_role.yaml b/k8-operator/config/rbac/infisicalpushsecret_viewer_role.yaml new file mode 100644 index 000000000..ff4df91cc --- /dev/null +++ b/k8-operator/config/rbac/infisicalpushsecret_viewer_role.yaml @@ -0,0 +1,23 @@ +# permissions for end users to view infisicalpushsecrets. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: k8-operator + app.kubernetes.io/managed-by: kustomize + name: infisicalpushsecret-viewer-role +rules: + - apiGroups: + - secrets.infisical.com + resources: + - infisicalpushsecrets + verbs: + - get + - list + - watch + - apiGroups: + - secrets.infisical.com + resources: + - infisicalpushsecrets/status + verbs: + - get From d1c4a9c75a8316ec0c653d3177c5845f883f4f10 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Fri, 6 Dec 2024 02:54:50 +0400 Subject: [PATCH 09/14] updated env slugs --- docs/integrations/platforms/kubernetes.mdx | 8 ++++---- k8-operator/api/v1alpha1/infisicalpushsecret_types.go | 2 +- k8-operator/config/samples/crd/pushsecret/pushSecret.yaml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/integrations/platforms/kubernetes.mdx b/docs/integrations/platforms/kubernetes.mdx index e62fd2535..9d1ba5145 100644 --- a/docs/integrations/platforms/kubernetes.mdx +++ b/docs/integrations/platforms/kubernetes.mdx @@ -1121,7 +1121,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y destination: projectId: - envSlug: + environmentSlug: secretsPath: push: @@ -1247,13 +1247,13 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y - The `destination` field is used to specify where you want to create the secrets in Infisical. The required fields are `projectId`, `envSlug`, and `secretsPath`. + The `destination` field is used to specify where you want to create the secrets in Infisical. The required fields are `projectId`, `environmentSlug`, and `secretsPath`. ```yaml spec: destination: projectId: - envSlug: + environmentSlug: secretsPath: ``` @@ -1261,7 +1261,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y The project ID where you want to create the secrets in Infisical. - + The environment slug where you want to create the secrets in Infisical. diff --git a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go index 334b5a847..a25e7ae9c 100644 --- a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go @@ -10,7 +10,7 @@ type InfisicalPushSecretDestination struct { SecretsPath string `json:"secretsPath"` // +kubebuilder:validation:Required // +kubebuilder:validation:Immutable - EnvironmentSlug string `json:"EnvironmentSlug"` + EnvironmentSlug string `json:"environmentSlug"` // +kubebuilder:validation:Required // +kubebuilder:validation:Immutable ProjectID string `json:"projectId"` diff --git a/k8-operator/config/samples/crd/pushsecret/pushSecret.yaml b/k8-operator/config/samples/crd/pushsecret/pushSecret.yaml index 8df2d3da5..ab54f1384 100644 --- a/k8-operator/config/samples/crd/pushsecret/pushSecret.yaml +++ b/k8-operator/config/samples/crd/pushsecret/pushSecret.yaml @@ -14,7 +14,7 @@ spec: destination: projectId: - envSlug: + environmentSlug: secretsPath: push: From 3a5e862def581dc1229de14a6963974aeec8e59f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sun, 8 Dec 2024 21:21:14 +0400 Subject: [PATCH 10/14] fix(k8-operator): helm and cleanup --- helm-charts/secrets-operator/Chart.yaml | 4 +- .../templates/infisicalpushsecret-crd.yaml | 268 ++++++++++++++++++ .../templates/manager-rbac.yaml | 26 ++ helm-charts/secrets-operator/values.yaml | 2 +- .../api/v1alpha1/infisicalpushsecret_types.go | 1 - ...ts.infisical.com_infisicalpushsecrets.yaml | 5 +- 6 files changed, 299 insertions(+), 7 deletions(-) create mode 100644 helm-charts/secrets-operator/templates/infisicalpushsecret-crd.yaml diff --git a/helm-charts/secrets-operator/Chart.yaml b/helm-charts/secrets-operator/Chart.yaml index fc76e455f..3dbcc247a 100644 --- a/helm-charts/secrets-operator/Chart.yaml +++ b/helm-charts/secrets-operator/Chart.yaml @@ -13,9 +13,9 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: v0.7.7 +version: v0.7.8 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "v0.7.7" +appVersion: "v0.7.8" diff --git a/helm-charts/secrets-operator/templates/infisicalpushsecret-crd.yaml b/helm-charts/secrets-operator/templates/infisicalpushsecret-crd.yaml new file mode 100644 index 000000000..3d3239fbb --- /dev/null +++ b/helm-charts/secrets-operator/templates/infisicalpushsecret-crd.yaml @@ -0,0 +1,268 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: infisicalpushsecrets.secrets.infisical.com + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + labels: + {{- include "secrets-operator.labels" . | nindent 4 }} +spec: + group: secrets.infisical.com + names: + kind: InfisicalPushSecret + listKind: InfisicalPushSecretList + plural: infisicalpushsecrets + singular: infisicalpushsecret + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: InfisicalPushSecret is the Schema for the infisicalpushsecrets + API + 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: InfisicalPushSecretSpec defines the desired state of InfisicalPushSecret + properties: + authentication: + properties: + awsIamAuth: + properties: + identityId: + type: string + required: + - identityId + type: object + azureAuth: + properties: + identityId: + type: string + resource: + type: string + required: + - identityId + type: object + gcpIamAuth: + properties: + identityId: + type: string + serviceAccountKeyFilePath: + type: string + required: + - identityId + - serviceAccountKeyFilePath + type: object + gcpIdTokenAuth: + properties: + identityId: + type: string + required: + - identityId + type: object + kubernetesAuth: + properties: + identityId: + type: string + serviceAccountRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + required: + - identityId + - serviceAccountRef + type: object + universalAuth: + description: PushSecretUniversalAuth defines universal authentication + properties: + credentialsRef: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret + is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - credentialsRef + type: object + type: object + deletionPolicy: + type: string + destination: + properties: + environmentSlug: + type: string + projectId: + type: string + secretsPath: + type: string + required: + - environmentSlug + - projectId + - secretsPath + type: object + hostAPI: + description: Infisical host to pull secrets from + type: string + push: + properties: + secret: + properties: + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The name space where the Kubernetes Secret is located + type: string + required: + - secretName + - secretNamespace + type: object + required: + - secret + type: object + resyncInterval: + type: string + tls: + properties: + caRef: + description: Reference to secret containing CA cert + properties: + key: + description: The name of the secret property with the CA certificate + value + type: string + secretName: + description: The name of the Kubernetes Secret + type: string + secretNamespace: + description: The namespace where the Kubernetes Secret is located + type: string + required: + - key + - secretName + - secretNamespace + type: object + type: object + updatePolicy: + type: string + required: + - destination + - push + - resyncInterval + type: object + status: + description: InfisicalPushSecretStatus defines the observed state of InfisicalPushSecret + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a foo's + current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details + about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers of + specific condition types may define expected values and meanings + for this field, and whether the values are considered a guaranteed + API. The value should be a CamelCase string. This field may + not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + managedSecrets: + additionalProperties: + type: string + description: managed secrets is a map where the key is the ID, and the + value is the secret key (string[id], string[key] ) + type: object + required: + - conditions + - managedSecrets + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/helm-charts/secrets-operator/templates/manager-rbac.yaml b/helm-charts/secrets-operator/templates/manager-rbac.yaml index 33f00198f..20d625397 100644 --- a/helm-charts/secrets-operator/templates/manager-rbac.yaml +++ b/helm-charts/secrets-operator/templates/manager-rbac.yaml @@ -51,6 +51,32 @@ rules: - list - update - watch +- apiGroups: + - secrets.infisical.com + resources: + - infisicalpushsecrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - secrets.infisical.com + resources: + - infisicalpushsecrets/finalizers + verbs: + - update +- apiGroups: + - secrets.infisical.com + resources: + - infisicalpushsecrets/status + verbs: + - get + - patch + - update - apiGroups: - secrets.infisical.com resources: diff --git a/helm-charts/secrets-operator/values.yaml b/helm-charts/secrets-operator/values.yaml index 4b25f9d14..bd86075b2 100644 --- a/helm-charts/secrets-operator/values.yaml +++ b/helm-charts/secrets-operator/values.yaml @@ -32,7 +32,7 @@ controllerManager: - ALL image: repository: infisical/kubernetes-operator - tag: v0.7.7 + tag: v0.7.8 resources: limits: cpu: 500m diff --git a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go index a25e7ae9c..1eb466d0d 100644 --- a/k8-operator/api/v1alpha1/infisicalpushsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicalpushsecret_types.go @@ -52,7 +52,6 @@ type PushSecretGcpIamAuth struct { ServiceAccountKeyFilePath string `json:"serviceAccountKeyFilePath"` } -// Rest of your types should be defined similarly... type PushSecretKubernetesAuth struct { // +kubebuilder:validation:Required IdentityID string `json:"identityId"` 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 de0cd0b16..5e37fdbfa 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicalpushsecrets.yaml @@ -72,7 +72,6 @@ spec: - identityId type: object kubernetesAuth: - description: Rest of your types should be defined similarly... properties: identityId: type: string @@ -114,14 +113,14 @@ spec: type: string destination: properties: - EnvironmentSlug: + environmentSlug: type: string projectId: type: string secretsPath: type: string required: - - EnvironmentSlug + - environmentSlug - projectId - secretsPath type: object From b735618601b7f1581c25d52e3643f01a977f6183 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Sun, 8 Dec 2024 21:29:45 +0400 Subject: [PATCH 11/14] fix(k8-operator): resource-based finalizer names --- k8-operator/packages/constants/constants.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8-operator/packages/constants/constants.go b/k8-operator/packages/constants/constants.go index ff28e408d..909d806e5 100644 --- a/k8-operator/packages/constants/constants.go +++ b/k8-operator/packages/constants/constants.go @@ -13,7 +13,7 @@ const OPERATOR_SETTINGS_CONFIGMAP_NAME = "infisical-config" const OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE = "infisical-operator-system" const INFISICAL_DOMAIN = "https://app.infisical.com/api" -const INFISICAL_PUSH_SECRET_FINALIZER_NAME = "infisical.secrets.infisical.com/finalizer" +const INFISICAL_PUSH_SECRET_FINALIZER_NAME = "pushsecret.secrets.infisical.com/finalizer" type PushSecretReplacePolicy string type PushSecretDeletionPolicy string From 39e598e408124db602c6d8f70897f3938441bb6f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 18 Dec 2024 01:39:32 +0100 Subject: [PATCH 12/14] fix: move fixes from different branch --- .../infisicalpushsecret_helper.go | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go index ebe125a49..9ba514dbc 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go @@ -148,15 +148,6 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context IncludeImports: false, }) - existingSecretsContainsKey := func(key string) bool { - for _, secret := range existingSecrets { - if secret.SecretKey == key { - return true - } - } - return false - } - getExistingSecretByKey := func(key string) *infisicalSdk.Secret { for _, secret := range existingSecrets { if secret.SecretKey == key { @@ -175,6 +166,15 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context return nil } + updateExistingSecretByKey := func(key string, newSecretValue string) { + for i := range existingSecrets { + if existingSecrets[i].SecretKey == key { + existingSecrets[i].SecretValue = newSecretValue + break + } + } + } + if err != nil { return fmt.Errorf("unable to list secrets [err=%s]", err) } @@ -192,7 +192,8 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context infisicalPushSecret.Status.ManagedSecrets = make(map[string]string) // (string[id], string[key] ) for secretKey, secretValue := range kubeSecrets { - if existingSecretsContainsKey(secretKey) { + if exists := getExistingSecretByKey(secretKey); exists != nil { + if updatePolicy == string(constants.PUSH_SECRET_REPLACE_POLICY_ENABLED) { updatedSecret, err := infisicalClient.Secrets().Update(infisicalSdk.UpdateSecretOptions{ SecretKey: secretKey, @@ -306,7 +307,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 { - if !existingSecretsContainsKey(currentSecretKey) { + if exists := getExistingSecretByKey(currentSecretKey); exists == nil { // Some secrets has been added, verify that the secret that has been added is not already managed by the operator if _, ok := infisicalPushSecret.Status.ManagedSecrets[currentSecretKey]; !ok { @@ -332,21 +333,29 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context } } else { if updatePolicy == string(constants.PUSH_SECRET_REPLACE_POLICY_ENABLED) { - updatedSecret, err := infisicalClient.Secrets().Update(infisicalSdk.UpdateSecretOptions{ - SecretKey: currentSecretKey, - NewSecretValue: kubeSecrets[currentSecretKey], - ProjectID: destination.ProjectID, - Environment: destination.EnvironmentSlug, - SecretPath: destination.SecretsPath, - }) - if err != nil { - secretsFailedToUpdate = append(secretsFailedToUpdate, currentSecretKey) - logger.Info(fmt.Sprintf("unable to update secret [key=%s] [err=%s]", currentSecretKey, err)) - continue + existingSecret := getExistingSecretByKey(currentSecretKey) + + if existingSecret != nil && existingSecret.SecretValue != kubeSecrets[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], + ProjectID: destination.ProjectID, + Environment: destination.EnvironmentSlug, + SecretPath: destination.SecretsPath, + }) + + if err != nil { + secretsFailedToUpdate = append(secretsFailedToUpdate, currentSecretKey) + logger.Info(fmt.Sprintf("unable to update secret [key=%s] [err=%s]", currentSecretKey, err)) + continue + } + + updateExistingSecretByKey(currentSecretKey, kubeSecrets[currentSecretKey]) + infisicalPushSecret.Status.ManagedSecrets[updatedSecret.ID] = currentSecretKey } - - infisicalPushSecret.Status.ManagedSecrets[updatedSecret.ID] = currentSecretKey } } } From 0b54948b15f63a40487935a8985280ad8412030e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 18 Dec 2024 02:47:48 +0100 Subject: [PATCH 13/14] fix: better condition error --- k8-operator/controllers/infisicalpushsecret/conditions.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/k8-operator/controllers/infisicalpushsecret/conditions.go b/k8-operator/controllers/infisicalpushsecret/conditions.go index 442cdaaf2..65f32d4a3 100644 --- a/k8-operator/controllers/infisicalpushsecret/conditions.go +++ b/k8-operator/controllers/infisicalpushsecret/conditions.go @@ -2,6 +2,7 @@ package controllers import ( "context" + "fmt" "github.com/Infisical/infisical/k8-operator/api/v1alpha1" "k8s.io/apimachinery/pkg/api/meta" @@ -19,7 +20,7 @@ func (r *InfisicalPushSecretReconciler) SetSuccessfullyReconciledConditions(ctx Type: "secrets.infisical.com/SuccessfullyReconciled", Status: metav1.ConditionTrue, Reason: "Error", - Message: "Reconcile failed, secrets were not pushed to Infisical. Check operator logs for more info", + Message: fmt.Sprintf("Reconcile failed, secrets were not pushed to Infisical. Error: %s", err.Error()), }) } else { meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ From 7eb05afe2af9c26094547def2d8efee32d723142 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 18 Dec 2024 02:54:21 +0100 Subject: [PATCH 14/14] misc: better condition naming --- .../infisicalpushsecret/conditions.go | 16 ++++++++-------- .../infisicalpushsecret_controller.go | 2 +- .../infisicalpushsecret_helper.go | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/k8-operator/controllers/infisicalpushsecret/conditions.go b/k8-operator/controllers/infisicalpushsecret/conditions.go index 65f32d4a3..dd17bc913 100644 --- a/k8-operator/controllers/infisicalpushsecret/conditions.go +++ b/k8-operator/controllers/infisicalpushsecret/conditions.go @@ -9,7 +9,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func (r *InfisicalPushSecretReconciler) SetSuccessfullyReconciledConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, err error) error { +func (r *InfisicalPushSecretReconciler) SetReconcileStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, err error) error { if infisicalPushSecret.Status.Conditions == nil { infisicalPushSecret.Status.Conditions = []metav1.Condition{} @@ -17,14 +17,14 @@ func (r *InfisicalPushSecretReconciler) SetSuccessfullyReconciledConditions(ctx if err != nil { meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ - Type: "secrets.infisical.com/SuccessfullyReconciled", + Type: "secrets.infisical.com/Reconcile", Status: metav1.ConditionTrue, Reason: "Error", Message: fmt.Sprintf("Reconcile failed, secrets were not pushed to Infisical. Error: %s", err.Error()), }) } else { meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{ - Type: "secrets.infisical.com/SuccessfullyReconciled", + Type: "secrets.infisical.com/Reconcile", Status: metav1.ConditionFalse, Reason: "OK", Message: "Reconcile succeeded, secrets were pushed to Infisical", @@ -35,7 +35,7 @@ func (r *InfisicalPushSecretReconciler) SetSuccessfullyReconciledConditions(ctx } -func (r *InfisicalPushSecretReconciler) SetFailedToReplaceSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { +func (r *InfisicalPushSecretReconciler) SetFailedToReplaceSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { if infisicalPushSecret.Status.Conditions == nil { infisicalPushSecret.Status.Conditions = []metav1.Condition{} } @@ -59,7 +59,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToReplaceSecretsConditions(ctx return r.Client.Status().Update(ctx, infisicalPushSecret) } -func (r *InfisicalPushSecretReconciler) SetFailedToCreateSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { +func (r *InfisicalPushSecretReconciler) SetFailedToCreateSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { if infisicalPushSecret.Status.Conditions == nil { infisicalPushSecret.Status.Conditions = []metav1.Condition{} } @@ -83,7 +83,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToCreateSecretsConditions(ctx c return r.Client.Status().Update(ctx, infisicalPushSecret) } -func (r *InfisicalPushSecretReconciler) SetFailedToUpdateSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { +func (r *InfisicalPushSecretReconciler) SetFailedToUpdateSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { if infisicalPushSecret.Status.Conditions == nil { infisicalPushSecret.Status.Conditions = []metav1.Condition{} } @@ -107,7 +107,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToUpdateSecretsConditions(ctx c return r.Client.Status().Update(ctx, infisicalPushSecret) } -func (r *InfisicalPushSecretReconciler) SetFailedToDeleteSecretsConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { +func (r *InfisicalPushSecretReconciler) SetFailedToDeleteSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error { if infisicalPushSecret.Status.Conditions == nil { infisicalPushSecret.Status.Conditions = []metav1.Condition{} } @@ -131,7 +131,7 @@ func (r *InfisicalPushSecretReconciler) SetFailedToDeleteSecretsConditions(ctx c return r.Client.Status().Update(ctx, infisicalPushSecret) } -func (r *InfisicalPushSecretReconciler) SetAuthenticatedConditions(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, errorToConditionOn error) error { +func (r *InfisicalPushSecretReconciler) SetAuthenticatedStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, errorToConditionOn error) error { if infisicalPushSecret.Status.Conditions == nil { infisicalPushSecret.Status.Conditions = []metav1.Condition{} } diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go index af5428ee7..f2125485e 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_controller.go @@ -159,7 +159,7 @@ func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl. } err = r.ReconcileInfisicalPushSecret(ctx, logger, infisicalPushSecretCRD) - r.SetSuccessfullyReconciledConditions(ctx, &infisicalPushSecretCRD, err) + 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)) diff --git a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go index 9ba514dbc..47fc8e69b 100644 --- a/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go +++ b/k8-operator/controllers/infisicalpushsecret/infisicalpushsecret_helper.go @@ -112,7 +112,7 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context if authDetails.AuthStrategy == "" { logger.Info("No authentication strategy found. Attempting to authenticate") authDetails, err = r.handleAuthentication(ctx, infisicalPushSecret, infisicalClient) - r.SetAuthenticatedConditions(ctx, &infisicalPushSecret, err) + r.SetAuthenticatedStatusCondition(ctx, &infisicalPushSecret, err) if err != nil { return fmt.Errorf("unable to authenticate [err=%s]", err) @@ -401,28 +401,28 @@ func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context } else { errorMessage = "" } - r.SetFailedToCreateSecretsConditions(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to create secrets: [%s]", errorMessage)) + r.SetFailedToCreateSecretsStatusCondition(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to create secrets: [%s]", errorMessage)) if len(secretsFailedToUpdate) > 0 { errorMessage = fmt.Sprintf("Failed to update secrets: [%s]", strings.Join(secretsFailedToUpdate, ", ")) } else { errorMessage = "" } - r.SetFailedToUpdateSecretsConditions(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to update secrets: [%s]", errorMessage)) + r.SetFailedToUpdateSecretsStatusCondition(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to update secrets: [%s]", errorMessage)) if len(secretsFailedToDelete) > 0 { errorMessage = fmt.Sprintf("Failed to delete secrets: [%s]", strings.Join(secretsFailedToDelete, ", ")) } else { errorMessage = "" } - r.SetFailedToDeleteSecretsConditions(ctx, &infisicalPushSecret, errorMessage) + r.SetFailedToDeleteSecretsStatusCondition(ctx, &infisicalPushSecret, errorMessage) if len(secretsFailedToReplaceById) > 0 { errorMessage = fmt.Sprintf("Failed to replace secrets: [%s]", strings.Join(secretsFailedToReplaceById, ", ")) } else { errorMessage = "" } - r.SetFailedToReplaceSecretsConditions(ctx, &infisicalPushSecret, errorMessage) + r.SetFailedToReplaceSecretsStatusCondition(ctx, &infisicalPushSecret, errorMessage) // Update the status of the InfisicalPushSecret if err := r.Client.Status().Update(ctx, &infisicalPushSecret); err != nil {