Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add encryption providers proposal #1213

Merged
merged 4 commits into from
Feb 11, 2021
Merged
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
115 changes: 115 additions & 0 deletions docs/proposals/20210112-encryption-roviders.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Encryption Providers for encrypted secrets at rest

**Auther**: Mohamed Elsayed (@moelsayed)
**Status**: Draft


## Abstract

By default, all Kubernetes secret objects are stored on disk in plain text inside etcd. The [Encryption Providers](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/
) feature was added to Kubernets starting with version 1.13.

At rest data encryption is a requirement for security compliance and adds an additional layer of security for secret data, especially when etcd nodes are separated from the control plan and in off-node backups.

KubeOne needs to support this feature natively. Meaning the user should be able to enable, disable the feature and rotate keys when needed without having to apply any actions manually.

## Goals

* Provide a safe path to enable/disable Encryption Providers.
* Support atomic(?) rotation for existing keys.
* Rewriting all secret resources (no just secrets) after enable/disable/rotate operations.

## Non-Goals

* Deploy External KMS.
* Safely manage (disable/enable/rotate) configuration when a custom configuration file is used.

## Challenges

The feature has a lot of moving parts; as it requires performing a specific sequence of actions, including changing the KubeAPI configuration, restarting KubeAPI and rewriting all secret resources to apply the encryption. This requires the implementation to be as idempotent as possible with ability to rollback on failure, with out breaking the cluster.

## Implementation

Unfortunately, it's not possible to simply update the KubeAPI configuration and expect the configuration to reconcile. KubeOne will have to _read_ the _current_ configuration on the cluster, _mutate_ it based on the _required_ state and then apply it. Additionally, KubeOne will have to be able to revert changes on any errors and recover safely if the process is interrupted at any point.

The configuration for this will be added under `features` in the KubeOneCluster spec:

```yaml
apiVersion: kubeone.io/v1beta1
kind: KubeOneCluster
features:
encryptionProviders:
enabled: true
Copy link
Member

Choose a reason for hiding this comment

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

Keeping in mind future API extensions that will be inevitably required, simple boolean flag is not enough. Flags are needed per provider type, and not only flags but possible config options too.

So we are looking at 3 possible providers:

  • none
  • static (the one for being implemented now)
  • kms (the one for being implemented later)

Those should be expressed as additional objects inside encryptionProviders, like

#### VERY DRAFT, MUCH FANTASY ####
####### only for demo purpose #######
apiVersion: kubeone.io/v1beta1
kind: KubeOneCluster
features:
  encryptionProviders:
    - kind: none
    - kind: static
    - kind: kms
      endpoint: unix:///tmp/socketfile.sock
      cachesize: 100
      timeout: 3s

Alternatively, we could not reimplement parts of what's already done in upstream and "just" provide the

apiVersion: kubeone.io/v1beta1
kind: KubeOneCluster
features:
  encryptionConfiguration: |
    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
    - resources:
      - secrets
      providers:
      - kms:
          name: myKmsPlugin
          endpoint: unix:///tmp/socketfile.sock
          cachesize: 100
          timeout: 3s
      - identity: {}

Given that second option, we can actually extract the endpoint from KMS providers and automatically mount them to the kube-apiserver pod ( with extra mounts in kubeadm config).

Provided that we can detect changes in encryptionConfiguration (compare actual file to encryptionConfiguration`, we can automate and orchestrate kubeapi pod restarts.

Copy link
Member

Choose a reason for hiding this comment

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

Providing ONLY encryptionConfiguration we could give all encryption options to the end user, including KMS support in one shot.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with what @kron4eg said, but in that case, I'd leave the enable field in the API and implement it like @moelsayed initially proposed. For example:

features:
  encryptionProviders:
    # enable/disable 
    enable: true
    # setting this value will disable automated rotation.
    customProviderFile: |
      apiVersion: apiserver.config.k8s.io/v1
      kind: EncryptionConfiguration
      ...

The difference between the originally proposed API and this API is that I've removed the rotate field. Instead of the rotate field, we would keep the CLI flag as it's currently proposed.

  • If enable is set to false
    • Do nothing if the feature has never been enabled
    • Otherwise, disable the feature
  • If enable is set to true:
    • If customProviderFile is not set, generate it ourselves and handle key management
    • If customProviderFile is set, upload it to the instance. Additionally, if KMS is configured, create the appropriate volumes on the API server pods
    • Configure the API server and everything else as described in the proposal

Note: if customProviderFile is set and the CLI flag is set, KubeOne would either show a warning or fail.

In this case, we can quickly implement support for all setups. Technically, users could use Terraform/Ansible to deploy KMS and with such API, they could actually use it in their clusters. I think that it wouldn't require too much additional effort on our side, but we could solve everything (besides deploying KMS, which should be discussed) in a single run.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kron4eg @xmudrii I updated the proposal accordingly. Please check.

For the initial implementation, I will stop at implementing the the custom config basic functionality. I will create a new issue/PR to address deploying the KMS and creating the required volume mounts for the API server. I am considering that out of scope for the initial implementation.

Copy link
Member

Choose a reason for hiding this comment

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

@moelsayed Can you create an Epic for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xmudrii there is already an epic to track the proposal and the implementation: #769

Copy link
Member

Choose a reason for hiding this comment

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

@moelsayed I was thinking about an Epic for addressing the KMS deployment and the volume creation. You can also extend the current Epic. I only see issues relevant to the initial implementation in the Epic, but please correct me if I'm wrong.

Copy link
Member

Choose a reason for hiding this comment

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

What happens if:

  • enable: true, but no customProviderFile is given (e.g. it's empty)?
  • enable: true, but no customProviderFile is given and install/apply is run, and then customProviderFile becomes defined?

Copy link
Contributor Author

@moelsayed moelsayed Feb 8, 2021

Choose a reason for hiding this comment

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

enable: true, but no customProviderFile is given (e.g. it's empty)?

We manage everything automatically. A new config file is created and pushed to the nodes. In this case key rotation is supported.

enable: true, but no customProviderFile is given and install/apply is run, and then customProviderFile becomes defined?

K1 should push the defined custom file but it will refuse to the rotate the keys if the rotate flag is set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xmudrii I updated the epic with an issue to add KMS #1242.

customProvidersFile: |
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- identity: {}
- aescbc:
keys:
- name: key1
secret: <BASE 64 ENCODED SECRET>
```

To allow users to rotate the keys, a new flag will be added to the `apply` command:

```bash
--rotate-encryption-key automatically rotate encryption provider key
```

### pre-flight checks

* Cluster is healthy.
* Current Encryption Providers state/configuration is valid and identical on all control plane nodes.

### Enable Encryption Providers for new cluster

* Generate a valid configuration file with the `identity` provider set last.
* Sync the configuration file to all Control Plane nodes.
* Set the required KubeAPI configuration and deploy KubeAPI.

### Enable Encryption Providers for existing cluster

* Ensure there is no Encryption Provider Config (manually added by the user, broken previous enable process, etc..) present.
* Generate a valid configuration file with the `identity` provider set last.
* Sync the configuration file to all Control Plane nodes.
* Update and restart KubeAPI on all nodes.
* Rewrite secrets to ensure they are encrypted successfully.

### Disable Encryption Providers for existing cluster

* Read the current active Encryption Provider configuration from control plane nodes.
* Mutate the configuration to add `identity` provider first and the active provider last.
* Sync the configuration file to all Control Plane nodes.
* Restart KubeAPI on all control plane nodes.
* Rewrite secrets to ensure they are decrypted successfully.
* Update KubeAPI configuration to remove the Encryption Provider configuration and restart KubeAPI on all control plane nodes.
* Remove the old configuration file from all control plane nodes.

### Rotate Encryption Provider Key for existing cluster

* Read the current active Encryption Provider configuration from control plane nodes.
* Generate a new encryption key.
* Mutate the configuration file to include the new key first, current key second and `identity` last.
* Sync the updated configuration file to all control plane nodes and restart KubeAPI.
* Rewrite all secrets to ensure they are encrypted with the new key.
* Mutate the configuration file again to remove the old key.
* Sync the updated configuration file to all control plane nodes and restart KubeAPI.

### Apply Custom Encryption Provider file
This use case is useful for users who would like to utilize an external KMS provider or specify resources other than secrets for encryption. In this case, KubeOne will not manage the content of the file, it will only validate it to make sure it's syntactically valid. Additionally, KubeOne will not rewrite the resources in this case.

* Ensure the configuration file is valid.
* Sync the configuration file to all control plane nodes.
* Restart KubeAPI on all nodes.
kron4eg marked this conversation as resolved.
Show resolved Hide resolved

## Tasks & effort

* Implement the needed pre-flight checks.
* Implement validation for Encryption Provider configuration files.
* Implement the workflow for each use case.
* Add e2e tests for each workflow.
* Add documentation for the feature.