- Overview
- Architecture
- Prerequisites
- Installation & Setup
- Configuration
- API Reference
- Security Features
- Testing & Validation
- Production Deployment
- Troubleshooting
- Best Practices
- Advanced Usage
- Monitoring & Logging
- Backup & Recovery
- Integration Examples
This system provides a secure, cryptographically-signed license management solution using HashiCorp Vault's Transit secrets engine. It enables organizations to:
- Issue digitally signed software licenses
- Validate license authenticity and integrity
- Renew existing licenses with extended terms
- Revoke compromised or expired licenses
- Audit all license operations
- Cryptographic Security: Uses RSA-2048 or Ed25519 signatures
- Tamper Detection: Any modification invalidates the license
- Centralized Key Management: Vault handles all cryptographic operations
- Audit Trail: Complete logging of all operations
- Scalable: Can handle thousands of licenses per second
- Cloud-Ready: Easily deployable in any environment
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Client App │───▶│ License API │───▶│ HashiCorp │
│ │ │ (Node.js) │ │ Vault │
│ - Request │ │ - Issue │ │ - Transit │
│ - Validate │ │ - Validate │ │ - Signing │
│ - Renew │ │ - Renew │ │ - Verification │
└─────────────────┘ └──────────────────┘ └─────────────────┘
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Database │ │ File Storage │ │ Monitoring │
│ (Optional) │ │ (Vault Data) │ │ & Logging │
│ - License Meta │ │ - Keys │ │ - Metrics │
│ - Audit Logs │ │ - Configurations │ │ - Alerts │
└─────────────────┘ └──────────────────┘ └─────────────────┘
- Vault Server: Handles cryptographic operations and key management
- License API: RESTful API for license operations
- Client Libraries: SDKs for easy integration
- Admin Dashboard: Web interface for license management
- Monitoring Stack: Logging, metrics, and alerting
- OS: Linux, macOS, or Windows
- Memory: Minimum 512MB RAM (2GB+ recommended)
- Storage: 1GB+ free space
- Network: Port 8200 available for Vault
- Node.js: Version 14+ for the API server
# HashiCorp Vault
wget https://releases.hashicorp.com/vault/1.14.2/vault_1.14.2_linux_amd64.zip
unzip vault_1.14.2_linux_amd64.zip
sudo mv vault /usr/local/bin/
# Node.js dependencies
npm install express axios body-parser jsonwebtoken winston helmet cors- TLS Certificates: For production deployment
- Network Security: Firewall rules and VPN access
- Access Control: RBAC policies for different user roles
- Backup Strategy: For Vault unseal keys and data
- Clone and Setup Project
mkdir vault-license-system
cd vault-license-system
# Create package.json
npm init -y
npm install express axios body-parser jsonwebtoken winston helmet cors- Create Vault Configuration
# Create vault.hcl
cat > vault.hcl << 'EOF'
storage "file" {
path = "./vault/data"
}
listener "tcp" {
address = "127.0.0.1:8200"
tls_disable = true
}
ui = true
api_addr = "http://127.0.0.1:8200"
cluster_addr = "https://127.0.0.1:8201"
EOF- Start Vault Server
# Terminal 1: Start Vault
vault server -config=vault.hcl- Initialize and Configure Vault
# Terminal 2: Setup Vault
export VAULT_ADDR='http://127.0.0.1:8200'
# Initialize (save these keys securely!)
vault operator init
# Unseal (use 3 of the 5 unseal keys)
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>
# Login with root token
vault login <root_token>
# Enable transit secrets engine
vault secrets enable transit
# Create signing key
vault write -f transit/keys/license-signing-key type=rsa-2048- Deploy License API
# Create index.js with the provided code
node index.jsFor production deployment, use the comprehensive setup scripts provided in the artifacts section.
storage "file" {
path = "./vault/data"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = true
}
ui = true
api_addr = "http://127.0.0.1:8200"storage "consul" {
address = "consul.service.consul:8500"
path = "vault/"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/opt/vault/tls/vault.crt"
tls_key_file = "/opt/vault/tls/vault.key"
}
ui = true
api_addr = "https://vault.company.com:8200"
cluster_addr = "https://vault.company.com:8201"
telemetry {
prometheus_retention_time = "30s"
disable_hostname = true
}# Vault Configuration
VAULT_ADDR=http://127.0.0.1:8200
VAULT_TOKEN=hvs.S6ANSkVJAubnWTcbnMnRthNn
VAULT_NAMESPACE=admin # For Vault Enterprise
# API Configuration
API_PORT=3000
API_HOST=0.0.0.0
NODE_ENV=production
# Security
JWT_SECRET=your-jwt-secret-key
API_KEY_REQUIRED=true
RATE_LIMIT_WINDOW=15 # minutes
RATE_LIMIT_MAX=100 # requests per window
# Logging
LOG_LEVEL=info
LOG_FILE=/var/log/license-api.log
# Database (optional)
DATABASE_URL=postgresql://user:pass@localhost/licenses
REDIS_URL=redis://localhost:6379| Key Type | Security Level | Performance | Use Case |
|---|---|---|---|
ed25519 |
High | Excellent | Digital signatures, IoT |
rsa-2048 |
High | Good | General purpose, legacy support |
rsa-3072 |
Very High | Moderate | High security requirements |
rsa-4096 |
Extreme | Slower | Government, critical systems |
ecdsa-p256 |
High | Very Good | Modern applications |
ecdsa-p384 |
Very High | Good | Enterprise applications |
All API endpoints require authentication via:
- Vault Token:
X-Vault-Tokenheader - API Key:
X-API-Keyheader (optional) - JWT:
Authorization: Bearer <token>(optional)
http://localhost:3000 # Development
https://api.company.com/license # Production
GET /healthResponse:
{
"status": "healthy",
"vault_status": {
"initialized": true,
"sealed": false,
"standby": false
},
"api_version": "1.0.0",
"uptime": 3600
}POST /license/issue
Content-Type: application/json
{
"customer": "Acme Corp",
"modules": ["payroll", "hr", "accounting"],
"expires_at": "2025-12-31",
"metadata": {
"max_users": 100,
"environment": "production"
}
}Response:
{
"success": true,
"license": {
"license_id": "1727510234567",
"customer": "Acme Corp",
"modules": ["payroll", "hr", "accounting"],
"issued_at": "2025-09-28T08:37:14.567Z",
"expires_at": "2025-12-31T23:59:59.999Z",
"metadata": {
"max_users": 100,
"environment": "production"
},
"signature": "vault:v1:MEUCI...",
"issuer": "License Authority v1.0",
"version": "1.0"
}
}POST /license/validate
Content-Type: application/json
{
"license": {
"license_id": "1727510234567",
"customer": "Acme Corp",
"modules": ["payroll", "hr"],
"issued_at": "2025-09-28T08:37:14.567Z",
"expires_at": "2025-12-31T23:59:59.999Z",
"signature": "vault:v1:MEUCI..."
}
}Response:
{
"valid": true,
"signature_valid": true,
"expired": false,
"days_until_expiry": 94,
"license": {
"license_id": "1727510234567",
"customer": "Acme Corp",
"modules": ["payroll", "hr"],
"expires_at": "2025-12-31T23:59:59.999Z"
},
"validation_timestamp": "2025-09-28T10:15:30.123Z"
}POST /license/renew
Content-Type: application/json
{
"license": {
"license_id": "1727510234567",
"customer": "Acme Corp",
"signature": "vault:v1:MEUCI..."
},
"new_expires_at": "2026-12-31",
"extend_modules": ["reporting"],
"metadata_updates": {
"max_users": 200
}
}Response:
{
"success": true,
"license": {
"license_id": "1727510234568",
"customer": "Acme Corp",
"modules": ["payroll", "hr", "accounting", "reporting"],
"issued_at": "2025-09-28T10:20:00.000Z",
"expires_at": "2026-12-31T23:59:59.999Z",
"renewed_from": "1727510234567",
"signature": "vault:v1:NEWCI..."
}
}POST /license/revoke
Content-Type: application/json
{
"license_id": "1727510234567",
"reason": "Security breach",
"effective_date": "2025-09-28T12:00:00Z"
}GET /license/list?customer=Acme&status=active&page=1&limit=50POST /license/batch/issue
Content-Type: application/json
{
"licenses": [
{
"customer": "Customer A",
"modules": ["basic"],
"expires_at": "2025-12-31"
},
{
"customer": "Customer B",
"modules": ["premium"],
"expires_at": "2025-12-31"
}
]
}{
"error": "Validation failed",
"code": "INVALID_LICENSE",
"details": "License signature is invalid",
"timestamp": "2025-09-28T10:15:30.123Z",
"request_id": "req_1727510234567"
}| Code | HTTP Status | Description |
|---|---|---|
INVALID_LICENSE |
400 | License format or data is invalid |
EXPIRED_LICENSE |
400 | License has expired |
INVALID_SIGNATURE |
401 | Signature verification failed |
VAULT_ERROR |
503 | Vault service unavailable |
RATE_LIMITED |
429 | Too many requests |
UNAUTHORIZED |
401 | Invalid authentication |
- Digital Signatures: Every license is cryptographically signed
- Key Rotation: Automatic key rotation with version tracking
- Tamper Detection: Any modification invalidates the license
- Non-Repudiation: Signatures provide legal proof of issuance
# License issuer policy
path "transit/sign/license-signing-key" {
capabilities = ["create", "update"]
}
path "transit/verify/license-signing-key" {
capabilities = ["create", "update"]
}
path "transit/keys/license-signing-key" {
capabilities = ["read"]
}// JWT-based authentication
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.sendStatus(401);
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}- TLS Encryption: All communications encrypted in transit
- IP Whitelisting: Restrict access to known IP ranges
- VPN/Private Networks: Deploy in secure network segments
- WAF Protection: Web Application Firewall for API endpoints
- Comprehensive Logging: All operations logged with timestamps
- Immutable Audit Trail: Cryptographically signed log entries
- Compliance Reports: SOC 2, ISO 27001, GDPR-ready
- Real-time Monitoring: Suspicious activity detection
// test/license.test.js
const request = require('supertest');
const app = require('../index');
describe('License API', () => {
test('should issue valid license', async () => {
const response = await request(app)
.post('/license/issue')
.send({
customer: 'Test Corp',
modules: ['basic'],
expires_at: '2025-12-31'
});
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.license.signature).toBeDefined();
});
});# Run comprehensive test suite
npm test
# Performance testing
npm run test:performance
# Security testing
npm run test:security// k6 load test script
import http from 'k6/http';
import { check } from 'k6';
export let options = {
vus: 100,
duration: '5m',
};
export default function() {
let response = http.post('http://localhost:3000/license/issue',
JSON.stringify({
customer: `Customer-${__VU}`,
modules: ['basic'],
expires_at: '2025-12-31'
}),
{ headers: { 'Content-Type': 'application/json' } }
);
check(response, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500,
});
}Use the provided test scripts for comprehensive manual validation:
# Run full test suite
./test-license-api.sh
# Individual tests
curl http://localhost:3000/health
curl -X POST http://localhost:3000/license/issue -d '...'FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm ci --only=production
# Copy application
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs
EXPOSE 3000
CMD ["node", "index.js"]version: '3.8'
services:
vault:
image: vault:1.14.2
container_name: vault
ports:
- "8200:8200"
volumes:
- ./vault/config:/vault/config
- ./vault/data:/vault/data
- ./vault/logs:/vault/logs
command: ["vault", "server", "-config=/vault/config/vault.hcl"]
environment:
- VAULT_ADDR=http://127.0.0.1:8200
cap_add:
- IPC_LOCK
license-api:
build: .
container_name: license-api
ports:
- "3000:3000"
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_TOKEN}
- NODE_ENV=production
depends_on:
- vault
restart: unless-stopped
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
depends_on:
- license-apiapiVersion: apps/v1
kind: StatefulSet
metadata:
name: vault
spec:
replicas: 3
serviceName: vault
selector:
matchLabels:
app: vault
template:
metadata:
labels:
app: vault
spec:
containers:
- name: vault
image: vault:1.14.2
ports:
- containerPort: 8200
- containerPort: 8201
env:
- name: VAULT_ADDR
value: "http://127.0.0.1:8200"
volumeMounts:
- name: vault-data
mountPath: /vault/data
- name: vault-config
mountPath: /vault/config
volumeClaimTemplates:
- metadata:
name: vault-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10GiapiVersion: apps/v1
kind: Deployment
metadata:
name: license-api
spec:
replicas: 3
selector:
matchLabels:
app: license-api
template:
metadata:
labels:
app: license-api
spec:
containers:
- name: license-api
image: license-api:latest
ports:
- containerPort: 3000
env:
- name: VAULT_ADDR
value: "http://vault:8200"
- name: VAULT_TOKEN
valueFrom:
secretKeyRef:
name: vault-secrets
key: root-tokenresource "aws_ecs_cluster" "license_cluster" {
name = "license-management"
}
resource "aws_ecs_task_definition" "vault" {
family = "vault"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = 1024
memory = 2048
container_definitions = jsonencode([
{
name = "vault"
image = "vault:1.14.2"
portMappings = [
{
containerPort = 8200
protocol = "tcp"
}
]
environment = [
{
name = "VAULT_ADDR"
value = "http://127.0.0.1:8200"
}
]
}
])
}Symptoms:
Error: Vault is sealed
Solution:
export VAULT_ADDR='http://127.0.0.1:8200'
vault operator unseal <key1>
vault operator unseal <key2>
vault operator unseal <key3>Symptoms:
http: server gave HTTP response to HTTPS client
Solution:
export VAULT_ADDR='http://127.0.0.1:8200' # Use http, not httpsSymptoms:
Error: key "license-signing-key" not found
Solution:
vault write -f transit/keys/license-signing-key type=rsa-2048Symptoms:
signature verification failed
Solution:
- Check key name consistency
- Verify payload hasn't been modified
- Ensure correct key version
Symptoms:
- Slow response times
- Timeout errors
Solutions:
- Scale Vault cluster horizontally
- Implement caching layer
- Optimize database queries
- Use connection pooling
# Check Vault status
vault status
# List all keys
vault list transit/keys
# Get key information
vault read transit/keys/license-signing-key
# Test signing
vault write transit/sign/license-signing-key input=$(echo -n "test" | base64)
# Check API logs
tail -f /var/log/license-api.log
# Monitor Vault logs
tail -f /var/log/vault.log# Enable debug logging
export VAULT_LOG_LEVEL=debug
vault server -config=vault.hcl
# Analyze audit logs
vault audit enable file file_path=/var/log/vault-audit.log// Enhanced logging
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console()
]
});-
Never Store Unseal Keys Together
- Distribute among multiple trusted parties
- Use hardware security modules (HSMs)
- Implement Shamir's secret sharing
-
Rotate Keys Regularly
vault write -f transit/keys/license-signing-key/rotate
-
Use Least Privilege Access
- Create specific policies for different roles
- Regular access reviews
- Time-bound tokens
-
Implement Defense in Depth
- Network segmentation
- Application-level security
- Database encryption
- Monitoring and alerting
-
Connection Pooling
const axiosConfig = { maxSockets: 100, keepAlive: true, timeout: 30000 };
-
Caching Strategy
const NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 600 }); // 10 minutes // Cache key information const keyInfo = cache.get(`key_${keyName}`); if (!keyInfo) { // Fetch from Vault and cache }
-
Batch Operations
- Process multiple licenses in single requests
- Use bulk validation endpoints
- Implement queue-based processing
-
Monitoring and Metrics
const prometheus = require('prometheus'); const licenseCounter = new prometheus.Counter({ name: 'licenses_issued_total', help: 'Total number of licenses issued' });
-
Automated Backups
#!/bin/bash # Backup Vault data tar -czf "vault-backup-$(date +%Y%m%d).tar.gz" ./vault/data/ aws s3 cp "vault-backup-$(date +%Y%m%d).tar.gz" s3://vault-backups/
-
Health Monitoring
// Comprehensive health check app.get('/health/detailed', async (req, res) => { const health = { vault: await checkVaultHealth(), database: await checkDatabaseHealth(), dependencies: await checkExternalDependencies(), metrics: await getSystemMetrics() }; const overallHealth = Object.values(health).every(h => h.status === 'healthy'); res.status(overallHealth ? 200 : 503).json(health); });
-
Disaster Recovery
- Document recovery procedures
- Regular DR testing
- Automated failover mechanisms
- Cross-region replication
const jwt = require('jsonwebtoken');
function createJWTLicense(payload, vaultSignature) {
return jwt.sign({
...payload,
vault_signature: vaultSignature,
iss: 'license-authority',
aud: 'client-application'
}, process.env.JWT_SECRET, {
algorithm: 'HS256',
expiresIn: payload.expires_at
});
}const msgpack = require('msgpack');
function createBinaryLicense(payload, signature) {
const binaryData = msgpack.pack({
version: 1,
payload: payload,
signature: signature,
checksum: generateChecksum(payload)
});
return Buffer.from(binaryData).toString('base64');
}# Create versioned keys
vault write -f transit/keys/license-v2 type=ed25519
vault write -f transit/keys/license-v3 type=ecdsa-p384
# Sign with specific version
vault write transit/sign/license-signing-key/1 input=<base64-data>seal "pkcs11" {
lib = "/usr/lib/softhsm/libsofthsm2.so"
slot = "0"
pin = "1234"
key_label = "vault-hsm-key"
hmac_key_label = "vault-hsm-hmac-key"
}class LicenseManager {
constructor(tenant) {
this.tenant = tenant;
this.keyPath = `transit/keys/${tenant}-license-key`;
this.vaultNamespace = `tenant-${tenant}`;
}
async signLicense(payload) {
const response = await axios.post(
`${VAULT_ADDR}/v1/${this.keyPath}/sign`,
{ input: Buffer.from(JSON.stringify(payload)).toString('base64') },
{
headers: {
'X-Vault-Token': this.getVaultToken(),
'X-Vault-Namespace': this.vaultNamespace
}
}
);
return response.data.data.signature;
}
}const EventEmitter = require('events');
class LicenseEventEmitter extends EventEmitter {}
const licenseEvents = new LicenseEventEmitter();
// License lifecycle events
licenseEvents.on('license:issued', (license) => {
console.log(`License ${license.license_id} issued to ${license.customer}`);
// Send notifications, update CRM, etc.
});
licenseEvents.on('license:expired', (license) => {
console.log(`License ${license.license_id} expired`);
// Send renewal reminders, disable features, etc.
});app.post('/webhooks/license', (req, res) => {
const { event, license } = req.body;
switch (event) {
case 'license.issued':
// Integrate with CRM
break;
case 'license.renewed':
// Update billing system
break;
case 'license.revoked':
// Disable user access
break;
}
res.status(200).json({ received: true });
});const prometheus = require('prom-client');
// Custom metrics
const licenseIssued = new prometheus.Counter({
name: 'licenses_issued_total',
help: 'Total licenses issued',
labelNames: ['customer', 'tier']
});
const licenseValidation = new prometheus.Histogram({
name: 'license_validation_duration_seconds',
help: