add support for custom TLS certificates (#798)
* add support for custom TLS certificates
This commit is contained in:
		
							parent
							
								
									650b8daf77
								
							
						
					
					
						commit
						65fb2ce1a6
					
				| 
						 | 
					@ -359,3 +359,24 @@ CPU and memory limits for the sidecar container.
 | 
				
			||||||
* **memory**
 | 
					* **memory**
 | 
				
			||||||
  memory limits for the sidecar container. Optional, overrides the
 | 
					  memory limits for the sidecar container. Optional, overrides the
 | 
				
			||||||
  `default_memory_limits` operator configuration parameter. Optional.
 | 
					  `default_memory_limits` operator configuration parameter. Optional.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Custom TLS certificates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Those parameters are grouped under the `tls` top-level key.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **secretName**
 | 
				
			||||||
 | 
					  By setting the `secretName` value, the cluster will switch to load the given
 | 
				
			||||||
 | 
					  Kubernetes Secret into the container as a volume and uses that as the
 | 
				
			||||||
 | 
					  certificate instead. It is up to the user to create and manage the
 | 
				
			||||||
 | 
					  Kubernetes Secret either by hand or using a tool like the CertManager
 | 
				
			||||||
 | 
					  operator.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **certificateFile**
 | 
				
			||||||
 | 
					  Filename of the certificate. Defaults to "tls.crt".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **privateKeyFile**
 | 
				
			||||||
 | 
					  Filename of the private key. Defaults to "tls.key".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **caFile**
 | 
				
			||||||
 | 
					  Optional filename to the CA certificate. Useful when the client connects
 | 
				
			||||||
 | 
					  with `sslmode=verify-ca` or `sslmode=verify-full`.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										47
									
								
								docs/user.md
								
								
								
								
							
							
						
						
									
										47
									
								
								docs/user.md
								
								
								
								
							| 
						 | 
					@ -511,3 +511,50 @@ monitoring is outside the scope of operator responsibilities. See
 | 
				
			||||||
[configuration reference](reference/cluster_manifest.md) and
 | 
					[configuration reference](reference/cluster_manifest.md) and
 | 
				
			||||||
[administrator documentation](administrator.md) for details on how backups are
 | 
					[administrator documentation](administrator.md) for details on how backups are
 | 
				
			||||||
executed.
 | 
					executed.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Custom TLS certificates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					By default, the spilo image generates its own TLS certificate during startup.
 | 
				
			||||||
 | 
					This certificate is not secure since it cannot be verified and thus doesn't
 | 
				
			||||||
 | 
					protect from active MITM attacks. In this section we show how a Kubernete
 | 
				
			||||||
 | 
					Secret resources can be loaded with a custom TLS certificate.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Before applying these changes, the operator must also be configured with the
 | 
				
			||||||
 | 
					`spilo_fsgroup` set to the GID matching the postgres user group. If the value
 | 
				
			||||||
 | 
					is not provided, the cluster will default to `103` which is the GID from the
 | 
				
			||||||
 | 
					default spilo image.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Upload the cert as a kubernetes secret:
 | 
				
			||||||
 | 
					```sh
 | 
				
			||||||
 | 
					kubectl create secret tls pg-tls \
 | 
				
			||||||
 | 
					  --key pg-tls.key \
 | 
				
			||||||
 | 
					  --cert pg-tls.crt
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Or with a CA:
 | 
				
			||||||
 | 
					```sh
 | 
				
			||||||
 | 
					kubectl create secret generic pg-tls \
 | 
				
			||||||
 | 
					  --from-file=tls.crt=server.crt \
 | 
				
			||||||
 | 
					  --from-file=tls.key=server.key \
 | 
				
			||||||
 | 
					  --from-file=ca.crt=ca.crt
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Alternatively it is also possible to use
 | 
				
			||||||
 | 
					[cert-manager](https://cert-manager.io/docs/) to generate these secrets.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Then configure the postgres resource with the TLS secret:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					apiVersion: "acid.zalan.do/v1"
 | 
				
			||||||
 | 
					kind: postgresql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					metadata:
 | 
				
			||||||
 | 
					  name: acid-test-cluster
 | 
				
			||||||
 | 
					spec:
 | 
				
			||||||
 | 
					  tls:
 | 
				
			||||||
 | 
					    secretName: "pg-tls"
 | 
				
			||||||
 | 
					    caFile: "ca.crt" # add this if the secret is configured with a CA
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Certificate rotation is handled in the spilo image which checks every 5
 | 
				
			||||||
 | 
					minutes if the certificates have changed and reloads postgres accordingly.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										1
									
								
								go.mod
								
								
								
								
							| 
						 | 
					@ -11,6 +11,7 @@ require (
 | 
				
			||||||
	github.com/lib/pq v1.2.0
 | 
						github.com/lib/pq v1.2.0
 | 
				
			||||||
	github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d
 | 
						github.com/motomux/pretty v0.0.0-20161209205251-b2aad2c9a95d
 | 
				
			||||||
	github.com/sirupsen/logrus v1.4.2
 | 
						github.com/sirupsen/logrus v1.4.2
 | 
				
			||||||
 | 
						github.com/stretchr/testify v1.4.0
 | 
				
			||||||
	golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect
 | 
						golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect
 | 
				
			||||||
	golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
 | 
						golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
 | 
				
			||||||
	golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
 | 
						golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										1
									
								
								go.sum
								
								
								
								
							| 
						 | 
					@ -275,6 +275,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 | 
				
			||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 | 
					github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 | 
				
			||||||
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
					github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
				
			||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
					github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 | 
				
			||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 | 
					github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 | 
				
			||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
					github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -100,3 +100,10 @@ spec:
 | 
				
			||||||
#      env:
 | 
					#      env:
 | 
				
			||||||
#        - name: "USEFUL_VAR"
 | 
					#        - name: "USEFUL_VAR"
 | 
				
			||||||
#          value: "perhaps-true"
 | 
					#          value: "perhaps-true"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Custom TLS certificate. Disabled unless tls.secretName has a value.
 | 
				
			||||||
 | 
					  tls:
 | 
				
			||||||
 | 
					    secretName: ""  # should correspond to a Kubernetes Secret resource to load
 | 
				
			||||||
 | 
					    certificateFile: "tls.crt"
 | 
				
			||||||
 | 
					    privateKeyFile: "tls.key"
 | 
				
			||||||
 | 
					    caFile: ""  # optionally configure Postgres with a CA certificate
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -251,6 +251,19 @@ spec:
 | 
				
			||||||
                  type: string
 | 
					                  type: string
 | 
				
			||||||
            teamId:
 | 
					            teamId:
 | 
				
			||||||
              type: string
 | 
					              type: string
 | 
				
			||||||
 | 
					            tls:
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
 | 
					              required:
 | 
				
			||||||
 | 
					                - secretName
 | 
				
			||||||
 | 
					              properties:
 | 
				
			||||||
 | 
					                secretName:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                certificateFile:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                privateKeyFile:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                caFile:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
            tolerations:
 | 
					            tolerations:
 | 
				
			||||||
              type: array
 | 
					              type: array
 | 
				
			||||||
              items:
 | 
					              items:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -417,6 +417,24 @@ var PostgresCRDResourceValidation = apiextv1beta1.CustomResourceValidation{
 | 
				
			||||||
					"teamId": {
 | 
										"teamId": {
 | 
				
			||||||
						Type: "string",
 | 
											Type: "string",
 | 
				
			||||||
					},
 | 
										},
 | 
				
			||||||
 | 
										"tls": {
 | 
				
			||||||
 | 
											Type: "object",
 | 
				
			||||||
 | 
											Required: []string{"secretName"},
 | 
				
			||||||
 | 
											Properties: map[string]apiextv1beta1.JSONSchemaProps{
 | 
				
			||||||
 | 
												"secretName": {
 | 
				
			||||||
 | 
													Type: "string",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												"certificateFile": {
 | 
				
			||||||
 | 
													Type: "string",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												"privateKeyFile": {
 | 
				
			||||||
 | 
													Type: "string",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
												"caFile": {
 | 
				
			||||||
 | 
													Type: "string",
 | 
				
			||||||
 | 
												},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
					"tolerations": {
 | 
										"tolerations": {
 | 
				
			||||||
						Type: "array",
 | 
											Type: "array",
 | 
				
			||||||
						Items: &apiextv1beta1.JSONSchemaPropsOrArray{
 | 
											Items: &apiextv1beta1.JSONSchemaPropsOrArray{
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,6 +61,7 @@ type PostgresSpec struct {
 | 
				
			||||||
	StandbyCluster        *StandbyDescription  `json:"standby"`
 | 
						StandbyCluster        *StandbyDescription  `json:"standby"`
 | 
				
			||||||
	PodAnnotations        map[string]string    `json:"podAnnotations"`
 | 
						PodAnnotations        map[string]string    `json:"podAnnotations"`
 | 
				
			||||||
	ServiceAnnotations    map[string]string    `json:"serviceAnnotations"`
 | 
						ServiceAnnotations    map[string]string    `json:"serviceAnnotations"`
 | 
				
			||||||
 | 
						TLS                   *TLSDescription      `json:"tls"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// deprecated json tags
 | 
						// deprecated json tags
 | 
				
			||||||
	InitContainersOld       []v1.Container `json:"init_containers,omitempty"`
 | 
						InitContainersOld       []v1.Container `json:"init_containers,omitempty"`
 | 
				
			||||||
| 
						 | 
					@ -126,6 +127,13 @@ type StandbyDescription struct {
 | 
				
			||||||
	S3WalPath string `json:"s3_wal_path,omitempty"`
 | 
						S3WalPath string `json:"s3_wal_path,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TLSDescription struct {
 | 
				
			||||||
 | 
						SecretName      string `json:"secretName,omitempty"`
 | 
				
			||||||
 | 
						CertificateFile string `json:"certificateFile,omitempty"`
 | 
				
			||||||
 | 
						PrivateKeyFile  string `json:"privateKeyFile,omitempty"`
 | 
				
			||||||
 | 
						CAFile          string `json:"caFile,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CloneDescription describes which cluster the new should clone and up to which point in time
 | 
					// CloneDescription describes which cluster the new should clone and up to which point in time
 | 
				
			||||||
type CloneDescription struct {
 | 
					type CloneDescription struct {
 | 
				
			||||||
	ClusterName       string `json:"cluster,omitempty"`
 | 
						ClusterName       string `json:"cluster,omitempty"`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -521,6 +521,11 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
 | 
				
			||||||
			(*out)[key] = val
 | 
								(*out)[key] = val
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if in.TLS != nil {
 | 
				
			||||||
 | 
							in, out := &in.TLS, &out.TLS
 | 
				
			||||||
 | 
							*out = new(TLSDescription)
 | 
				
			||||||
 | 
							**out = **in
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if in.InitContainersOld != nil {
 | 
						if in.InitContainersOld != nil {
 | 
				
			||||||
		in, out := &in.InitContainersOld, &out.InitContainersOld
 | 
							in, out := &in.InitContainersOld, &out.InitContainersOld
 | 
				
			||||||
		*out = make([]corev1.Container, len(*in))
 | 
							*out = make([]corev1.Container, len(*in))
 | 
				
			||||||
| 
						 | 
					@ -752,6 +757,22 @@ func (in *StandbyDescription) DeepCopy() *StandbyDescription {
 | 
				
			||||||
	return out
 | 
						return out
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
 | 
					func (in *TLSDescription) DeepCopyInto(out *TLSDescription) {
 | 
				
			||||||
 | 
						*out = *in
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSDescription.
 | 
				
			||||||
 | 
					func (in *TLSDescription) DeepCopy() *TLSDescription {
 | 
				
			||||||
 | 
						if in == nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						out := new(TLSDescription)
 | 
				
			||||||
 | 
						in.DeepCopyInto(out)
 | 
				
			||||||
 | 
						return out
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
					// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | 
				
			||||||
func (in *TeamsAPIConfiguration) DeepCopyInto(out *TeamsAPIConfiguration) {
 | 
					func (in *TeamsAPIConfiguration) DeepCopyInto(out *TeamsAPIConfiguration) {
 | 
				
			||||||
	*out = *in
 | 
						*out = *in
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ package cluster
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
| 
						 | 
					@ -30,6 +31,9 @@ const (
 | 
				
			||||||
	patroniPGBinariesParameterName   = "bin_dir"
 | 
						patroniPGBinariesParameterName   = "bin_dir"
 | 
				
			||||||
	patroniPGParametersParameterName = "parameters"
 | 
						patroniPGParametersParameterName = "parameters"
 | 
				
			||||||
	patroniPGHBAConfParameterName    = "pg_hba"
 | 
						patroniPGHBAConfParameterName    = "pg_hba"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// the gid of the postgres user in the default spilo image
 | 
				
			||||||
 | 
						spiloPostgresGID = 103
 | 
				
			||||||
	localHost        = "127.0.0.1/32"
 | 
						localHost        = "127.0.0.1/32"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -446,6 +450,7 @@ func generatePodTemplate(
 | 
				
			||||||
	podAntiAffinityTopologyKey string,
 | 
						podAntiAffinityTopologyKey string,
 | 
				
			||||||
	additionalSecretMount string,
 | 
						additionalSecretMount string,
 | 
				
			||||||
	additionalSecretMountPath string,
 | 
						additionalSecretMountPath string,
 | 
				
			||||||
 | 
						volumes []v1.Volume,
 | 
				
			||||||
) (*v1.PodTemplateSpec, error) {
 | 
					) (*v1.PodTemplateSpec, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	terminateGracePeriodSeconds := terminateGracePeriod
 | 
						terminateGracePeriodSeconds := terminateGracePeriod
 | 
				
			||||||
| 
						 | 
					@ -464,6 +469,7 @@ func generatePodTemplate(
 | 
				
			||||||
		InitContainers:                initContainers,
 | 
							InitContainers:                initContainers,
 | 
				
			||||||
		Tolerations:                   *tolerationsSpec,
 | 
							Tolerations:                   *tolerationsSpec,
 | 
				
			||||||
		SecurityContext:               &securityContext,
 | 
							SecurityContext:               &securityContext,
 | 
				
			||||||
 | 
							Volumes:                       volumes,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if shmVolume != nil && *shmVolume {
 | 
						if shmVolume != nil && *shmVolume {
 | 
				
			||||||
| 
						 | 
					@ -724,6 +730,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
 | 
				
			||||||
		sidecarContainers   []v1.Container
 | 
							sidecarContainers   []v1.Container
 | 
				
			||||||
		podTemplate         *v1.PodTemplateSpec
 | 
							podTemplate         *v1.PodTemplateSpec
 | 
				
			||||||
		volumeClaimTemplate *v1.PersistentVolumeClaim
 | 
							volumeClaimTemplate *v1.PersistentVolumeClaim
 | 
				
			||||||
 | 
							volumes             []v1.Volume
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Improve me. Please.
 | 
						// Improve me. Please.
 | 
				
			||||||
| 
						 | 
					@ -840,21 +847,76 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// generate environment variables for the spilo container
 | 
						// generate environment variables for the spilo container
 | 
				
			||||||
	spiloEnvVars := deduplicateEnvVars(
 | 
						spiloEnvVars := c.generateSpiloPodEnvVars(
 | 
				
			||||||
		c.generateSpiloPodEnvVars(c.Postgresql.GetUID(), spiloConfiguration, &spec.Clone,
 | 
							c.Postgresql.GetUID(),
 | 
				
			||||||
			spec.StandbyCluster, customPodEnvVarsList), c.containerName(), c.logger)
 | 
							spiloConfiguration,
 | 
				
			||||||
 | 
							&spec.Clone,
 | 
				
			||||||
 | 
							spec.StandbyCluster,
 | 
				
			||||||
 | 
							customPodEnvVarsList,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// pickup the docker image for the spilo container
 | 
						// pickup the docker image for the spilo container
 | 
				
			||||||
	effectiveDockerImage := util.Coalesce(spec.DockerImage, c.OpConfig.DockerImage)
 | 
						effectiveDockerImage := util.Coalesce(spec.DockerImage, c.OpConfig.DockerImage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// determine the FSGroup for the spilo pod
 | 
				
			||||||
 | 
						effectiveFSGroup := c.OpConfig.Resources.SpiloFSGroup
 | 
				
			||||||
 | 
						if spec.SpiloFSGroup != nil {
 | 
				
			||||||
 | 
							effectiveFSGroup = spec.SpiloFSGroup
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	volumeMounts := generateVolumeMounts(spec.Volume)
 | 
						volumeMounts := generateVolumeMounts(spec.Volume)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// configure TLS with a custom secret volume
 | 
				
			||||||
 | 
						if spec.TLS != nil && spec.TLS.SecretName != "" {
 | 
				
			||||||
 | 
							if effectiveFSGroup == nil {
 | 
				
			||||||
 | 
								c.logger.Warnf("Setting the default FSGroup to satisfy the TLS configuration")
 | 
				
			||||||
 | 
								fsGroup := int64(spiloPostgresGID)
 | 
				
			||||||
 | 
								effectiveFSGroup = &fsGroup
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// this is combined with the FSGroup above to give read access to the
 | 
				
			||||||
 | 
							// postgres user
 | 
				
			||||||
 | 
							defaultMode := int32(0640)
 | 
				
			||||||
 | 
							volumes = append(volumes, v1.Volume{
 | 
				
			||||||
 | 
								Name: "tls-secret",
 | 
				
			||||||
 | 
								VolumeSource: v1.VolumeSource{
 | 
				
			||||||
 | 
									Secret: &v1.SecretVolumeSource{
 | 
				
			||||||
 | 
										SecretName:  spec.TLS.SecretName,
 | 
				
			||||||
 | 
										DefaultMode: &defaultMode,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mountPath := "/tls"
 | 
				
			||||||
 | 
							volumeMounts = append(volumeMounts, v1.VolumeMount{
 | 
				
			||||||
 | 
								MountPath: mountPath,
 | 
				
			||||||
 | 
								Name:      "tls-secret",
 | 
				
			||||||
 | 
								ReadOnly:  true,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// use the same filenames as Secret resources by default
 | 
				
			||||||
 | 
							certFile := ensurePath(spec.TLS.CertificateFile, mountPath, "tls.crt")
 | 
				
			||||||
 | 
							privateKeyFile := ensurePath(spec.TLS.PrivateKeyFile, mountPath, "tls.key")
 | 
				
			||||||
 | 
							spiloEnvVars = append(
 | 
				
			||||||
 | 
								spiloEnvVars,
 | 
				
			||||||
 | 
								v1.EnvVar{Name: "SSL_CERTIFICATE_FILE", Value: certFile},
 | 
				
			||||||
 | 
								v1.EnvVar{Name: "SSL_PRIVATE_KEY_FILE", Value: privateKeyFile},
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if spec.TLS.CAFile != "" {
 | 
				
			||||||
 | 
								caFile := ensurePath(spec.TLS.CAFile, mountPath, "")
 | 
				
			||||||
 | 
								spiloEnvVars = append(
 | 
				
			||||||
 | 
									spiloEnvVars,
 | 
				
			||||||
 | 
									v1.EnvVar{Name: "SSL_CA_FILE", Value: caFile},
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// generate the spilo container
 | 
						// generate the spilo container
 | 
				
			||||||
	c.logger.Debugf("Generating Spilo container, environment variables: %v", spiloEnvVars)
 | 
						c.logger.Debugf("Generating Spilo container, environment variables: %v", spiloEnvVars)
 | 
				
			||||||
	spiloContainer := generateContainer(c.containerName(),
 | 
						spiloContainer := generateContainer(c.containerName(),
 | 
				
			||||||
		&effectiveDockerImage,
 | 
							&effectiveDockerImage,
 | 
				
			||||||
		resourceRequirements,
 | 
							resourceRequirements,
 | 
				
			||||||
		spiloEnvVars,
 | 
							deduplicateEnvVars(spiloEnvVars, c.containerName(), c.logger),
 | 
				
			||||||
		volumeMounts,
 | 
							volumeMounts,
 | 
				
			||||||
		c.OpConfig.Resources.SpiloPrivileged,
 | 
							c.OpConfig.Resources.SpiloPrivileged,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
| 
						 | 
					@ -893,16 +955,10 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
 | 
				
			||||||
	tolerationSpec := tolerations(&spec.Tolerations, c.OpConfig.PodToleration)
 | 
						tolerationSpec := tolerations(&spec.Tolerations, c.OpConfig.PodToleration)
 | 
				
			||||||
	effectivePodPriorityClassName := util.Coalesce(spec.PodPriorityClassName, c.OpConfig.PodPriorityClassName)
 | 
						effectivePodPriorityClassName := util.Coalesce(spec.PodPriorityClassName, c.OpConfig.PodPriorityClassName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// determine the FSGroup for the spilo pod
 | 
					 | 
				
			||||||
	effectiveFSGroup := c.OpConfig.Resources.SpiloFSGroup
 | 
					 | 
				
			||||||
	if spec.SpiloFSGroup != nil {
 | 
					 | 
				
			||||||
		effectiveFSGroup = spec.SpiloFSGroup
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	annotations := c.generatePodAnnotations(spec)
 | 
						annotations := c.generatePodAnnotations(spec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// generate pod template for the statefulset, based on the spilo container and sidecars
 | 
						// generate pod template for the statefulset, based on the spilo container and sidecars
 | 
				
			||||||
	if podTemplate, err = generatePodTemplate(
 | 
						podTemplate, err = generatePodTemplate(
 | 
				
			||||||
		c.Namespace,
 | 
							c.Namespace,
 | 
				
			||||||
		c.labelsSet(true),
 | 
							c.labelsSet(true),
 | 
				
			||||||
		annotations,
 | 
							annotations,
 | 
				
			||||||
| 
						 | 
					@ -920,10 +976,9 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
 | 
				
			||||||
		c.OpConfig.EnablePodAntiAffinity,
 | 
							c.OpConfig.EnablePodAntiAffinity,
 | 
				
			||||||
		c.OpConfig.PodAntiAffinityTopologyKey,
 | 
							c.OpConfig.PodAntiAffinityTopologyKey,
 | 
				
			||||||
		c.OpConfig.AdditionalSecretMount,
 | 
							c.OpConfig.AdditionalSecretMount,
 | 
				
			||||||
		c.OpConfig.AdditionalSecretMountPath); err != nil {
 | 
							c.OpConfig.AdditionalSecretMountPath,
 | 
				
			||||||
		return nil, fmt.Errorf("could not generate pod template: %v", err)
 | 
							volumes,
 | 
				
			||||||
	}
 | 
						)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("could not generate pod template: %v", err)
 | 
							return nil, fmt.Errorf("could not generate pod template: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -1539,7 +1594,8 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1beta1.CronJob, error) {
 | 
				
			||||||
		false,
 | 
							false,
 | 
				
			||||||
		"",
 | 
							"",
 | 
				
			||||||
		c.OpConfig.AdditionalSecretMount,
 | 
							c.OpConfig.AdditionalSecretMount,
 | 
				
			||||||
		c.OpConfig.AdditionalSecretMountPath); err != nil {
 | 
							c.OpConfig.AdditionalSecretMountPath,
 | 
				
			||||||
 | 
							nil); err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("could not generate pod template for logical backup pod: %v", err)
 | 
							return nil, fmt.Errorf("could not generate pod template for logical backup pod: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1671,3 +1727,13 @@ func (c *Cluster) generateLogicalBackupPodEnvVars() []v1.EnvVar {
 | 
				
			||||||
func (c *Cluster) getLogicalBackupJobName() (jobName string) {
 | 
					func (c *Cluster) getLogicalBackupJobName() (jobName string) {
 | 
				
			||||||
	return "logical-backup-" + c.clusterName().Name
 | 
						return "logical-backup-" + c.clusterName().Name
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ensurePath(file string, defaultDir string, defaultFile string) string {
 | 
				
			||||||
 | 
						if file == "" {
 | 
				
			||||||
 | 
							return path.Join(defaultDir, defaultFile)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !path.IsAbs(file) {
 | 
				
			||||||
 | 
							return path.Join(defaultDir, file)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return file
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,16 +3,17 @@ package cluster
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	v1 "k8s.io/api/core/v1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
 | 
						acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/util"
 | 
						"github.com/zalando/postgres-operator/pkg/util"
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/util/config"
 | 
						"github.com/zalando/postgres-operator/pkg/util/config"
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/util/constants"
 | 
						"github.com/zalando/postgres-operator/pkg/util/constants"
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/util/k8sutil"
 | 
						"github.com/zalando/postgres-operator/pkg/util/k8sutil"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
	policyv1beta1 "k8s.io/api/policy/v1beta1"
 | 
						policyv1beta1 "k8s.io/api/policy/v1beta1"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/intstr"
 | 
						"k8s.io/apimachinery/pkg/util/intstr"
 | 
				
			||||||
| 
						 | 
					@ -451,3 +452,65 @@ func TestSecretVolume(t *testing.T) {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestTLS(t *testing.T) {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						var spec acidv1.PostgresSpec
 | 
				
			||||||
 | 
						var cluster *Cluster
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						makeSpec := func(tls acidv1.TLSDescription) acidv1.PostgresSpec {
 | 
				
			||||||
 | 
							return acidv1.PostgresSpec{
 | 
				
			||||||
 | 
								TeamID: "myapp", NumberOfInstances: 1,
 | 
				
			||||||
 | 
								Resources: acidv1.Resources{
 | 
				
			||||||
 | 
									ResourceRequests: acidv1.ResourceDescription{CPU: "1", Memory: "10"},
 | 
				
			||||||
 | 
									ResourceLimits:   acidv1.ResourceDescription{CPU: "1", Memory: "10"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Volume: acidv1.Volume{
 | 
				
			||||||
 | 
									Size: "1G",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								TLS: &tls,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cluster = New(
 | 
				
			||||||
 | 
							Config{
 | 
				
			||||||
 | 
								OpConfig: config.Config{
 | 
				
			||||||
 | 
									PodManagementPolicy: "ordered_ready",
 | 
				
			||||||
 | 
									ProtectedRoles:      []string{"admin"},
 | 
				
			||||||
 | 
									Auth: config.Auth{
 | 
				
			||||||
 | 
										SuperUsername:       superUserName,
 | 
				
			||||||
 | 
										ReplicationUsername: replicationUserName,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}, k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger)
 | 
				
			||||||
 | 
						spec = makeSpec(acidv1.TLSDescription{SecretName: "my-secret", CAFile: "ca.crt"})
 | 
				
			||||||
 | 
						s, err := cluster.generateStatefulSet(&spec)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fsGroup := int64(103)
 | 
				
			||||||
 | 
						assert.Equal(t, &fsGroup, s.Spec.Template.Spec.SecurityContext.FSGroup, "has a default FSGroup assigned")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defaultMode := int32(0640)
 | 
				
			||||||
 | 
						volume := v1.Volume{
 | 
				
			||||||
 | 
							Name: "tls-secret",
 | 
				
			||||||
 | 
							VolumeSource: v1.VolumeSource{
 | 
				
			||||||
 | 
								Secret: &v1.SecretVolumeSource{
 | 
				
			||||||
 | 
									SecretName:  "my-secret",
 | 
				
			||||||
 | 
									DefaultMode: &defaultMode,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						assert.Contains(t, s.Spec.Template.Spec.Volumes, volume, "the pod gets a secret volume")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Contains(t, s.Spec.Template.Spec.Containers[0].VolumeMounts, v1.VolumeMount{
 | 
				
			||||||
 | 
							MountPath: "/tls",
 | 
				
			||||||
 | 
							Name:      "tls-secret",
 | 
				
			||||||
 | 
							ReadOnly:  true,
 | 
				
			||||||
 | 
						}, "the volume gets mounted in /tls")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Contains(t, s.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "SSL_CERTIFICATE_FILE", Value: "/tls/tls.crt"})
 | 
				
			||||||
 | 
						assert.Contains(t, s.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "SSL_PRIVATE_KEY_FILE", Value: "/tls/tls.key"})
 | 
				
			||||||
 | 
						assert.Contains(t, s.Spec.Template.Spec.Containers[0].Env, v1.EnvVar{Name: "SSL_CA_FILE", Value: "/tls/ca.crt"})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue