From 72b239659baaa1a9d4d0934567ea93de89415afd Mon Sep 17 00:00:00 2001 From: Simon Lord Date: Thu, 17 May 2018 15:59:42 +0100 Subject: [PATCH] Group replication SSL - SSL with autogenerated certs as standard (a feature of mysql, certs valid for 10 years) - Confirgurable with your own CA cert, tls cert and tls key via a tls secret --- .gitignore | 2 + .../cluster-with-custom-ssl-certs.yaml | 8 ++++ examples/custom-ssl-secret.yaml | 9 ++++ pkg/apis/mysql/v1/cluster.go | 12 +++++ pkg/apis/mysql/v1/cluster_test.go | 18 +++++++ .../cluster/manager/cluster_manager.go | 6 +-- pkg/resources/statefulsets/statefulset.go | 48 +++++++++++++++++++ .../statefulsets/statefulset_test.go | 26 ++++++++++ 8 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 examples/cluster/cluster-with-custom-ssl-certs.yaml create mode 100644 examples/custom-ssl-secret.yaml diff --git a/.gitignore b/.gitignore index d77c2a458..b78fa82e5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ dist/ bin/ .push-wcr* .container-wcr* +.push-iad* +.container-iad* test/system/runner.log test/system/terraform/cluster/.terraform/ diff --git a/examples/cluster/cluster-with-custom-ssl-certs.yaml b/examples/cluster/cluster-with-custom-ssl-certs.yaml new file mode 100644 index 000000000..a9d08c29e --- /dev/null +++ b/examples/cluster/cluster-with-custom-ssl-certs.yaml @@ -0,0 +1,8 @@ +apiVersion: "mysql.oracle.com/v1" +kind: MySQLCluster +metadata: + name: mysql +spec: + replicas: 3 + sslSecretRef: + name: mysql-ssl-secret diff --git a/examples/custom-ssl-secret.yaml b/examples/custom-ssl-secret.yaml new file mode 100644 index 000000000..fd67444c5 --- /dev/null +++ b/examples/custom-ssl-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mysql-ssl-secret +type: Opaque +data: + ca.crt: + tls.crt: + tls.key: diff --git a/pkg/apis/mysql/v1/cluster.go b/pkg/apis/mysql/v1/cluster.go index 53e29e406..a08e356b9 100644 --- a/pkg/apis/mysql/v1/cluster.go +++ b/pkg/apis/mysql/v1/cluster.go @@ -95,6 +95,11 @@ type MySQLClusterSpec struct { // ConfigRef allows a user to specify a custom configuration file for MySQL. // +optional ConfigRef *corev1.LocalObjectReference `json:"configRef,omitempty"` + + // SSLSecretRef allows a user to specify custom CA certificate, server certificate + // and server key for group replication SSL + // +optional + SSLSecretRef *corev1.LocalObjectReference `json:"sslSecretRef,omitempty"` } // MySQLClusterPhase describes the state of the cluster. @@ -203,6 +208,13 @@ func (c *MySQLCluster) RequiresSecret() bool { return c.Spec.SecretRef == nil } +// RequiresCustomSSLSetup returns true is the user has provided a secret +// that contains CA cert, server cert and server key for group replication +// SSL support +func (c *MySQLCluster) RequiresCustomSSLSetup() bool { + return c.Spec.SSLSecretRef != nil +} + // GetObjectKind is required for codegen func (c *MySQLCluster) GetObjectKind() schema.ObjectKind { return &c.TypeMeta diff --git a/pkg/apis/mysql/v1/cluster_test.go b/pkg/apis/mysql/v1/cluster_test.go index d9728b8de..5cbb6bfa0 100644 --- a/pkg/apis/mysql/v1/cluster_test.go +++ b/pkg/apis/mysql/v1/cluster_test.go @@ -17,6 +17,7 @@ package v1 import ( "testing" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/validation/field" ) @@ -84,3 +85,20 @@ func TestRequiresConfigMount(t *testing.T) { t.Errorf("Cluster with configRef should require a config mount") } } + +func TestRequiresCustomSSLSetup(t *testing.T) { + cluster := &MySQLCluster{} + cluster.EnsureDefaults() + + assert.False(t, cluster.RequiresCustomSSLSetup(), "Cluster without SSLSecretRef should not require custom SSL setup") + + cluster = &MySQLCluster{ + Spec: MySQLClusterSpec{ + SSLSecretRef: &corev1.LocalObjectReference{ + Name: "custom-ssl-secret", + }, + }, + } + + assert.True(t, cluster.RequiresCustomSSLSetup(), "Cluster with SSLSecretRef should require custom SSL setup") +} diff --git a/pkg/controllers/cluster/manager/cluster_manager.go b/pkg/controllers/cluster/manager/cluster_manager.go index ef8ca92cf..d23bce5f2 100644 --- a/pkg/controllers/cluster/manager/cluster_manager.go +++ b/pkg/controllers/cluster/manager/cluster_manager.go @@ -258,7 +258,7 @@ func (m *ClusterManager) handleInstanceMissing(ctx context.Context, primaryAddr glog.V(4).Infof("Attempting to rejoin instance to cluster") if err := primarySh.RejoinInstanceToCluster(ctx, m.Instance.GetShellURI(), mysqlsh.Options{ "ipWhitelist": whitelistCIDR, - "memberSslMode": "DISABLED", + "memberSslMode": "REQUIRED", }); err != nil { glog.Errorf("Failed to rejoin cluster: %v", err) return false @@ -288,7 +288,7 @@ func (m *ClusterManager) handleInstanceNotFound(ctx context.Context, primaryAddr } if err := psh.AddInstanceToCluster(ctx, m.Instance.GetShellURI(), mysqlsh.Options{ - "memberSslMode": "DISABLED", + "memberSslMode": "REQUIRED", "ipWhitelist": whitelistCIDR, }); err != nil { glog.Errorf("Failed to add to cluster: %v", err) @@ -324,7 +324,7 @@ func (m *ClusterManager) bootstrap(ctx context.Context) (*innodb.ClusterStatus, return nil, errors.Wrap(err, "getting CIDR to whitelist for GR") } opts := mysqlsh.Options{ - "memberSslMode": "DISABLED", + "memberSslMode": "REQUIRED", "ipWhitelist": whitelistCIDR, } if m.Instance.MultiMaster { diff --git a/pkg/resources/statefulsets/statefulset.go b/pkg/resources/statefulsets/statefulset.go index 4448f833e..b3b874abc 100644 --- a/pkg/resources/statefulsets/statefulset.go +++ b/pkg/resources/statefulsets/statefulset.go @@ -44,6 +44,7 @@ const ( mySQLBackupVolumeName = "mysqlbackupvolume" mySQLVolumeName = "mysqlvolume" + mySQLSSLVolumeName = "mysqlsslvolume" replicationGroupPort = 13306 ) @@ -82,6 +83,13 @@ func volumeMounts(cluster *api.MySQLCluster) []v1.VolumeMount { }) } + if cluster.RequiresCustomSSLSetup() { + mounts = append(mounts, v1.VolumeMount{ + Name: mySQLSSLVolumeName, + MountPath: "/etc/ssl/mysql", + }) + } + return mounts } @@ -211,6 +219,13 @@ func mysqlServerContainer(cluster *api.MySQLCluster, mysqlServerImage string, ro "--log-error-verbosity=3", } + if cluster.RequiresCustomSSLSetup() { + args = append(args, + "--ssl-ca=/etc/ssl/mysql/ca.crt", + "--ssl-cert=/etc/ssl/mysql/tls.crt", + "--ssl-key=/etc/ssl/mysql/tls.key") + } + entryPointArgs := strings.Join(args, " ") cmd := fmt.Sprintf(` @@ -331,6 +346,39 @@ func NewForCluster(cluster *api.MySQLCluster, images operatoropts.Images, servic }) } + if cluster.RequiresCustomSSLSetup() { + podVolumes = append(podVolumes, v1.Volume{ + Name: mySQLSSLVolumeName, + VolumeSource: v1.VolumeSource{ + Projected: &v1.ProjectedVolumeSource{ + Sources: []v1.VolumeProjection{ + v1.VolumeProjection{ + Secret: &v1.SecretProjection{ + LocalObjectReference: v1.LocalObjectReference{ + Name: cluster.Spec.SSLSecretRef.Name, + }, + Items: []v1.KeyToPath{ + v1.KeyToPath{ + Key: "ca.crt", + Path: "ca.crt", + }, + v1.KeyToPath{ + Key: "tls.crt", + Path: "tls.crt", + }, + v1.KeyToPath{ + Key: "tls.key", + Path: "tls.key", + }, + }, + }, + }, + }, + }, + }, + }) + } + containers := []v1.Container{ mysqlServerContainer(cluster, images.MySQLServerImage, rootPassword, serviceName, replicas, baseServerID), mysqlAgentContainer(cluster, images.MySQLAgentImage, rootPassword, serviceName, replicas)} diff --git a/pkg/resources/statefulsets/statefulset_test.go b/pkg/resources/statefulsets/statefulset_test.go index 3ea32bdf8..1a859c9d6 100644 --- a/pkg/resources/statefulsets/statefulset_test.go +++ b/pkg/resources/statefulsets/statefulset_test.go @@ -18,6 +18,7 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -139,3 +140,28 @@ func TestClusterCustomConfig(t *testing.T) { t.Errorf("Cluster is missing expected volume mount for custom config map") } } + +func TestClusterCustomSSLSetup(t *testing.T) { + cluster := &api.MySQLCluster{ + Spec: api.MySQLClusterSpec{ + SSLSecretRef: &v1.LocalObjectReference{ + Name: "my-ssl", + }, + }, + } + + statefulSet := NewForCluster(cluster, mockOperatorConfig().Images, "mycluster") + containers := statefulSet.Spec.Template.Spec.Containers + + var hasExpectedVolumeMount = false + for _, container := range containers { + for _, mount := range container.VolumeMounts { + if mount.MountPath == "/etc/ssl/mysql" { + hasExpectedVolumeMount = true + break + } + } + } + + assert.True(t, hasExpectedVolumeMount, "Cluster is missing expected volume mount for custom SSL certs") +}