Skip to content
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
13 changes: 13 additions & 0 deletions charts/postgres-operator/crds/postgresqls.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,19 @@ spec:
type: string
recoveryEventType:
type: string
tde:
nullable: true
properties:
enable:
type: boolean
keybits:
type: integer
format: int32
default: 128
enum:
- 128
- 192
- 256
teamId:
type: string
tls:
Expand Down
9 changes: 5 additions & 4 deletions docs/hugo/content/en/crd/crd-postgresql.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ weight: 331
| spiloRunAsUser | int | false | Sets the user ID which should be used in the container to run the process. This must be set to run the container without root. |
| [standby](#standby) | map | false | Enables the creation of a standby cluster at the time of the creation of a new cluster |
| [streams](#streams) | array | false | Enables change data capture streams for defined database tables |
| [tde](#tde) | map | false | Enables the activation of TDE if a new cluster is created |
| [tde](#tde) | map | false | Enables TDE and allows you to define options such as key bits. |
| teamId | string | true | name of the team the cluster belongs to. Will be removed soon |
| [tls](#tls) | map | false | Custom TLS certificate |
| [tolerations](#tolerations) | array | false | a list of tolerations that apply to the cluster pods. Each element of that list is a dictionary with the following fields:
Expand Down Expand Up @@ -256,9 +256,10 @@ key, operator, value, effect and tolerationSeconds |

#### tde

| Name | Type | required | Description |
| ------------------------------ |:-------:| ---------:| ------------------:|
| enable | boolean | true | enable TDE during initDB |
| Name | Type | default | Description |
| ------------------------------ |:-------:| ---------:| -------------------------:|
| enable | boolean | false | enable TDE during initDB |
| keybits | integer | 256 | used to specify the key length in bits. Accepted values are 128, 192 and 256 (default). More Informations: [PGEE-Documentation](https://repository.cybertec.at/doc/18ee/encryption.html) |

{{< back >}}

Expand Down
51 changes: 48 additions & 3 deletions docs/hugo/content/en/tde/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Further information on TDE and PGEE can be found here: [CYBERTEC TDE](https://ww

## Securing clusters with TDE

The CYBERTEC pg operator, together with Patroni, takes over the setup and administration of the TDE functionality in conjunction with the cost-effective PGEE containers
The CYBERTEC-pg-operator, together with Patroni, takes over the setup and administration of the TDE functionality in conjunction with the cost-effective PGEE containers

### Preconditions
- CYBERTEC-pgee-container
Expand All @@ -36,7 +36,7 @@ The CYBERTEC pg operator, together with Patroni, takes over the setup and admini
### Deploy a TDE-Cluster

Setting up a TDE cluster is basically the same as setting up a conventional cluster.
The only difference is the defined Postgres. container and the object TDE.enabled: true, which instructs the operator to initialise the database with the TDE functionality.
The only difference is the defined Postgres-container and the object TDE.enabled: true, which instructs the operator to initialise the database with the TDE functionality.

```yaml
apiVersion: cpo.opensource.cybertec.at/v1
Expand All @@ -58,20 +58,65 @@ spec:
memory: 500Mi
tde:
enable: true
keybits: 256
teamId: acid
volume:
size: 5Gi
```
- `dockerImage` - Must contain a PostgreSQL image of the pgee container suite
- `tde.enabled`- initialises the DB with TDE
- `tde.keybits`- Defines keylength in bits. Possible Values: 128,192,256 Default: 256

{{< hint type=important >}} Please note that the activation of TDE is only possible when creating new clusters. Subsequent activation is not possible. {{< /hint >}}

### Key-Management

For TDE, we or the operator must work with the required encryption key. The key is transferred to the Postgres containers using a secret. There are two basic options for the necessary key management.

#### Automatic key generation (default)
If no existing secret is provided, the operator automatically generates a cryptographically secure key when creating a new cluster. This is stored in the secret in the cluster's namespace.

It is important to note for key management that TDE allows you to choose from the available key lengths (128 bit, 192 bit, 256 bit). By default, the operator chooses 256 bit. The keybits parameter allows you to adjust this if desired. See [CRD](crd/crd-postgresql).
Further information on TDE can also be found in the [PGEE-Documentation](https://repository.cybertec.at/doc/18ee/encryption.html)

#### Use of your own keys (Bring Your Own Key)
You have the option of defining your own encryption key before the cluster is created.
To do this, create a secret with the desired key in advance.
When starting, the operator checks whether a corresponding secret already exists and uses this instead of a newly generated key.
Use case: This enables the integration of external secret store solutions (e.g. HashiCorp Vault) to stream or synchronise keys directly into the Kubernetes secret.

{{< hint type=important >}} If you provide your own key, you must ensure that the length of the key (in bytes) matches the configured keybits exactly.
| keybits | Keylength (Byte) | Notes |
| -------- |:----------------:| --------------:|
| 128 | 16 Byte | |
| 192 | 24 Byte | |
| 256 | 32 Byte | (Default) |

A discrepancy between the configured bit length and the actual byte length of the provided key will result in errors when starting the database.
{{< /hint >}}

The secret name follows the following fixed naming convention: [CLUSTERNAME]-tde

```yaml
kind: Secret
apiVersion: v1
metadata:
name: [CLUSTERNAME]-tde
stringData:
key: [TDE-KEY]
type: Opaque
```

### Check TDE-Status

```sh

sh-5.1$ /usr/pgsql-17/bin/pg_controldata | grep -i "Encryption"
Data encryption: on
Encryption key length: 128

[postgres@tde-cluster-1-0 ~]$ psql
psql (17.4 EE 1.4.1)
psql (18.1 EE 1.5.0, server 17.7 EE 1.4.4)
____ ____ _____ _____
| _ \ / ___| ____| ____|
| |_) | | _| _| | _|
Expand Down
14 changes: 14 additions & 0 deletions manifests/postgresql.crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,20 @@ spec:
type: string
recoveryEventType:
type: string
tde:
nullable: true
properties:
enable:
type: boolean
keybits:
type: integer
format: int32
default: 128
enum:
- 128
- 192
- 256
type: object
teamId:
type: string
tls:
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/cpo.opensource.cybertec.at/v1/crds.go
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,18 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
"enable": {
Type: "boolean",
},
"keybits": {
Type: "integer",
Format: "int32",
Enum: []apiextv1.JSON{
{Raw: []byte("128")},
{Raw: []byte("192")},
{Raw: []byte("256")},
},
Default: &apiextv1.JSON{
Raw: []byte("128"),
},
},
},
},
"monitor": {
Expand Down
3 changes: 2 additions & 1 deletion pkg/apis/cpo.opensource.cybertec.at/v1/postgresql_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,8 @@ type Configuration struct {
}

type TDE struct {
Enable bool `json:"enable"`
Enable bool `json:"enable"`
Keybits *int32 `json:"keybits"`
}

// Monitoring Sidecar defines a container to be run in the same pod as the Postgres container.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 26 additions & 12 deletions pkg/cluster/k8sres.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ type spiloConfiguration struct {
Bootstrap pgBootstrap `json:"bootstrap"`
}

type TDEConfig struct {
Enabled bool
KeyBits string
}

func (c *Cluster) statefulSetName() string {
return c.Name
}
Expand Down Expand Up @@ -327,7 +332,7 @@ func (c *Cluster) generateResourceRequirements(
return &result, nil
}

func generateSpiloJSONConfiguration(pg *cpov1.PostgresqlParam, patroni *cpov1.Patroni, opConfig *config.Config, enableTDE bool, logger *logrus.Entry) (string, error) {
func generateSpiloJSONConfiguration(pg *cpov1.PostgresqlParam, patroni *cpov1.Patroni, opConfig *config.Config, tdeOptions TDEConfig, logger *logrus.Entry) (string, error) {
config := spiloConfiguration{}

config.Bootstrap = pgBootstrap{}
Expand All @@ -350,8 +355,9 @@ func generateSpiloJSONConfiguration(pg *cpov1.PostgresqlParam, patroni *cpov1.Pa
map[string]string{"auth-local": "trust"}}
}

if enableTDE {
if tdeOptions.Enabled {
config.Bootstrap.Initdb = append(config.Bootstrap.Initdb, map[string]string{"encryption-key-command": "/tmp/tde.sh"})
config.Bootstrap.Initdb = append(config.Bootstrap.Initdb, map[string]string{"key-bits": tdeOptions.KeyBits})
}

initdbOptionNames := []string{}
Expand Down Expand Up @@ -878,17 +884,16 @@ func (c *Cluster) generatePodTemplate(
podSpec.PriorityClassName = priorityClassName
}

if c.Postgresql.Spec.Monitoring != nil {
addEmptyDirVolume(&podSpec, "exporter-tmp", "postgres-exporter", "/tmp")
}

if c.OpConfig.ReadOnlyRootFilesystem != nil && *c.OpConfig.ReadOnlyRootFilesystem && !isRepoHost {
if c.OpConfig.ReadOnlyRootFilesystem != nil && *c.OpConfig.ReadOnlyRootFilesystem && spiloContainer.Name == "postgres" {
addRunVolume(&podSpec, "postgres-run", "postgres", "/run")
addEmptyDirVolume(&podSpec, "postgres-tmp", "postgres", "/tmp")
if c.Postgresql.Spec.Monitoring != nil {
addEmptyDirVolume(&podSpec, "exporter-tmp", "postgres-exporter", "/tmp")
}
}

if c.OpConfig.ReadOnlyRootFilesystem != nil && *c.OpConfig.ReadOnlyRootFilesystem && isRepoHost {
addEmptyDirVolume(&podSpec, "pgbackrest-tmp", "pgbackrest", "/tmp")
if c.OpConfig.ReadOnlyRootFilesystem != nil && *c.OpConfig.ReadOnlyRootFilesystem && strings.Contains(spiloContainer.Name, "pgbackrest") {
addEmptyDirVolume(&podSpec, "pgbackrest-tmp", spiloContainer.Name, "/tmp")
}

if sharePgSocketWithSidecars != nil && *sharePgSocketWithSidecars {
Expand Down Expand Up @@ -1393,11 +1398,20 @@ func (c *Cluster) generateStatefulSet(spec *cpov1.PostgresSpec) (*appsv1.Statefu
}
}

enableTDE := false
// Keybits must be converted to strings for initdb processing.
tdeOptions := TDEConfig{Enabled: false}
if spec.TDE != nil && spec.TDE.Enable {
enableTDE = true
tdeOptions.Enabled = true
tdeOptions.KeyBits = strconv.Itoa(256)
ptr := c.Postgresql.Spec.TDE.Keybits
if ptr != nil {
val := *ptr
if val == 128 || val == 192 {
tdeOptions.KeyBits = fmt.Sprintf("%d", val)
}
}
}
spiloConfiguration, err := generateSpiloJSONConfiguration(&spec.PostgresqlParam, &spec.Patroni, &c.OpConfig, enableTDE, c.logger)
spiloConfiguration, err := generateSpiloJSONConfiguration(&spec.PostgresqlParam, &spec.Patroni, &c.OpConfig, tdeOptions, c.logger)
if err != nil {
return nil, fmt.Errorf("could not generate Spilo JSON configuration: %v", err)
}
Expand Down
Loading
Loading