A secure, scalable API for managing private keys, certificate signing requests (CSRs), and certificates with AWS DynamoDB storage and KMS encryption.
This is my first public Go project and none of this should be ran in Production until it has been properly peer reviewed for security vulnerabilities. It is currently my way of gaining Go development experience
- Private Key Generation: Support for RSA (2048/4096) and ECDSA (P-256/P-384) key types
- CSR Creation: Automatic generation of certificate signing requests
- Certificate Management: Upload and validate certificates against CSRs
- PFX Generation: Create password-protected PKCS#12 files for legacy application compatibility
- Secure Storage: Private keys encrypted with AWS KMS and stored in DynamoDB
- Search & Filtering: Query certificates by date, tags, status, and key type
- RESTful API: Clean, well-documented endpoints
- Docker Support: Production-ready containerization
- Go 1.22+
- AWS account with DynamoDB and KMS access
- Docker (for containerized deployment)
-
Clone the repository
git clone <repository-url> cd CertificateMonkey
-
Install dependencies
go mod tidy
-
Set environment variables
export AWS_REGION=us-east-1 export DYNAMODB_TABLE=certificate-monkey export KMS_KEY_ID=alias/certificate-monkey export API_KEY_1=your_api_key_here export SERVER_PORT=8080
-
Run the application
go run cmd/server/main.go
-
Build the image
docker build -t certificate-monkey . -
Run the container
docker run -p 8080:8080 \ -e AWS_REGION=us-east-1 \ -e DYNAMODB_TABLE=certificate-monkey \ -e KMS_KEY_ID=alias/certificate-monkey \ -e API_KEY_1=your_api_key_here \ certificate-monkey
Certificate Monkey can be deployed to Kubernetes using Helm charts.
-
Add the Helm repository
helm repo add certificate-monkey https://sefiris.github.io/CertificateMonkey/ helm repo update
-
Create required secrets
# Create namespace kubectl create namespace certificate-monkey # Create API keys secret kubectl create secret generic certificate-monkey-api-keys \ --namespace=certificate-monkey \ --from-literal=API_KEY_1=your-primary-key \ --from-literal=API_KEY_2=your-secondary-key
-
Install the API chart
helm install certificate-monkey-api certificate-monkey/certificate-monkey-api \ --namespace=certificate-monkey \ --set aws.region=us-east-1 \ --set aws.dynamodbTable=certificate-monkey \ --set aws.kmsKeyId=alias/certificate-monkey
π Helm Documentation:
- Deployment Guide - Complete production deployment instructions
- Testing Guide - Minikube testing and development
- Helm Charts - Chart documentation and configuration options
Key Features:
- β Multi-replica deployments with horizontal pod autoscaling
- β IRSA (IAM Roles for Service Accounts) support for secure AWS authentication
- β Ingress and TLS configuration
- β Health checks and monitoring integration
- β Production-ready security defaults
All API endpoints (except /health) require authentication via API key:
# Using X-API-Key header
curl -H "X-API-Key: your_api_key_here" http://localhost:8080/api/v1/keys
# Using Authorization header
curl -H "Authorization: Bearer your_api_key_here" http://localhost:8080/api/v1/keysGET /health
Returns basic service health status.
GET /health/aws
Returns detailed health status for AWS services (DynamoDB and KMS connectivity).
Example response:
{
"status": "healthy",
"service": "certificate-monkey",
"version": "0.1.0",
"timestamp": "2025-11-09T17:30:00Z",
"checks": {
"dynamodb": {
"status": "healthy",
"message": "DynamoDB table is accessible",
"response_ms": 45
},
"kms": {
"status": "healthy",
"message": "KMS key is accessible",
"response_ms": 32
}
}
}GET /build-info
Returns version and build information:
{
"service": "certificate-monkey",
"version": "0.1.0",
"build_time": "2025-05-24_21:16:57_UTC",
"git_commit": "b739e97",
"go_version": "go1.24.3",
"timestamp": "2024-01-15T10:30:00Z"
}Creates a new private key and certificate signing request (CSR).
Endpoint: POST /api/v1/keys
Request Body:
{
"common_name": "example.com",
"subject_alternative_names": ["www.example.com", "api.example.com"],
"organization": "ACME Corporation",
"organizational_unit": "IT Department",
"country": "US",
"state": "California",
"city": "San Francisco",
"email_address": "[email protected]",
"key_type": "RSA2048",
"tags": {
"environment": "production",
"project": "web-server"
}
}X.509 Certificate Fields:
common_name(required): CN - Common Name, typically the primary domain namesubject_alternative_names(optional): SAN - Alternative domain names or IP addressesorganization(optional): O - Organization nameorganizational_unit(optional): OU - Department or division within the organizationcountry(optional): C - Two-letter country code (e.g., "US", "CA", "GB")state(optional): ST - State or province namecity(optional): L - City or locality nameemail_address(optional): Email address associated with the certificatekey_type(required): Cryptographic algorithm and key sizetags(optional): Custom metadata for organization and searching
Supported Key Types:
RSA2048: RSA 2048-bit keyRSA4096: RSA 4096-bit keyECDSA-P256: Elliptic Curve P-256 keyECDSA-P384: Elliptic Curve P-384 key
Example - Simple Certificate:
curl -X POST http://localhost:8080/api/v1/keys \
-H "Content-Type: application/json" \
-H "X-API-Key: cm_dev_12345" \
-d '{
"common_name": "example.com",
"key_type": "RSA2048"
}'Example - Full Certificate with All Fields:
curl -X POST http://localhost:8080/api/v1/keys \
-H "Content-Type: application/json" \
-H "X-API-Key: cm_dev_12345" \
-d '{
"common_name": "secure.example.com",
"subject_alternative_names": [
"www.secure.example.com",
"api.secure.example.com",
"192.168.1.100"
],
"organization": "ACME Corporation Ltd",
"organizational_unit": "Information Technology",
"country": "US",
"state": "California",
"city": "San Francisco",
"email_address": "[email protected]",
"key_type": "ECDSA-P256",
"tags": {
"environment": "production",
"project": "api-gateway",
"cost-center": "IT-001",
"expiry-notification": "[email protected]"
}
}'PUT /api/v1/keys/{id}/certificate
Request Body:
{
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
}Response:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"status": "CERT_UPLOADED",
"valid_from": "2024-01-01T10:00:00Z",
"valid_to": "2025-01-01T10:00:00Z",
"serial_number": "123456789",
"fingerprint": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD",
"updated_at": "2024-01-01T10:05:00Z"
}POST /api/v1/keys/{id}/pfx
Request Body:
{
"password": "your_secure_password"
}Response:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"pfx_data": "base64_encoded_pfx_data",
"filename": "example.com-123e4567.pfx"
}GET /api/v1/keys/{id}
GET /api/v1/keys/{id}/private-key
Response:
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKB...\n-----END PRIVATE KEY-----",
"key_type": "RSA2048",
"common_name": "example.com",
"exported_at": "2024-01-15T10:30:00Z"
}Security Features:
- Comprehensive audit logging with client IP and User-Agent
- Authentication required (API key or Bearer token)
- Structured response with metadata
- RFC3339 timestamp for export tracking
GET /api/v1/keys?status=CERT_UPLOADED&key_type=RSA2048&environment=production
Query Parameters:
status: Filter by certificate statuskey_type: Filter by key typedate_from: Filter by creation date (RFC3339 format)date_to: Filter by creation date (RFC3339 format)page: Page number for paginationpage_size: Number of results per page (max 100)- Any tag key: Filter by tag value (e.g.,
environment=production)
The API supports various filtering and sorting options:
# List all keys sorted by creation date (newest first) - default
curl -H "X-API-Key: your-api-key" \
"http://localhost:8080/api/v1/keys"
# Sort by common name (alphabetical)
curl -H "X-API-Key: your-api-key" \
"http://localhost:8080/api/v1/keys?sort_by=common_name&sort_order=asc"
# Sort by expiration date (descending)
curl -H "X-API-Key: your-api-key" \
"http://localhost:8080/api/v1/keys?sort_by=valid_to&sort_order=desc"
# Sort by status with filtering
curl -H "X-API-Key: your-api-key" \
"http://localhost:8080/api/v1/keys?status=CERT_UPLOADED&sort_by=status&sort_order=asc"
# Combined filtering and sorting with pagination
curl -H "X-API-Key: your-api-key" \
"http://localhost:8080/api/v1/keys?key_type=RSA2048&sort_by=updated_at&sort_order=desc&page=1&page_size=10"Supported sort fields:
created_at(default)updated_atcommon_namestatusvalid_tovalid_fromkey_type
Sort orders:
desc(default) - Descending orderasc- Ascending order
Certificate Monkey includes comprehensive API documentation with an interactive Swagger UI interface.
Access Swagger UI:
http://localhost:8080/swagger/index.html
Features:
- π Complete API endpoint documentation
- π§ Interactive API testing interface
- π Request/response schema definitions
- π Built-in authentication support
- π Model definitions and examples
Quick Start with Swagger:
-
Start the server:
./scripts/start-swagger-demo.sh
-
Open Swagger UI:
http://localhost:8080/swagger/index.html -
Authenticate:
- Click "Authorize" button
- Enter API key:
demo-api-key-12345 - Or use Bearer token:
Bearer demo-api-key-12345
-
Test APIs:
- Try the health endpoint first
- Create a new certificate with POST /keys
- List certificates with GET /keys
- Upload certificates and generate PFX files
Regenerating Documentation:
If you modify API endpoints or models, regenerate the Swagger docs:
# Install swag CLI (one time)
go install github.com/swaggo/swag/cmd/swag@latest
# Regenerate documentation
swag init -g cmd/server/main.go -o docs --parseInternalHere are some common API usage examples:
The application can be configured using environment variables:
| Variable | Default | Description |
|---|---|---|
SERVER_HOST |
0.0.0.0 |
Server bind address |
SERVER_PORT |
8080 |
Server port |
AWS_REGION |
us-east-1 |
AWS region |
DYNAMODB_TABLE |
certificate-monkey |
DynamoDB table name |
KMS_KEY_ID |
alias/certificate-monkey |
KMS key for encryption |
API_KEY_1 |
cm_dev_12345 |
Primary API key |
API_KEY_2 |
cm_prod_67890 |
Secondary API key |
Required Table Configuration:
# Table Name: certificate-monkey (configurable via DYNAMODB_TABLE env var)
# Primary Key
- Partition Key: id (String)
# Global Secondary Index
- Index Name: created_at-index
- Partition Key: created_at (String)
- Projection: ALL
# Recommended Settings for Production
- Billing Mode: On-Demand (or Provisioned based on your needs)
- Encryption: Enabled with AWS managed key
- Point-in-time Recovery: Enabled
- Backup: EnabledUsing AWS CLI:
# Create the table
aws dynamodb create-table \
--table-name certificate-monkey \
--attribute-definitions \
AttributeName=id,AttributeType=S \
AttributeName=created_at,AttributeType=S \
--key-schema \
AttributeName=id,KeyType=HASH \
--global-secondary-indexes \
'IndexName=created_at-index,KeySchema=[{AttributeName=created_at,KeyType=HASH}],Projection={ProjectionType=ALL},BillingMode=PAY_PER_REQUEST' \
--billing-mode PAY_PER_REQUESTUsing Terraform:
resource "aws_dynamodb_table" "certificate_monkey" {
name = "certificate-monkey"
billing_mode = "PAY_PER_REQUEST"
hash_key = "id"
attribute {
name = "id"
type = "S"
}
attribute {
name = "created_at"
type = "S"
}
global_secondary_index {
name = "created_at-index"
hash_key = "created_at"
projection_type = "ALL"
}
server_side_encryption {
enabled = true
}
point_in_time_recovery {
enabled = true
}
tags = {
Name = "certificate-monkey"
Environment = "production"
}
}Create a KMS key for encrypting private keys:
# Create KMS key
aws kms create-key --description "Certificate Monkey encryption key"
# Create alias
aws kms create-alias --alias-name alias/certificate-monkey --target-key-id <key-id>The application requires the following AWS permissions (following least privilege principle):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:GetItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Scan"
],
"Resource": [
"arn:aws:dynamodb:*:*:table/certificate-monkey",
"arn:aws:dynamodb:*:*:table/certificate-monkey/index/*"
]
},
{
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt"
],
"Resource": "arn:aws:kms:*:*:key/your-kms-key-id",
"Condition": {
"StringEquals": {
"kms:ViaService": "dynamodb.your-region.amazonaws.com"
}
}
}
]
}Note: Replace your-kms-key-id and your-region with your actual values. The application does not require admin permissions like CreateTable, DescribeTable, or GenerateDataKey.
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β API Client βββββΆβ Certificate βββββΆβ DynamoDB β
β β β Monkey β β (Storage) β
βββββββββββββββββββ β β βββββββββββββββββββ
β β β
β β β encrypted
β β βΌ
β β βββββββββββββββββββ
β βββββΆβ AWS KMS β
β β β (Encryption) β
βββββββββββββββββββ βββββββββββββββββββ
- Private Key Encryption: All private keys are encrypted using AWS KMS before storage
- API Key Authentication: Secure access control with configurable API keys
- Input Validation: Comprehensive validation of all inputs
- Certificate Validation: Ensures uploaded certificates match their CSRs
- No Sensitive Data Exposure: Private keys are redacted in API responses
certificate-monkey/
βββ cmd/server/ # Application entry point
βββ internal/
β βββ api/
β β βββ handlers/ # HTTP request handlers
β β βββ middleware/ # Authentication, logging
β β βββ routes/ # Route definitions
β βββ config/ # Configuration management
β βββ crypto/ # Cryptographic operations
β βββ models/ # Data structures
β βββ storage/ # DynamoDB operations
β βββ version/ # Version management
βββ Dockerfile # Container configuration
βββ VERSION # Current version number
βββ CHANGELOG.md # Version history and changes
βββ README.md
# Run all tests
go test ./...
# Run tests with coverage
go test -cover ./...
# Run specific package tests
go test ./internal/crypto -vThe project includes several utility scripts to streamline development and testing:
CSR Signing Script (scripts/sign-csr.sh):
# Setup test CA (first time only)
./scripts/sign-csr.sh --setup-ca
# Sign a CSR
./scripts/sign-csr.sh -c request.csr -o certificate.crt
# See all options
./scripts/sign-csr.sh --helpCSR Extraction Script (scripts/extract-csr.sh):
# Extract CSR from API
./scripts/extract-csr.sh -k key-id -a api-key -o output.csr
# See complete workflow examples
./scripts/extract-csr.sh --workflowComplete PFX Workflow Test (scripts/test-pfx-workflow.sh):
# Run end-to-end PFX generation test
./scripts/test-pfx-workflow.shThis script demonstrates the complete workflow:
- Create private key and CSR via API
- Extract CSR from API response
- Sign CSR with test CA
- Upload certificate via API
- Generate PFX file via API
- Validate the generated PFX
Comprehensive Test Runner (scripts/run-tests.sh):
# Run all tests with detailed output
./scripts/run-tests.sh
# Features:
# - Colored output for better readability
# - Individual package test results
# - Coverage reporting
# - Test summary and timing
# - CI/CD integration ready# Build binary
go build -o certificate-monkey cmd/server/main.go
# Build Docker image
docker build -t certificate-monkey .Certificate Monkey uses Conventional Commits and Semantic Versioning (SemVer) with automated release management.
We use a hybrid approach that combines:
- β Conventional Commits for structured commit messages
- β Semantic Versioning for predictable version numbers
- β Automated version calculation based on commit analysis
- β Human oversight for release timing and quality
- β Automated changelog generation from commit history
π Complete Versioning Guide - Detailed documentation with examples π Helm Integration Guide - Docker image tags and future Helm chart strategy
MAJOR.MINOR.PATCH
- MAJOR: Breaking changes (e.g., API redesign)
- MINOR: New features (backwards-compatible)
- PATCH: Bug fixes (backwards-compatible)
All commits must follow the Conventional Commits specification:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Examples:
feat: add certificate expiration notifications
fix: resolve SSL handshake timeout issue
docs: update API documentation
feat!: redesign API endpoints (breaking change)Quick Reference:
# Show commit format help
make commit-help# Preview next version based on commits
make version-preview
# Automatically bump version based on commits
make version-bump-auto
# Manual version bumps
make version-bump-patch # 0.1.0 β 0.1.1
make version-bump-minor # 0.1.0 β 0.2.0
make version-bump-major # 0.1.0 β 1.0.0
# Complete automated release
make release-auto# Check current version and build info
make version
# Show detailed project information
make infoWhen commits are pushed to main:
- Analyzes commits since last release
- Calculates next version automatically
- Creates releases for
featandfixcommits - Generates changelogs from commit messages
- Creates GitHub releases with detailed notes
Every PR automatically:
- β Validates commit messages against conventional format
- β Previews version impact (major/minor/patch/none)
- β Comments on PR with version information
- β Runs comprehensive tests and quality checks
The application embeds build information at compile time:
# Build with version information
make build
# Build release binary (optimized)
make build-release
# Check build info via API
curl http://localhost:8080/build-infoEvery release creates multiple Docker image tags for flexible deployment:
- Patch-pinned:
0.1.0,v0.1.0- Recommended for production - Minor-tracking:
0.1,v0.1- Auto-receive patch updates - Major-tracking:
0,v0- Auto-receive feature updates - Latest:
latest- Always the newest main branch build - Immutable:
main-abc1234- SHA-based tags for compliance
See Helm Integration Guide for deployment strategies and future Helm chart support.
All changes are automatically documented in CHANGELOG.md following the Keep a Changelog format.
The changelog includes:
- π₯ Breaking Changes: API changes requiring updates
- β¨ Features: New functionality
- π Bug Fixes: Issue resolutions
- π Documentation: Documentation updates
- π§ Maintenance: Internal improvements
For contributors familiar with manual versioning:
- Start using conventional commits for all new changes
- Let automation handle version calculation and releases
- Review PR comments to understand version impact
- Use manual overrides when needed (
make version-bump-major)
During the 0.x.x series, the API is considered unstable and may include breaking changes in minor versions. Once the API stabilizes, version 1.0.0 will be released with a commitment to backwards compatibility.
Certificate Monkey uses GitHub Actions for continuous integration and deployment with comprehensive automated workflows.
Automatically runs on every pull request to ensure code quality:
- Testing: Full test suite with race detection and coverage reporting
- Linting: Code quality checks with golangci-lint
- Security Scanning: Vulnerability analysis with Gosec
- Docker Build Test: Verifies container builds and runs correctly
- Coverage Enforcement: Ensures minimum 33% test coverage (target: 80% - see TODO.md)
- Multi-platform Support: Tests Linux AMD64 and ARM64 builds
# Triggered on:
# - Pull requests to main/develop
# - Pushes to develop branchBuilds and publishes Docker containers on releases and main branch pushes:
- Automated Testing: Validates all tests pass before deployment
- Docker Registry: Publishes to GitHub Container Registry (ghcr.io)
- Multi-platform Images: Builds for Linux AMD64 and ARM64
- Version Tagging: Creates semantic version tags and latest tag
- Security Scanning: Trivy vulnerability scanning and SBOM generation
- Release Creation: Automated GitHub releases with changelog integration
- Staging Deployment: Optional staging environment deployment
# Triggered on:
# - Pushes to main branch
# - Git tags (v*)
# - Published releasesContinuous security monitoring:
- CodeQL Analysis: GitHub's semantic code analysis
- Scheduled Scans: Weekly security scans
- Dependency Scanning: Monitors for vulnerable dependencies
- Security Alerts: Integration with GitHub Security tab
# Triggered on:
# - Pushes to main/develop
# - Pull requests to main
# - Weekly schedule (Mondays 2:30 AM UTC)Security Permissions: All workflows that upload security scan results (SARIF files) to GitHub's Security tab require the security-events: write permission. This enables:
- Trivy Vulnerability Scanning: Container image vulnerability analysis uploaded to Security tab
- Gosec Code Analysis: Go security linting results uploaded to Security tab
- CodeQL Analysis: GitHub's semantic code analysis for vulnerability detection
Supported Security Scanners:
- CodeQL: GitHub's native security analysis (codeql.yml workflow)
- Trivy: Container vulnerability scanning (release.yml workflow)
- Gosec: Go-specific security linting (pr-validation.yml workflow)
All security scan results are automatically uploaded to the repository's Security tab for centralized vulnerability management and tracking.
Docker images are published to GitHub Container Registry:
# Pull latest image
docker pull ghcr.io/username/certificate-monkey:latest
# Pull specific version
docker pull ghcr.io/username/certificate-monkey:0.1.0
# Available tags:
# - latest (main branch)
# - semver versions (v0.1.0, 0.1.0, 0.1, v0.1, 0, v0)
# - branch names (main, develop)
# - commit SHAs (main-abc1234)Helm-ready images: All images include OCI labels and metadata for Helm chart compatibility. See Helm Integration Guide for deployment strategies.
# Build Docker image locally
make docker-build
# Run container locally
make docker-run
# Test container health
make docker-test
# View container logs
make docker-logs
# Stop and cleanup
make docker-stop
make docker-cleanAll builds include embedded metadata:
# Check build information
curl http://localhost:8080/build-info
# Example response:
{
"service": "certificate-monkey",
"version": "0.1.0",
"build_time": "2024-01-15_14:30:25_UTC",
"git_commit": "abc1234",
"go_version": "go1.22",
"timestamp": "2024-01-15T14:30:25Z"
}Required for production deployments:
# Application configuration
API_KEY_1=your-production-api-key
DYNAMODB_TABLE=certificate-monkey-prod
KMS_KEY_ID=alias/certificate-monkey-prod
# Optional: AWS credentials (if not using IAM roles)
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1-
Feature Development:
git checkout -b feature/new-feature # Make changes git push origin feature/new-feature # Create pull request
-
PR Validation: Automated checks run on pull request
- All tests must pass
- Linting must pass
- Docker build must succeed
- Coverage must be β₯80%
-
Merge to Main: After PR approval
git checkout main git merge feature/new-feature git push origin main
-
Release Process:
# Update version and changelog make version-minor # Edit CHANGELOG.md make release-prepare # Create release git add . git commit -m "Release v0.2.0" git tag v0.2.0 git push origin main --tags
-
Automated Deployment: GitHub Actions automatically:
- Builds and tests the release
- Creates Docker images
- Publishes to container registry
- Creates GitHub release with changelog
- Deploys to staging environment
- Health Checks: Built-in container health checks
- Build Artifacts: Coverage reports and SBOMs
- Security Reports: Vulnerability scanning results
- Performance: Multi-platform build optimization
- Dependency Tracking: Automated dependency updates
π See TODO.md for comprehensive roadmap and technical debt tracking
- Complete PFX generation implementation
- Semantic versioning system
- GitHub Actions CI/CD pipeline
- Docker containerization
- Security scanning integration
- Increase unit test coverage (current: 33.5%, target: 80%)
- Enhanced error handling and logging
- Certificate template system
- Certificate expiration monitoring
- Audit logging
- Role-based access control
- Webhook notifications
- Certificate chain validation
- Integration with external CAs
For detailed implementation plans, priorities, and technical debt items, see TODO.md.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
For issues and questions, please open a GitHub issue or contact the development team.
Certificate Monkey - Secure certificate management made simple! ππ