Code cleanup and readme

This commit is contained in:
jillianwilson
2021-10-31 20:57:58 -03:00
parent 591b8949cd
commit fe57e68769
4 changed files with 150 additions and 72 deletions

View File

@@ -1,78 +1,125 @@
## Deploy
# 1Password Secrets Injector for Kubernetes
The 1Password Secrets Injector for Kubernetes provides the ability to integrate Kubernetes with 1Password. The 1Password Secrets Injector implements a mutating webhook to inject 1Password secrets as environment variables into a pod or deployment. This differs from the secert creation provided by the 1Password Kubernetes operator in that a Kubernetes Secret will not be created when injecting a secret into your resource.
1. Create namespace `op-secret-injector` in which the 1Password secret injector webhook is deployed:
## Use with the 1Password Kubernetes Operator
The 1Password Secrets Injector for Kubernetes can be used in conjuction with the 1Password Kubernetes Operator in order to provide automatic deployment restarts when a 1Password item being used by your deployment has been updated.
[Click here for more details on the 1Password Kubernetes Operator](operator/README.md)
## Setup and Deployment
The 1Password Secrets Injector for Kubernetes uses a webhook server in order to inject secrets into pods and deployments. Admission to the webhook server is needs to be s secure operation, thus communication with the webhook server requires a TLS certificate signed by a Kubernetes CA.
For a simple setup we suggest using s script by morvencao for [creating a signed cert for the webook](https://github.com/morvencao/kube-mutating-webhook-tutorial/blob/master/deploy/webhook-create-signed-cert.sh). A copy of this script can also be found in this repo [here](secret-injector/deploy/webhook-create-signed-cert.sh).
Run the script with the following:
```
./deploy/webhook-create-signed-cert.sh \
--service <name of webhook service> \
--secret <name of kubernetes secret where certificate will be stored> \
--namespace <your namespace>
```
This will genrate a Kubernetes Secret with your signed certificate.
Next we must set the webhook configuration. An example of this configuration can be found [here](secret-injector/deploy/mutatingwebhook.sh). If you choose to use this example, replace `${CA_BUNDLE}` file's with the value stored for `client-ca-file` in the Kubernetes Secret you generated in the previous step.
```
# kubectl create ns op-secret-injector
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: op-secret-injector-webhook-cfg
labels:
app: op-secret-injector
webhooks:
- name: op-secret-injector.morven.me
clientConfig:
service:
name: op-secret-injector-webhook-svc
namespace: op-secret-injector
path: "/inject"
caBundle: ${CA_BUNDLE} //replace this with your own CA Bundle
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
namespaceSelector:
matchLabels:
op-secret-injection: enabled
```
2. Create a signed cert/key pair and store it in a Kubernetes `secret` that will be consumed by 1Password secret injector deployment:
You can automate this step using the script by [morvencao](https://github.com/morvencao/kube-mutating-webhook-tutorial).
```
# ./deploy/webhook-create-signed-cert.sh \
--service op-secret-injector-webhook-svc \
--secret op-secret-injector-webhook-certs \
--namespace op-secret-injector
```
3. Patch the `MutatingWebhookConfiguration` by set `caBundle` with correct value from Kubernetes cluster:
```
# cat deploy/mutatingwebhook.yaml | \
cat deploy/mutatingwebhook.yaml | \
deploy/webhook-patch-ca-bundle.sh > \
deploy/mutatingwebhook-ca-bundle.yaml
```
4. Deploy resources:
Lastly, we must apply the deployment, service, and mutating webhook configuration to kubernetes:
```
# kubectl create -f deploy/deployment.yaml
# kubectl create -f deploy/service.yaml
# kubectl create -f deploy/mutatingwebhook-ca-bundle.yaml
kubectl create -f deploy/deployment.yaml
kubectl create -f deploy/service.yaml
kubectl create -f deploy/mutatingwebhook-ca-bundle.yaml
```
## Verify
## Usage
1. The sidecar inject webhook should be in running state:
For every namespace you want the 1Password Secret Injector to inject secrets for, you must add the label `sidecar-injector=enabled` label to the namespace:
```
# kubectl -n sidecar-injector get pod
NAME READY STATUS RESTARTS AGE
sidecar-injector-webhook-deployment-7c8bc5f4c9-28c84 1/1 Running 0 30s
# kubectl -n sidecar-injector get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
sidecar-injector-webhook-deployment 1/1 1 1 67s
kubectl label namespace injection sidecar-injection=enabled
```
2. Create new namespace `injection` and label it with `sidecar-injector=enabled`:
To inject a 1Password secret as an environment variable, your pod or deployment you must add an environment variable to the resource with a value referencing your 1Password item in the format `op://<vault>/<item>[/section]/<field>`. You must also annotate your pod/deployment spec with `operator.1password.io/inject` which expects a comma separated list of the names of the containers to that will be mutated and have secrets injected.
```
# kubectl create ns injection
# kubectl label namespace injection sidecar-injection=enabled
# kubectl get namespace -L sidecar-injection
NAME STATUS AGE SIDECAR-INJECTION
default Active 26m
injection Active 13s enabled
kube-public Active 26m
kube-system Active 26m
sidecar-injector Active 17m
#example
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-example
spec:
selector:
matchLabels:
app: app-example
template:
metadata:
annotations:
operator.1password.io/inject: "app-example,another-example"
labels:
app: app-example
spec:
containers:
- name: app-example
image: my-image
env:
- name: DB_USERNAME
value: op://my-vault/my-item/sql/username
- name: DB_PASSWORD
value: op://my-vault/my-item/sql/password
- name: another-example
image: my-image
env:
- name: DB_USERNAME
value: op://my-vault/my-item/sql/username
- name: DB_PASSWORD
value: op://my-vault/my-item/sql/password
- name: my-app //because my-app is not listed in the inject annotaion above the environment values for this container will not be updated with secret values
image: my-image
env:
- name: DB_USERNAME
value: op://my-vault/my-item/sql/username
- name: DB_PASSWORD
value: op://my-vault/my-item/sql/password
```
3. Deploy an app in Kubernetes cluster, take `alpine` app as an example
## Attributions
```
# kubectl run alpine --image=alpine --restart=Never -n injection --overrides='{"apiVersion":"v1","metadata":{"annotations":{"sidecar-injector-webhook.morven.me/inject":"yes"}}}' --command -- sleep infinity
```
4. Verify sidecar container is injected:
```
# kubectl get pod
NAME READY STATUS RESTARTS AGE
alpine 2/2 Running 0 1m
# kubectl -n injection get pod alpine -o jsonpath="{.spec.containers[*].name}"
alpine sidecar-nginx
```
This project is based on and heavily inspired by [morvencao's Kubernetes Mutating Webhook for Sidecar Injection tutorial](https://github.com/morvencao/kube-mutating-webhook-tutorial).
## Troubleshooting

View File

@@ -37,17 +37,17 @@ func main() {
connectHost, present := os.LookupEnv(connectHostEnv)
if !present {
glog.Error("")
glog.Error("Connect host not set")
}
connectTokenName, present := os.LookupEnv(connectTokenSecretNameEnv)
if !present {
glog.Error("")
glog.Error("Connect token name not set")
}
connectTokenKey, present := os.LookupEnv(connectTokenSecretKeyEnv)
if !present {
glog.Error("")
glog.Error("Connect token key not set")
}
webhookConfig := webhook.Config{

View File

@@ -24,6 +24,9 @@ const (
// binVolumeMountPath is the mount path where the OP CLI binary can be found.
binVolumeMountPath = "/op/bin/"
connectTokenEnv = "OP_CONNECT_TOKEN"
connectHostEnv = "OP_CONNECT_HOST"
)
// binVolume is the shared, in-memory volume where the OP CLI binary lives.
@@ -281,7 +284,7 @@ func (whsvr *WebhookServer) mutate(ar *v1beta1.AdmissionReview) *v1beta1.Admissi
// into a shared volume mount.
var binInitContainer = corev1.Container{
Name: "copy-op-bin",
Image: "op-example" + ":" + version,
Image: "1password/op" + ":" + version,
ImagePullPolicy: corev1.PullIfNotPresent,
Command: []string{"sh", "-c",
fmt.Sprintf("cp /usr/local/bin/op %s", binVolumeMountPath)},
@@ -332,26 +335,31 @@ func createOPCLIPatch(pod *corev1.Pod, annotations map[string]string, containers
func createOPConnectPatch(container *corev1.Container, containerIndex int, host, tokenSecretName, tokenSecretKey string) []patchOperation {
var patch []patchOperation
connectHostEnvVar := corev1.EnvVar{
Name: "OP_CONNECT_HOST",
Value: host,
envs := []corev1.EnvVar{}
hostConfig, tokenConfig := isConnectConfigurationSet(container)
if hostConfig {
connectHostEnvVar := corev1.EnvVar{
Name: "OP_CONNECT_HOST",
Value: host,
}
envs = append(envs, connectHostEnvVar)
}
connectTokenEnvVar := corev1.EnvVar{
Name: "OP_CONNECT_TOKEN",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
Key: tokenSecretKey,
LocalObjectReference: corev1.LocalObjectReference{
Name: tokenSecretName,
if tokenConfig {
connectTokenEnvVar := corev1.EnvVar{
Name: "OP_CONNECT_TOKEN",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
Key: tokenSecretKey,
LocalObjectReference: corev1.LocalObjectReference{
Name: tokenSecretName,
},
},
},
},
}
envs := []corev1.EnvVar{
connectHostEnvVar,
connectTokenEnvVar,
}
envs = append(envs, connectTokenEnvVar)
}
patch = append(patch, setEnvironment(*container, containerIndex, envs, "/spec/containers")...)
@@ -359,6 +367,27 @@ func createOPConnectPatch(container *corev1.Container, containerIndex int, host,
return patch
}
func isConnectConfigurationSet(container *corev1.Container) (bool, bool) {
hostConfig := false
tokenConfig := false
for _, env := range container.Env {
if env.Name == connectHostEnv {
hostConfig = true
}
if env.Name == connectTokenEnv {
tokenConfig = true
}
if tokenConfig && hostConfig {
break
}
}
return hostConfig, tokenConfig
}
func (whsvr *WebhookServer) mutateContainer(_ context.Context, container *corev1.Container, containerIndex int) (*corev1.Container, bool, []patchOperation, error) {
// Because we are running a command in the pod before starting the container app,
// we need to prepend the pod comand with the op run command