Skip to content

Latest commit

 

History

56 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KIZARD — KMIP Wizard Lizard

KIZARD (KMIP Wizard Lizard) is a learning tool for understanding how a KMIP (Key Management Interoperability Protocol) client integrates with Thales CipherTrust Manager acting as a KMIP server.

It is a web-based KMIP client built on top of PyKMIP that exposes common key-management operations through a REST API and a browser UI.

Status: the symmetric-key lifecycle is implemented end-to-end — locate, create, register (import), get, get-attributes, activate, rotate (rekey), revoke/deactivate, destroy, and encrypt/decrypt (AES/3DES/DES, CBC, PKCS5) — exposed over both the REST API and the UI. The whole KMIP connection is configured at runtime through the browser, so the app comes up before any CipherTrust details are known (.env remains an optional way to pre-seed them). The UI walks through a guided, ordered setup:

  1. Reach the server — a pre-flight probe checks DNS, TCP, and the TLS handshake against the KMIP endpoint before any client material exists.
  2. Confirm the CA — review the CA that issued CipherTrust's certificate and approve it, supplying the full trust chain (Issuing CA + Root CA).
  3. Provide a client certificate — upload an existing mTLS identity, or generate a key + CSR in the browser (RSA 2048/4096 or ECDSA P-256) to be signed by your CA.
  4. Verify the connection — a live KMIP Locate round-trip confirms the server accepts the client certificate and speaks KMIP end-to-end; the key pane unlocks only once this passes.

The key pane names every object (auto-generating Kizard-Key-… when none is given), flags objects a rotation has replaced, and greys out any action the key's current state would reject. Encrypt/Decrypt (AES/3DES/DES · CBC · PKCS5) runs client-side by default — KIZARD fetches the key with a KMIP Get and ciphers locally — or, with a checkbox, server-side via the KMIP Encrypt/ Decrypt operations so the key never leaves CipherTrust. Each key uses a fixed IV, created once and reused for every operation.

Architecture

┌──────────────┐      HTTPS/REST      ┌──────────────────┐      KMIP/mTLS       ┌──────────────────────┐
│  React (UI)  │ ───────────────────> │  FastAPI backend │ ───────────────────> │ CipherTrust Manager  │
│   frontend/  │                      │     backend/     │   (PyKMIP client)    │   (KMIP server)      │
└──────────────┘                      └──────────────────┘                      └──────────────────────┘
  • frontend/ — React single-page app (Vite). Calls the backend REST API.
  • backend/ — Python FastAPI service that wraps PyKMIP's ProxyKmipClient and exposes key operations as JSON endpoints.
  • KMIP server — CipherTrust Manager (or any KMIP 1.x/2.x server) reached over a mutually-authenticated TLS connection.

Repository layout

KIZARD/
├── backend/                 FastAPI service wrapping PyKMIP
│   ├── app/
│   │   ├── main.py          App entrypoint / FastAPI instance
│   │   ├── core/            Settings & configuration
│   │   ├── kmip/            PyKMIP client wrapper
│   │   ├── models/          Pydantic request/response schemas
│   │   └── api/routes/      REST endpoints (health, keys, ...)
│   ├── tests/
│   ├── requirements.txt
│   └── .env.example
├── frontend/                React (Vite) single-page app
│   └── src/
├── docs/                    Architecture & design notes
├── LICENSE                  Apache License 2.0
└── README.md

Prerequisites

  • Python 3.10–3.13 (see Compatibility notes).
  • Node.js 18+ and npm.
  • git.
  • Network access to a KMIP server (e.g. CipherTrust Manager) and a client certificate / private key trusted by that server.
  • The host installer is a Bash script, so on Windows use Git Bash or WSL when running the installation commands below.

Host-based installation (single host)

Use this path when you want to run KIZARD directly on a workstation or server instead of deploying it in containers.

1. Confirm the required software is present

Run these checks before installing:

git --version
python3 --version
node --version
npm --version
python3 -m venv --help

The installer expects:

  • git available
  • Python 3.10 or newer (3.10–3.13 recommended)
  • Node.js 18 or newer
  • npm available
  • venv support available for Python

If any command fails, install the missing package(s) first and re-run the checks.

Linux examples

On Ubuntu/Debian:

sudo apt update
sudo apt install -y git python3 python3-venv python3-pip nodejs npm

On RHEL/Fedora/Rocky/Alma:

sudo dnf install -y git python3 python3-venv python3-pip nodejs npm

On openSUSE:

sudo zypper install -y git python3 python3-venv python3-pip nodejs npm

If your distribution uses yum instead of dnf, replace dnf with yum.

2. Install KIZARD on the host

From a fresh clone, run the host installer. It checks for the runtime requirements, installs any missing system packages where supported, creates the backend virtual environment, installs frontend dependencies, and starts the app:

git clone <repo-url>
cd KIZARD
bash scripts/host-deploy.sh

Useful variations:

bash scripts/host-deploy.sh --nostart    # install/setup only, do not start the app
scripts/host-manage.sh start             # start the app later
scripts/host-manage.sh stop              # stop the app
scripts/host-manage.sh status            # check whether it is running
scripts/host-undeploy.sh                 # uninstall the host install

The installer is safe to re-run. On first use it creates backend/.env from the example template; edit it with your KMIP host and TLS material before starting the app.

Compatibility notes

  • Python 3.10–3.13 are supported. PyKMIP 0.10 calls the module-level ssl.wrap_socket(), which was removed in Python 3.12, so on 3.12+ the TLS connection would otherwise raise AttributeError: module 'ssl' has no attribute 'wrap_socket'. The backend reinstalls a faithful SSLContext-based ssl.wrap_socket (see app/core/ssl_compat.py, applied on import of the KMIP client) — preserving mutual-TLS and CA verification — so no Python-version pin is needed. The shim is a no-op on 3.10/3.11, which still ship the original function.
  • Use KMIP 1.4 with CipherTrust Manager. PyKMIP 0.10 cannot parse CipherTrust's KMIP 2.0 responses (the response header fails to decode), so set KMIP_VERSION=1.4. Versions 1.0–1.4 all work; 2.0 does not.
  • CipherTrust client-cert mapping. The KMIP interface authenticates the client by certificate. The cert's identity (e.g. its CN) must map to a CipherTrust user, and that user needs auto-registration enabled — otherwise the server completes the TLS handshake but closes every KMIP request.

Getting started

Quick start (host-based)

The host-based installer covers the same steps as the manual setup below, but with a single command. The script checks for the runtime dependencies (git, Python ≥ 3.10, Node.js ≥ 18, npm), installs any that are missing via the system package manager, and sets up the backend virtualenv and frontend packages:

scripts/host-deploy.sh              # install deps, set up venv/node_modules, then start the app
                                    #   backend :8000, UI :5173  (safe to re-run)
scripts/host-deploy.sh --nostart    # ...or install/set up without starting
scripts/host-manage.sh stop         # stop the app
scripts/host-manage.sh start        # start it again later
scripts/host-manage.sh reset        # clear the KMIP config so the guided setup starts over
scripts/host-manage.sh status       # is it running?  (also: logs [backend|frontend] [-f])
scripts/host-undeploy.sh            # uninstall (removes venv/node_modules)

These mirror the Kubernetes scripts (k8s-deploy.sh / k8s-manage.sh / k8s-undeploy.sh) — see Running on Kubernetes.

On the first run backend/.env is seeded from the template — edit it with your KMIP host and TLS material (under backend/certs/), then scripts/host-manage.sh start to connect to your KMIP server.

Supported package managers: apt, dnf, yum, zypper, pacman. To run the pieces by hand instead, follow the steps below.

Backend

cd backend
python -m venv .venv
# Windows:  .venv\Scripts\activate
# Linux/Mac: source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env          # then edit .env with your KMIP server details
uvicorn app.main:app --reload

The API listens on port 8000, with interactive docs at /docs — e.g. http://<kizard-host>:8000/docs, or http://localhost:8000/docs when you are on the machine itself.

Frontend

cd frontend
npm install
npm run dev

The UI listens on port 5173 and proxies API calls to the backend. Reach it at http://<kizard-host>:5173, where <kizard-host> is the machine you installed KIZARD on — localhost only from that machine itself.

Connecting to CipherTrust Manager

CipherTrust Manager exposes a KMIP interface (default port 5696) that requires mutual TLS. To connect, you will need:

  1. The CipherTrust Manager KMIP interface hostname and port.
  2. A KMIP client certificate and its private key (or generate a CSR in the UI and have your CA sign it).
  3. The CA chain that signed the server certificate (Issuing CA + Root CA).

The recommended path is the guided flow in the browser. Open the UI and work through the setup steps — reach the server, confirm the CA, provide the client certificate, then verify — each of which is described inline. Nothing needs to be known ahead of time; the app starts with no configuration.

Alternatively, pre-seed any of these in backend/.env (see backend/.env.example) so the app comes up already configured — useful for a repeatable demo. Values set in the UI at runtime take precedence.

Starting the setup over

KIZARD remembers what the guided flow configured — the server target, the CA approval, and the mTLS material — under backend/data/. To walk the four setup steps again from scratch, clear that state. Two equivalent ways:

  • In the browser — the Start over button below the setup steps (DELETE /api/connection). Takes effect immediately, no restart.
  • On the command linescripts/host-manage.sh reset, which also restarts the backend if it is running:
scripts/host-manage.sh reset        # prompts, then clears and restarts the backend
scripts/host-manage.sh reset --yes  # no prompt
scripts/host-manage.sh reset --all  # also forget each key's fixed IV (old ciphertext
                                    #   stops decrypting; keys on the server are untouched)

Either way all four steps go back to un-done and the flow restarts at "Reach the server". Keys already created on CipherTrust are untouched — they live on the server, not in KIZARD.

A reset deliberately ignores anything pre-seeded in backend/.env, so a student never skips a step: the server target is left explicitly blank rather than re-seeded from KMIP_HOST, and client material at KMIP_CERT_PATH &c. stops counting as configured. Those certificate files are yours, so they are disregarded rather than deleted — nothing under backend/certs/ is removed. The marker that suppresses them lives at backend/data/certs/ignore-env-material and is dropped automatically the moment you supply a certificate through the UI.

The install — virtualenv, node modules, backend/.env — is left alone. To uninstall instead, use scripts/host-undeploy.sh.

Running on Kubernetes (RKE2)

KIZARD can also run as two containers — the backend (FastAPI/uvicorn) and the frontend (the React build served by nginx) — on a Rancher RKE2 cluster. Each runs in its own Deployment, Pod, and Service; the frontend's nginx serves the SPA and reverse-proxies /api to the backend Service, so the browser stays same-origin (the production stand-in for the dev-only Vite proxy).

Browser ──HTTP──▶ ingress-nginx (host: kizard.test256.io)
                     └─▶ frontend Service :80 ── nginx ──┬─ /       static SPA
                                                          └─ /api ─▶ backend Service :8000
                                                                          └─ KMIP/mTLS ─▶ CipherTrust :5696

Layout

  • backend/Dockerfile, frontend/Dockerfile (+ frontend/nginx.conf) — the two images.
  • k8s/ — manifests: namespace, configmap (KMIP settings), pvc (runtime state), backend, frontend, ingress, and secret.example.yaml (optional pre-seeded mTLS material).
  • scripts/k8s-deploy.sh / scripts/k8s-undeploy.sh — build, import, deploy, tear down.
  • scripts/k8s-manage.sh — the cluster counterpart of host-manage.sh (start / stop / reset / status / logs) for a running deployment.

Node prerequisites

Run the deploy on an RKE2 node. Set up two things there first:

  1. kubectl that reaches the cluster. The script uses KUBECONFIG if set, else ~/.kube/config, else the root-only /etc/rancher/rke2/rke2.yaml. Give your user a readable copy once:

    mkdir -p ~/.kube
    sudo cp /etc/rancher/rke2/rke2.yaml ~/.kube/config
    sudo chown "$(id -u):$(id -g)" ~/.kube/config
    export PATH="$PATH:/var/lib/rancher/rke2/bin"   # so kubectl is found
    kubectl get nodes                               # verify
  2. A build tool — docker or nerdctl — with its daemon running. On a fresh node, install Docker; note it ships its own containerd (separate from RKE2's), which must be up:

    curl -fsSL https://get.docker.com | sudo sh
    sudo usermod -aG docker "$USER" && newgrp docker    # use docker without sudo
    sudo systemctl enable --now containerd docker
    docker ps                                           # verify

    If Docker won't start with connect: connection refused on /run/containerd/containerd.sock, that socket was left behind as a stale directory: sudo rm -rf /run/containerd/containerd.sock, then sudo systemctl restart containerd docker.

Deploy

Single-node cluster — images go into the local containerd:

scripts/k8s-deploy.sh --host <fqdn>

Multi-node cluster — push to a registry so every node can pull (one-time registry setup below):

scripts/k8s-deploy.sh --host <fqdn> --registry <registry-host>:5000

Other flags: --namespace NS (default kizard), --tag TAG (default latest), --skip-build (re-apply manifests only). The script installs local-path-provisioner as the default StorageClass if the cluster has none, applies the manifests, and waits for both rollouts.

<fqdn> is just the ingress vhost — it can be the node's own name (e.g. kube.test256.io) or a dedicated name. Point DNS (or /etc/hosts on the browsing machine) for <fqdn> at an ingress node IP, then open http://<fqdn>/ and walk the guided setup. Verify from the node with:

kubectl -n kizard get pods            # both Running
curl -s http://<fqdn>/api/health      # {"status":"ok",...}

Runtime state (server target, CA approval, per-key fixed IVs, UI-uploaded mTLS material) lives on a PersistentVolume mounted at /app/data, so it survives pod restarts. KMIP configuration is done through the browser by default; the kizard-config ConfigMap can pre-seed KMIP_HOST/KMIP_VERSION, and k8s/secret.example.yaml shows how to inject a client certificate instead. The backend runs a single replica (it caches the KMIP target and IV map in memory over a ReadWriteOnce volume).

Multi-node: image registry

Without --registry, the script imports the freshly-built images into only the node it runs on. That is fine for a single-node cluster, but on a multi-node cluster pods scheduled on other nodes fail with ImagePullBackOff. Use a registry:

scripts/k8s-deploy.sh --host <fqdn> --registry <registry-host:5000>

This pushes the images and sets the Deployments to pull them cluster-wide (imagePullPolicy: Always). One-time cluster setup for a plain-HTTP lab registry:

# 1. Run a registry (e.g. on the build node) and let docker push to it:
docker run -d --restart=always --name registry -p 5000:5000 \
  -v /opt/registry:/var/lib/registry registry:2
echo '{ "insecure-registries": ["<registry-host>:5000"] }' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker

# 2. Let every RKE2 node's containerd pull over HTTP, then restart RKE2 on each:
#    /etc/rancher/rke2/registries.yaml —
#      mirrors:
#        "<registry-host>:5000":
#          endpoint: ["http://<registry-host>:5000"]
sudo systemctl restart rke2-server   # control-plane node(s)
sudo systemctl restart rke2-agent    # worker nodes

(Alternatively, skip the registry and ctr images import the same image tar on every node.)

Cluster DNS for private zones

Symptom: KIZARD reaches your KMIP server by IP address but not by name — cannot resolve 'cm-kirk.example.lab': [Errno -2] Name or service not known — even though every node resolves that name correctly.

Why: pods do not use the node's resolver. They query CoreDNS, which ships with

forward . /etc/resolv.conf

and so load-balances across every upstream the node lists. A typical node lists the LAN resolver plus public fallbacks (1.1.1.1, 8.8.4.4). The public ones have never heard of your private zone, so they answer NXDOMAIN — a perfectly valid negative answer, which CoreDNS returns as-is rather than retrying another upstream. Lookups fail whenever the query lands on a public resolver, and CoreDNS's negative cache then makes the failure stick.

Fix: forward the private zone to the LAN resolver that actually knows it. On an RKE2 control-plane node, drop a HelmChartConfig into the auto-applying manifests directory so the change survives RKE2 upgrades:

# /var/lib/rancher/rke2/server/manifests/rke2-coredns-config.yaml
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
  name: rke2-coredns
  namespace: kube-system
spec:
  valuesContent: |-
    servers:
      - zones:
          - zone: .
        port: 53
        plugins:
          - name: errors
          - name: health
            configBlock: |-
              lameduck 10s
          - name: ready
          - name: kubernetes
            parameters: in-addr.arpa ip6.arpa        # chart adds the cluster domain
            configBlock: |-
              pods insecure
              fallthrough in-addr.arpa ip6.arpa
              ttl 30
          - name: prometheus
            parameters: 0.0.0.0:9153
          - name: forward
            parameters: . /etc/resolv.conf
          - name: cache
            parameters: 30
          - name: loop
          - name: reload
          - name: loadbalance
      - zones:
          - zone: example.lab                        # your private zone
        port: 53
        plugins:
          - name: errors
          - name: cache
            parameters: 30
          - name: forward
            parameters: . 192.168.1.1                # your LAN DNS resolver
          - name: reload

Substitute your own zone and resolver — 192.168.1.1 is only this lab's router; find yours with resolvectl status | grep 'DNS Servers' on a node.

Two things to watch:

  • Overriding servers replaces the whole Corefile, so the first block reproduces the RKE2 default verbatim. Compare it against your cluster's current Corefile (kubectl -n kube-system get cm rke2-coredns-rke2-coredns -o jsonpath='{.data.Corefile}') before applying, in case your RKE2 version ships different defaults.
  • Do not repeat cluster.local in the kubernetes plugin's parameters — the chart injects the cluster domain itself, and passing it again duplicates the zone.

RKE2 reconciles within ~10s and restarts the CoreDNS pods. Verify:

kubectl -n kube-system get cm rke2-coredns-rke2-coredns -o jsonpath='{.data.Corefile}'
kubectl -n kube-system wait --for=condition=ready pod -l k8s-app=kube-dns --timeout=120s
kubectl run dnstest --rm -it --restart=Never --image=busybox:1.36 -- \
  nslookup cm-kirk.example.lab

k8s-deploy.sh probes --host against cluster DNS on every run and warns if the name does not resolve, since that is the same failure KIZARD will hit at KMIP time.

Manage a running deployment

scripts/k8s-manage.sh is the Kubernetes counterpart of host-manage.sh — same subcommands, but driving the cluster via kubectl instead of host processes:

scripts/k8s-manage.sh start           # scale backend + frontend up (replicas=1)
scripts/k8s-manage.sh stop            # scale down to 0 — keeps the PVC and config
scripts/k8s-manage.sh reset           # clear the guided-setup config (DELETE /api/connection)
scripts/k8s-manage.sh reset --all     # ...and forget each key's fixed IV, then restart
scripts/k8s-manage.sh status          # deployments, pods, PVC, ingress
scripts/k8s-manage.sh logs backend -f # tail a component's logs

reset calls the app's own "Start over" endpoint in-process (no restart, same as the browser button); --all additionally clears the per-key IVs and restarts the backend to drop its in-memory cache.

Tear down

scripts/k8s-undeploy.sh            # remove workloads, keep the data PVC
scripts/k8s-undeploy.sh --purge    # also delete the PVC and namespace (destroys state)

License

Licensed under the Apache License 2.0. PyKMIP is also distributed under the Apache License 2.0.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages