Skip to content

Commit

Permalink
Parity updates with TSS-K8s (#7)
Browse files Browse the repository at this point in the history
* Removed script for self-signed cert creation in favor of using helms genSelfSignedCert
* Install into own namespace by default when using the Makefile + File clean up
* Documentation updates
  • Loading branch information
Ricky White authored Mar 1, 2022
1 parent 1798958 commit 373131c
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 264 deletions.
33 changes: 11 additions & 22 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ HELM_CHART:=charts/$(NAME)
VERSION?=latest

# The Kubernetes Namespace that the webhook will be deployed in 📁
NAMESPACE?=default
NAMESPACE?=dsv

# Your roles.json file; see the README.md)
# Your roles.json file; see the README.md
ROLES_JSON?=configs/roles.json

# 👇 Podman works too
Expand All @@ -14,38 +14,27 @@ DOCKER=docker
# Helm is required to install the webhook
HELM=helm

.PHONY: cert clean clean-docker clean-cert image install install-image uninstall
.PHONY: clean image install install-image uninstall

all: install

# Build the dsv-injector service container image 📦
image:
$(DOCKER) $(DOCKER_ARGS) build . -t $(NAME):$(VERSION) $(DOCKER_BUILD_ARGS)

# Create a self-signed SSL certificate 🔐
$(HELM_CHART)/$(NAME).key $(HELM_CHART)/$(NAME).pem:
sh scripts/get_cert.sh -n "$(NAME)" -d "$(HELM_CHART)" -N "$(NAMESPACE)"
cert: $(HELM_CHART)/$(NAME).pem

# Install will use the cert and key below, no matter how they got there. 😉😇
install: $(HELM_CHART)/$(NAME).key $(HELM_CHART)/$(NAME).pem
# Install the Helm chart using a roles.json file 📄
install:
$(HELM) $(HELM_ARGS) --namespace $(NAMESPACE) install --create-namespace \
--set-file caBundle=$(HELM_CHART)/$(NAME).pem,rolesJson=$(ROLES_JSON) \
$(HELM_INSTALL_ARGS) $(HELM_REPO_ARGS) $(NAME) $(HELM_CHART)
# Install image uses the locally built image in place of the default ⚙️
--set-file rolesJson=$(ROLES_JSON) $(HELM_INSTALL_ARGS) $(HELM_REPO_ARGS) \
$(NAME) $(HELM_CHART)
# Install the chart with the locally built image in place of the default ⚙️
install-image: HELM_REPO_ARGS = --set image.pullPolicy=Never,image.repository=$(NAME)
install-image: image install

# Uninstall the Helm Chart
# Uninstall the Helm Chart
uninstall:
-$(HELM) $(HELM_ARGS) --namespace $(NAMESPACE) uninstall $(NAME)

# Remove the X.509 certificate and RSA private key
clean-cert:
-rm -f $(HELM_CHART)/$(NAME).key $(HELM_CHART)/$(NAME).pem

# Remove the Docker images
clean-docker:
# Remove the Docker images 🗑️
clean:
-$(DOCKER) $(DOCKER_ARGS) rmi -f $(NAME):$(VERSION)

clean: clean-cert clean-docker
167 changes: 74 additions & 93 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,52 @@

A [Kubernetes](https://kubernetes.io/)
[Mutating Webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#admission-webhooks)
that injects Secret data from Delinea DevOps Secrets Vault (DSV) into Kubernetes Secrets.
The webhook can be hosted as a pod or as a stand-alone service.
that injects Secret data from Delinea DevOps Secrets Vault (DSV) into Kubernetes
Secrets. The webhook can be hosted as a pod or as a stand-alone service.

The webhook works by intercepting `CREATE` and `UPDATE` Secret admissions and mutating the Secret with data from DSV.
The webhook configuration consists of one or more _role_ to Client Credential Tenant mappings.
The webhook updates Kubernetes Secrets based on annotations on the Secret itself. [See below](#use).
The webhook works by intercepting `CREATE` and `UPDATE` Secret admissions and
mutating the Secret with data from DSV. The webhook configuration consists of
one or more _role_ to Client Credential Tenant mappings. It updates Kubernetes
Secrets based on annotations on the Secret itself.

The webhook uses the [Golang SDK](https://github.com/thycotic/dsv-sdk-go) to communicate with the DSV API.
The webhook uses the [Golang SDK](https://github.com/thycotic/dsv-sdk-go) to
communicate with the DSV API.

It was tested with [Minikube](https://minikube.sigs.k8s.io/) and [Minishift](https://docs.okd.io/3.11/minishift/index.html).
It was tested with [Minikube](https://minikube.sigs.k8s.io/) and
[Minishift](https://docs.okd.io/3.11/minishift/index.html).

## Configure

The webhook requires a JSON formatted list of _role_ to Client Credential and Tenant mappings.
The _role_ is a simple name that does not relate to DSV or Kubernetes Roles per se.
Declaring the role annotation selects which credentials to use to get the DSV Secret.
Using the name of the DSV Role used to generate the credentials is good practice.
The webhook requires a JSON formatted list of _role_ to Client Credential and
Tenant mappings. The _role_ is a simple name that does not relate to Kubernetes
Roles. It simply selects which credentials to use to get the Secret from DSV.

```json
{
"my-role": {
"credentials": {
"clientId": "93d866d4-635f-4d4e-9ce3-0ef7f879f319",
"clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxx-xxxxx"
},
"tenant": "mytenant"
"my-role": {
"credentials": {
"clientId": "93d866d4-635f-4d4e-9ce3-0ef7f879f319",
"clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxx-xxxxx"
},
"default": {
"credentials": {
"clientId": "64241412-3934-4aed-af26-95b1eaba0e6a",
"clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxx-xxxxx"
},
"tenant": "mytenant"
}
"tenant": "mytenant"
},
"default": {
"credentials": {
"clientId": "64241412-3934-4aed-af26-95b1eaba0e6a",
"clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxx-xxxxx"
},
"tenant": "mytenant"
}
}
```

NOTE: the injector uses the _default_ role when it mutates a Kubernetes Secret that does not have a _roleAnnotation_.
> NOTE: the injector uses the _default_ role when it mutates a Kubernetes Secret
> that does not have a _roleAnnotation_. [See below](#use)
## Run

The `Makefile` demonstrates a typical installation via [Helm](https://helm.sh/).
It creates a self-signed certificate and associated key using `get_cert.sh`.
The Helm Chart templates the certificate and key as a Kubernetes Secret.
It also provides `roles.json`, which the chart likewise templates as a k8s Secret.

The `dsv-injector` image contains the `dsv-injector-svc` executable, however,
the container should get the certificate, key, and the `roles.json` via mounts.
The injector is a Golang executable that runs a built-in HTTPS server hosting
the Kubernetes Mutating Webhook Webservice.

```bash
$ /usr/bin/dsv-injector-svc -?
Expand All @@ -69,43 +67,35 @@ Usage of ./dsv-injector-svc:
the path of JSON formatted roles file (default "roles.json")
```

### Certificate

`scripts/get_cert.sh` generates a self-signed certificate and key.
It requires [openssl](https://www.openssl.org/).

```bash
$ sh scripts/get_cert.sh
Usage: get_cert.sh -n NAME [OPTIONS]...

-n, -name, --name NAME
Maps to the host portion of the FQDN that is the subject of the
certificate; also the basename of the certificate and key files.
-d, -directory, --directory=DIRECTORY
The location of the resulting certificate and private-key. The
default is '.'
-N, -namespace, --namespace=NAMESPACE
Represents the Kubernetes cluster Namespace and maps to the
domain of the FQDN that is the subject of the certificate.
the default is 'default'
-b, -bits, --bits=BITS
the RSA key size in bits; default is 2048
```
Thus the injector can run "anywhere," but, typically, the injector runs as a POD
in the Kubernetes cluster that uses it.

## Build

Building the `dsv-injector` image requires [Docker](https://www.docker.com/) or
[Podman](https://podman.io/).
To build it, run:
> NOTE: Building the `dsv-injector` image is not required to install it as it is
> available on multiple public registries.
Building the injector requires [Docker](https://www.docker.com/) or
[Podman](https://podman.io/). To build it, run:

```sh
make image
```

### Minikube and Minishift

Remember to run `eval $(minikube docker-env)` in the shell to push the image to
Minikube's Docker daemon.💡 Likewise for Minishift except its
`eval $(minishift docker-env)`.

### Install

Installation requires [Helm](https://helm.sh).

The `Makefile` demonstrates a typical installation via the
[Helm](https://helm.sh/) chart. It imports `roles.json` as a file that it
templates as a Kubernetes Secret for the injector.

The Helm `values.yaml` file `image.repository` is `thycotic/dsv-injector`:

```yaml
Expand All @@ -116,7 +106,7 @@ image:
tag: ""
```
So, by default, `make install` will pull from Docker, GitHub, or Quay.
That means, by default, `make install` will pull from Docker, GitHub, or Quay.

```sh
make install
Expand All @@ -129,22 +119,16 @@ to use the image built with `make image`:
make install-image
```

`make cert` exists as a shortcut for making the certificate and key.

`make uninstall` uninstalls the Helm Chart.

`make clean` removes the Docker image by calling `make clean-docker` then removes the certificate and key by calling `make clean-cert`

### Minikube and Minishift

Remember to run `eval $(minikube docker-env)` in the shell to push the image to Minikube's Docker daemon.
Likewise for Minishift except its `eval $(minishift docker-env)`.
`make clean` removes the Docker image.

## Use

Once the `dsv-injector` is available in the Kubernetes cluster, and the
Once the injector is available in the Kubernetes cluster, and the
[MutatingAdmissionWebhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook)
is in place, any appropriately annotated k8s Secrets are modified on create and update.
is in place, any appropriately annotated Kubernetes Secrets are modified on
create and update.

The four annotations that affect the behavior of the webhook are:

Expand All @@ -157,20 +141,24 @@ const(
)
```

`roleAnnotation` selects the credentials that the injector uses to retrieve the DSV Secret.
If it is present then the role must exist in the role to Client Credential and Tenant mappings.
If it is absent then the _default_ mapping is used.
`roleAnnotation` selects the credentials that the injector uses to retrieve the
DSV Secret. If the role is present, it must map to Client Credential and Tenant
mapping. If the role is absent, the injector will use the _default_ Credential
and Tenant a mapping.

The `setAnnotation`, `addAnnotation` and `updateAnnotation` contain the path to
the DSV Secret that the injector will use to mutate the submitted Kubernetes Secret.
the DSV Secret that the injector will use to mutate the Kubernetes Secret.

* `addAnnotation` adds missing fields without overwriting or removing existing fields.
* `updateAnnotation` adds and overwrites existing fields but does not remove fields.
* `setAnnotation` overwrites fields and removes fields that do not exist in the DSV Secret.
- `addAnnotation` adds missing fields without overwriting or removing existing
fields.
- `updateAnnotation` adds and overwrites existing fields but does not remove
fields.
- `setAnnotation` overwrites fields and removes fields that do not exist in the
DSV Secret.

A Kubernetes Secret should specify only one of these, however, if the Secret specifies more
than one then, the order of precedence is `setAnnotation` then
`addAnnotation` then `updateAnnotation`.
NOTE: A Kubernetes Secret should specify only one of the "add," "update," or
"set" annotations. The order of precedence is `setAnnotation`, then
`addAnnotation`, then `updateAnnotation` when multiple are present.

### Examples

Expand All @@ -192,18 +180,11 @@ data:

The above example specifies a Role, so a mapping for that role must exist in the
current webhook configuration. It uses the `setAnnotation` so the data in the
secret will be overwritten; if `/test/secret` contains a `username` and
`password` but no `domain` then the secret would contain the `username` and
`password` from the DSV Secret Data and, the `domain` field is removed.

There are more examples in the `examples` directory. Each one shows
how each annotation works when run against an example with only a username and
password in it.

```shell
$ thy secret read /test/secret -f .data
{
"password": "alongpassword",
"username": "someuser"
}
```
injector will overwrite the existing contents of the Kubernetes Secret; if
`/test/secret` contains a `username` and `password` but no `domain`, then the
Kubernetes Secret would get the `username` and `password` from the DSV Secret
Data but, the injector will remove the `domain` field.

There are more examples in the `examples` directory. Each one shows how each
annotation works when run against an example with only a username and
private-key in it but no domain.
6 changes: 5 additions & 1 deletion charts/dsv-injector/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
{{ include "dsv.fullname" . }} is now available in the {{ .Release.Namespace }} namespace on port {{ .Values.service.port }}
✨ The {{ include "dsv.fullname" . }} Mutating Webhook ✨

📂 Namepsace: {{ .Release.Namespace }}

🔗 Url: https://{{ include "dsv.dnsname" . }}:{{ .Values.service.port }}{{ .Values.webhookUri }}
7 changes: 7 additions & 0 deletions charts/dsv-injector/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ If release name contains chart name it will be used as a full name.
{{- end }}
{{- end }}

{{/*
Create a DNS name i.e. a fully-qualified domain name (FQDN) for the webhook.
*/}}
{{- define "dsv.dnsname" -}}
{{- print (include "dsv.name" .) "." .Release.Namespace ".svc" -}}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
Expand Down
8 changes: 0 additions & 8 deletions charts/dsv-injector/templates/cert-secret.yaml

This file was deleted.

2 changes: 1 addition & 1 deletion charts/dsv-injector/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ spec:
secretName: {{ include "dsv.name" . }}-roles
- name: cert
secret:
secretName: {{ include "dsv.name" . }}-cert
secretName: {{ include "dsv.name" . }}-tls
13 changes: 12 additions & 1 deletion charts/dsv-injector/templates/webhook.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
---
{{- $cert := genSelfSignedCert (include "dsv.dnsname" .) nil (list (include "dsv.dnsname" .) (include "dsv.name" .)) (default 365 .Values.webhookCertExpireDays | int) -}}
{{- if .Capabilities.APIVersions.Has "admissionregistration.k8s.io/v1" -}}
apiVersion: admissionregistration.k8s.io/v1
{{- else -}}
Expand All @@ -20,7 +22,7 @@ webhooks:
scope: {{ default "*" .Values.webhookScope }}
{{- end }}
clientConfig:
caBundle: {{ .Values.caBundle | b64enc }}
caBundle: {{ $cert.Cert | b64enc }}
service:
namespace: {{ .Release.Namespace }}
name: {{ include "dsv.name" . }}
Expand All @@ -30,3 +32,12 @@ webhooks:
admissionReviewVersions: ["v1", "v1beta1"]
sideEffects: {{ default "None" .Values.sideEffects }}
{{- end }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ include "dsv.name" . }}-tls
data:
cert: {{ $cert.Cert | b64enc }}
key: {{ $cert.Key | b64enc }}
type: Opaque
Loading

0 comments on commit 373131c

Please sign in to comment.