Giter VIP home page Giter VIP logo

k8s-cloudkms-plugin's Introduction

Kubernetes KMS Plugin for Cloud KMS

This repo contains an implementation of a Kubernetes KMS Plugin for Cloud KMS.

If you are running on Kubernetes Engine (GKE), you do not need this plugin. You can enable Application-layer Secrets Encryption and GKE will manage the communication between GKE and KMS automatically.

Use with Compute Engine

If you are running Kubernetes on VMs, the configuration is logically divided into the following stages. These steps are specific to Compute Engine (GCE), but could be expanded to any VM-based machine.

  1. Create Cloud KMS Keys
  2. Grant service account IAM permissions on Cloud KMS keys
  3. Deploy the KMS plugin binary to the Kubernetes master nodes
  4. Create Kubernetes encryption configuration

Assumptions

This guide makes a few assumptions:

  • You have gcloud and the Cloud SDK installed locally. Alternatively you can run in Cloud Shell where these tools are already installed.

  • You have billing enabled on your project. This project uses Cloud KMS, and you will not be able to use Cloud KMS without billing enabled on your project. Even if you are on a free trial, you will need to enable billing (your trial credits will still be used first).

  • The Cloud KMS keys exist in the same project where the GCE VMs are running. This is not a hard requirement (in fact, you may want to separate them depending on your security posture and threat model adhering to the principle of separation of duties), but this guide assumes they are in the same project for simplicity.

  • The VMs that run the Kubernetes masters run with dedicated Service Account to follow the principle of least privilege. The dedicated service account will be given permission to encrypt/decrypt data using a Cloud KMS key.

  • The KMS plugin will share its security context with the underlying VM. Even though principle of least privilege advocates for a dedicated service account for the KMS plugin, doing so breaks the threat model since it forces you to store a service account key on disk. An attacker with access to an offline VM image would therefore decrypt the contents of etcd offline by leveraging the service account stored on the image. GCE VM service accounts are not stored on disk and therefore do not share this same threat vector.

Set environment variables

Set the following environment variables to your values:

# The ID of the project. Please note that this is the ID, not the NAME of the
# project. In some cases they are the same, but they can be different. Run
# `gcloud projects list` and choose the value in "project id" column.
PROJECT_ID="<project id>"

# The FQDN email of the service account that will be attached to the VMs which
# run the Kubernetes master nodes.
SERVICE_ACCOUNT_EMAIL="<service-account email>"

# These values correspond to the KMS key. KMS crypto keys belong to a key ring
# which belong to a location. For a full list of locations, run
# `gcloud kms locations list`.
KMS_LOCATION="<location>"
KMS_KEY_RING="<key-ring name>"
KMS_CRYPTO_KEY="<crypto-key name>"

Here is an example (replace with your values):

PROJECT_ID="my-gce-project23"
SERVICE_ACCOUNT_EMAIL="kms-plugin@[email protected]"
KMS_LOCATION="us-east4"
KMS_KEY_RING="my-keyring"
KMS_CRYPTO_KEY="my-key"

Create Cloud KMS key

Create the key in Cloud KMS.

# Enable the Cloud KMS API (this only needs to be done once per project
# where KMS is being used).
$ gcloud services enable --project "${PROJECT_ID}" \
    cloudkms.googleapis.com

# Create the Cloud KMS key ring in the specified location.
$ gcloud kms keyrings create "${KMS_KEY_RING}" \
    --project "${PROJECT_ID}" \
    --location "${KMS_LOCATION}"

# Create the Cloud KMS crypto key inside the key ring.
$ gcloud kms keys create "${KMS_KEY_NAME}" \
    --project "${PROJECT_ID}" \
    --location "${KMS_LOCATION}" \
    --keyring "${KMS_KEY_RING}"

Grant Service Account key permissions

Grant the dedicated service account permission to encrypt/decrypt data using the Cloud KMS crypto key we just created:

$ gcloud kms keys add-iam-policy-binding "${KMS_KEY_NAME}" \
    --project "${PROJECT_ID}" \
    --location "${KMS_LOCATION}" \
    --keyring "${KMS_KEY_RING}" \
    --member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
    --role "roles/cloudkms.cryptoKeyEncrypterDecrypter"

In addition to the IAM permissions, you also need to increase the oauth scopes on the VM. The following command assumes your VM master nodes require the gke-default scopes. If that is not the case, alter the command accordingly. Replace "my-master-instance" with the name of your VM:

$ gcloud compute instances set-service-account "my-master-instance" \
   --service-account "${SERVICE_ACCOUNT_EMAIL}" \
   --scopes "gke-default, https://www.googleapis.com/auth/cloudkms"

Restart any instances to pickup the scope changes:

$ gcloud compute instances stop "my-master-instance"
$ gcloud compute instances start "my-master-instance"

Deploy the KMS plugin

There are a few options for deploying the KMS plugin into a Kubernetes cluster:

  1. As a Docker image

    A pre-built image is available at:

    gcr.io/cloud-kms-lab/k8s-cloudkms-plugin:$TAG
    

    where TAG refers to a published git tag or "latest" for builds off the master branch. You can also build and publish your own image by cloning this repository and running:

    $ gcloud builds submit \
        --tag gcr.io/$PROJECT_ID/k8s-cloudkms-plugin \
        .
  2. As a Go binary

  3. As a static pod

Pull image onto master VMs

On the Kubernetes master VM, run the following command to pull the Docker image. Replace the URL with your repository's URL:

# Pick a tag and pin to that tag
$ docker pull "gcr.io/cloud-kms-lab/k8s-cloudkms-plugin:1.2.3"

Test the interaction between KMS Plugin and Cloud KMS

On the Kubernetes master VM, instruct the KMS plugin to perform a self-test. First, set some environment variables:

KMS_FULL_KEY="projects/${PROJECT_ID}/locations/${KMS_LOCATION}/keyRings/${KMS_KEY_RING}/cryptoKeys/${KMS_CRYPTO_KEY}"
SOCKET_DIR="/var/kms-plugin"
SOCKET_PATH="${SOCKET_DIR}/socket.sock"
PLUGIN_HEALTHZ_PORT="8081"
PLUGIN_IMAGE="gcr.io/cloud-kms-lab/k8s-cloudkms-plugin:1.2.3" # Pick a tag

Start the container:

$ docker run \
    --name="kms-plugin" \
    --network="host" \
    --detach \
    --rm \
    --volume "${SOCKET_DIR}:/var/run/kmsplugin:rw" \
    "${PLUGIN_IMAGE}" \
      /bin/k8s-cloudkms-plugin \
        --logtostderr \
        --integration-test="true" \
        --path-to-unix-socket="${SOCKET_PATH}" \
        --key-uri="${KMS_FULL_KEY}"

Probe the plugin on it's healthz port. You should expect the command to return "OK":

$ curl "http://localhost:${PLUGIN_HEALTHZ_PORT}/healthz?ping-kms=true"

Stop the container:

$ docker kill --signal="SIGHUP" kms-plugin

Finally, depending on your deployment strategy, configure the KMS plugin container to automatically boot at-startup. This can be done with systemd or an orchestration/configuration management tool.

Configure kube-apiserver

Update the kube-apiserver's encryption configuration to point to the shared socket from the KMS plugin. If you changed the SOCKET_DIR or SOCKET_PATH variables above, update the endpoint in the configuration below accordingly:

kind: EncryptionConfiguration
apiVersion: apiserver.config.k8s.io/v1
resources:
  - resources:
    - secrets
    providers:
    - kms:
        apiVersion: v2
        name: myKmsPlugin
        endpoint: unix:///var/kms-plugin/socket.sock
   - identity: {}

More information about this file and configuration options can be found in the Kubernetes KMS plugin documentation.

Learn more

k8s-cloudkms-plugin's People

Contributors

dependabot[bot] avatar hoskeri avatar immutablet avatar mayakacz avatar sethvargo avatar sshcherbakov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

k8s-cloudkms-plugin's Issues

Different keys for different namespaces

According to the EncryptionConfiguration specification, it is only possible to configure a single KMS provider for a given K8s resource type.

Since Google Cloud KMS plugin is only configured with a single Cloud KMS key resource name, that means that that single key will be used to encrypt resources of all workloads running in the cluster.

If the key has to be replaced, that would affect encrypted data of all applications running in the cluster.

Even though the procedure for key replacement for the entire cluster and for the single application be similar, would it make sense and possible to extend the plugin to support different keys for different namespaces or workload identities?

simplify metrics registration

Instead of RegisterMetrics with a sync.Once, just add

func init() {
  prometheus.MustRegister(CloudKMSOperationalLatencies)
  prometheus.MustRegister(CloudKMSOperationalFailuresTotal)
}

(you can have multiple init funcs btw).

required package (gopkg.in/gcfg.v1) not vendored

The package is imported in
https://github.com/GoogleCloudPlatform/k8s-cloudkms-plugin/blob/master/plugin/http_client.go#L33

error during build:

[opc@instance-iad k8s-cloudkms-plugin]$ make
rm -f k8s-cloud-kms-plugin
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go test ./...
plugin/http_client.go:33:2: cannot find package "gopkg.in/gcfg.v1" in any of:
/home/opc/go/src/github.com/GoogleCloudPlatform/k8s-cloudkms-plugin/vendor/gopkg.in/gcfg.v1 (vendor tree)
/usr/local/go/src/gopkg.in/gcfg.v1 (from $GOROOT)
/home/opc/go/src/gopkg.in/gcfg.v1 (from $GOPATH)
make: *** [build] Error 1

Incompatible flag running push

Hello!

I cannot push to my project the image, i am getting this error when I run bazel run cmd/plugin:push

INFO: Writing tracer profile to '/private/var/tmp/_bazel_murbano/73b6f5ffed7a5444a1c9c28cf5208578/command.profile.gz'
ERROR: /private/var/tmp/_bazel_murbano/73b6f5ffed7a5444a1c9c28cf5208578/external/io_bazel_rules_go/BUILD.bazel:66:1: in go_context_data rule @io_bazel_rules_go//:go_con
text_data: 
Traceback (most recent call last):
        File "/private/var/tmp/_bazel_murbano/73b6f5ffed7a5444a1c9c28cf5208578/external/io_bazel_rules_go/BUILD.bazel", line 66
                go_context_data(name = 'go_context_data')
        File "/private/var/tmp/_bazel_murbano/73b6f5ffed7a5444a1c9c28cf5208578/external/io_bazel_rules_go/go/private/context.bzl", line 400, in _go_context_data_impl
                cc_common.configure_features(<3 more arguments>)
Incompatible flag --incompatible_require_ctx_in_configure_features has been flipped, and the mandatory parameter 'ctx' of cc_common.configure_features is missing. Please add 'ctx' as a named parameter. See https://github.com/bazelbuild/bazel/issues/7793 for details.
DEBUG: Rule 'distroless_static' indicated that a canonical reproducible form can be obtained by modifying arguments digest = "sha256:c6d5981545ce1406d33e61434c61e9452da
d93ecd8397c41e89036ef977a88f4"
DEBUG: Call stack for the definition of repository 'distroless_static' which is a container_pull (rule definition at /private/var/tmp/_bazel_murbano/73b6f5ffed7a5444a1c
9c28cf5208578/external/io_bazel_rules_docker/container/pull.bzl:173:18):
 - /Users/murbano/go/src/github.com/GoogleCloudPlatform/k8s-cloudkms-plugin/WORKSPACE:107:1
ERROR: Analysis of target '//cmd/plugin:push' failed; build aborted: Analysis of target '@io_bazel_rules_go//:go_context_data' failed; build aborted
INFO: Elapsed time: 0.560s
INFO: 0 processes.
FAILED: Build did NOT complete successfully (1 packages loaded, 31 targets configured)
FAILED: Build did NOT complete successfully (1 packages loaded, 31 targets configured)

I am using last version of bazel 1.1.0.

Fix golint warnings

$ golint 
config.go:5:2: exported const HealthzPort should have comment (or a comment on this block) or be unexported
http_client.go:36:6: exported type TokenConfig should have comment or be unexported
metrics.go:35:2: exported var CloudKMSOperationalLatencies should have comment or be unexported
metrics.go:58:1: exported function RegisterMetrics should have comment or be unexported
metrics.go:64:1: exported function RecordCloudKMSOperation should have comment or be unexported
metrics.go:72:6: exported type Metrics should have comment or be unexported
metrics.go:79:1: exported function NewMetrics should have comment or be unexported
metrics.go:88:1: exported method Metrics.MustServeMetrics should have comment or be unexported
orchestrator.go:21:6: exported type Orchestrator should have comment or be unexported
orchestrator.go:26:1: exported function NewOrchestrator should have comment or be unexported
orchestrator.go:35:1: exported method Orchestrator.Run should have comment or be unexported
plugin.go:46:6: exported type Plugin should have comment or be unexported
plugin.go:54:1: exported function New should have comment or be unexported
plugin.go:72:1: exported method Plugin.Stop should have comment or be unexported
plugin.go:82:1: exported method Plugin.Version should have comment or be unexported
plugin.go:104:1: exported method Plugin.Encrypt should have comment or be unexported
plugin.go:124:1: exported method Plugin.Decrypt should have comment or be unexported
plugin_test.go:207:4: should replace actualCount += 1 with actualCount++
plugin_test.go:250:6: func mustGetHttpBody should be mustGetHTTPBody
test_config.go:5:2: exported const TestKeyURI should have comment (or a comment on this block) or be unexported
validation.go:27:6: exported type Validator should have comment or be unexported
validation.go:31:1: exported function NewValidator should have comment or be unexported

"/k8s-cloud-kms-plugin not found"

Following the example on the main documentation, references the pre-built image gcr.io/cloud-kms-lab/cloud-kms-plugin:dev

When running the image using the example code:

docker run \
    --name="kms-plugin" \
    --network="host" \
    --detach \
    --rm \
    --volume "${SOCKET_DIR}:${SOCKET_DIR}:rw" \
    "${PLUGIN_IMAGE}" \
      /k8s-cloud-kms-plugin \
        --logtostderr \
        --integration-test="true" \
        --path-to-unix-socket="${SOCKET_PATH}" \
        --key-uri="${KMS_FULL_KEY}"

(with the variables set accordingly), docker throws out this error:

docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "exec: \"/k8s-cloud-kms-plugin\": stat /k8s-cloud-kms-plugin: no such file or directory": unknown.

I'm assuming this isn't intentional? Rebuilding the image using the guide also gives the same error using the newly built image.

Wrong import path in k8s-cloud-kms-plugin.go

$ git clone [email protected]:GoogleCloudPlatform/k8s-cloudkms-plugin.git
Cloning into 'k8s-cloudkms-plugin'...
remote: Counting objects: 311, done.
remote: Compressing objects: 100% (176/176), done.
remote: Total 311 (delta 195), reused 247 (delta 132), pack-reused 0
Receiving objects: 100% (311/311), 361.59 KiB | 1.83 MiB/s, done.
Resolving deltas: 100% (195/195), done.

$ cd k8s-cloudkms-plugin/

$ go build ./...
k8s-cloud-kms-plugin.go:29:2: cannot find package "github.com/immutablet/k8s-cloudkms-plugin/plugin" in any of:
	/usr/local/google/home/awly/.go/src/github.com/immutablet/k8s-cloudkms-plugin/plugin (from $GOROOT)
	/usr/local/google/home/awly/go/src/github.com/immutablet/k8s-cloudkms-plugin/plugin (from $GOPATH)
plugin/plugin.go:27:2: cannot find package "github.com/immutablet/k8s-cloudkms-plugin/v1beta1" in any of:
	/usr/local/google/home/awly/.go/src/github.com/immutablet/k8s-cloudkms-plugin/v1beta1 (from $GOROOT)
	/usr/local/google/home/awly/go/src/github.com/immutablet/k8s-cloudkms-plugin/v1beta1 (from $GOPATH)

gvault

This isn't really the appropriate place to be posting this. But I figured I would make someone aware of a project I'm working on that uses KMS to encrypt secrets to be stored in source control which can also sync with K8s.

https://github.com/sourcec0de/gvault

I'm curious how I could get my tool to play well with this plugin :)

Package plugin exports identifiers unnecessarily

The only identifiers used in main are:

  • MetricsPort/Path
  • HealthzPort/Path
  • New
  • NewOrchestrator
  • KeyURIPattern

Unexport:

  • APIVersion
  • TestKeyURI
  • CloudKMSOperationalLatencies
  • CloudKMSOperationalFailuresTotal
  • RecordCloudKMSOperation
  • RegisterMetrics
  • Metrics/NewMetrics
  • TokenConfig
  • Validator/NewValidator

Looking for feedback on KMS v2 proposal

Hello ๐Ÿ‘‹๐Ÿป

As part of Kubernetes 1.25 enhancement we (sig-auth kms wg) are proposing a new v2alpha1 KeyManagementService service contract to:

  • enable fully automated key rotation for the latest key
  • improve KMS plugin health check reliability
  • improve observability of envelop operations between kube-apiserver, KMS plugins and KMS

This is the doc that documents the limitations with the current KMS v1 API.

In addition, we are also proposing a SIG-Auth maintained KMS plugin reference implementation. This implementation will support a key hierarchy design that implements the v2alpha1 API and will serve as a baseline that provides:

  • improve readiness times for clusters with a large number of encrypted resources
  • reduce the likelihood of hitting the external KMS request rate limit
  • metrics and tracing support

We have a KEP open for this proposal that details the changes and design: kubernetes/enhancements#3302

Call to action

We are looking for feedback on the proposed changes in the KEP from all the plugin authors who are currently using the KMS v1 API. Please review the proposal and comment on the PR if there are any questions/concerns with the proposed design.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.