1. support tls
This commit is contained in:
parent
51b860f4fd
commit
c156c03efd
|
|
@ -30,6 +30,43 @@ type MongoDBClusterSpec struct {
|
|||
MongoDBMonitoring *MongoDBMonitoring `json:"mongoDBMonitoring,omitempty"`
|
||||
PodDisruptionBudget *MongoDBPodDisruptionBudget `json:"podDisruptionBudget,omitempty"`
|
||||
MongoDBAdditionalConfig *string `json:"mongoDBAdditionalConfig,omitempty"`
|
||||
Security Security `json:"security"`
|
||||
}
|
||||
|
||||
type Security struct {
|
||||
TLS TLS `json:"tls"`
|
||||
}
|
||||
|
||||
// TLS is the configuration used to set up TLS encryption
|
||||
type TLS struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// Optional configures if TLS should be required or optional for connections
|
||||
// +optional
|
||||
Optional bool `json:"optional,omitempty"`
|
||||
|
||||
// CertificateKeySecret is a reference to a Secret containing a private key and certificate to use for TLS.
|
||||
// The key and cert are expected to be PEM encoded and available at "tls.key" and "tls.crt".
|
||||
// This is the same format used for the standard "kubernetes.io/tls" Secret type, but no specific type is required.
|
||||
// Alternatively, an entry tls.pem, containing the concatenation of cert and key, can be provided.
|
||||
// If all of tls.pem, tls.crt and tls.key are present, the tls.pem one needs to be equal to the concatenation of tls.crt and tls.key
|
||||
// +optional
|
||||
CertificateKeySecret LocalObjectReference `json:"certificateKeySecretRef,omitempty"`
|
||||
|
||||
// CaCertificateSecret is a reference to a Secret containing the certificate for the CA which signed the server certificates
|
||||
// The certificate is expected to be available under the key "ca.crt"
|
||||
// +optional
|
||||
CaCertificateSecret *LocalObjectReference `json:"caCertificateSecretRef,omitempty"`
|
||||
|
||||
// CaConfigMap is a reference to a ConfigMap containing the certificate for the CA which signed the server certificates
|
||||
// The certificate is expected to be available under the key "ca.crt"
|
||||
// This field is ignored when CaCertificateSecretRef is configured
|
||||
// +optional
|
||||
CaConfigMap *LocalObjectReference `json:"caConfigMapRef,omitempty"`
|
||||
}
|
||||
|
||||
type LocalObjectReference struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// MongoDBPodDisruptionBudget defines the struct for MongoDB cluster
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
## Append samples you want in your CSV to this file as resources ##
|
||||
resources:
|
||||
- _v1alpha1_mongodb.yaml
|
||||
- _v1alpha1_mongodbcluster.yaml
|
||||
- cluster.yaml
|
||||
#+kubebuilder:scaffold:manifestskustomizesamples
|
||||
|
|
|
|||
|
|
@ -130,6 +130,10 @@ func (o *optionBuilder) withRunningState() *optionBuilder {
|
|||
return o.withState(_type.Running, -1)
|
||||
}
|
||||
|
||||
func (o *optionBuilder) withExpandingState(retryAfter int) *optionBuilder {
|
||||
return o.withState(_type.Expanding, retryAfter)
|
||||
}
|
||||
|
||||
type stateOption struct {
|
||||
state string
|
||||
retryAfter int
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"mongodb-operator/k8sgo/status"
|
||||
"mongodb-operator/k8sgo/type"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
|
@ -65,8 +64,40 @@ func (r *MongoDBClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
|||
return ctrl.Result{RequeueAfter: time.Second * 10}, err
|
||||
}
|
||||
}
|
||||
|
||||
isValid, err := k8sgo.ValidateTLSConfig(instance)
|
||||
|
||||
if err != nil {
|
||||
return status.Update(r.Client.Status(), instance,
|
||||
statusOptions().
|
||||
withMessage(Error, fmt.Sprintf("Error validating TLS config: %s", err)).
|
||||
withFailedState(),
|
||||
)
|
||||
}
|
||||
|
||||
if !isValid {
|
||||
return status.Update(r.Client.Status(), instance,
|
||||
statusOptions().
|
||||
withMessage(Info, "TLS config is not yet valid, retrying in 10 seconds").
|
||||
withPendingState(10),
|
||||
)
|
||||
}
|
||||
if err := k8sgo.EnsureTLSResources(instance); err != nil {
|
||||
return status.Update(r.Client.Status(), instance,
|
||||
statusOptions().
|
||||
withMessage(Error, fmt.Sprintf("Error ensuring TLS resources: %s", err)).
|
||||
withFailedState(),
|
||||
)
|
||||
}
|
||||
|
||||
err = k8sgo.CreateMongoClusterSetup(instance)
|
||||
if err != nil {
|
||||
if err.Error() == "Cannot create cluster StatefulSet for MongoDB,expanding" {
|
||||
return status.Update(r.Client.Status(), instance, statusOptions().
|
||||
withMessage(Info, "expanding pvc").
|
||||
withExpandingState(5),
|
||||
)
|
||||
}
|
||||
return ctrl.Result{RequeueAfter: time.Second * 10}, err
|
||||
}
|
||||
err = k8sgo.CreateMongoClusterMonitoringService(instance)
|
||||
|
|
@ -83,8 +114,8 @@ func (r *MongoDBClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque
|
|||
}
|
||||
|
||||
if instance.Status.State == "" {
|
||||
instance.Status.State = _type.Creating
|
||||
err := r.Status().Update(ctx, instance)
|
||||
instance.Status.State = types.Creating
|
||||
err := r.Client.Status().Update(ctx, instance)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ import (
|
|||
// CreateMongoClusterService is a method to create service for mongodb cluster
|
||||
func CreateMongoClusterService(cr *opstreelabsinv1alpha1.MongoDBCluster) error {
|
||||
logger := logGenerator(cr.ObjectMeta.Name, cr.Namespace, "Service")
|
||||
appName := fmt.Sprintf("%s-%s", cr.ObjectMeta.Name, "cluster")
|
||||
appName := cr.ObjectMeta.Name
|
||||
labels := map[string]string{
|
||||
"app": appName,
|
||||
"mongodb_setup": "cluster",
|
||||
"role": "cluster",
|
||||
}
|
||||
params := serviceParameters{
|
||||
ServiceMeta: generateObjectMetaInformation(appName, cr.Namespace, labels, generateAnnotations()),
|
||||
ServiceMeta: generateObjectMetaInformation(fmt.Sprintf("%s-%s", appName, "service"), cr.Namespace, labels, generateAnnotations()),
|
||||
OwnerDef: mongoClusterAsOwner(cr),
|
||||
Namespace: cr.Namespace,
|
||||
Labels: labels,
|
||||
|
|
@ -36,7 +36,7 @@ func CreateMongoClusterService(cr *opstreelabsinv1alpha1.MongoDBCluster) error {
|
|||
// CreateMongoClusterMonitoringService is a method to create a monitoring service for mongodb cluster
|
||||
func CreateMongoClusterMonitoringService(cr *opstreelabsinv1alpha1.MongoDBCluster) error {
|
||||
logger := logGenerator(cr.ObjectMeta.Name, cr.Namespace, "Service")
|
||||
appName := fmt.Sprintf("%s-%s", cr.ObjectMeta.Name, "cluster")
|
||||
appName := cr.ObjectMeta.Name
|
||||
labels := map[string]string{
|
||||
"app": appName,
|
||||
"mongodb_setup": "cluster",
|
||||
|
|
@ -81,7 +81,7 @@ func CreateMongoClusterSetup(cr *opstreelabsinv1alpha1.MongoDBCluster) error {
|
|||
// CreateMongoClusterMonitoringSecret is a method to create secret for monitoring
|
||||
func CreateMongoClusterMonitoringSecret(cr *opstreelabsinv1alpha1.MongoDBCluster) error {
|
||||
logger := logGenerator(cr.ObjectMeta.Name, cr.Namespace, "Secret")
|
||||
err := CreateSecret(getMongoDBClusterSecretParams(cr))
|
||||
err := CreateSecret(getMongoDBClusterSecretParams(cr), "password")
|
||||
if err != nil {
|
||||
logger.Error(err, "Cannot create mongodb monitoring secret for cluster")
|
||||
return err
|
||||
|
|
@ -92,19 +92,19 @@ func CreateMongoClusterMonitoringSecret(cr *opstreelabsinv1alpha1.MongoDBCluster
|
|||
// getMongoDBClusterSecretParams is a method to create secret for MongoDB Monitoring
|
||||
func getMongoDBClusterSecretParams(cr *opstreelabsinv1alpha1.MongoDBCluster) secretsParameters {
|
||||
password := randstr.String(16)
|
||||
appName := fmt.Sprintf("%s-%s", cr.ObjectMeta.Name, "cluster-monitoring")
|
||||
appName := cr.ObjectMeta.Name
|
||||
labels := map[string]string{
|
||||
"app": appName,
|
||||
"mongodb_setup": "cluster",
|
||||
"role": "cluster",
|
||||
}
|
||||
params := secretsParameters{
|
||||
SecretsMeta: generateObjectMetaInformation(appName, cr.Namespace, labels, generateAnnotations()),
|
||||
SecretsMeta: generateObjectMetaInformation(fmt.Sprintf("%s-%s", appName, "monitoring"), cr.Namespace, labels, generateAnnotations()),
|
||||
OwnerDef: mongoClusterAsOwner(cr),
|
||||
Namespace: cr.Namespace,
|
||||
Labels: labels,
|
||||
Annotations: generateAnnotations(),
|
||||
Password: password,
|
||||
Data: password,
|
||||
Name: appName,
|
||||
}
|
||||
return params
|
||||
|
|
@ -114,8 +114,8 @@ func getMongoDBClusterSecretParams(cr *opstreelabsinv1alpha1.MongoDBCluster) sec
|
|||
func getMongoDBClusterParams(cr *opstreelabsinv1alpha1.MongoDBCluster) statefulSetParameters {
|
||||
trueProperty := true
|
||||
falseProperty := false
|
||||
appName := fmt.Sprintf("%s-%s", cr.ObjectMeta.Name, "cluster")
|
||||
monitoringSecretName := fmt.Sprintf("%s-%s", appName, "monitoring")
|
||||
appName := cr.ObjectMeta.Name
|
||||
monitoringSecretName := fmt.Sprintf("%s-%s", appName, "monitoring-secret")
|
||||
labels := map[string]string{
|
||||
"app": appName,
|
||||
"mongodb_setup": "cluster",
|
||||
|
|
@ -176,19 +176,24 @@ func getMongoDBClusterParams(cr *opstreelabsinv1alpha1.MongoDBCluster) statefulS
|
|||
} else {
|
||||
params.ContainerParams.PersistenceEnabled = &falseProperty
|
||||
}
|
||||
|
||||
if cr.Spec.Security.TLS.Enabled {
|
||||
params.TLS = true
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
// getPodDisruptionParams is a method to create parameters for pod disruption budget
|
||||
func getPodDisruptionParams(cr *opstreelabsinv1alpha1.MongoDBCluster) PodDisruptionParameters {
|
||||
appName := fmt.Sprintf("%s-%s", cr.ObjectMeta.Name, "cluster")
|
||||
appName := cr.ObjectMeta.Name
|
||||
labels := map[string]string{
|
||||
"app": appName,
|
||||
"mongodb_setup": "cluster",
|
||||
"role": "cluster",
|
||||
}
|
||||
params := PodDisruptionParameters{
|
||||
PDBMeta: generateObjectMetaInformation(appName, cr.Namespace, labels, generateAnnotations()),
|
||||
PDBMeta: generateObjectMetaInformation(fmt.Sprintf("%s-%s", appName, "Pod-Disruption"), cr.Namespace, labels, generateAnnotations()),
|
||||
OwnerDef: mongoClusterAsOwner(cr),
|
||||
Namespace: cr.Namespace,
|
||||
Labels: labels,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package k8sgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ReadData extracts the contents of the Data field in a given config map
|
||||
func ReadData(namespace string, cmName string) (map[string]string, error) {
|
||||
configmap, err := generateK8sClient().CoreV1().ConfigMaps(namespace).Get(context.TODO(), cmName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configmap.Data, nil
|
||||
}
|
||||
|
|
@ -24,11 +24,12 @@ type containerParameters struct {
|
|||
MonitoringResources *corev1.ResourceRequirements
|
||||
ExtraVolumeMount *corev1.VolumeMount
|
||||
AdditonalConfig *string
|
||||
TLS bool
|
||||
}
|
||||
|
||||
// generateContainerDef is to generate container definition for MongoDB
|
||||
func generateContainerDef(name string, params containerParameters) []corev1.Container {
|
||||
volumeMounts := getVolumeMount(name, params.PersistenceEnabled, params.AdditonalConfig)
|
||||
volumeMounts := getVolumeMount(name, params.PersistenceEnabled, params.AdditonalConfig, params.TLS)
|
||||
if params.ExtraVolumeMount != nil {
|
||||
volumeMounts = append(volumeMounts, *params.ExtraVolumeMount)
|
||||
}
|
||||
|
|
@ -58,7 +59,7 @@ func generateContainerDef(name string, params containerParameters) []corev1.Cont
|
|||
}
|
||||
|
||||
// getVolumeMount is a method to create volume mounting list
|
||||
func getVolumeMount(name string, persistenceEnabled *bool, additionalConfig *string) []corev1.VolumeMount {
|
||||
func getVolumeMount(name string, persistenceEnabled *bool, additionalConfig *string, tls bool) []corev1.VolumeMount {
|
||||
var volumeMounts []corev1.VolumeMount
|
||||
if persistenceEnabled != nil && *persistenceEnabled {
|
||||
volumeMounts = []corev1.VolumeMount{
|
||||
|
|
@ -75,6 +76,20 @@ func getVolumeMount(name string, persistenceEnabled *bool, additionalConfig *str
|
|||
MountPath: "/etc/mongo.d/extra",
|
||||
})
|
||||
}
|
||||
|
||||
if tls {
|
||||
// mount ca volume
|
||||
volumeMounts = append(volumeMounts, corev1.VolumeMount{
|
||||
Name: tlsCAVolumeName,
|
||||
MountPath: tlsCAMountPath,
|
||||
})
|
||||
// mount crt volume
|
||||
volumeMounts = append(volumeMounts, corev1.VolumeMount{
|
||||
Name: tlsCertVolumeName,
|
||||
MountPath: tlsOperatorSecretMountPath,
|
||||
})
|
||||
}
|
||||
|
||||
return volumeMounts
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package k8sgo
|
|||
|
||||
import (
|
||||
"context"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
|
@ -10,7 +11,7 @@ import (
|
|||
type secretsParameters struct {
|
||||
Name string
|
||||
OwnerDef metav1.OwnerReference
|
||||
Password string
|
||||
Data string
|
||||
Namespace string
|
||||
Labels map[string]string
|
||||
Annotations map[string]string
|
||||
|
|
@ -20,8 +21,8 @@ type secretsParameters struct {
|
|||
}
|
||||
|
||||
// CreateSecret is a method to create secret
|
||||
func CreateSecret(params secretsParameters) error {
|
||||
secretDef := generateSecret(params)
|
||||
func CreateSecret(params secretsParameters, key string) error {
|
||||
secretDef := generateSecret(params, key)
|
||||
logger := logGenerator(params.Name, params.Namespace, "Secret")
|
||||
_, err := generateK8sClient().CoreV1().Secrets(params.Namespace).Create(context.TODO(), secretDef, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
|
|
@ -33,13 +34,13 @@ func CreateSecret(params secretsParameters) error {
|
|||
}
|
||||
|
||||
// generateSecret is a method that will generate a secret interface
|
||||
func generateSecret(params secretsParameters) *corev1.Secret {
|
||||
password := []byte(params.Password)
|
||||
func generateSecret(params secretsParameters, key string) *corev1.Secret {
|
||||
data := []byte(params.Data)
|
||||
secret := &corev1.Secret{
|
||||
TypeMeta: generateMetaInformation("Secret", "v1"),
|
||||
ObjectMeta: params.SecretsMeta,
|
||||
Data: map[string][]byte{
|
||||
"password": password,
|
||||
key: data,
|
||||
},
|
||||
}
|
||||
AddOwnerRefToObject(secret, params.OwnerDef)
|
||||
|
|
@ -66,3 +67,28 @@ func CheckSecretExist(namespace string, secret string) bool {
|
|||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ReadStringData reads the StringData field of the secret with the given objectKey
|
||||
func ReadStringData(namespace string, secretName string) (map[string]string, error) {
|
||||
secret, err := generateK8sClient().CoreV1().Secrets(namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dataToStringData(secret.Data), nil
|
||||
}
|
||||
|
||||
func dataToStringData(data map[string][]byte) map[string]string {
|
||||
stringData := make(map[string]string)
|
||||
for k, v := range data {
|
||||
stringData[k] = string(v)
|
||||
}
|
||||
return stringData
|
||||
}
|
||||
|
||||
func ReadKey(secretName string, key string, data map[string]string) (string, error) {
|
||||
if val, ok := data[key]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return "", errors.Errorf(`key "%s" not present in the Secret %s`, key, secretName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
package k8sgo
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type builder struct {
|
||||
data map[string][]byte
|
||||
dataType corev1.SecretType
|
||||
labels map[string]string
|
||||
name string
|
||||
namespace string
|
||||
ownerReferences []metav1.OwnerReference
|
||||
}
|
||||
|
||||
func (b *builder) SetName(name string) *builder {
|
||||
b.name = name
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *builder) SetNamespace(namespace string) *builder {
|
||||
b.namespace = namespace
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *builder) SetField(key, value string) *builder {
|
||||
b.data[key] = []byte(value)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *builder) SetOwnerReferences(ownerReferences []metav1.OwnerReference) *builder {
|
||||
b.ownerReferences = ownerReferences
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *builder) SetLabels(labels map[string]string) *builder {
|
||||
newLabels := make(map[string]string, len(labels))
|
||||
for k, v := range labels {
|
||||
newLabels[k] = v
|
||||
}
|
||||
b.labels = newLabels
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *builder) SetByteData(stringData map[string][]byte) *builder {
|
||||
newStringDataBytes := make(map[string][]byte, len(stringData))
|
||||
for k, v := range stringData {
|
||||
newStringDataBytes[k] = v
|
||||
}
|
||||
b.data = newStringDataBytes
|
||||
return b
|
||||
}
|
||||
func (b *builder) SetStringData(stringData map[string]string) *builder {
|
||||
newStringDataBytes := make(map[string][]byte, len(stringData))
|
||||
for k, v := range stringData {
|
||||
newStringDataBytes[k] = []byte(v)
|
||||
}
|
||||
b.data = newStringDataBytes
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *builder) SetDataType(dataType corev1.SecretType) *builder {
|
||||
b.dataType = dataType
|
||||
return b
|
||||
}
|
||||
|
||||
func (b builder) Build() corev1.Secret {
|
||||
return corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: b.name,
|
||||
Namespace: b.namespace,
|
||||
OwnerReferences: b.ownerReferences,
|
||||
Labels: b.labels,
|
||||
},
|
||||
Data: b.data,
|
||||
Type: b.dataType,
|
||||
}
|
||||
}
|
||||
|
||||
func Builder() *builder {
|
||||
return &builder{
|
||||
labels: map[string]string{},
|
||||
data: map[string][]byte{},
|
||||
ownerReferences: []metav1.OwnerReference{},
|
||||
}
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ func CreateMongoStandaloneSetup(cr *opstreelabsinv1alpha1.MongoDB) error {
|
|||
// CreateMongoMonitoringSecret is a method to create secret for monitoring
|
||||
func CreateMongoMonitoringSecret(cr *opstreelabsinv1alpha1.MongoDB) error {
|
||||
logger := logGenerator(cr.ObjectMeta.Name, cr.Namespace, "Secret")
|
||||
err := CreateSecret(getMongoDBSecretParams(cr))
|
||||
err := CreateSecret(getMongoDBSecretParams(cr), "password")
|
||||
if err != nil {
|
||||
logger.Error(err, "Cannot create mongodb monitoring secret")
|
||||
return err
|
||||
|
|
@ -85,7 +85,7 @@ func getMongoDBSecretParams(cr *opstreelabsinv1alpha1.MongoDB) secretsParameters
|
|||
Namespace: cr.Namespace,
|
||||
Labels: labels,
|
||||
Annotations: generateAnnotations(),
|
||||
Password: password,
|
||||
Data: password,
|
||||
Name: appName,
|
||||
}
|
||||
return params
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ type statefulSetParameters struct {
|
|||
PriorityClassName string
|
||||
AdditionalConfig *string
|
||||
SecurityContext *corev1.PodSecurityContext
|
||||
TLS bool
|
||||
}
|
||||
|
||||
// pvcParameters is the structure for MongoDB PVC
|
||||
|
|
@ -200,6 +201,11 @@ func generateStatefulSetDef(params statefulSetParameters) *appsv1.StatefulSet {
|
|||
if params.ImagePullSecret != nil {
|
||||
statefulset.Spec.Template.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: *params.ImagePullSecret}}
|
||||
}
|
||||
if params.TLS {
|
||||
statefulset.Spec.Template.Spec.Volumes = append(statefulset.Spec.Template.Spec.Volumes, getVolumeFromSecret(tlsCAVolumeName, params.StatefulSetMeta.Name+"-ca-certificate")...)
|
||||
statefulset.Spec.Template.Spec.Volumes = append(statefulset.Spec.Template.Spec.Volumes, getVolumeFromSecret(tlsCertVolumeName, params.StatefulSetMeta.Name+"-server-certificate-key")...)
|
||||
}
|
||||
|
||||
AddOwnerRefToObject(statefulset, params.OwnerDef)
|
||||
return statefulset
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,239 @@
|
|||
package k8sgo
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
opstreelabsinv1alpha1 "mongodb-operator/api/v1alpha1"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
tlsCAMountPath = "/var/lib/tls/ca/"
|
||||
tlsCACertName = "ca.crt"
|
||||
tlsOperatorSecretMountPath = "/var/lib/tls/server/" //nolint
|
||||
tlsSecretCertName = "tls.crt" //nolint
|
||||
tlsSecretKeyName = "tls.key"
|
||||
tlsSecretPemName = "tls.pem"
|
||||
tlsCAVolumeName = "tls-ca"
|
||||
tlsCertVolumeName = "tls-secret"
|
||||
)
|
||||
|
||||
func ValidateTLSConfig(instance *opstreelabsinv1alpha1.MongoDBCluster) (bool, error) {
|
||||
if !instance.Spec.Security.TLS.Enabled {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
log.Info("Ensuring TLS is correctly configured")
|
||||
|
||||
// Ensure CA cert is configured
|
||||
_, err := getCaCrt(instance)
|
||||
|
||||
if err != nil {
|
||||
if apiErrors.IsNotFound(err) {
|
||||
log.Error(err, "CA resource not found")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Ensure Secret exists
|
||||
_, err = ReadStringData(instance.Namespace, instance.Spec.Security.TLS.CertificateKeySecret.Name)
|
||||
if err != nil {
|
||||
if apiErrors.IsNotFound(err) {
|
||||
log.Error(err, "CertificateKeySecret not found")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// validate whether the secret contains "tls.crt" and "tls.key", or it contains "tls.pem"
|
||||
// if it contains all three, then the pem entry should be equal to the concatenation of crt and key
|
||||
_, err = getPemOrConcatenatedCrtAndKey(instance, instance.Spec.Security.TLS.CertificateKeySecret.Name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Info("Successfully validated TLS config")
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func getCaCrt(instance *opstreelabsinv1alpha1.MongoDBCluster) (string, error) {
|
||||
var caData map[string]string
|
||||
var err error
|
||||
if instance.Spec.Security.TLS.CaCertificateSecret != nil {
|
||||
caData, err = ReadStringData(instance.Namespace, instance.Spec.Security.TLS.CaCertificateSecret.Name)
|
||||
} else {
|
||||
caData, err = ReadData(instance.Namespace, instance.Spec.Security.TLS.CaConfigMap.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if cert, ok := caData[tlsCACertName]; !ok || cert == "" {
|
||||
return "", errors.New("CA certificate resource should have a CA certificate in field ")
|
||||
} else {
|
||||
return cert, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getPemOrConcatenatedCrtAndKey will get the final PEM to write to the secret.
|
||||
// This is either the tls.pem entry in the given secret, or the concatenation
|
||||
// of tls.crt and tls.key
|
||||
// It performs a basic validation on the entries.
|
||||
func getPemOrConcatenatedCrtAndKey(instance *opstreelabsinv1alpha1.MongoDBCluster, secretName string) (string, error) {
|
||||
data, err := ReadStringData(instance.Namespace, secretName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
certKey := getCertAndKey(data, secretName)
|
||||
pem := getPem(data, secretName)
|
||||
if certKey == "" && pem == "" {
|
||||
return "", fmt.Errorf(`neither "%s" nor the pair "%s"/"%s" were present in the TLS secret`, tlsSecretPemName, tlsSecretCertName, tlsSecretKeyName)
|
||||
}
|
||||
if certKey == "" {
|
||||
return pem, nil
|
||||
}
|
||||
if pem == "" {
|
||||
return certKey, nil
|
||||
}
|
||||
if certKey != pem {
|
||||
return "", fmt.Errorf(`if all of "%s", "%s" and "%s" are present in the secret, the entry for "%s" must be equal to the concatenation of "%s" with "%s"`, tlsSecretCertName, tlsSecretKeyName, tlsSecretPemName, tlsSecretPemName, tlsSecretCertName, tlsSecretKeyName)
|
||||
}
|
||||
return certKey, nil
|
||||
}
|
||||
|
||||
// getCertAndKey will fetch the certificate and key from the user-provided Secret.
|
||||
func getCertAndKey(data map[string]string, secretName string) string {
|
||||
cert, err := ReadKey(secretName, tlsSecretCertName, data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
key, err := ReadKey(secretName, tlsSecretKeyName, data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return combineCertificateAndKey(cert, key)
|
||||
}
|
||||
|
||||
func combineCertificateAndKey(cert, key string) string {
|
||||
trimmedCert := strings.TrimRight(cert, "\n")
|
||||
trimmedKey := strings.TrimRight(key, "\n")
|
||||
return fmt.Sprintf("%s\n%s", trimmedCert, trimmedKey)
|
||||
}
|
||||
|
||||
// getPem will fetch the pem from the user-provided secret
|
||||
func getPem(data map[string]string, secretName string) string {
|
||||
pem, err := ReadKey(secretName, tlsSecretPemName, data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return pem
|
||||
}
|
||||
|
||||
// ensureTLSResources creates any required TLS resources that the MongoDBCommunity
|
||||
// requires for TLS configuration.
|
||||
func EnsureTLSResources(instance *opstreelabsinv1alpha1.MongoDBCluster) error {
|
||||
if !instance.Spec.Security.TLS.Enabled {
|
||||
return nil
|
||||
}
|
||||
// the TLS secret needs to be created beforehand, as both the StatefulSet and AutomationConfig
|
||||
// require the contents.
|
||||
|
||||
log.Info("TLS is enabled, creating/updating CA secret")
|
||||
if err := ensureCASecret(instance); err != nil {
|
||||
return errors.Errorf("could not ensure CA secret: %s", err)
|
||||
}
|
||||
|
||||
log.Info("TLS is enabled, creating/updating TLS secret")
|
||||
if err := ensureTLSSecret(instance); err != nil {
|
||||
return errors.Errorf("could not ensure TLS secret: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureCASecret will create or update the operator managed Secret containing
|
||||
// the CA certficate from the user provided Secret or ConfigMap.
|
||||
func ensureCASecret(instance *opstreelabsinv1alpha1.MongoDBCluster) error {
|
||||
cert, err := getCaCrt(instance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//caFileName := tlsOperatorSecretFileName(cert)
|
||||
labels := map[string]string{
|
||||
"app": instance.Name,
|
||||
"mongodb_setup": "cluster",
|
||||
"role": "cluster",
|
||||
}
|
||||
params := secretsParameters{
|
||||
SecretsMeta: generateObjectMetaInformation(instance.Name, instance.Namespace, labels, generateAnnotations()),
|
||||
OwnerDef: mongoClusterAsOwner(instance),
|
||||
Namespace: instance.Namespace,
|
||||
Labels: labels,
|
||||
Annotations: generateAnnotations(),
|
||||
Name: instance.Name + "-ca-certificate",
|
||||
Data: cert,
|
||||
}
|
||||
|
||||
return CreateSecret(params, tlsCACertName)
|
||||
}
|
||||
|
||||
// ensureTLSSecret will create or update the operator-managed Secret containing
|
||||
// the concatenated certificate and key from the user-provided Secret.
|
||||
func ensureTLSSecret(instance *opstreelabsinv1alpha1.MongoDBCluster) error {
|
||||
certKey, err := getPemOrConcatenatedCrtAndKey(instance, instance.Spec.Security.TLS.CertificateKeySecret.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Calculate file name from certificate and key
|
||||
//fileName := tlsOperatorSecretFileName(certKey)
|
||||
labels := map[string]string{
|
||||
"app": instance.Name,
|
||||
"mongodb_setup": "cluster",
|
||||
"role": "cluster",
|
||||
}
|
||||
params := secretsParameters{
|
||||
SecretsMeta: generateObjectMetaInformation(instance.Name, instance.Namespace, labels, generateAnnotations()),
|
||||
OwnerDef: mongoClusterAsOwner(instance),
|
||||
Namespace: instance.Namespace,
|
||||
Labels: labels,
|
||||
Annotations: generateAnnotations(),
|
||||
Name: instance.Name + "-server-certificate-key",
|
||||
Data: certKey,
|
||||
}
|
||||
|
||||
return CreateSecret(params, tlsSecretCertName)
|
||||
}
|
||||
|
||||
// tlsOperatorSecretFileName calculates the file name to use for the mounted
|
||||
// certificate-key file. The name is based on the hash of the combined cert and key.
|
||||
// If the certificate or key changes, the file path changes as well which will trigger
|
||||
// the agent to perform a restart.
|
||||
// The user-provided secret is being watched and will trigger a reconciliation
|
||||
// on changes. This enables the operator to automatically handle cert rotations.
|
||||
func tlsOperatorSecretFileName(certKey string) string {
|
||||
hash := sha256.Sum256([]byte(certKey))
|
||||
return fmt.Sprintf("%x.pem", hash)
|
||||
}
|
||||
|
||||
func getVolumeFromSecret(volumeName, secretName string) []corev1.Volume {
|
||||
permission := int32(416)
|
||||
return []corev1.Volume{
|
||||
{
|
||||
Name: volumeName,
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: secretName,
|
||||
DefaultMode: &permission,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue