Vault Unseal Controller

A Kubernetes operator that automatically unseals HashiCorp Vault instances when they become sealed.
Overview
The Vault Unseal Controller monitors Vault nodes and automatically triggers unseal jobs when sealed nodes are detected. This ensures high availability of your Vault infrastructure without manual intervention.
Key Features
- ๐ Automatic unsealing of sealed Vault nodes
- ๐ Multi-node support for Vault clusters
- ๐ Secure secret management using Kubernetes secrets
- ๐ TLS support with custom CA certificates or skip verification
- โ๏ธ Configurable retry logic for failed unseal attempts
Architecture
The controller creates Kubernetes Jobs in the same namespace as your unseal keys secrets, allowing proper secret mounting across namespaces while maintaining security boundaries.
Prerequisites
- Kubernetes cluster v1.31+
- kubectl v1.31+
- Go 1.25+ (for development)
- Docker or Podman (for building images)
Installation
Using Pre-built Bundle
Download and apply the latest release bundle:
kubectl apply -f https://github.com/didactiklabs/vault-unseal-controller/releases/latest/download/bundle.yaml
Building from Source
# Build and push images
make docker-build docker-push IMG=<your-registry>/vault-unseal-controller VERSION=<version>
make docker-build-unsealer docker-push-unsealer UNSEALER_IMG=<your-registry>/vault-unsealer VERSION=<version>
# Deploy to cluster
make deploy IMG=<your-registry>/vault-unseal-controller VERSION=<version>
Usage
Creating an Unseal Resource
Create a secret with your Vault unseal keys:
kubectl create secret generic vault-keys \
--from-literal=key1=<key-1> \
--from-literal=key2=<key-2> \
--from-literal=key3=<key-3> \
-n vault-namespace
Create an Unseal custom resource:
apiVersion: platform.didactiklabs.io/v1alpha1
kind: Unseal
metadata:
name: vault-cluster
spec:
vaultNodes:
- https://my-vault.example.com:8200 # Exposed Vault URL
unsealKeysSecretRef:
name: vault-keys
namespace: vault-namespace
Or you can iterate over a list of available nodes endpoints:
apiVersion: platform.didactiklabs.io/v1alpha1
kind: Unseal
metadata:
name: vault-cluster
spec:
vaultNodes:
- https://vault-0.example.com:8200
- https://vault-1.example.com:8200
- https://vault-2.example.com:8200
unsealKeysSecretRef:
name: vault-keys
namespace: vault-namespace
Apply it:
kubectl apply -f unseal.yaml
With TLS Skip Verify
For development or self-signed certificates:
apiVersion: platform.didactiklabs.io/v1alpha1
kind: Unseal
metadata:
name: vault-cluster-dev
spec:
vaultNodes:
- https://vault.example.com:8200
unsealKeysSecretRef:
name: vault-keys
namespace: default
tlsSkipVerify: true
With Custom CA Certificate
For production with custom CA:
kubectl create secret generic vault-ca-cert \
--from-file=ca.crt=path/to/ca.crt \
-n vault-namespace # CA certificate secret must be in the same namespace as the unseal keys
apiVersion: platform.didactiklabs.io/v1alpha1
kind: Unseal
metadata:
name: vault-cluster-prod
spec:
vaultNodes:
- https://vault-0.vault.svc:8200
unsealKeysSecretRef:
name: vault-keys
namespace: vault-namespace
caCertSecret: vault-ca-cert
retryCount: 5
Configuration
Unseal Spec
| Field |
Type |
Required |
Description |
vaultNodes |
[]string |
Yes |
List of Vault node URLs to monitor |
unsealKeysSecretRef |
SecretRef |
Yes |
Reference to secret containing unseal keys |
caCertSecret |
string |
No |
Name of secret containing CA certificate (must be in same namespace as unseal keys) |
tlsSkipVerify |
bool |
No |
Skip TLS certificate verification (default: false) |
retryCount |
int32 |
No |
Number of retry attempts for unseal jobs (default: 3) |
Status Fields
The controller maintains status information:
vaultStatus: Current state (UNSEALED, UNSEALING, CLEANING)
sealedNodes: List of currently sealed Vault nodes
Development
Running Tests
# Unit tests
make test
# E2E tests (requires Kind)
make test-e2e
# Lint
make lint
Building Locally
# Build controller binary
make build
# Build unsealer binary
make build-unsealer
# Run locally (outside cluster)
make run
Releasing
Create a new git tag to trigger the release workflow:
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
This will:
- Build and publish multi-arch Docker images (amd64, arm64)
- Create a GitHub release with GoReleaser
- Generate and attach deployment bundle
How It Works
- Monitoring: The controller periodically checks the seal status of configured Vault nodes
- Detection: When sealed nodes are detected, status transitions to
UNSEALING
- Job Creation: A Kubernetes Job is created for each sealed node in the secret's namespace
- Unsealing: The job uses unseal keys from the secret to unseal the node
- Cleanup: After successful unsealing, jobs are cleaned up and status returns to
UNSEALED
Troubleshooting
Pods Can't Pull Images
Ensure the controller deployment has imagePullPolicy: IfNotPresent or that images are available in your registry.
Jobs Fail to Mount Secrets
Verify that:
- The secret exists in the specified namespace
- Jobs are created in the same namespace as the secret
- ServiceAccount has proper RBAC permissions
Unseal Jobs Keep Failing
Check:
- Unseal keys are correct and base64-encoded
- Vault nodes are reachable from the cluster
- TLS configuration matches your Vault setup
- RetryCount is sufficient for your environment
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Ensure all tests pass:
make test lint
- Submit a pull request
License
Copyright 2026 Didactik Labs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.