mirror of
https://github.com/1Password/onepassword-operator.git
synced 2025-10-21 23:18:06 +00:00
Code cleanup and readme
This commit is contained in:
@@ -12,9 +12,11 @@ The 1Password Connect Kubernetes Operator will continually check for updates fro
|
||||
|
||||
[Click here for more details on the 1Password Kubernetes Operator](operator/README.md)
|
||||
|
||||
## 1Password Secret Injector
|
||||
## 1Password Secrets Injector for Kubernetes
|
||||
|
||||
[Click here for more details on the 1Password Secret Injector](secret-injector/README.md)
|
||||
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.
|
||||
|
||||
[Click here for more details on the 1Password Secrets Injector for Kubernetes](secret-injector/README.md)
|
||||
|
||||
|
||||
# Security
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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{
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user