diff --git a/README.md b/README.md index 2090e90..57e3833 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,38 @@ spec: # The name of the ServiceAccount to use to run the managed pod. # serviceAccountName: "default" + # The X509 certificate to verify the connection to the configuration snapshot + # service. The default value for this property is "operator", which reads the "tls.cert" + # value from the verify-access-operator secret created in the namespace that the Verify + # Access pods are deployed to. + snapshotTLSCacert: "operator" + + + # The IBM License Metric Tool annotations to add to the runtime container. These annotations a required + # by IBM to track license useage for the IBM Security Verfy Access product. Administartors have the option + # of using licence codes for WebSEAL, Advanced Access Cotnrol, Federation or Enterprise; as well as production + # or non-production (development) licenses. The actual license codes you sould deploy will depend on your + # licensing agreement with IBM. + ilmtAnnotations: + module: welseal + production: true + + # Administarators can optionally set additional annotations to add to deployed Verify Access runtime + # containers. This may be used for integration with third party applications such as log aggregation + # or infrastructure monitoring tools. Character restrictions for custom annotations are the same for + # any other Kubernets annotation. + # More info can be found at: + # https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#syntax-and-character-set + customAnnotations: + - key: my.custom/Annotation + value: annotationToAdd + + + # The ordered list of secrets used to decrypt configuration snapshot files. This + # property is required if the configuration snapshot file being used was encrypted + # when it was created. + snapshotSecrets: "secreteToDecryptSnapshotFiles||AnotherSecretToDecryptFiles" + # Any specific container information which is associated with this # container. The container options include: # env diff --git a/src/Makefile b/src/Makefile index 251d5e8..3bb386f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -132,7 +132,7 @@ controller-gen: ## Download controller-gen locally if necessary. KUSTOMIZE = $(shell pwd)/bin/kustomize kustomize: ## Download kustomize locally if necessary. - $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) + $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v4@v4.5.2) # go-get-tool will 'go get' any package $2 and install it to $1. PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) @@ -143,7 +143,7 @@ TMP_DIR=$$(mktemp -d) ;\ cd $$TMP_DIR ;\ go mod init tmp ;\ echo "Downloading $(2)" ;\ -GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ +GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\ rm -rf $$TMP_DIR ;\ } endef diff --git a/src/api/v1/groupversion_info.go b/src/api/v1/groupversion_info.go index 9415803..9b9134f 100644 --- a/src/api/v1/groupversion_info.go +++ b/src/api/v1/groupversion_info.go @@ -3,8 +3,8 @@ */ // Package v1 contains API Schema definitions for the ibm v1 API group -//+kubebuilder:object:generate=true -//+groupName=ibm.com +// +kubebuilder:object:generate=true +// +groupName=ibm.com package v1 import ( diff --git a/src/api/v1/ibmsecurityverifyaccess_types.go b/src/api/v1/ibmsecurityverifyaccess_types.go index 80c03ad..c398d0a 100644 --- a/src/api/v1/ibmsecurityverifyaccess_types.go +++ b/src/api/v1/ibmsecurityverifyaccess_types.go @@ -5,211 +5,269 @@ package v1 import ( + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - corev1 "k8s.io/api/core/v1" ) // IBMSecurityVerifyAccessContainer defines the make-up of the container. It // is loosely based on the corev1.Container structure. type IBMSecurityVerifyAccessContainer struct { - // List of sources to populate environment variables in the container. - // The keys defined within a source must be a C_IDENTIFIER. All invalid keys - // will be reported as an event when the container is starting. When a key - // exists in multiple sources, the value associated with the last source - // will take precedence. Values defined by an Env with a duplicate key - // will take precedence. - // Cannot be updated. - // +optional - EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty" protobuf:"bytes,19,rep,name=envFrom"` - - // List of environment variables to set in the container. - // Cannot be updated. - // +optional - // +patchMergeKey=name - // +patchStrategy=merge - Env []corev1.EnvVar `json:"env,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,7,rep,name=env"` - - // Compute Resources required by this container. - // Cannot be updated. - // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - // +optional - Resources corev1.ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,8,opt,name=resources"` - - // Pod volumes to mount into the container's filesystem. - // Cannot be updated. - // +optional - // +patchMergeKey=mountPath - // +patchStrategy=merge - VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty" patchStrategy:"merge" patchMergeKey:"mountPath" protobuf:"bytes,9,rep,name=volumeMounts"` - - // volumeDevices is the list of block devices to be used by the container. - // +patchMergeKey=devicePath - // +patchStrategy=merge - // +optional - VolumeDevices []corev1.VolumeDevice `json:"volumeDevices,omitempty" patchStrategy:"merge" patchMergeKey:"devicePath" protobuf:"bytes,21,rep,name=volumeDevices"` - - // Periodic probe of container liveness. - // Container will be restarted if the probe fails. - // Cannot be updated. - // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes - // +optional - LivenessProbe *corev1.Probe `json:"livenessProbe,omitempty" protobuf:"bytes,10,opt,name=livenessProbe"` - - // Periodic probe of container service readiness. - // Container will be removed from service endpoints if the probe fails. - // Cannot be updated. - // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes - // +optional - ReadinessProbe *corev1.Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"` - - // StartupProbe indicates that the Pod has successfully initialized. - // If specified, no other probes are executed until this completes - // successfully. If this probe fails, the Pod will be restarted, just as - // if the livenessProbe failed. This can be used to provide different - // probe parameters at the beginning of a Pod's lifecycle, when it might - // take a long time to load data or warm a cache, than during steady-state - // operation. - // This cannot be updated. - // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes - // +optional - StartupProbe *corev1.Probe `json:"startupProbe,omitempty" protobuf:"bytes,22,opt,name=startupProbe"` - - // Image pull policy. - // One of Always, Never, IfNotPresent. - // Defaults to Always if :latest tag is specified, or IfNotPresent - // otherwise. - // Cannot be updated. - // More info: https://kubernetes.io/docs/concepts/containers/images#updating-images - // +optional - ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty" protobuf:"bytes,14,opt,name=imagePullPolicy,casttype=PullPolicy"` - - // SecurityContext defines the security options the container should be run - // with. If set, the fields of SecurityContext override the equivalent - // fields of PodSecurityContext. - // More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ - // +optional - SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty" protobuf:"bytes,15,opt,name=securityContext"` + // List of sources to populate environment variables in the container. + // The keys defined within a source must be a C_IDENTIFIER. All invalid keys + // will be reported as an event when the container is starting. When a key + // exists in multiple sources, the value associated with the last source + // will take precedence. Values defined by an Env with a duplicate key + // will take precedence. + // Cannot be updated. + // +optional + EnvFrom []corev1.EnvFromSource `json:"envFrom,omitempty" protobuf:"bytes,19,rep,name=envFrom"` + + // List of environment variables to set in the container. + // Cannot be updated. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + Env []corev1.EnvVar `json:"env,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,7,rep,name=env"` + + // Compute Resources required by this container. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + // +optional + Resources corev1.ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,8,opt,name=resources"` + + // Pod volumes to mount into the container's filesystem. + // Cannot be updated. + // +optional + // +patchMergeKey=mountPath + // +patchStrategy=merge + VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty" patchStrategy:"merge" patchMergeKey:"mountPath" protobuf:"bytes,9,rep,name=volumeMounts"` + + // volumeDevices is the list of block devices to be used by the container. + // +patchMergeKey=devicePath + // +patchStrategy=merge + // +optional + VolumeDevices []corev1.VolumeDevice `json:"volumeDevices,omitempty" patchStrategy:"merge" patchMergeKey:"devicePath" protobuf:"bytes,21,rep,name=volumeDevices"` + + // Periodic probe of container liveness. + // Container will be restarted if the probe fails. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + LivenessProbe *corev1.Probe `json:"livenessProbe,omitempty" protobuf:"bytes,10,opt,name=livenessProbe"` + + // Periodic probe of container service readiness. + // Container will be removed from service endpoints if the probe fails. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + ReadinessProbe *corev1.Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"` + + // StartupProbe indicates that the Pod has successfully initialized. + // If specified, no other probes are executed until this completes + // successfully. If this probe fails, the Pod will be restarted, just as + // if the livenessProbe failed. This can be used to provide different + // probe parameters at the beginning of a Pod's lifecycle, when it might + // take a long time to load data or warm a cache, than during steady-state + // operation. + // This cannot be updated. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + StartupProbe *corev1.Probe `json:"startupProbe,omitempty" protobuf:"bytes,22,opt,name=startupProbe"` + + // Image pull policy. + // One of Always, Never, IfNotPresent. + // Defaults to Always if :latest tag is specified, or IfNotPresent + // otherwise. + // Cannot be updated. + // More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + // +optional + ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty" protobuf:"bytes,14,opt,name=imagePullPolicy,casttype=PullPolicy"` + + // SecurityContext defines the security options the container should be run + // with. If set, the fields of SecurityContext override the equivalent + // fields of PodSecurityContext. + // More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + // +optional + SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty" protobuf:"bytes,15,opt,name=securityContext"` } -// Language is the language in which messages will be displayed in the +// Language is the language in which messages will be displayed in the // deployment. type Language string + +const ( + Chinese_Simplified Language = "zh_CN.utf8" + Chinese_Traditional Language = "zh_TW.utf8" + Czech Language = "cs_CZ.utf8" + English Language = "en_US.utf8" + French Language = "fr_FR.utf8" + German Language = "de_DE.utf8" + Hungarian Language = "hu_HU.utf8" + Italian Language = "it_IT.utf8" + Japanese Language = "ja_JP.utf8" + Korean Language = "ko_KR.utf8" + Polish Language = "pl_PL.utf8" + Portuguese Language = "pt_BR.utf8" + Russian Language = "ru_RU.utf8" + Spanish Language = "es_ES.utf8" +) + +type LicenseModule string + const ( - Chinese_Simplified Language = "zh_CN.utf8" - Chinese_Traditional Language = "zh_TW.utf8" - Czech Language = "cs_CZ.utf8" - English Language = "en_US.utf8" - French Language = "fr_FR.utf8" - German Language = "de_DE.utf8" - Hungarian Language = "hu_HU.utf8" - Italian Language = "it_IT.utf8" - Japanese Language = "ja_JP.utf8" - Korean Language = "ko_KR.utf8" - Polish Language = "pl_PL.utf8" - Portuguese Language = "pt_BR.utf8" - Russian Language = "ru_RU.utf8" - Spanish Language = "es_ES.utf8" + Access_Control LicenseModule = "access_control" + Federation LicenseModule = "federation" + ReverseProxy LicenseModule = "webseal" + Enterprise LicenseModule = "enterprise" ) +// IBM License Metric Tool Annotations define the licence annotations added to runtime containers +// to track license usage by the IBM Licence Metric Tool. +type ILMTAnnotations struct { + // +kubebuilder:default=webseal + // +kubebuilder:validation:Enum=webseal;federation;access_control;enterprise + // Licensed module to attach to container. + Module LicenseModule `json:"module" protobuf:"bytes,32,rep,name=module,casttype=LicenseModule"` + // +kubebuilder: default=true + // Boolean flag to switch between production and development annotations. + Production bool `json:"production"` +} + +// Custom annotations to add to deployed Verify Access runtime container. +type CustomAnnotation struct { + // Key of the annotation to create. + Key string `json:"key" protobuf:"bytes,64,rep,name=key"` + // Value of the annotation to create. + Value string `json:"value" protobuf:"bytes,64,rep,name=value"` +} + // IBMSecurityVerifyAccessSpec defines the desired state of an // IBMSecurityVerifyAccess resource. type IBMSecurityVerifyAccessSpec struct { - // The name of the image which will be used in the deployment. - // Cannot be updated. - Image string `json:"image"` - - //+kubebuilder:validation:Minimum=0 - //+kubebuilder:default=1 - // Replicas is the number of pods which will be started for the deployment. - // +optional - Replicas int32 `json:"replicas"` - - //+kubebuilder:default=true - // AutoRestart is a boolean which indicates whether the deployment should - // be restarted if a new snapshot is published - // +optional - AutoRestart bool `json:"autoRestart"` - - //+kubebuilder:default=published - // SnapshotId is a string which is used to indicate the identifier of the - // snapshot which should be used. If no identifier is specified a default - // snapshot of 'published' will be used. - // Cannot be updated. - // +optional - SnapshotId string `json:"snapshotId"` - - // Fixpacks is an array of strings which indicate the name of fixpacks - // which should be installed in the deployment. This corresponds to - // setting the FIXPACKS environment variable in the deployment itself. - // Cannot be updated. - // +optional - Fixpacks []string `json:"fixpacks,omitempty"` - - // Instance is the name of the Verify Access instance which is being - // started. This value is only used for WRP and DSC deployments and is - // ignored for Runtime deployments. - // Defaults to 'default'. - // Cannot be updated. - // +optional - Instance string `json:"instance"` - - // +kubebuilder:validation:Enum=zh_CN.utf8;zh_TW.utf8;cs_CZ.utf8;en_US.utf8;fr_FR.utf8;de_DE.utf8;hu_HU.utf8;it_IT.utf8;ja_JP.utf8;ko_KR.utf8;pl_PL.utf8;pt_BR.utf8;ru_RU.utf8;es_ES.utf8 - // Language is the language which will be used for messages which are logged - // by the deployment. - // Cannot be updated. - // +optional - Language Language `json:"language,omitempty" protobuf:"bytes,14,opt,name=language,casttype=Language"` - - // List of volumes that can be mounted by containers belonging to the pod. - // More info: https://kubernetes.io/docs/concepts/storage/volumes - // +optional - // +patchMergeKey=name - // +patchStrategy=merge,retainKeys - Volumes []corev1.Volume `json:"volumes,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name" protobuf:"bytes,1,rep,name=volumes"` - - // ImagePullSecrets is an optional list of references to secrets in the same - // namespace to use for pulling any of the images used by this PodSpec. - // If specified, these secrets will be passed to individual puller - // implementations for them to use. For example, - // in the case of docker, only DockerConfig type secrets are honored. - // More info: - // https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod - // +optional - // +patchMergeKey=name - // +patchStrategy=merge - ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,15,rep,name=imagePullSecrets"` - - // ServiceAccountName is the name of the ServiceAccount to use to run this pod. - // More info: - // https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ - // +optional - ServiceAccountName string `json:"serviceAccountName,omitempty" protobuf:"bytes,8,opt,name=serviceAccountName"` - - // The definition for the container which is being created. - // Cannot be updated. - // +optional - Container IBMSecurityVerifyAccessContainer `json:"container,omitempty"` + // The name of the image which will be used in the deployment. + // Cannot be updated. + Image string `json:"image"` + + //+kubebuilder:validation:Minimum=0 + //+kubebuilder:default=1 + // Replicas is the number of pods which will be started for the deployment. + // +optional + Replicas int32 `json:"replicas"` + + //+kubebuilder:default=true + // AutoRestart is a boolean which indicates whether the deployment should + // be restarted if a new snapshot is published + // +optional + AutoRestart bool `json:"autoRestart"` + + //+kubebuilder:default=published + // SnapshotId is a string which is used to indicate the identifier of the + // snapshot which should be used. If no identifier is specified a default + // snapshot of 'published' will be used. + // Cannot be updated. + // +optional + SnapshotId string `json:"snapshotId"` + + // List of secrets to decrypt configuration snapshot files. Secrets are separated by '||'. This option is the + // equivalent of setting the CONFIG_SNAPSHOT_SECRETS environment property. + // +optional + SnapshotSecrets string `json:"snapshotSecrets"` + + //+kubebuilder:default=operator + // SnapshotTLSCacert is a string which defines how the Verify Access runtime containers + // verify connections to the snapshot management service. This option is the equivalent + // of setting the CONFIG_SERVICE_TLS_CACERT environment property. The default option for this + // property is to read the X509 certificate for the Operator's snapshot management service + // from the verify-access-operator secret. + // Note: Administrators must ensure that the service account for the runtime containers has + // permission to read Secrets in the namespace that the Pod is deployed to in order for this to work. + // +optional + SnapshotTLSCacert string `json:"snapshotTLSCacert"` + + // Fixpacks is an array of strings which indicate the name of fixpacks + // which should be installed in the deployment. This corresponds to + // setting the FIXPACKS environment variable in the deployment itself. + // Cannot be updated. + // +optional + Fixpacks []string `json:"fixpacks,omitempty"` + + // Instance is the name of the Verify Access instance which is being + // started. This value is only used for WRP and DSC deployments and is + // ignored for Runtime deployments. + // Defaults to 'default'. + // Cannot be updated. + // +optional + Instance string `json:"instance"` + + // +kubebuilder:validation:Enum=zh_CN.utf8;zh_TW.utf8;cs_CZ.utf8;en_US.utf8;fr_FR.utf8;de_DE.utf8;hu_HU.utf8;it_IT.utf8;ja_JP.utf8;ko_KR.utf8;pl_PL.utf8;pt_BR.utf8;ru_RU.utf8;es_ES.utf8 + // Language is the language which will be used for messages which are logged + // by the deployment. + // Cannot be updated. + // +optional + Language Language `json:"language,omitempty" protobuf:"bytes,14,opt,name=language,casttype=Language"` + + // List of volumes that can be mounted by containers belonging to the pod. + // More info: https://kubernetes.io/docs/concepts/storage/volumes + // +optional + // +patchMergeKey=name + // +patchStrategy=merge,retainKeys + Volumes []corev1.Volume `json:"volumes,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name" protobuf:"bytes,1,rep,name=volumes"` + + // ImagePullSecrets is an optional list of references to secrets in the same + // namespace to use for pulling any of the images used by this PodSpec. + // If specified, these secrets will be passed to individual puller + // implementations for them to use. For example, + // in the case of docker, only DockerConfig type secrets are honored. + // More info: + // https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,15,rep,name=imagePullSecrets"` + + // ServiceAccountName is the name of the ServiceAccount to use to run this pod. + // More info: + // https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + // +optional + ServiceAccountName string `json:"serviceAccountName,omitempty" protobuf:"bytes,8,opt,name=serviceAccountName"` + + // The set of custom annotations to add to the container being created. + // Cannot be updated. + // +optional + // +patchMergeKey=key + // +patchStrategy=merge, + CustomAnnotations []CustomAnnotation `json:"customAnnotations,omitempty" patchStrategy:"merge" patchMergeKey:"key" protobuf:"bytes,5,opt,name=customAnnotations"` + + // The IBM License Metric Tool annotations to add to runtime containers. Annotations are used by IBM to + // track license usage for containerised environments. + // +optional + LicenseAnnotations *ILMTAnnotations `json:"ilmtAnnotations,omitempty" protobuf:"bytes,64,opt,name=ilmt_annotations,casttype=ILMTAnnotations"` + + // The definition for the container which is being created. + // Cannot be updated. + // +optional + Container IBMSecurityVerifyAccessContainer `json:"container,omitempty"` } // IBMSecurityVerifyAccessStatus defines the observed state of an // IBMSecurityVerifyAccess resource. type IBMSecurityVerifyAccessStatus struct { - // Conditions is the list of status conditions for this resource - Conditions []metav1.Condition `json:"conditions,omitempty"` + // Conditions is the list of status conditions for this resource + Conditions []metav1.Condition `json:"conditions,omitempty"` } //+kubebuilder:object:root=true //+kubebuilder:subresource:status // IBMSecurityVerifyAccess is the Schema for the ibmsecurityverifyaccesses API. -//+kubebuilder:subresource:status +// +kubebuilder:subresource:status type IBMSecurityVerifyAccess struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` - Spec IBMSecurityVerifyAccessSpec `json:"spec,omitempty"` - Status IBMSecurityVerifyAccessStatus `json:"status,omitempty"` + Spec IBMSecurityVerifyAccessSpec `json:"spec,omitempty"` + Status IBMSecurityVerifyAccessStatus `json:"status,omitempty"` } //+kubebuilder:object:root=true @@ -217,13 +275,12 @@ type IBMSecurityVerifyAccess struct { // IBMSecurityVerifyAccessList contains a list of IBMSecurityVerifyAccess // resources. type IBMSecurityVerifyAccessList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []IBMSecurityVerifyAccess `json:"items"` + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []IBMSecurityVerifyAccess `json:"items"` } func init() { - SchemeBuilder.Register( - &IBMSecurityVerifyAccess{}, &IBMSecurityVerifyAccessList{}) + SchemeBuilder.Register( + &IBMSecurityVerifyAccess{}, &IBMSecurityVerifyAccessList{}) } - diff --git a/src/bundle.Dockerfile b/src/bundle.Dockerfile index 1b797c5..5aa9616 100644 --- a/src/bundle.Dockerfile +++ b/src/bundle.Dockerfile @@ -1,5 +1,3 @@ -# Copyright contributors to the IBM Security Verify Access Operator project - FROM scratch # Core bundle labels. @@ -8,7 +6,7 @@ LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ LABEL operators.operatorframework.io.bundle.package.v1=ibm-security-verify-access-operator LABEL operators.operatorframework.io.bundle.channels.v1=stable -LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.7.1+git +LABEL operators.operatorframework.io.metrics.builder=operator-sdk-v1.32.0 LABEL operators.operatorframework.io.metrics.mediatype.v1=metrics+v1 LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3 @@ -16,8 +14,6 @@ LABEL operators.operatorframework.io.metrics.project_layout=go.kubebuilder.io/v3 LABEL operators.operatorframework.io.test.mediatype.v1=scorecard+v1 LABEL operators.operatorframework.io.test.config.v1=tests/scorecard/ -LABEL com.redhat.openshift.versions="v4.6" - # Copy files to locations specified by labels. COPY bundle/manifests /manifests/ COPY bundle/metadata /metadata/ diff --git a/src/config/manager/manager.yaml b/src/config/manager/manager.yaml index c037416..b033f70 100644 --- a/src/config/manager/manager.yaml +++ b/src/config/manager/manager.yaml @@ -50,7 +50,7 @@ spec: resources: limits: cpu: 200m - memory: 100Mi + memory: 200Mi requests: cpu: 100m memory: 20Mi diff --git a/src/config/samples/ibm_v1_ibmsecurityverifyaccess.yaml b/src/config/samples/ibm_v1_ibmsecurityverifyaccess.yaml index 7bee4bd..deca820 100644 --- a/src/config/samples/ibm_v1_ibmsecurityverifyaccess.yaml +++ b/src/config/samples/ibm_v1_ibmsecurityverifyaccess.yaml @@ -9,7 +9,7 @@ metadata: spec: # The name of the image which will be used in the deployment. - image: "icr.io/isva/verify-access-wrp:10.0.5.0" + image: "icr.io/isva/verify-access-wrp:10.0.7.0" # The number of pods which will be started for the deployment. # replicas: 1 diff --git a/src/controllers/constants.go b/src/controllers/constants.go index d2cb507..89f8944 100644 --- a/src/controllers/constants.go +++ b/src/controllers/constants.go @@ -11,8 +11,7 @@ package controllers * in which the snapshotmgr is running. */ -const k8sNamespaceFile string = - "/var/run/secrets/kubernetes.io/serviceaccount/namespace" +const k8sNamespaceFile string = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" /* * The name which is given to our operator. This same name will also be @@ -25,8 +24,7 @@ const operatorName string = "verify-access-operator" * The name of our exported snapshot manager configuration service. */ -const serviceName string = - "verify-access-operator-controller-manager-snapshot-service" +const serviceName string = "verify-access-operator-controller-manager-snapshot-service" /* * The custom resource type. @@ -45,12 +43,12 @@ const snapshotMgrUser string = "apikey" * The name of the various fields in the secret. */ -const userFieldName string = "user" -const urlFieldName string = "url" +const userFieldName string = "user" +const urlFieldName string = "url" const roPwdFieldName string = "ro.pwd" const rwPwdFieldName string = "rw.pwd" -const certFieldName string = "tls.cert" -const keyFieldName string = "tls.key" +const certFieldName string = "tls.cert" +const keyFieldName string = "tls.key" /* * The length of our generated passwords. @@ -62,7 +60,7 @@ const pwdLength int = 36 * The length of the generated X509 key. */ -const keyLength int = 2048; +const keyLength int = 2048 /* * The port on which the snapshot manager will listen for requests. @@ -77,10 +75,8 @@ const httpsPort int = 7443 const dataRoot string = "/data" /* - * The maximum amount of memory which should be used when receiving a + * The maximum amount of memory which should be used when receiving a * file. */ const maxMemory int64 = 1024 - - diff --git a/src/controllers/ibmsecurityverifyaccess_controller.go b/src/controllers/ibmsecurityverifyaccess_controller.go index 0260a2b..d6cdf0b 100644 --- a/src/controllers/ibmsecurityverifyaccess_controller.go +++ b/src/controllers/ibmsecurityverifyaccess_controller.go @@ -7,44 +7,45 @@ package controllers /*****************************************************************************/ import ( - apiv1 "k8s.io/api/core/v1" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + appsv1 "k8s.io/api/apps/v1" + apiv1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" - "context" - "fmt" - "strings" - "sync" - "time" + "context" + "encoding/base64" + "fmt" + "strings" + "sync" + "time" - "github.com/go-logr/logr" + "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" - ibmv1 "github.com/ibm-security/verify-access-operator/api/v1" + ibmv1 "github.com/ibm-security/verify-access-operator/api/v1" ) /*****************************************************************************/ /* - * The IBMSecurityVerifyAccessReconciler structure reconciles an + * The IBMSecurityVerifyAccessReconciler structure reconciles an * IBMSecurityVerifyAccess object. */ type IBMSecurityVerifyAccessReconciler struct { - client.Client + client.Client - Log logr.Logger - Scheme *runtime.Scheme - localNamespace string - snapshotMgr SnapshotMgr - secretMutex *sync.Mutex + Log logr.Logger + Scheme *runtime.Scheme + localNamespace string + snapshotMgr SnapshotMgr + secretMutex *sync.Mutex } /*****************************************************************************/ @@ -67,125 +68,125 @@ type IBMSecurityVerifyAccessReconciler struct { */ func (r *IBMSecurityVerifyAccessReconciler) Reconcile( - ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - r.Log.V(9).Info("Entering a function", "Function", "Reconcile") + r.Log.V(9).Info("Entering a function", "Function", "Reconcile") - /* - * Fetch the definition document. - */ + /* + * Fetch the definition document. + */ - verifyaccess := &ibmv1.IBMSecurityVerifyAccess{} - err := r.Get(ctx, req.NamespacedName, verifyaccess) + verifyaccess := &ibmv1.IBMSecurityVerifyAccess{} + err := r.Get(ctx, req.NamespacedName, verifyaccess) - if err != nil { - if errors.IsNotFound(err) { - /* - * The requested object was not found. It could have been deleted - * after the reconcile request. - */ + if err != nil { + if errors.IsNotFound(err) { + /* + * The requested object was not found. It could have been deleted + * after the reconcile request. + */ - r.Log.Info("The VerifyAccess resource was not found. " + - "Ignoring this error since the object must have been deleted") + r.Log.Info("The VerifyAccess resource was not found. " + + "Ignoring this error since the object must have been deleted") - err = nil - } else { - /* - * There was an error reading the object - requeue the request. - */ + err = nil + } else { + /* + * There was an error reading the object - requeue the request. + */ - r.Log.Error(err, "Failed to get the VerifyAccess resource") - } + r.Log.Error(err, "Failed to get the VerifyAccess resource") + } - return ctrl.Result{}, err - } + return ctrl.Result{}, err + } - /* - * Check if the deployment already exists, and if one doesn't we create a - * new one now. - */ + /* + * Check if the deployment already exists, and if one doesn't we create a + * new one now. + */ - found := &appsv1.Deployment{} - err = r.Get( - ctx, - types.NamespacedName{ - Name: verifyaccess.Name, - Namespace: verifyaccess.Namespace}, - found) + found := &appsv1.Deployment{} + err = r.Get( + ctx, + types.NamespacedName{ + Name: verifyaccess.Name, + Namespace: verifyaccess.Namespace}, + found) - if err != nil { - if errors.IsNotFound(err) { - /* - * The deployment requires a secret which contains the snapshot - * manager credentials. We need to create the secret in the - * destination namespace if it doesn't already exist. - */ + if err != nil { + if errors.IsNotFound(err) { + /* + * The deployment requires a secret which contains the snapshot + * manager credentials. We need to create the secret in the + * destination namespace if it doesn't already exist. + */ - err = r.createSecret(ctx, verifyaccess) + err = r.createSecret(ctx, verifyaccess) - if err == nil { - /* - * A deployment does not already exist and so we create a new - * deployment. - */ + if err == nil { + /* + * A deployment does not already exist and so we create a new + * deployment. + */ - dep := r.deploymentForVerifyAccess(verifyaccess) + dep := r.deploymentForVerifyAccess(verifyaccess) - r.Log.Info("Creating a new deployment", "Deployment.Namespace", - dep.Namespace, "Deployment.Name", dep.Name) + r.Log.Info("Creating a new deployment", "Deployment.Namespace", + dep.Namespace, "Deployment.Name", dep.Name) - err = r.Create(ctx, dep) + err = r.Create(ctx, dep) - if err != nil { - r.Log.Error(err, "Failed to create the new deployment", - "Deployment.Namespace", dep.Namespace, - "Deployment.Name", dep.Name) - } - } + if err != nil { + r.Log.Error(err, "Failed to create the new deployment", + "Deployment.Namespace", dep.Namespace, + "Deployment.Name", dep.Name) + } + } - } else { - r.Log.Error(err, "Failed to retrieve the Deployment resource") - } + } else { + r.Log.Error(err, "Failed to retrieve the Deployment resource") + } - r.setCondition(err, true, ctx, verifyaccess) + r.setCondition(err, true, ctx, verifyaccess) - return ctrl.Result{}, err + return ctrl.Result{}, err - } + } - /* - * The deployment already exists. We now need to check to see if any - * of our CR fields have been updated which will require an update of - * the deployment. - */ + /* + * The deployment already exists. We now need to check to see if any + * of our CR fields have been updated which will require an update of + * the deployment. + */ - r.Log.V(5).Info("Found a matching deployment", - "Deployment.Namespace", found.Namespace, - "Deployment.Name", found.Name) + r.Log.V(5).Info("Found a matching deployment", + "Deployment.Namespace", found.Namespace, + "Deployment.Name", found.Name) - replicas := verifyaccess.Spec.Replicas + replicas := verifyaccess.Spec.Replicas - if *found.Spec.Replicas != replicas { - found.Spec.Replicas = &replicas + if *found.Spec.Replicas != replicas { + found.Spec.Replicas = &replicas - err = r.Update(ctx, found) + err = r.Update(ctx, found) - if err != nil { - r.Log.Error(err, "Failed to update deployment", - "Deployment.Namespace", found.Namespace, - "Deployment.Name", found.Name) - } else { - r.Log.Info("Updated an existing deployment", - "Deployment.Namespace", found.Namespace, - "Deployment.Name", found.Name) - } + if err != nil { + r.Log.Error(err, "Failed to update deployment", + "Deployment.Namespace", found.Namespace, + "Deployment.Name", found.Name) + } else { + r.Log.Info("Updated an existing deployment", + "Deployment.Namespace", found.Namespace, + "Deployment.Name", found.Name) + } - r.setCondition(err, false, ctx, verifyaccess) + r.setCondition(err, false, ctx, verifyaccess) - return ctrl.Result{}, err - } + return ctrl.Result{}, err + } - return ctrl.Result{}, nil + return ctrl.Result{}, nil } /*****************************************************************************/ @@ -196,51 +197,51 @@ func (r *IBMSecurityVerifyAccessReconciler) Reconcile( */ func (r *IBMSecurityVerifyAccessReconciler) setCondition( - err error, - isCreate bool, - ctx context.Context, - m *ibmv1.IBMSecurityVerifyAccess) error { - - var condReason string - var condMessage string - - if isCreate { - condReason = "DeploymentCreated" - condMessage = "The deployment has been created." - } else { - condReason = "DeploymentUpdated" - condMessage = "The deployment has been updated." - } - - currentTime := metav1.NewTime(time.Now()) - - if err == nil { - m.Status.Conditions = []metav1.Condition{{ - Type: "Available", - Status: metav1.ConditionTrue, - Reason: condReason, - Message: condMessage, - LastTransitionTime: currentTime, - }} - } else { - m.Status.Conditions = []metav1.Condition{{ - Type: "Available", - Status: metav1.ConditionFalse, - Reason: condReason, - Message: err.Error(), - LastTransitionTime: currentTime, - }} - } - - if err := r.Status().Update(ctx, m); err != nil { - r.Log.Error(err, "Failed to update the condition for the resource", - "Deployment.Namespace", m.Namespace, - "Deployment.Name", m.Name) - - return err - } - - return nil + err error, + isCreate bool, + ctx context.Context, + m *ibmv1.IBMSecurityVerifyAccess) error { + + var condReason string + var condMessage string + + if isCreate { + condReason = "DeploymentCreated" + condMessage = "The deployment has been created." + } else { + condReason = "DeploymentUpdated" + condMessage = "The deployment has been updated." + } + + currentTime := metav1.NewTime(time.Now()) + + if err == nil { + m.Status.Conditions = []metav1.Condition{{ + Type: "Available", + Status: metav1.ConditionTrue, + Reason: condReason, + Message: condMessage, + LastTransitionTime: currentTime, + }} + } else { + m.Status.Conditions = []metav1.Condition{{ + Type: "Available", + Status: metav1.ConditionFalse, + Reason: condReason, + Message: err.Error(), + LastTransitionTime: currentTime, + }} + } + + if err := r.Status().Update(ctx, m); err != nil { + r.Log.Error(err, "Failed to update the condition for the resource", + "Deployment.Namespace", m.Namespace, + "Deployment.Name", m.Name) + + return err + } + + return nil } /*****************************************************************************/ @@ -251,69 +252,95 @@ func (r *IBMSecurityVerifyAccessReconciler) setCondition( */ func (r *IBMSecurityVerifyAccessReconciler) createSecret( - ctx context.Context, - m *ibmv1.IBMSecurityVerifyAccess) (err error) { - - r.secretMutex.Lock() - - /* - * Check to see if the secret already exists. - */ - - secret := &corev1.Secret{} - err = r.Get( - ctx, - types.NamespacedName{ - Name: operatorName, - Namespace: m.Namespace, - }, - secret) - - if err != nil { - if errors.IsNotFound(err) { - /* - * The secret doesn't already exist and so we need to create - * the secret now. - */ - - r.Log.V(5).Info("Creating the secret", - "Deployment.Namespace", m.Namespace, - "Secret.Name", operatorName) - - secret = &corev1.Secret{ - Type: apiv1.SecretTypeOpaque, - ObjectMeta: metav1.ObjectMeta { - Name: operatorName, - Namespace: m.Namespace, - }, - StringData: map[string]string { - userFieldName: snapshotMgrUser, - urlFieldName: r.snapshotMgr.creds[urlFieldName], - roPwdFieldName: r.snapshotMgr.creds[roPwdFieldName], - }, - } - - err = r.Create(ctx, secret) - - if err != nil { - r.Log.Error(err, "Failed to create the secret", - "Deployment.Namespace", m.Namespace, - "Secret.Name", operatorName) - } - } else { - r.Log.Error(err, "Failed to retrieve the secret", - "Deployment.Namespace", m.Namespace, - "Secret.Name", operatorName) - } - } else { - r.Log.V(5).Info("Found an existing secret", - "Deployment.Namespace", m.Namespace, - "Secret.Name", operatorName) - } - - r.secretMutex.Unlock() - - return + ctx context.Context, + m *ibmv1.IBMSecurityVerifyAccess) (err error) { + + r.secretMutex.Lock() + + /* + * Check to see if the secret already exists. + */ + + secret := &corev1.Secret{} + err = r.Get( + ctx, + types.NamespacedName{ + Name: operatorName, + Namespace: m.Namespace, + }, + secret) + + if err != nil { + if errors.IsNotFound(err) { + /* + * The secret doesn't already exist and so we need to create + * the secret now. + */ + + r.Log.V(5).Info("Creating the secret", + "Deployment.Namespace", m.Namespace, + "Secret.Name", operatorName) + + secret = &corev1.Secret{ + Type: apiv1.SecretTypeOpaque, + ObjectMeta: metav1.ObjectMeta{ + Name: operatorName, + Namespace: m.Namespace, + }, + StringData: map[string]string{ + userFieldName: snapshotMgrUser, + urlFieldName: r.snapshotMgr.creds[urlFieldName], + roPwdFieldName: r.snapshotMgr.creds[roPwdFieldName], + certFieldName: r.snapshotMgr.creds[certFieldName], + }, + } + + err = r.Create(ctx, secret) + + if err != nil { + r.Log.Error(err, "Failed to create the secret", + "Deployment.Namespace", m.Namespace, + "Secret.Name", operatorName) + } + } else { + r.Log.Error(err, "Failed to retrieve the secret", + "Deployment.Namespace", m.Namespace, + "Secret.Name", operatorName) + } + } else { + r.Log.V(5).Info("Found an existing secret, checking values are correct", + "Deployment.Namespace", m.Namespace, + "Secret.Name", operatorName) + var requireUpdate bool + for k, v := range secret.Data { + decodedValue := make([]byte, base64.StdEncoding.DecodedLen(len(v))) + l, _ := base64.StdEncoding.Decode(decodedValue, v) + if r.snapshotMgr.creds[k] != string(decodedValue[:l]) { + requireUpdate = true + } + } + if requireUpdate == true { + secret = &corev1.Secret{ + Type: apiv1.SecretTypeOpaque, + ObjectMeta: metav1.ObjectMeta{ + Name: operatorName, + Namespace: m.Namespace, + }, + StringData: map[string]string{ + userFieldName: snapshotMgrUser, + urlFieldName: r.snapshotMgr.creds[urlFieldName], + roPwdFieldName: r.snapshotMgr.creds[roPwdFieldName], + certFieldName: r.snapshotMgr.creds[certFieldName], + }, + } + + err = r.Update(ctx, secret) + } + } + + r.secretMutex.Unlock() + + return } /*****************************************************************************/ @@ -349,236 +376,323 @@ func (r *IBMSecurityVerifyAccessReconciler) createSecret( */ func (r *IBMSecurityVerifyAccessReconciler) deploymentForVerifyAccess( - m *ibmv1.IBMSecurityVerifyAccess) *appsv1.Deployment { - /* - * Work out the name of the service. We determine this from the name of - * the image, and the value of the INSTANCE environment variable. - */ - - serviceName := "unknown" - imageComponent := strings.Split(m.Spec.Image, ":")[0] - - if strings.HasSuffix(imageComponent, "wrp") { - if m.Spec.Instance != "" { - serviceName = fmt.Sprintf("wrp-%s", m.Spec.Instance) - } else { - serviceName = "wrp-default" - } - } else if strings.HasSuffix(imageComponent, "runtime") { - serviceName = "runtime" - } else if strings.HasSuffix(imageComponent, "dsc") { - if m.Spec.Instance != "" { - serviceName = fmt.Sprintf("dsc-%s", m.Spec.Instance) - } else { - serviceName = "dsc-1" - } - } - - /* - * The labels which are used in our deployment. - */ - - labels := map[string]string{ - "kind": kindName, - "app": m.Name, - "VerifyAccess_cr": m.Name, - "service": serviceName, - } - - falseVar := false - - /* - * The port which is exported by the deployment. - */ - - ports := []corev1.ContainerPort {{ - Name: "https", - ContainerPort: 9443, - Protocol: corev1.ProtocolTCP, - }} - - /* - * The liveness, readiness and start-up probe definitions. - */ - - livenessProbe := m.Spec.Container.LivenessProbe - - if livenessProbe == nil { - livenessProbe = &corev1.Probe { - TimeoutSeconds: 3, - Handler: corev1.Handler { - Exec: &corev1.ExecAction { - Command: []string{ - "/sbin/health_check.sh", - "livenessProbe", - }, - }, - }, - } - } - - readinessProbe := m.Spec.Container.ReadinessProbe - - if readinessProbe == nil { - readinessProbe = &corev1.Probe { - TimeoutSeconds: 3, - Handler: corev1.Handler { - Exec: &corev1.ExecAction { - Command: []string{ - "/sbin/health_check.sh", - }, - }, - }, - } - } - - startupProbe := m.Spec.Container.StartupProbe - - if startupProbe == nil { - startupProbe = &corev1.Probe { - InitialDelaySeconds: 5, - TimeoutSeconds: 20, - FailureThreshold: 30, - Handler: corev1.Handler { - Exec: &corev1.ExecAction { - Command: []string{ - "/sbin/health_check.sh", - "startupProbe", - }, - }, - }, - } - } - - /* - * Set up the environment variables which are used to access the - * embedded snapshot manager. - */ - - maxEnv := 7 - env := make([]corev1.EnvVar, 0, maxEnv) - - env = append(env, corev1.EnvVar { - Name: "CONFIG_SERVICE_URL", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: operatorName, - }, - Key: urlFieldName, - Optional: &falseVar, - }, - }, - }) - - env = append(env, corev1.EnvVar { - Name: "CONFIG_SERVICE_USER_NAME", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: operatorName, - }, - Key: userFieldName, - Optional: &falseVar, - }, - }, - }) - - env = append(env, corev1.EnvVar { - Name: "CONFIG_SERVICE_USER_PWD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: operatorName, - }, - Key: roPwdFieldName, - Optional: &falseVar, - }, - }, - }) - - /* - * Add the rest of the environment variables (if specified). - */ - - if m.Spec.SnapshotId != "" { - env = append(env, corev1.EnvVar { - Name: "SNAPSHOT_ID", - Value: m.Spec.SnapshotId, - }) - } - - if len(m.Spec.Fixpacks) > 0 { - env = append(env, corev1.EnvVar { - Name: "FIXPACKS", - Value: strings.Join(m.Spec.Fixpacks,","), - }) - } - - if m.Spec.Instance != "" { - env = append(env, corev1.EnvVar { - Name: "INSTANCE", - Value: m.Spec.Instance, - }) - } - - if m.Spec.Language != "" { - env = append(env, corev1.EnvVar { - Name: "LANG", - Value: string(m.Spec.Language), - }) - } - - /* - * Set up the rest of the deployment descriptor. - */ - - dep := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: m.Name, - Namespace: m.Namespace, - Labels: labels, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: &m.Spec.Replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: labels, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: labels, - }, - Spec: corev1.PodSpec{ - Volumes: m.Spec.Volumes, - ImagePullSecrets: m.Spec.ImagePullSecrets, - ServiceAccountName: m.Spec.ServiceAccountName, - Containers: []corev1.Container{{ - Env: m.Spec.Container.Env, - EnvFrom: m.Spec.Container.EnvFrom, - Image: m.Spec.Image, - ImagePullPolicy: m.Spec.Container.ImagePullPolicy, - LivenessProbe: livenessProbe, - Name: m.Name, - Ports: ports, - ReadinessProbe: readinessProbe, - Resources: m.Spec.Container.Resources, - SecurityContext: m.Spec.Container.SecurityContext, - StartupProbe: startupProbe, - VolumeDevices: m.Spec.Container.VolumeDevices, - VolumeMounts: m.Spec.Container.VolumeMounts, - }}, - }, - }, - }, - } - - dep.Spec.Template.Spec.Containers[0].Env = append( - dep.Spec.Template.Spec.Containers[0].Env, env...) - - // Set the VerifyAccess instance as the owner and controller - ctrl.SetControllerReference(m, dep, r.Scheme) - - return dep + m *ibmv1.IBMSecurityVerifyAccess) *appsv1.Deployment { + /* + * Work out the name of the service. We determine this from the name of + * the image, and the value of the INSTANCE environment variable. + */ + + serviceName := "unknown" + imageComponent := strings.Split(m.Spec.Image, ":")[0] + + if strings.HasSuffix(imageComponent, "wrp") { + if m.Spec.Instance != "" { + serviceName = fmt.Sprintf("wrp-%s", m.Spec.Instance) + } else { + serviceName = "wrp-default" + } + } else if strings.HasSuffix(imageComponent, "runtime") { + serviceName = "runtime" + } else if strings.HasSuffix(imageComponent, "dsc") { + if m.Spec.Instance != "" { + serviceName = fmt.Sprintf("dsc-%s", m.Spec.Instance) + } else { + serviceName = "dsc-1" + } + } + + /* + * The labels which are used in our deployment. + */ + + labels := map[string]string{ + "kind": kindName, + "app": m.Name, + "VerifyAccess_cr": m.Name, + "service": serviceName, + } + + falseVar := false + + /* + * The port which is exported by the deployment. + */ + + ports := []corev1.ContainerPort{{ + Name: "https", + ContainerPort: 9443, + Protocol: corev1.ProtocolTCP, + }} + + /* + * The liveness, readiness and start-up probe definitions. + */ + + livenessProbe := m.Spec.Container.LivenessProbe + + if livenessProbe == nil { + livenessProbe = &corev1.Probe{ + TimeoutSeconds: 3, + Handler: corev1.Handler{ + Exec: &corev1.ExecAction{ + Command: []string{ + "/sbin/health_check.sh", + "livenessProbe", + }, + }, + }, + } + } + + readinessProbe := m.Spec.Container.ReadinessProbe + + if readinessProbe == nil { + readinessProbe = &corev1.Probe{ + TimeoutSeconds: 3, + Handler: corev1.Handler{ + Exec: &corev1.ExecAction{ + Command: []string{ + "/sbin/health_check.sh", + }, + }, + }, + } + } + startupProbe := m.Spec.Container.StartupProbe + + if startupProbe == nil { + startupProbe = &corev1.Probe{ + InitialDelaySeconds: 5, + TimeoutSeconds: 20, + FailureThreshold: 30, + Handler: corev1.Handler{ + Exec: &corev1.ExecAction{ + Command: []string{ + "/sbin/health_check.sh", + "startupProbe", + }, + }, + }, + } + } + + /* + * Set up the environment variables which are used to access the + * embedded snapshot manager. + */ + + maxEnv := 7 + env := make([]corev1.EnvVar, 0, maxEnv) + + env = append(env, corev1.EnvVar{ + Name: "CONFIG_SERVICE_URL", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: operatorName, + }, + Key: urlFieldName, + Optional: &falseVar, + }, + }, + }) + + env = append(env, corev1.EnvVar{ + Name: "CONFIG_SERVICE_USER_NAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: operatorName, + }, + Key: userFieldName, + Optional: &falseVar, + }, + }, + }) + + env = append(env, corev1.EnvVar{ + Name: "CONFIG_SERVICE_USER_PWD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: operatorName, + }, + Key: roPwdFieldName, + Optional: &falseVar, + }, + }, + }) + + /* If a config snapshot secrets property has been defiend add + it to runtime containers + */ + if m.Spec.SnapshotSecrets != "" { + env = append(env, corev1.EnvVar{ + Name: "CONFIG_SNAPSHOT_SECRETS", + Value: m.Spec.SnapshotSecrets, + }) + } + + /* Add TLS CAcert properties if they exist, else use kubernetes + PKI as the default + */ + if m.Spec.SnapshotTLSCacert != "" { + env = append(env, corev1.EnvVar{ + Name: "CONFIG_SERVICE_TLS_CACERT", + Value: m.Spec.SnapshotTLSCacert, + }) + } else { + env = append(env, corev1.EnvVar{ + Name: "CONFIG_SERVICE_TLS_CACERT", + Value: "operator", + }) + } + + /* + * Add the rest of the environment variables (if specified). + */ + + if m.Spec.SnapshotId != "" { + env = append(env, corev1.EnvVar{ + Name: "SNAPSHOT_ID", + Value: m.Spec.SnapshotId, + }) + } + + if len(m.Spec.Fixpacks) > 0 { + env = append(env, corev1.EnvVar{ + Name: "FIXPACKS", + Value: strings.Join(m.Spec.Fixpacks, ","), + }) + } + + if m.Spec.Instance != "" { + env = append(env, corev1.EnvVar{ + Name: "INSTANCE", + Value: m.Spec.Instance, + }) + } + + if m.Spec.Language != "" { + env = append(env, corev1.EnvVar{ + Name: "LANG", + Value: string(m.Spec.Language), + }) + } + + /* + * Set up the rest of the deployment descriptor. + */ + + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: m.Name, + Namespace: m.Namespace, + Labels: labels, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &m.Spec.Replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + Volumes: m.Spec.Volumes, + ImagePullSecrets: m.Spec.ImagePullSecrets, + ServiceAccountName: m.Spec.ServiceAccountName, + Containers: []corev1.Container{{ + Env: m.Spec.Container.Env, + EnvFrom: m.Spec.Container.EnvFrom, + Image: m.Spec.Image, + ImagePullPolicy: m.Spec.Container.ImagePullPolicy, + LivenessProbe: livenessProbe, + Name: m.Name, + Ports: ports, + ReadinessProbe: readinessProbe, + Resources: m.Spec.Container.Resources, + SecurityContext: m.Spec.Container.SecurityContext, + StartupProbe: startupProbe, + VolumeDevices: m.Spec.Container.VolumeDevices, + VolumeMounts: m.Spec.Container.VolumeMounts, + }}, + }, + }, + }, + } + + licenseAnnotations := m.Spec.LicenseAnnotations + + if licenseAnnotations != nil { + annotations := map[string]string{ + "productMetric": "PROCESSOR_VALUE_UNIT", + "productChargedContainers": "All", + "productName": "IBM Security Verify Access Virtual Edition", + "productId": "e2ba21cf5df245bb8524be1957857d9f", + } + prod := m.Spec.LicenseAnnotations.Production + module := m.Spec.LicenseAnnotations.Module + switch module { + case "access_control": + if prod == false { + annotations["productName"] = "IBM Security Verify Access Virtual Edition AAC Module Non-Production AOS" + annotations["productId"] = "707987d5b0ca48e8af8e5856c027980f" + } else { + annotations["productName"] = "IBM Security Verify Access Virtual Edition AAC Module AOS" + annotations["productId"] = "25d814176e0f4f21b64db66b916414d4" + } + + case "federation": + if prod == false { + annotations["productName"] = "IBM Security Verify Access Virtual Ed Federation Module Non-Production AOS" + annotations["productId"] = "01a9d83608044a4687b3d29a0d4d0a35" + } else { + annotations["productName"] = "IBM Security Verify Access Virtual Edition Federation Module AOS" + annotations["productId"] = "13ce5584032a42eab5704711369a11a4" + } + + case "enterprise": + if prod == false { + annotations["productName"] = "IBM Security Verify Access Virtual Enterprise Edition Non-Production" + annotations["productId"] = "de0d1dce07f145ce9380be5182a68544" + } else { + annotations["productName"] = "IBM Security Verify Access Virtual Enterprise Edition" + annotations["productId"] = "62b1cf23e32140a684284a0cf9a37329" + } + + default: + if prod == false { + annotations["productName"] = "IBM Security Verify Access Virtual Edition Non-Production" + annotations["productId"] = "8e4a78ab1e9249b1b46b6870babf4945" + } // else we use the default + } + dep.Spec.Template.ObjectMeta.SetAnnotations(annotations) + } + + // If administrator provided annotations exist, apply them here. This could rewrite the license annotations + customAnnotations := m.Spec.CustomAnnotations + if customAnnotations != nil { + annotations := make(map[string]string) + if dep.Spec.Template.ObjectMeta.Annotations != nil { + for k, v := range dep.Spec.Template.ObjectMeta.Annotations { + annotations[k] = v + } + } + for _, e := range customAnnotations { + annotations[e.Key] = e.Value + } + dep.Spec.Template.ObjectMeta.SetAnnotations(annotations) + } + + dep.Spec.Template.Spec.Containers[0].Env = append( + dep.Spec.Template.Spec.Containers[0].Env, env...) + + // Set the VerifyAccess instance as the owner and controller + ctrl.SetControllerReference(m, dep, r.Scheme) + + return dep } /*****************************************************************************/ @@ -588,43 +702,42 @@ func (r *IBMSecurityVerifyAccessReconciler) deploymentForVerifyAccess( */ func (r *IBMSecurityVerifyAccessReconciler) SetupWithManager( - mgr ctrl.Manager) error { + mgr ctrl.Manager) error { - r.secretMutex = &sync.Mutex{} + r.secretMutex = &sync.Mutex{} - /* - * Work out the namespace in which we are running. - */ + /* + * Work out the namespace in which we are running. + */ - r.localNamespace, _ = getLocalNamespace(r.Log) + r.localNamespace, _ = getLocalNamespace(r.Log) - /* - * Initialise and start the snapshot manager. - */ + /* + * Initialise and start the snapshot manager. + */ - r.snapshotMgr = SnapshotMgr{ - config: mgr.GetConfig(), - scheme: mgr.GetScheme(), - log: r.Log.WithName("SnapshotMgr"), - } + r.snapshotMgr = SnapshotMgr{ + config: mgr.GetConfig(), + scheme: mgr.GetScheme(), + log: r.Log.WithName("SnapshotMgr"), + } - err := r.snapshotMgr.initialize() + err := r.snapshotMgr.initialize() - if err != nil { - return err - } + if err != nil { + return err + } - go r.snapshotMgr.start() + go r.snapshotMgr.start() - /* - * Register our controller. - */ + /* + * Register our controller. + */ - return ctrl.NewControllerManagedBy(mgr). - For(&ibmv1.IBMSecurityVerifyAccess{}). - Owns(&appsv1.Deployment{}). - Complete(r) + return ctrl.NewControllerManagedBy(mgr). + For(&ibmv1.IBMSecurityVerifyAccess{}). + Owns(&appsv1.Deployment{}). + Complete(r) } /*****************************************************************************/ - diff --git a/src/controllers/snapshotmgr.go b/src/controllers/snapshotmgr.go index de1b178..34f8971 100644 --- a/src/controllers/snapshotmgr.go +++ b/src/controllers/snapshotmgr.go @@ -7,59 +7,60 @@ package controllers /*****************************************************************************/ import ( - "bytes" - "encoding/pem" - "errors" - "context" - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "fmt" - "io" - "math/big" - "net" - "net/http" - "os" - "os/signal" - "path/filepath" - "strconv" - "strings" - "sync" - "syscall" - "time" - - "github.com/go-logr/logr" - - "sigs.k8s.io/controller-runtime/pkg/client" - - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/rest" - "k8s.io/client-go/kubernetes" - - apiV1 "k8s.io/api/core/v1" - appsV1 "k8s.io/client-go/kubernetes/typed/apps/v1" - coreV1 "k8s.io/client-go/kubernetes/typed/core/v1" - metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - ibmv1 "github.com/ibm-security/verify-access-operator/api/v1" + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io" + "math/big" + "net" + "net/http" + "os" + "os/signal" + "path/filepath" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "github.com/go-logr/logr" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + + apiV1 "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + appsV1 "k8s.io/client-go/kubernetes/typed/apps/v1" + coreV1 "k8s.io/client-go/kubernetes/typed/core/v1" + + ibmv1 "github.com/ibm-security/verify-access-operator/api/v1" ) /*****************************************************************************/ type SnapshotMgr struct { - config *rest.Config - scheme *runtime.Scheme + config *rest.Config + scheme *runtime.Scheme - log logr.Logger + log logr.Logger - server *http.Server - creds map[string]string + server *http.Server + creds map[string]string - restartMutex *sync.Mutex - webMutex *sync.RWMutex + restartMutex *sync.Mutex + webMutex *sync.RWMutex } /*****************************************************************************/ @@ -71,314 +72,314 @@ type SnapshotMgr struct { func (mgr *SnapshotMgr) rollingRestart(path string, modified string) { - mgr.log.V(9).Info("Entering a function", "Function", "rollingRestart") + mgr.log.V(9).Info("Entering a function", "Function", "rollingRestart") - /* - * Work out the list of modified services. - */ + /* + * Work out the list of modified services. + */ - var services []string + var services []string - if len(modified) > 0 { - services = strings.Split(strings.Replace(modified, ":", "-", -1), ",") - } + if len(modified) > 0 { + services = strings.Split(strings.Replace(modified, ":", "-", -1), ",") + } - /* - * Grab a lock to ensure that we don't process multiple simultaneous - * restarts. - */ + /* + * Grab a lock to ensure that we don't process multiple simultaneous + * restarts. + */ - mgr.restartMutex.Lock() + mgr.restartMutex.Lock() - /* - * Work out the snapshot identifier, if a snapshot has been provided. - */ + /* + * Work out the snapshot identifier, if a snapshot has been provided. + */ - snapshotId := "" + snapshotId := "" - if (strings.HasPrefix(path, "/snapshots/")) { - snapshotName := filepath.Base(filepath.Clean(path)) + if strings.HasPrefix(path, "/snapshots/") { + snapshotName := filepath.Base(filepath.Clean(path)) - /* - * We now need to pull out the snapshot identifier from the - * name of the snapshot. The snapshot name is of the format: - * isva__.snapshot - */ + /* + * We now need to pull out the snapshot identifier from the + * name of the snapshot. The snapshot name is of the format: + * isva__.snapshot + */ - parts := strings.Split(snapshotName, "_") + parts := strings.Split(snapshotName, "_") - if len(parts) != 3 { - mgr.restartMutex.Unlock() + if len(parts) != 3 { + mgr.restartMutex.Unlock() - mgr.log.Info("No deployments will be restarted as the " + - "snapshot name is invalid", "Snapshot.Name", snapshotName); + mgr.log.Info("No deployments will be restarted as the "+ + "snapshot name is invalid", "Snapshot.Name", snapshotName) - return; - } + return + } - parts = strings.Split(parts[2], ".") + parts = strings.Split(parts[2], ".") - if len(parts) != 2 { - mgr.restartMutex.Unlock() + if len(parts) != 2 { + mgr.restartMutex.Unlock() - mgr.log.Info("No deployments will be restarted as the " + - "snapshot name is invalid", "Snapshot.Name", snapshotName); + mgr.log.Info("No deployments will be restarted as the "+ + "snapshot name is invalid", "Snapshot.Name", snapshotName) - return; - } + return + } - snapshotId = parts[0] + snapshotId = parts[0] - mgr.log.V(5).Info("Processing a snapshot", "Snapshot.Id", "snapshotId") - } + mgr.log.V(5).Info("Processing a snapshot", "Snapshot.Id", "snapshotId") + } - /* - * Create a new client based on our configuration. - */ + /* + * Create a new client based on our configuration. + */ - appsV1Client, err := appsV1.NewForConfig(mgr.config) - if err != nil { - mgr.restartMutex.Unlock() + appsV1Client, err := appsV1.NewForConfig(mgr.config) + if err != nil { + mgr.restartMutex.Unlock() - mgr.log.Error(err, "Failed to create a new K8S Application client") + mgr.log.Error(err, "Failed to create a new K8S Application client") - return - } - - rtClient, err := client.New(mgr.config, - client.Options{ - Scheme: mgr.scheme, - }) - - if err != nil { - mgr.restartMutex.Unlock() - - mgr.log.Error(err, "Failed to create a new controller runtime client") - - return - } + return + } - /* - * Restart the deployments. - */ + rtClient, err := client.New(mgr.config, + client.Options{ + Scheme: mgr.scheme, + }) - if len(services) > 0 { - for _, service := range services { - mgr.restartDeployments( - path, - snapshotId, - fmt.Sprintf("kind=%s, service=%s", kindName, service), - appsV1Client, - rtClient) - } - } else { - mgr.restartDeployments( - path, - snapshotId, - fmt.Sprintf("kind=%s", kindName), - appsV1Client, - rtClient) - } + if err != nil { + mgr.restartMutex.Unlock() - /* - * Finished, so we can release our lock. - */ + mgr.log.Error(err, "Failed to create a new controller runtime client") - mgr.restartMutex.Unlock() + return + } + + /* + * Restart the deployments. + */ + + if len(services) > 0 { + for _, service := range services { + mgr.restartDeployments( + path, + snapshotId, + fmt.Sprintf("kind=%s, service=%s", kindName, service), + appsV1Client, + rtClient) + } + } else { + mgr.restartDeployments( + path, + snapshotId, + fmt.Sprintf("kind=%s", kindName), + appsV1Client, + rtClient) + } + + /* + * Finished, so we can release our lock. + */ + + mgr.restartMutex.Unlock() } /*****************************************************************************/ /* - * This function is used to trigger a rolling restart of the specified - * deployments. + * This function is used to trigger a rolling restart of the specified + * deployments. */ func (mgr *SnapshotMgr) restartDeployments( - path string, - snapshotId string, - labels string, - appsV1Client *appsV1.AppsV1Client, - rtClient client.Client) { - - /* - * Retrieve the existing deployments for our operator. - */ - - deployments, err := appsV1Client.Deployments("").List( - context.TODO(), - metaV1.ListOptions{ - LabelSelector: labels, - }) - - if err != nil { - mgr.log.Error(err, "Failed to list deployments") - - return - } - - /* - * Now we need to iterate over each of the deployments, performing - * a rolling restart of the deployment. - */ - - for _, deployment := range deployments.Items { - - mgr.log.V(5).Info("Checking a deployment", - "Deployment.Namespace", deployment.Namespace, - "Deployment.Name", deployment.Name) - - /* - * Detect and retrieve the custom resource for this deployment. The - * name of the custom resource is contained in the VerifyAccess_cr - * label. - */ - - crName := deployment.Labels["VerifyAccess_cr"] - - if len(crName) == 0 { - mgr.log.Info("The deployment does not have a VerifyAccess_cr label", - "Deployment.Namespace", deployment.Namespace, - "Deployment.Name", deployment.Name) - - continue - } - - verifyaccess := &ibmv1.IBMSecurityVerifyAccess{} - - err = rtClient.Get(context.TODO(), - client.ObjectKey{ - Namespace: deployment.Namespace, - Name: crName, - }, - verifyaccess) - - if err != nil { - mgr.log.Error(err, - "Failed to retrieve the IBMSecurityVerifyAccess resource", - "CustomResource.Name", crName) - - continue - } - - /* - * We don't bother to restart the deployment if the AutoRestart field - * has been set to false. - */ - - if !verifyaccess.Spec.AutoRestart { - mgr.log.Info("Not performing an autorestart of the deployment as " + - "the AutoRestart field is set to false", - "Deployment.Namespace", deployment.Namespace, - "Deployment.Name", deployment.Name) - - continue - } - - /* - * Check to see if the supplied file is actually used by the - * deployment. - */ - - if (strings.HasPrefix(path, "/fixpacks/")) { - /* - * A new fixpack has been supplied and so we only worry about - * restarting the deployment if it is currently using this - * fixpack. - */ - - fixpackName := filepath.Base(filepath.Clean(path)) - - fixpackInUse := false - - for _, fixpack := range verifyaccess.Spec.Fixpacks { - if fixpackName == fixpack { - fixpackInUse = true - break - } - } - - if ! fixpackInUse { - mgr.log.Info("Not performing an autorestart as the " + - "supplied fixpack is not used by the deployment", - "Deployment.Namespace", deployment.Namespace, - "Deployment.Name", deployment.Name, - "Fixpack.Name", fixpackName) - - continue - } - - } else if (strings.HasPrefix(path, "/snapshots/")) { - /* - * A new snapshot has been uploaded. We need to see if the - * snapshot identifier for the deployment matches our supplied - * snapshot identifier. - */ - - if snapshotId != verifyaccess.Spec.SnapshotId { - mgr.log.Info("Not performing an autorestart as the " + - "supplied snapshot is not used by the deployment", - "Deployment.Namespace", deployment.Namespace, - "Deployment.Name", deployment.Name, - "Deployment.Snapshot.Id", verifyaccess.Spec.SnapshotId, - "Snapshot.Id", snapshotId) - - continue - } - } - - /* - * Determine the revision number of the deployment. This is incremented - * to trigger a rolling update. - */ - - mgr.log.Info("Performing a rolling restart of the deployment", - "Deployment.Namespace", deployment.Namespace, - "Deployment.Name", deployment.Name) - - revision, err := strconv.Atoi( - deployment.Spec.Template.Annotations["revision"]) - - if err != nil { - revision = 1 - } else { - revision++ - } - - mgr.log.V(5).Info("New revision number", "Revision", revision) - - /* - * Patch the deployment descriptor with the incremented revision - * number. - */ - - payloadBytes := fmt.Sprintf( - "{\"spec\":" + - "{\"template\":" + - "{\"metadata\":" + - "{\"annotations\":{" + - "\"revision\":\"%d\"}" + - "}" + - "}" + - "}" + - "}", revision) - - _, err = appsV1Client.Deployments(deployment.Namespace).Patch( - context.TODO(), - deployment.Name, - types.StrategicMergePatchType, - []byte(payloadBytes), - metaV1.PatchOptions{}) - - if err != nil { - mgr.log.Error(err, "Failed to update the deployment", - "Deployment.Name", deployment.Name) - - return - } - - mgr.log.V(5).Info("Successfully updated the deployment") - } + path string, + snapshotId string, + labels string, + appsV1Client *appsV1.AppsV1Client, + rtClient client.Client) { + + /* + * Retrieve the existing deployments for our operator. + */ + + deployments, err := appsV1Client.Deployments("").List( + context.TODO(), + metaV1.ListOptions{ + LabelSelector: labels, + }) + + if err != nil { + mgr.log.Error(err, "Failed to list deployments") + + return + } + + /* + * Now we need to iterate over each of the deployments, performing + * a rolling restart of the deployment. + */ + + for _, deployment := range deployments.Items { + + mgr.log.V(5).Info("Checking a deployment", + "Deployment.Namespace", deployment.Namespace, + "Deployment.Name", deployment.Name) + + /* + * Detect and retrieve the custom resource for this deployment. The + * name of the custom resource is contained in the VerifyAccess_cr + * label. + */ + + crName := deployment.Labels["VerifyAccess_cr"] + + if len(crName) == 0 { + mgr.log.Info("The deployment does not have a VerifyAccess_cr label", + "Deployment.Namespace", deployment.Namespace, + "Deployment.Name", deployment.Name) + + continue + } + + verifyaccess := &ibmv1.IBMSecurityVerifyAccess{} + + err = rtClient.Get(context.TODO(), + client.ObjectKey{ + Namespace: deployment.Namespace, + Name: crName, + }, + verifyaccess) + + if err != nil { + mgr.log.Error(err, + "Failed to retrieve the IBMSecurityVerifyAccess resource", + "CustomResource.Name", crName) + + continue + } + + /* + * We don't bother to restart the deployment if the AutoRestart field + * has been set to false. + */ + + if !verifyaccess.Spec.AutoRestart { + mgr.log.Info("Not performing an autorestart of the deployment as "+ + "the AutoRestart field is set to false", + "Deployment.Namespace", deployment.Namespace, + "Deployment.Name", deployment.Name) + + continue + } + + /* + * Check to see if the supplied file is actually used by the + * deployment. + */ + + if strings.HasPrefix(path, "/fixpacks/") { + /* + * A new fixpack has been supplied and so we only worry about + * restarting the deployment if it is currently using this + * fixpack. + */ + + fixpackName := filepath.Base(filepath.Clean(path)) + + fixpackInUse := false + + for _, fixpack := range verifyaccess.Spec.Fixpacks { + if fixpackName == fixpack { + fixpackInUse = true + break + } + } + + if !fixpackInUse { + mgr.log.Info("Not performing an autorestart as the "+ + "supplied fixpack is not used by the deployment", + "Deployment.Namespace", deployment.Namespace, + "Deployment.Name", deployment.Name, + "Fixpack.Name", fixpackName) + + continue + } + + } else if strings.HasPrefix(path, "/snapshots/") { + /* + * A new snapshot has been uploaded. We need to see if the + * snapshot identifier for the deployment matches our supplied + * snapshot identifier. + */ + + if snapshotId != verifyaccess.Spec.SnapshotId { + mgr.log.Info("Not performing an autorestart as the "+ + "supplied snapshot is not used by the deployment", + "Deployment.Namespace", deployment.Namespace, + "Deployment.Name", deployment.Name, + "Deployment.Snapshot.Id", verifyaccess.Spec.SnapshotId, + "Snapshot.Id", snapshotId) + + continue + } + } + + /* + * Determine the revision number of the deployment. This is incremented + * to trigger a rolling update. + */ + + mgr.log.Info("Performing a rolling restart of the deployment", + "Deployment.Namespace", deployment.Namespace, + "Deployment.Name", deployment.Name) + + revision, err := strconv.Atoi( + deployment.Spec.Template.Annotations["revision"]) + + if err != nil { + revision = 1 + } else { + revision++ + } + + mgr.log.V(5).Info("New revision number", "Revision", revision) + + /* + * Patch the deployment descriptor with the incremented revision + * number. + */ + + payloadBytes := fmt.Sprintf( + "{\"spec\":"+ + "{\"template\":"+ + "{\"metadata\":"+ + "{\"annotations\":{"+ + "\"revision\":\"%d\"}"+ + "}"+ + "}"+ + "}"+ + "}", revision) + + _, err = appsV1Client.Deployments(deployment.Namespace).Patch( + context.TODO(), + deployment.Name, + types.StrategicMergePatchType, + []byte(payloadBytes), + metaV1.PatchOptions{}) + + if err != nil { + mgr.log.Error(err, "Failed to update the deployment", + "Deployment.Name", deployment.Name) + + return + } + + mgr.log.V(5).Info("Successfully updated the deployment") + } } /*****************************************************************************/ @@ -390,280 +391,324 @@ func (mgr *SnapshotMgr) restartDeployments( func (mgr *SnapshotMgr) serve(w http.ResponseWriter, r *http.Request) { - mgr.log.V(9).Info("Entering a function", "Function", "serve") - - /* - * Check the authorization to this Web server. The username should always - * be the same, but we use a different password for the GET/POST methods. - */ - - username, password, _ := r.BasicAuth() - - authOk := mgr.creds[userFieldName] == username && - (mgr.creds[rwPwdFieldName] == password || - (r.Method == "GET" && mgr.creds[roPwdFieldName] == password)) - - if !authOk { - w.Header().Set("WWW-Authenticate", - fmt.Sprintf("Basic realm=\"%s\"", operatorName)) - - http.Error(w, - http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - - mgr.log.V(5).Info("Authentication failed", "Username", username) - - return - } - - /* - * Validate the supplied path, and from this determine the name of the - * file which will be used. We need to ensure that we don't traverse - * out of our data path. The only valid directories are: '/fixpacks', - * and '/snapshots'. - */ - - isValid := false - - if (strings.HasPrefix(r.URL.Path, "/fixpacks/")) { - basePath := filepath.Base(filepath.Clean(r.URL.Path)) - - if r.URL.Path == "/fixpacks/" + basePath { - isValid = true - } - } else if (strings.HasPrefix(r.URL.Path, "/snapshots/")) { - basePath := filepath.Base(filepath.Clean(r.URL.Path)) - - if r.URL.Path == "/snapshots/" + basePath && - strings.HasPrefix(basePath, "isva_") && - strings.HasSuffix(basePath, ".snapshot") { - isValid = true - } - } - - if !isValid { - http.Error(w, - http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - - mgr.log.V(5).Info("An invalid path has been requested", - "Path", r.URL.Path) - - return - } - - fileName := filepath.Join(dataRoot, r.URL.Path) - - /* - * Work out the client of the request. - */ - - - client := r.URL.Query().Get("client") - - if len(client) == 0 { - client = r.Header.Get("X-Forwarded-For") - - if len(client) == 0 { - client = r.RemoteAddr - - if len(client) == 0 { - client = "unknown" - } - } - } - - /* - * Process the request based on the specified method. - */ - - switch r.Method { - /* - * For a GET we simply want to return the file. The ServeFile function - * will take care of constructing the response. - */ - - case "GET": - mgr.log.Info("Processing a GET", "Path", r.URL.Path, "Client", client) - - mgr.webMutex.RLock() - http.ServeFile(w, r, fileName) - mgr.webMutex.RUnlock() - - /* - * For a POST we want to save the supplied file. - */ - - case "POST": - /* - * Work out some of the information associated with the request and - * then log the request. - */ + mgr.log.V(9).Info("Entering a function", "Function", "serve") - modified := r.URL.Query().Get("modified") - modifiedStr := modified + /* + * Check the authorization to this Web server. The username should always + * be the same, but we use a different password for the GET/POST methods. + */ - if len(modified) == 0 { - modifiedStr = "all" - } + username, password, _ := r.BasicAuth() - mgr.log.Info("Processing a POST", - "Path", r.URL.Path, - "Modified", modifiedStr, - "Client", client) + authOk := mgr.creds[userFieldName] == username && + (mgr.creds[rwPwdFieldName] == password || + (r.Method == "GET" && mgr.creds[roPwdFieldName] == password)) - /* - * Retrieve the file parameter from the form. - */ + if !authOk { + w.Header().Set("WWW-Authenticate", + fmt.Sprintf("Basic realm=\"%s\"", operatorName)) - r.ParseMultipartForm(maxMemory) + http.Error(w, + http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) - file, _, err := r.FormFile("file") - - if err != nil { - http.Error(w, - http.StatusText(http.StatusBadRequest), - http.StatusBadRequest) - - mgr.log.V(5).Error(err, "An invalid POST has been received") - - return - } - - defer file.Close() - - /* - * Create the file which is to be uploaded. - */ - - mgr.webMutex.Lock() - - dst, err := os.Create(fileName) - - if err != nil { - mgr.webMutex.Unlock() - - http.Error(w, err.Error(), http.StatusInternalServerError) - - mgr.log.V(5).Error(err, "Failed to create the file", - "File", fileName) + mgr.log.V(5).Info("Authentication failed", "Username", username) return - } + } + + /* + * Validate the supplied path, and from this determine the name of the + * file which will be used. We need to ensure that we don't traverse + * out of our data path. The only valid directories are: '/fixpacks', + * and '/snapshots'. + */ + + isValid := false + listFiles := false + + if strings.HasPrefix(r.URL.Path, "/fixpacks/") { + basePath := filepath.Base(filepath.Clean(r.URL.Path)) + + if r.URL.Path == "/fixpacks/"+basePath { + isValid = true + } + } else if strings.HasPrefix(r.URL.Path, "/snapshots/") { + basePath := filepath.Base(filepath.Clean(r.URL.Path)) + + if r.URL.Path == "/snapshots/"+basePath && + strings.HasPrefix(basePath, "isva_") && + strings.HasSuffix(basePath, ".snapshot") { + isValid = true + } + } else if strings.HasPrefix(r.URL.Path, "/snapshots") && + r.Method == "GET" { + //If we are making a get request to the snapshots base URI then we want to return a list + // of known snapshots + isValid = true + listFiles = true + } + + if !isValid { + http.Error(w, + http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + + mgr.log.V(5).Info("An invalid path has been requested", + "Path", r.URL.Path) - defer dst.Close() - - /* - * Save the file. - */ - - _, err = io.Copy(dst, file) - - if err != nil { - mgr.webMutex.Unlock() - - http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + fileName := filepath.Join(dataRoot, r.URL.Path) + + /* + * Work out the client of the request. + */ + + client := r.URL.Query().Get("client") + + if len(client) == 0 { + client = r.Header.Get("X-Forwarded-For") + + if len(client) == 0 { + client = r.RemoteAddr + + if len(client) == 0 { + client = "unknown" + } + } + } + + /* + * Process the request based on the specified method. + */ + + switch r.Method { + /* + * For a GET we simply want to return the file. The ServeFile function + * will take care of constructing the response. + */ + + case "GET": + mgr.log.Info("Processing a GET", "Path", r.URL.Path, "Client", client) + if listFiles == true { + mgr.webMutex.RLock() + fp, err := os.Open(fileName) + if err != nil { + mgr.webMutex.RUnlock() + mgr.log.V(5).Error(err, "Error reading snapshot directory") + http.Error(w, + http.StatusText(http.StatusBadRequest), + http.StatusBadRequest) + return + } + fileList, err := fp.Readdir(0) + if err != nil { + mgr.webMutex.RUnlock() + mgr.log.V(5).Error(err, "Error listing files in snapshot diectory") + http.Error(w, + http.StatusText(http.StatusBadRequest), + http.StatusBadRequest) + return + } + type SnapshotProperties map[string]interface{} + var snapshots []SnapshotProperties + for _, snapshot := range fileList { + snapshots = append(snapshots, SnapshotProperties{"name": snapshot.Name(), "size": snapshot.Size(), + "lastModified": snapshot.ModTime().String()}) + } + mgr.webMutex.RUnlock() + jsonStr, err := json.Marshal(snapshots) + if err != nil { + mgr.log.V(5).Error(err, "Error serializing snapshot properties") + http.Error(w, + http.StatusText(http.StatusBadRequest), + http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(jsonStr) + } else { + mgr.webMutex.RLock() + http.ServeFile(w, r, fileName) + mgr.webMutex.RUnlock() + } + + /* + * For a POST we want to save the supplied file. + */ + + case "POST": + /* + * Work out some of the information associated with the request and + * then log the request. + */ + + modified := r.URL.Query().Get("modified") + modifiedStr := modified + + if len(modified) == 0 { + modifiedStr = "all" + } + + mgr.log.Info("Processing a POST", + "Path", r.URL.Path, + "Modified", modifiedStr, + "Client", client) + + /* + * Retrieve the file parameter from the form. + */ + + r.ParseMultipartForm(maxMemory) + + file, _, err := r.FormFile("file") + + if err != nil { + http.Error(w, + http.StatusText(http.StatusBadRequest), + http.StatusBadRequest) + + mgr.log.V(5).Error(err, "An invalid POST has been received") + + return + } + + defer file.Close() + + /* + * Create the file which is to be uploaded. + */ + + mgr.webMutex.Lock() + + dst, err := os.Create(fileName) + + if err != nil { + mgr.webMutex.Unlock() + + http.Error(w, err.Error(), http.StatusInternalServerError) + + mgr.log.V(5).Error(err, "Failed to create the file", + "File", fileName) + + return + } + + defer dst.Close() + + /* + * Save the file. + */ + + _, err = io.Copy(dst, file) + + if err != nil { + mgr.webMutex.Unlock() + + http.Error(w, err.Error(), http.StatusInternalServerError) - mgr.log.V(5).Error(err, "Failed to copy the file", - "File", fileName) + mgr.log.V(5).Error(err, "Failed to copy the file", + "File", fileName) - return - } + return + } - mgr.webMutex.Unlock() + mgr.webMutex.Unlock() - /* - * Request a restart of all running containers in a separate - * thread. - */ + /* + * Request a restart of all running containers in a separate + * thread. + */ - go mgr.rollingRestart(filepath.Clean(r.URL.Path), modified) + go mgr.rollingRestart(filepath.Clean(r.URL.Path), modified) - /* - * Return a '201 Created' response. - */ + /* + * Return a '201 Created' response. + */ - http.Error(w, "", http.StatusCreated) + http.Error(w, "", http.StatusCreated) - mgr.log.V(5).Info("The file has been saved", "File", fileName) + mgr.log.V(5).Info("The file has been saved", "File", fileName) - /* - * For a DELETE we want to attempt to delete the specified file. The - * response will be different based on whether the file exists, and we - * were able to successfully delete the file. - */ + /* + * For a DELETE we want to attempt to delete the specified file. The + * response will be different based on whether the file exists, and we + * were able to successfully delete the file. + */ - case "DELETE": - mgr.log.Info("Processing a DELETE", - "Path", r.URL.Path, "Client", client) + case "DELETE": + mgr.log.Info("Processing a DELETE", + "Path", r.URL.Path, "Client", client) - mgr.webMutex.Lock() - err := os.Remove(fileName) - mgr.webMutex.Unlock() + mgr.webMutex.Lock() + err := os.Remove(fileName) + mgr.webMutex.Unlock() - var rspCode int - var rspText string + var rspCode int + var rspText string - if err == nil { - rspCode = http.StatusNoContent - rspText = "" - } else if os.IsNotExist(err) { - rspCode = http.StatusNotFound - rspText = http.StatusText(http.StatusNotFound) - } else { - rspCode = http.StatusInternalServerError - rspText = err.Error() - } + if err == nil { + rspCode = http.StatusNoContent + rspText = "" + } else if os.IsNotExist(err) { + rspCode = http.StatusNotFound + rspText = http.StatusText(http.StatusNotFound) + } else { + rspCode = http.StatusInternalServerError + rspText = err.Error() + } - if err == nil { - mgr.log.V(5).Error(err, "Failed to delete the file", - "File", fileName) - } else { - mgr.log.V(5).Info("Successfully deleted the file", - "File", fileName) - } + if err == nil { + mgr.log.V(5).Error(err, "Failed to delete the file", + "File", fileName) + } else { + mgr.log.V(5).Info("Successfully deleted the file", + "File", fileName) + } - http.Error(w, rspText, rspCode) + http.Error(w, rspText, rspCode) - /* - * All other methods are not supported. - */ + /* + * All other methods are not supported. + */ default: - mgr.log.V(5).Info("Received a request with an invalid method", - "Path", r.URL.Path, - "Client", client, - "Method", r.Method) + mgr.log.V(5).Info("Received a request with an invalid method", + "Path", r.URL.Path, + "Client", client, + "Method", r.Method) - http.Error(w, - http.StatusText(http.StatusNotImplemented), - http.StatusNotImplemented) + http.Error(w, + http.StatusText(http.StatusNotImplemented), + http.StatusNotImplemented) - } + } } /*****************************************************************************/ /* - * This function is used to generate a secure random password based on the + * This function is used to generate a secure random password based on the * specified password length. */ func (mgr *SnapshotMgr) generateRandomString(length int) (string, error) { - const letters = - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" + const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" - ret := make([]byte, length) + ret := make([]byte, length) - for i := 0; i < length; i++ { - num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) + for i := 0; i < length; i++ { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) - if err != nil { - return "", err - } + if err != nil { + return "", err + } - ret[i] = letters[num.Int64()] - } + ret[i] = letters[num.Int64()] + } - return string(ret), nil + return string(ret), nil } /*****************************************************************************/ @@ -675,79 +720,90 @@ func (mgr *SnapshotMgr) generateRandomString(length int) (string, error) { func (mgr *SnapshotMgr) generateKey() (cert string, key string, err error) { - mgr.log.V(9).Info("Entering a function", "Function", "generateKey") - - /* - * Generate the RSA key. - */ - - priv, err := rsa.GenerateKey(rand.Reader, keyLength) - - if err != nil { - mgr.log.Error(err, "Failed to generate an RSA key") - return - } - - /* - * Construct the x509 certificate. - */ + mgr.log.V(9).Info("Entering a function", "Function", "generateKey") - host, _ := os.Hostname() + /* + * Generate the RSA key. + */ - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - CommonName: host, - Organization: []string{"IBM"}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Hour * 24 * 365 * 20), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - } + priv, err := rsa.GenerateKey(rand.Reader, keyLength) - if ip := net.ParseIP(host); ip != nil { - template.IPAddresses = append(template.IPAddresses, ip) - } else { - template.DNSNames = append(template.DNSNames, host) - } - - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, - &priv.PublicKey, priv) - - if err != nil { - mgr.log.Error(err, "Failed to generate the certificate") - return - } + if err != nil { + mgr.log.Error(err, "Failed to generate an RSA key") + return + } + + /* + * Construct the x509 certificate. + */ + + host, _ := os.Hostname() + + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: host, + Organization: []string{"IBM"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 365 * 20), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + + if ip := net.ParseIP(host); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, host) + } + + //Hard code the service name as a SAN as this is what will be put in the generated secret + namespace, err := getLocalNamespace(mgr.log) + if err != nil { + namespace = "default" + } + template.DNSNames = append(template.DNSNames, fmt.Sprintf("%s.%s.svc.cluster.local:%d", + serviceName, namespace, httpsPort)) + template.DNSNames = append(template.DNSNames, fmt.Sprintf("%s.%s.svc.cluster.local", + serviceName, namespace)) + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, + &priv.PublicKey, priv) + + if err != nil { + mgr.log.Error(err, "Failed to generate the certificate") + return + } - /* - * Convert the certificate. - */ + /* + * Convert the certificate. + */ - out := &bytes.Buffer{} + out := &bytes.Buffer{} - pem.Encode(out, &pem.Block{ - Type: "CERTIFICATE", - Bytes: derBytes, - }) + pem.Encode(out, &pem.Block{ + Type: "CERTIFICATE", + Bytes: derBytes, + }) - cert = out.String() + cert = out.String() - /* - * Convert the key. - */ + /* + * Convert the key. + */ - out.Reset() + out.Reset() - pem.Encode(out, &pem.Block { - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(priv), - }) + pem.Encode(out, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(priv), + }) - key = out.String() + key = out.String() - return + return } /*****************************************************************************/ @@ -761,72 +817,72 @@ func (mgr *SnapshotMgr) generateKey() (cert string, key string, err error) { */ func (mgr *SnapshotMgr) createSecret( - client coreV1.SecretInterface, namespace string) ( - secret *apiV1.Secret, err error) { - - mgr.log.V(9).Info("Entering a function", "Function", "createSecret") + client coreV1.SecretInterface, namespace string) ( + secret *apiV1.Secret, err error) { - /* - * Generate a random password for the read-only and read-write credentials. - */ + mgr.log.V(9).Info("Entering a function", "Function", "createSecret") - ro_pwd, err := mgr.generateRandomString(pwdLength) + /* + * Generate a random password for the read-only and read-write credentials. + */ - if err != nil { - mgr.log.Error(err, "Failed to generate a password") + ro_pwd, err := mgr.generateRandomString(pwdLength) - return - } + if err != nil { + mgr.log.Error(err, "Failed to generate a password") - rw_pwd, err := mgr.generateRandomString(pwdLength) - - if err != nil { - mgr.log.Error(err, "Failed to generate a password") - - return - } - - /* - * Generate a self signed certificate and key. - */ - - cert, key, err := mgr.generateKey() - if err != nil { - return - } + return + } - url := fmt.Sprintf("https://%s.%s.svc.cluster.local:%d", - serviceName, namespace, httpsPort) + rw_pwd, err := mgr.generateRandomString(pwdLength) - /* - * Create the secret. - */ + if err != nil { + mgr.log.Error(err, "Failed to generate a password") - secret = &apiV1.Secret{ - Type: apiV1.SecretTypeOpaque, - ObjectMeta: metaV1.ObjectMeta { - Name: operatorName, - }, - StringData: map[string]string{ - userFieldName: snapshotMgrUser, - urlFieldName: url, - rwPwdFieldName: rw_pwd, - roPwdFieldName: ro_pwd, - certFieldName: cert, - keyFieldName: key, - }, - } + return + } - secret, err = client.Create(context.TODO(), secret, metaV1.CreateOptions{}) + /* + * Generate a self signed certificate and key. + */ - if err != nil { - mgr.log.Error(err, "Failed to create the secret", - "Secret.Name", operatorName) + cert, key, err := mgr.generateKey() + if err != nil { + return + } + + url := fmt.Sprintf("https://%s.%s.svc.cluster.local:%d", + serviceName, namespace, httpsPort) + + /* + * Create the secret. + */ + + secret = &apiV1.Secret{ + Type: apiV1.SecretTypeOpaque, + ObjectMeta: metaV1.ObjectMeta{ + Name: operatorName, + }, + StringData: map[string]string{ + userFieldName: snapshotMgrUser, + urlFieldName: url, + rwPwdFieldName: rw_pwd, + roPwdFieldName: ro_pwd, + certFieldName: cert, + keyFieldName: key, + }, + } + + secret, err = client.Create(context.TODO(), secret, metaV1.CreateOptions{}) + + if err != nil { + mgr.log.Error(err, "Failed to create the secret", + "Secret.Name", operatorName) - return - } + return + } - return + return } /*****************************************************************************/ @@ -840,90 +896,90 @@ func (mgr *SnapshotMgr) createSecret( */ func (mgr *SnapshotMgr) loadSecret() (err error) { - var secretsClient coreV1.SecretInterface - var secret *apiV1.Secret - var namespace string + var secretsClient coreV1.SecretInterface + var secret *apiV1.Secret + var namespace string - mgr.log.V(9).Info("Entering a function", "Function", "loadSecret") + mgr.log.V(9).Info("Entering a function", "Function", "loadSecret") - /* - * Work out the namespace in which we are running. - */ + /* + * Work out the namespace in which we are running. + */ - namespace, err = getLocalNamespace(mgr.log) + namespace, err = getLocalNamespace(mgr.log) - if err != nil { - return - } + if err != nil { + return + } - /* - * Create a new client based on our current configuration. - */ + /* + * Create a new client based on our current configuration. + */ - clientset, err := kubernetes.NewForConfig(mgr.config) - if err != nil { - mgr.log.Error(err, "Failed to create a new client") + clientset, err := kubernetes.NewForConfig(mgr.config) + if err != nil { + mgr.log.Error(err, "Failed to create a new client") - return - } + return + } - /* - * Attempt to retrieve the secret. - */ + /* + * Attempt to retrieve the secret. + */ - secretsClient = clientset.CoreV1().Secrets(namespace) - secret, err = secretsClient.Get( - context.TODO(), operatorName, metaV1.GetOptions{}) + secretsClient = clientset.CoreV1().Secrets(namespace) + secret, err = secretsClient.Get( + context.TODO(), operatorName, metaV1.GetOptions{}) - if err != nil { - mgr.log.V(5).Info("Creating the secret", "Secret.Name", operatorName) + if err != nil { + mgr.log.V(5).Info("Creating the secret", "Secret.Name", operatorName) - /* - * The secret doesn't already exist and so we try to create the - * secret now. - */ + /* + * The secret doesn't already exist and so we try to create the + * secret now. + */ - secret, err = mgr.createSecret(secretsClient, namespace) + secret, err = mgr.createSecret(secretsClient, namespace) - if err != nil { - return - } - } else { - mgr.log.V(5).Info("Found the secret", "Secret.Name", operatorName) - } + if err != nil { + return + } + } else { + mgr.log.V(5).Info("Found the secret", "Secret.Name", operatorName) + } - /* - * We now have the secret and so we need to store the data, also - * checking that all of the required data exists. - */ + /* + * We now have the secret and so we need to store the data, also + * checking that all of the required data exists. + */ - mgr.creds = make(map[string]string) + mgr.creds = make(map[string]string) - keys := []string { - userFieldName, - urlFieldName, - roPwdFieldName, - rwPwdFieldName, - certFieldName, - keyFieldName, - } + keys := []string{ + userFieldName, + urlFieldName, + roPwdFieldName, + rwPwdFieldName, + certFieldName, + keyFieldName, + } - for _, key := range keys { - value, ok := secret.Data[key] + for _, key := range keys { + value, ok := secret.Data[key] - if !ok { - mgr.log.Error(err, "The secret is missing a required field", - "Secret.Name", operatorName, "Field.Name", key) + if !ok { + mgr.log.Error(err, "The secret is missing a required field", + "Secret.Name", operatorName, "Field.Name", key) - err = errors.New("Missing field") + err = errors.New("Missing field") - return - } + return + } - mgr.creds[key] = string(value) - } + mgr.creds[key] = string(value) + } - return + return } /*****************************************************************************/ @@ -933,43 +989,43 @@ func (mgr *SnapshotMgr) loadSecret() (err error) { */ func (mgr *SnapshotMgr) initialize() (err error) { - /* - * Initialise this object. - */ + /* + * Initialise this object. + */ - err = mgr.loadSecret() - if err != nil { - return - } + err = mgr.loadSecret() + if err != nil { + return + } - mgr.restartMutex = &sync.Mutex{} - mgr.webMutex = &sync.RWMutex{} + mgr.restartMutex = &sync.Mutex{} + mgr.webMutex = &sync.RWMutex{} - /* - * Create the directories which will store our data. - */ + /* + * Create the directories which will store our data. + */ - dirs := []string { - dataRoot, - filepath.Join(dataRoot, "snapshots"), - filepath.Join(dataRoot, "fixpacks"), - } + dirs := []string{ + dataRoot, + filepath.Join(dataRoot, "snapshots"), + filepath.Join(dataRoot, "fixpacks"), + } - for _, dir := range dirs { - err = os.Mkdir(dir, 0700) + for _, dir := range dirs { + err = os.Mkdir(dir, 0700) - if err != nil && !os.IsExist(err) { - mgr.log.Error(err, "Failed to create the data directory", - "Directory", dir) + if err != nil && !os.IsExist(err) { + mgr.log.Error(err, "Failed to create the data directory", + "Directory", dir) - return - } else { - err = nil - } + return + } else { + err = nil + } - } + } - return + return } /*****************************************************************************/ @@ -980,62 +1036,59 @@ func (mgr *SnapshotMgr) initialize() (err error) { */ func (mgr *SnapshotMgr) start() { - var err error - - mgr.log.Info("Starting the snapshot manager", "Port", httpsPort) + var err error - /* - * Define the http server and server handler. - */ + mgr.log.Info("Starting the snapshot manager", "Port", httpsPort) - pair, err := tls.X509KeyPair( - []byte(mgr.creds[certFieldName]), - []byte(mgr.creds[keyFieldName])) + /* + * Define the http server and server handler. + */ - if err != nil { - mgr.log.Error(err, "Failed to generate the X509 key pair") + pair, err := tls.X509KeyPair( + []byte(mgr.creds[certFieldName]), + []byte(mgr.creds[keyFieldName])) - return - } + if err != nil { + mgr.log.Error(err, "Failed to generate the X509 key pair") - mgr.server = &http.Server{ - Addr: fmt.Sprintf(":%v", httpsPort), - TLSConfig: &tls.Config{Certificates: []tls.Certificate{pair}}, + return + } - } + mgr.server = &http.Server{ + Addr: fmt.Sprintf(":%v", httpsPort), + TLSConfig: &tls.Config{Certificates: []tls.Certificate{pair}}, + } - mux := http.NewServeMux() + mux := http.NewServeMux() - mux.HandleFunc("/", mgr.serve) + mux.HandleFunc("/", mgr.serve) - mgr.server.Handler = mux + mgr.server.Handler = mux - /* - * Start listening for requests in a different thread. - */ + /* + * Start listening for requests in a different thread. + */ - mgr.log.V(5).Info("Waiting for Web requests") + mgr.log.V(5).Info("Waiting for Web requests") - go func() { - if err := mgr.server.ListenAndServeTLS("", ""); - err != http.ErrServerClosed { - mgr.log.Error(err, "Failed to start the snapshot manager") - } - }() + go func() { + if err := mgr.server.ListenAndServeTLS("", ""); err != http.ErrServerClosed { + mgr.log.Error(err, "Failed to start the snapshot manager") + } + }() - /* - * Wait and listen for the OS shutdown singal. - */ + /* + * Wait and listen for the OS shutdown singal. + */ - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) - <-signalChan + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) + <-signalChan - mgr.log.Info("Received a shutdown signal, shutting down the snapshot " + - "manager gracefully") + mgr.log.Info("Received a shutdown signal, shutting down the snapshot " + + "manager gracefully") - mgr.server.Shutdown(context.Background()) + mgr.server.Shutdown(context.Background()) } /*****************************************************************************/ - diff --git a/src/controllers/utils.go b/src/controllers/utils.go index 9ae64b6..824cea6 100644 --- a/src/controllers/utils.go +++ b/src/controllers/utils.go @@ -5,10 +5,10 @@ package controllers import ( - "github.com/go-logr/logr" - "io/ioutil" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/tools/clientcmd/api" + "github.com/go-logr/logr" + "io/ioutil" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" ) /*****************************************************************************/ @@ -19,38 +19,37 @@ import ( */ func getLocalNamespace(log logr.Logger) (namespace string, err error) { - var namespaceBytes []byte - var clientCfg *api.Config + var namespaceBytes []byte + var clientCfg *api.Config - log.V(9).Info("Entering a function", "Function", "getLocalNamespace") + log.V(9).Info("Entering a function", "Function", "getLocalNamespace") - /* - * Work out the namespace which should be used. In a Kubernetes - * environment we read this from the namespace file, otherwise we use - * the default namespace in the kubectl file. - */ + /* + * Work out the namespace which should be used. In a Kubernetes + * environment we read this from the namespace file, otherwise we use + * the default namespace in the kubectl file. + */ - namespace = "default" + namespace = "default" - namespaceBytes, err = ioutil.ReadFile(k8sNamespaceFile) + namespaceBytes, err = ioutil.ReadFile(k8sNamespaceFile) - if err != nil { - clientCfg, err = clientcmd.NewDefaultClientConfigLoadingRules().Load() + if err != nil { + clientCfg, err = clientcmd.NewDefaultClientConfigLoadingRules().Load() - if err != nil { - log.Error(err, "Failed to load the client configuration") - return - } + if err != nil { + log.Error(err, "Failed to load the client configuration") + return + } - namespace = clientCfg.Contexts[clientCfg.CurrentContext].Namespace - } else { - namespace = string(namespaceBytes) - } + namespace = clientCfg.Contexts[clientCfg.CurrentContext].Namespace + } else { + namespace = string(namespaceBytes) + } - log.V(5).Info("Found a namespace to use", "Namespace", namespace) + log.V(5).Info("Found a namespace to use", "Namespace", namespace) - return + return } /*****************************************************************************/ -