diff --git a/ansible/roles/minikube/defaults/main.yml b/ansible/roles/minikube/defaults/main.yml index 1b520ec..07140bb 100644 --- a/ansible/roles/minikube/defaults/main.yml +++ b/ansible/roles/minikube/defaults/main.yml @@ -6,3 +6,9 @@ minikube_memory: 7800 minikube_disk_size: "200g" minikube_driver: podman minikube_container_runtime: cri-o + +# Remote access configuration +# These allow kubectl from other machines (e.g., gilbert) to connect +minikube_apiserver_names: + - indri +minikube_listen_address: "0.0.0.0" diff --git a/ansible/roles/minikube/tasks/main.yml b/ansible/roles/minikube/tasks/main.yml index ac6cc34..c3a9157 100644 --- a/ansible/roles/minikube/tasks/main.yml +++ b/ansible/roles/minikube/tasks/main.yml @@ -5,7 +5,8 @@ # NOTE: Similar to podman, minikube start may have issues when run via SSH. # If cluster fails to start, manually run on indri: # minikube start --driver=podman --container-runtime=cri-o \ -# --cpus=4 --memory=8192 --disk-size=200g +# --cpus=4 --memory=7800 --disk-size=200g \ +# --apiserver-names=indri --listen-address=0.0.0.0 - name: Install minikube via homebrew community.general.homebrew: @@ -33,6 +34,10 @@ --cpus={{ minikube_cpus }} --memory={{ minikube_memory }} --disk-size={{ minikube_disk_size }} + {% for name in minikube_apiserver_names %} + --apiserver-names={{ name }} + {% endfor %} + --listen-address={{ minikube_listen_address }} register: minikube_start changed_when: minikube_start.rc == 0 failed_when: false # Don't fail - may need manual intervention like podman diff --git a/bin/kubectl-credential-1password b/bin/kubectl-credential-1password new file mode 100755 index 0000000..04f2669 --- /dev/null +++ b/bin/kubectl-credential-1password @@ -0,0 +1,31 @@ +#!/bin/bash +# kubectl exec credential plugin for 1Password +# Usage: kubectl-credential-1password +# +# Fetches client certificate and key from 1Password and outputs +# ExecCredential JSON for kubectl authentication. + +set -euo pipefail + +VAULT_ID="$1" +ITEM_ID="$2" +CERT_FIELD="$3" +KEY_FIELD="$4" + +# Fetch credentials from 1Password (strips surrounding quotes from text fields) +CLIENT_CERT=$(op --vault "$VAULT_ID" item get "$ITEM_ID" --fields "$CERT_FIELD" | sed 's/^"//; s/"$//') +CLIENT_KEY=$(op --vault "$VAULT_ID" item get "$ITEM_ID" --fields "$KEY_FIELD" | sed 's/^"//; s/"$//') + +# Output ExecCredential JSON +# Note: jq is used to properly escape the PEM data for JSON +jq -n \ + --arg cert "$CLIENT_CERT" \ + --arg key "$CLIENT_KEY" \ + '{ + "apiVersion": "client.authentication.k8s.io/v1beta1", + "kind": "ExecCredential", + "status": { + "clientCertificateData": $cert, + "clientKeyData": $key + } + }' diff --git a/plans/k8s-migration.md b/plans/k8s-migration.md index 6671c0e..1ec67bc 100644 --- a/plans/k8s-migration.md +++ b/plans/k8s-migration.md @@ -637,7 +637,7 @@ Chose **Option 3: Recreate cluster with `--apiserver-names`** after researching 2. **SOCKS5 proxy with kubeconfig `proxy-url`** - Kubeconfig supports `proxy-url: socks5://localhost:1080` per-context, but still requires managing the proxy 3. **`--apiserver-names` + `--listen-address`** - Native minikube support, cleanest solution -**Approach:** Recreate the minikube cluster with additional flags: +**Cluster Setup:** Recreated the minikube cluster with additional flags: ```bash minikube delete minikube start \ @@ -650,14 +650,71 @@ minikube start \ - `--apiserver-names=indri` adds "indri" to the API server certificate SAN - `--listen-address=0.0.0.0` tells podman to expose the API port on all interfaces +- API server port is dynamic (check with `kubectl config view --minify -o jsonpath="{.clusters[0].cluster.server}"` on indri) -Then configure kubeconfig on gilbert pointing to `https://indri:` with certs copied from indri. +**Credential Management with 1Password:** + +Rather than copying private keys between machines, credentials are stored in 1Password and fetched on-demand using kubectl's exec credential plugin. This mirrors the 1Password SSH agent pattern for biometric-protected key access. + +1. **Store credentials in 1Password** (vault: `vg6xf6vvfmoh5hqjjhlhbeoaie`, item: `3jo4f2hnzvwfmamudfsbbbec7e`): + - `client-cert` - Contents of `~/.minikube/profiles/minikube/client.crt` (text field) + - `client-key` - Contents of `~/.minikube/profiles/minikube/client.key` (text field) + - `ca-cert` - Contents of `~/.minikube/ca.crt` (text field, not secret but stored for convenience) + +2. **Created credential helper script** at `bin/kubectl-credential-1password`: + ```bash + #!/bin/bash + # Fetches client cert/key from 1Password, outputs ExecCredential JSON + # Usage: kubectl-credential-1password + ``` + Symlinked to `~/.local/bin/kubectl-credential-1password` + +3. **Kubeconfig setup on gilbert:** + ```bash + # Store CA cert locally (not secret - public key for server verification) + mkdir -p ~/.kube/minikube-indri + op --vault item get --fields ca-cert | sed 's/^"//; s/"$//' > ~/.kube/minikube-indri/ca.crt + + # Configure cluster + kubectl config set-cluster minikube-indri \ + --server=https://indri: \ + --certificate-authority=/Users/eblume/.kube/minikube-indri/ca.crt + + # Configure credentials with exec plugin + kubectl config set-credentials minikube-indri \ + --exec-api-version=client.authentication.k8s.io/v1beta1 \ + --exec-command=kubectl-credential-1password \ + --exec-arg= \ + --exec-arg= \ + --exec-arg=client-cert \ + --exec-arg=client-key + + # Create context + kubectl config set-context minikube-indri \ + --cluster=minikube-indri \ + --user=minikube-indri + ``` + +4. **Usage:** + ```bash + kubectl --context=minikube-indri get nodes + # or + kubectl config use-context minikube-indri + kubectl get nodes + ``` + +**Security Notes:** +- Client private key never stored on disk - fetched from 1Password on each kubectl command +- CA cert stored on disk (not secret - it's a public key for server verification) +- 1Password biometric/password prompt required for credential access +- `op` command strips quotes from text fields with `sed 's/^"//; s/"$//'` **References:** - [minikube start options](https://minikube.sigs.k8s.io/docs/commands/start/) - [Using kubectl via SSH Tunnel](https://blog.scottlowe.org/2020/06/16/using-kubectl-via-an-ssh-tunnel/) - [SOCKS5 Proxy Access to K8s API](https://kubernetes.ltd/docs/tasks/extend-kubernetes/socks5-proxy-access-api/) - [kubectl-tokensshtunnel](https://github.com/jordiprats/kubectl-tokensshtunnel) +- [Securing kubectl config with 1Password](https://blog.mikael.green/post/1password-kubeconfig/) ---