Skip to content
This repository was archived by the owner on May 28, 2021. It is now read-only.

Group replication SSL #115

Merged
merged 1 commit into from
May 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ dist/
bin/
.push-wcr*
.container-wcr*
.push-iad*
.container-iad*

test/system/runner.log
test/system/terraform/cluster/.terraform/
Expand Down
8 changes: 8 additions & 0 deletions examples/cluster/cluster-with-custom-ssl-certs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: "mysql.oracle.com/v1"
kind: MySQLCluster
metadata:
name: mysql
spec:
replicas: 3
sslSecretRef:
name: mysql-ssl-secret
9 changes: 9 additions & 0 deletions examples/custom-ssl-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: mysql-ssl-secret
type: Opaque
data:
ca.crt: <base64'd Root CA certificate>
tls.crt: <base64'd server certificate>
tls.key: <base64'd server private key>
12 changes: 12 additions & 0 deletions pkg/apis/mysql/v1/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions pkg/apis/mysql/v1/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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{}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not relevant to this PR but I'd be in favour of abstracting this given how often it comes up in the codebase and how easy it would be to make a mistake by not setting defaults.

cluster := NewMySQLCluster() | NewMySQLClusterWithDefaults()

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")
}
6 changes: 3 additions & 3 deletions pkg/controllers/cluster/manager/cluster_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
48 changes: 48 additions & 0 deletions pkg/resources/statefulsets/statefulset.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (

mySQLBackupVolumeName = "mysqlbackupvolume"
mySQLVolumeName = "mysqlvolume"
mySQLSSLVolumeName = "mysqlsslvolume"

replicationGroupPort = 13306
)
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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(`
Expand Down Expand Up @@ -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{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a TLS secret?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we can use it i'd like to - unfortunately we have to provide the CA cert file too for MySQL to be happy - tls secrets only have the server cert and key pair. Also it isn't tls, it's ssl still.

So we could 1) add a ca.crt field to the tls secret. 2) have the ca.crt in a different secret or 3) leave it how it is.

Thoughts?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, shame. I’d follow the naming scheme from the TLS secret and add the ca.crt as you suggest.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

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)}
Expand Down
26 changes: 26 additions & 0 deletions pkg/resources/statefulsets/statefulset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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")
}