Skip to content

Commit 6b59a55

Browse files
committed
add support for custom TLS certificates
1 parent 7fb1632 commit 6b59a55

File tree

10 files changed

+248
-19
lines changed

10 files changed

+248
-19
lines changed

docs/user.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,3 +454,41 @@ monitoring is outside the scope of operator responsibilities. See
454454
[configuration reference](reference/cluster_manifest.md) and
455455
[administrator documentation](administrator.md) for details on how backups are
456456
executed.
457+
458+
## TLS configuration
459+
460+
By default self-signed certificates are generated when the postgres container
461+
starts up. But it's also possible to provide your own secrets like so:
462+
463+
Upload the cert as a kubernetes secret:
464+
```sh
465+
kubectl create secret tls pg-tls \
466+
--key pg-tls.key \
467+
--cert pg-tls.crt
468+
```
469+
470+
Or with a CA:
471+
```sh
472+
kubectl create secret generic pg-tls \
473+
--from-file=tls.crt=server.crt \
474+
--from-file=tls.key=server.key \
475+
--from-file=ca.crt=ca.crt
476+
```
477+
478+
Configure the postgres resource with the TLS secret:
479+
480+
```yaml
481+
apiVersion: "acid.zalan.do/v1"
482+
kind: postgresql
483+
484+
metadata:
485+
name: acid-test-cluster
486+
spec:
487+
tls:
488+
secretName: "pg-tls"
489+
```
490+
491+
Before applying these changes, the operator must also be configured with the
492+
`spilo_fsgroup` set to the GID matching the postgres user group. If the value
493+
is not provided, the cluster will default to `103` which is the GID from the
494+
default spilo image.

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/lib/pq v1.2.0
1212
github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d
1313
github.com/sirupsen/logrus v1.4.2
14+
github.com/stretchr/testify v1.4.0
1415
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect
1516
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
1617
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
275275
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
276276
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
277277
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
278+
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
278279
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
279280
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
280281
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

manifests/complete-postgres-manifest.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,9 @@ spec:
9898
# env:
9999
# - name: "USEFUL_VAR"
100100
# value: "perhaps-true"
101+
102+
# TLS configuration. Uses self-generated certs if tls.secretName is empty.
103+
tls:
104+
secretName: ""
105+
certificateFile: "tls.crt"
106+
privateKeyFile: "tls.key"

manifests/postgresql.crd.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,19 @@ spec:
247247
type: string
248248
teamId:
249249
type: string
250+
tls:
251+
type: object
252+
required:
253+
- secretName
254+
properties:
255+
secretName:
256+
type: string
257+
certificateFile:
258+
type: string
259+
privateKeyFile:
260+
type: string
261+
caFile:
262+
type: string
250263
tolerations:
251264
type: array
252265
items:

pkg/apis/acid.zalan.do/v1/crds.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,24 @@ var PostgresCRDResourceValidation = apiextv1beta1.CustomResourceValidation{
409409
"teamId": {
410410
Type: "string",
411411
},
412+
"tls": {
413+
Type: "object",
414+
Required: []string{"secretName"},
415+
Properties: map[string]apiextv1beta1.JSONSchemaProps{
416+
"secretName": {
417+
Type: "string",
418+
},
419+
"certificateFile": {
420+
Type: "string",
421+
},
422+
"privateKeyFile": {
423+
Type: "string",
424+
},
425+
"caFile": {
426+
Type: "string",
427+
},
428+
},
429+
},
412430
"tolerations": {
413431
Type: "array",
414432
Items: &apiextv1beta1.JSONSchemaPropsOrArray{

pkg/apis/acid.zalan.do/v1/postgresql_type.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ type PostgresSpec struct {
6060
LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"`
6161
StandbyCluster *StandbyDescription `json:"standby"`
6262
PodAnnotations map[string]string `json:"podAnnotations"`
63+
TLS *TLSDescription `json:"tls"`
6364

6465
// deprecated json tags
6566
InitContainersOld []v1.Container `json:"init_containers,omitempty"`
@@ -125,6 +126,13 @@ type StandbyDescription struct {
125126
S3WalPath string `json:"s3_wal_path,omitempty"`
126127
}
127128

129+
type TLSDescription struct {
130+
SecretName string `json:"secretName,omitempty"`
131+
CertificateFile string `json:"certificateFile,omitempty"`
132+
PrivateKeyFile string `json:"privateKeyFile,omitempty"`
133+
CAFile string `json:"caFile,omitempty"`
134+
}
135+
128136
// CloneDescription describes which cluster the new should clone and up to which point in time
129137
type CloneDescription struct {
130138
ClusterName string `json:"cluster,omitempty"`

pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cluster/k8sres.go

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cluster
33
import (
44
"encoding/json"
55
"fmt"
6+
"path"
67
"sort"
78

89
"github.com/sirupsen/logrus"
@@ -30,7 +31,10 @@ const (
3031
patroniPGBinariesParameterName = "bin_dir"
3132
patroniPGParametersParameterName = "parameters"
3233
patroniPGHBAConfParameterName = "pg_hba"
33-
localHost = "127.0.0.1/32"
34+
35+
// the gid of the postgres user in the default spilo image
36+
spiloPostgresGID = 103
37+
localHost = "127.0.0.1/32"
3438
)
3539

3640
type pgUser struct {
@@ -446,6 +450,7 @@ func generatePodTemplate(
446450
podAntiAffinityTopologyKey string,
447451
additionalSecretMount string,
448452
additionalSecretMountPath string,
453+
volumes []v1.Volume,
449454
) (*v1.PodTemplateSpec, error) {
450455

451456
terminateGracePeriodSeconds := terminateGracePeriod
@@ -464,6 +469,7 @@ func generatePodTemplate(
464469
InitContainers: initContainers,
465470
Tolerations: *tolerationsSpec,
466471
SecurityContext: &securityContext,
472+
Volumes: volumes,
467473
}
468474

469475
if shmVolume != nil && *shmVolume {
@@ -724,6 +730,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
724730
sidecarContainers []v1.Container
725731
podTemplate *v1.PodTemplateSpec
726732
volumeClaimTemplate *v1.PersistentVolumeClaim
733+
volumes []v1.Volume
727734
)
728735

729736
// Improve me. Please.
@@ -840,21 +847,71 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
840847
}
841848

842849
// generate environment variables for the spilo container
843-
spiloEnvVars := deduplicateEnvVars(
844-
c.generateSpiloPodEnvVars(c.Postgresql.GetUID(), spiloConfiguration, &spec.Clone,
845-
spec.StandbyCluster, customPodEnvVarsList), c.containerName(), c.logger)
850+
spiloEnvVars := c.generateSpiloPodEnvVars(
851+
c.Postgresql.GetUID(),
852+
spiloConfiguration,
853+
&spec.Clone,
854+
spec.StandbyCluster,
855+
customPodEnvVarsList,
856+
)
846857

847858
// pickup the docker image for the spilo container
848859
effectiveDockerImage := util.Coalesce(spec.DockerImage, c.OpConfig.DockerImage)
849860

861+
// determine the FSGroup for the spilo pod
862+
effectiveFSGroup := c.OpConfig.Resources.SpiloFSGroup
863+
if spec.SpiloFSGroup != nil {
864+
effectiveFSGroup = spec.SpiloFSGroup
865+
}
866+
850867
volumeMounts := generateVolumeMounts(spec.Volume)
851868

869+
// configure TLS with a custom secret volume
870+
if spec.TLS != nil && spec.TLS.SecretName != "" {
871+
if effectiveFSGroup == nil {
872+
c.logger.Warnf("Setting the default FSGroup to satisfy the TLS configuration")
873+
fsGroup := int64(spiloPostgresGID)
874+
effectiveFSGroup = &fsGroup
875+
}
876+
// this is combined with the FSGroup above to give read access to the
877+
// postgres user
878+
defaultMode := int32(0640)
879+
volumes = append(volumes, v1.Volume{
880+
Name: "tls-secret",
881+
VolumeSource: v1.VolumeSource{
882+
Secret: &v1.SecretVolumeSource{
883+
SecretName: spec.TLS.SecretName,
884+
DefaultMode: &defaultMode,
885+
},
886+
},
887+
})
888+
889+
mountPath := "/tls"
890+
volumeMounts = append(volumeMounts, v1.VolumeMount{
891+
MountPath: mountPath,
892+
Name: "tls-secret",
893+
ReadOnly: true,
894+
})
895+
896+
// use the same filenames as cert-manager by default
897+
certFile := ensurePath(spec.TLS.CertificateFile, mountPath, "tls.crt")
898+
privateKeyFile := ensurePath(spec.TLS.PrivateKeyFile, mountPath, "tls.key")
899+
caFile := ensurePath(spec.TLS.CAFile, mountPath, "ca.crt")
900+
901+
spiloEnvVars = append(
902+
spiloEnvVars,
903+
v1.EnvVar{Name: "SSL_CERTIFICATE_FILE", Value: certFile},
904+
v1.EnvVar{Name: "SSL_PRIVATE_KEY_FILE", Value: privateKeyFile},
905+
v1.EnvVar{Name: "SSL_CA_FILE", Value: caFile},
906+
)
907+
}
908+
852909
// generate the spilo container
853910
c.logger.Debugf("Generating Spilo container, environment variables: %v", spiloEnvVars)
854911
spiloContainer := generateContainer(c.containerName(),
855912
&effectiveDockerImage,
856913
resourceRequirements,
857-
spiloEnvVars,
914+
deduplicateEnvVars(spiloEnvVars, c.containerName(), c.logger),
858915
volumeMounts,
859916
c.OpConfig.Resources.SpiloPrivileged,
860917
)
@@ -893,16 +950,10 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
893950
tolerationSpec := tolerations(&spec.Tolerations, c.OpConfig.PodToleration)
894951
effectivePodPriorityClassName := util.Coalesce(spec.PodPriorityClassName, c.OpConfig.PodPriorityClassName)
895952

896-
// determine the FSGroup for the spilo pod
897-
effectiveFSGroup := c.OpConfig.Resources.SpiloFSGroup
898-
if spec.SpiloFSGroup != nil {
899-
effectiveFSGroup = spec.SpiloFSGroup
900-
}
901-
902953
annotations := c.generatePodAnnotations(spec)
903954

904955
// generate pod template for the statefulset, based on the spilo container and sidecars
905-
if podTemplate, err = generatePodTemplate(
956+
podTemplate, err = generatePodTemplate(
906957
c.Namespace,
907958
c.labelsSet(true),
908959
annotations,
@@ -920,10 +971,9 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
920971
c.OpConfig.EnablePodAntiAffinity,
921972
c.OpConfig.PodAntiAffinityTopologyKey,
922973
c.OpConfig.AdditionalSecretMount,
923-
c.OpConfig.AdditionalSecretMountPath); err != nil {
924-
return nil, fmt.Errorf("could not generate pod template: %v", err)
925-
}
926-
974+
c.OpConfig.AdditionalSecretMountPath,
975+
volumes,
976+
)
927977
if err != nil {
928978
return nil, fmt.Errorf("could not generate pod template: %v", err)
929979
}
@@ -1523,7 +1573,8 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1beta1.CronJob, error) {
15231573
false,
15241574
"",
15251575
c.OpConfig.AdditionalSecretMount,
1526-
c.OpConfig.AdditionalSecretMountPath); err != nil {
1576+
c.OpConfig.AdditionalSecretMountPath,
1577+
nil); err != nil {
15271578
return nil, fmt.Errorf("could not generate pod template for logical backup pod: %v", err)
15281579
}
15291580

@@ -1651,3 +1702,13 @@ func (c *Cluster) generateLogicalBackupPodEnvVars() []v1.EnvVar {
16511702
func (c *Cluster) getLogicalBackupJobName() (jobName string) {
16521703
return "logical-backup-" + c.clusterName().Name
16531704
}
1705+
1706+
func ensurePath(file string, defaultDir string, defaultFile string) string {
1707+
if file == "" {
1708+
return path.Join(defaultDir, defaultFile)
1709+
}
1710+
if !path.IsAbs(file) {
1711+
return path.Join(defaultDir, file)
1712+
}
1713+
return file
1714+
}

0 commit comments

Comments
 (0)