mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2025-12-22 10:47:08 +00:00
docs: add guide and tooling for custom CA trust in Dockerized Checkmate (Fixes #2747)
This commit is contained in:
@@ -54,6 +54,12 @@ See installation instructions in [Checkmate documentation portal](https://docs.c
|
||||
|
||||
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||
|
||||
### Using a Custom CA
|
||||
|
||||
If you need to monitor internal HTTPS endpoints with certificates from private Certificate Authorities (like Smallstep), see our [Custom CA Trust Guide](./docs/custom-ca-trust.md) for Docker configuration options.
|
||||
|
||||
For more documentation, see the [docs directory](./docs/).
|
||||
|
||||
|
||||
## Translations
|
||||
|
||||
|
||||
10
docker/.gitignore
vendored
10
docker/.gitignore
vendored
@@ -12,3 +12,13 @@ dist/docker-compose-test.yaml
|
||||
/dist-mono/redis/data/*
|
||||
/dist-arm/mongo/data/*
|
||||
*.env
|
||||
|
||||
# Custom CA certificates and keys (should not be committed)
|
||||
dev/certs/*
|
||||
!dev/certs/.gitkeep
|
||||
|
||||
# Certificate artifacts
|
||||
*.key
|
||||
*.csr
|
||||
*.srl
|
||||
*.log
|
||||
|
||||
0
docker/dev/certs/.gitkeep
Normal file
0
docker/dev/certs/.gitkeep
Normal file
30
docker/dev/docker-compose.custom-ca-example.yaml
Normal file
30
docker/dev/docker-compose.custom-ca-example.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
# Dev/Test only: Not required in production
|
||||
# Example Docker Compose configuration for custom CA trust
|
||||
# This file demonstrates how to configure Checkmate to trust custom CAs
|
||||
#
|
||||
# Usage: docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml up
|
||||
|
||||
services:
|
||||
server:
|
||||
image: uptime_server:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
env_file:
|
||||
- server.env
|
||||
environment:
|
||||
# Mount your custom CA certificate
|
||||
NODE_EXTRA_CA_CERTS: /certs/custom-ca.pem
|
||||
volumes:
|
||||
# Mount the certs directory (read-only for security)
|
||||
- ./certs:/certs:ro
|
||||
depends_on:
|
||||
- redis
|
||||
- mongodb
|
||||
# Optional: Add healthcheck to ensure the server starts properly
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:52345/api/v1/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
13
docker/dev/nginx-test/docker-compose.nginx-test.yaml
Normal file
13
docker/dev/nginx-test/docker-compose.nginx-test.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Dev/Test only: Not required in production
|
||||
# Docker Compose configuration for nginx test service
|
||||
|
||||
services:
|
||||
nginx-test:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8443:443"
|
||||
volumes:
|
||||
- ../certs/host-int-cert.pem:/etc/nginx/certs/server.crt:ro
|
||||
- ../certs/host-int-key.pem:/etc/nginx/certs/server.key:ro
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
restart: unless-stopped
|
||||
14
docker/dev/nginx-test/nginx.conf
Normal file
14
docker/dev/nginx-test/nginx.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
# Dev/Test only: Not required in production
|
||||
# Nginx configuration for testing custom CA trust functionality
|
||||
|
||||
events {}
|
||||
http {
|
||||
server {
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/nginx/certs/server.crt;
|
||||
ssl_certificate_key /etc/nginx/certs/server.key;
|
||||
location / {
|
||||
return 200 "hello from tls\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
38
docs/README.md
Normal file
38
docs/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Checkmate Documentation
|
||||
|
||||
Welcome to the Checkmate documentation. This directory contains guides and references for various aspects of Checkmate deployment and configuration.
|
||||
|
||||
## Available Documentation
|
||||
|
||||
### Deployment & Configuration
|
||||
- **[Custom CA Trust Guide](./custom-ca-trust.md)** - Configure Checkmate to trust custom Certificate Authorities
|
||||
- **[Custom CA Quick Reference](./custom-ca-quick-reference.md)** - Quick setup guide for custom CA trust
|
||||
|
||||
## Docker Configuration
|
||||
|
||||
### Custom CA Trust
|
||||
If you need to monitor internal HTTPS endpoints with certificates from private Certificate Authorities (like Smallstep), see our custom CA trust documentation:
|
||||
|
||||
- **Full Guide**: [Custom CA Trust Guide](./custom-ca-trust.md)
|
||||
- **Quick Reference**: [Custom CA Quick Reference](./custom-ca-quick-reference.md)
|
||||
|
||||
### Example Configurations
|
||||
The `docker/dev/` directory contains example configurations:
|
||||
- `docker-compose.custom-ca-example.yaml` - Example Docker Compose with custom CA
|
||||
- `server-custom-ca.Dockerfile` - Example Dockerfile for OS-level CA trust
|
||||
- `export-smallstep-ca.sh` - Helper script for Smallstep CA export
|
||||
|
||||
## Contributing to Documentation
|
||||
|
||||
If you find issues or want to improve the documentation:
|
||||
|
||||
1. Check existing issues and discussions
|
||||
2. Submit a pull request with your changes
|
||||
3. Follow the same markdown formatting style
|
||||
4. Include practical examples and code snippets
|
||||
|
||||
## Getting Help
|
||||
|
||||
- [GitHub Discussions](https://github.com/bluewave-labs/checkmate/discussions)
|
||||
- [Discord Channel](https://discord.gg/NAb6H3UTjK)
|
||||
- [Documentation Portal](https://docs.checkmate.so/)
|
||||
60
docs/custom-ca-quick-reference.md
Normal file
60
docs/custom-ca-quick-reference.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Custom CA Trust - Quick Reference
|
||||
|
||||
## Fast Setup (Node-level)
|
||||
|
||||
1. **Export your CA certificate:**
|
||||
```bash
|
||||
# For Smallstep
|
||||
step certificate inspect --format pem step-ca/root_ca.crt > docker/dev/certs/custom-ca.pem
|
||||
```
|
||||
|
||||
2. **Use the provided example override:**
|
||||
```bash
|
||||
docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml up -d
|
||||
```
|
||||
|
||||
3. **Or manually update docker-compose.yaml:**
|
||||
```yaml
|
||||
services:
|
||||
server:
|
||||
environment:
|
||||
NODE_EXTRA_CA_CERTS: /certs/custom-ca.pem
|
||||
volumes:
|
||||
- ./certs:/certs:ro
|
||||
```
|
||||
|
||||
## Alternative Setup (OS-level)
|
||||
|
||||
1. **Use custom Dockerfile:**
|
||||
```bash
|
||||
docker-compose -f docker-compose.yaml -f docker-compose.custom-ca.yaml up
|
||||
```
|
||||
|
||||
## File Structure
|
||||
```
|
||||
docker/dev/
|
||||
├── docker-compose.yaml
|
||||
├── docker-compose.custom-ca-example.yaml # Example config
|
||||
├── certs/
|
||||
│ ├── README.md
|
||||
│ └── custom-ca.pem # Your CA certificate
|
||||
└── export-smallstep-ca.sh # Helper script
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
- `NODE_EXTRA_CA_CERTS=/certs/custom-ca.pem` - Node.js trust
|
||||
- Mount `./certs:/certs:ro` - Volume mount
|
||||
|
||||
## Security
|
||||
- ✅ Only trust CAs you control
|
||||
- ✅ Use read-only volume mounts
|
||||
- ✅ Keep certificates out of version control
|
||||
- ❌ Never trust untrusted CAs
|
||||
|
||||
## Troubleshooting
|
||||
- Check container logs: `docker-compose logs server`
|
||||
- Verify certificate: `docker exec -it <container> ls -la /certs/`
|
||||
- Test connection: `docker exec -it <container> wget --ca-certificate=/certs/custom-ca.pem https://your-internal-site.com`
|
||||
|
||||
## Full Documentation
|
||||
See [Custom CA Trust Guide](./custom-ca-trust.md) for detailed instructions.
|
||||
234
docs/custom-ca-trust.md
Normal file
234
docs/custom-ca-trust.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Custom Certificate Authority Trust
|
||||
|
||||
This guide explains how to configure Checkmate to trust custom Certificate Authorities (CAs) when running in Docker, particularly useful for internal/private CAs like Smallstep.
|
||||
|
||||
## Understanding Certificate Authorities
|
||||
|
||||
Certificate Authorities (CAs) are entities that issue and manage digital certificates. While public CAs (like Let's Encrypt, DigiCert) are trusted by default in most systems, private or internal CAs (like those issued by Smallstep, internal PKI systems) require explicit trust configuration.
|
||||
|
||||
When Checkmate monitors HTTPS endpoints with certificates from private CAs, it may show them as "DOWN" due to certificate validation failures, even if the service is actually accessible.
|
||||
|
||||
## Node-level Trust Approach
|
||||
|
||||
The simplest approach is to mount your custom CA certificate and configure Node.js to trust it using the `NODE_EXTRA_CA_CERTS` environment variable.
|
||||
|
||||
### Docker Compose Configuration
|
||||
|
||||
Add a volume mount for your CA certificate and set the environment variable:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
server:
|
||||
image: uptime_server:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
env_file:
|
||||
- server.env
|
||||
environment:
|
||||
NODE_EXTRA_CA_CERTS: /certs/custom-ca.pem
|
||||
volumes:
|
||||
- ./certs:/certs:ro
|
||||
depends_on:
|
||||
- redis
|
||||
- mongodb
|
||||
```
|
||||
|
||||
### Directory Structure
|
||||
|
||||
Create a `certs` directory in your Docker Compose project root:
|
||||
|
||||
```
|
||||
docker/dev/
|
||||
├── docker-compose.yaml
|
||||
├── certs/
|
||||
│ └── custom-ca.pem
|
||||
└── ...
|
||||
```
|
||||
|
||||
## OS-level Trust Approach (Debian-based)
|
||||
|
||||
For more comprehensive trust configuration, you can create a derived Dockerfile that installs your CA at the OS level.
|
||||
|
||||
### Custom Dockerfile
|
||||
|
||||
Create `docker/dev/server-custom-ca.Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine
|
||||
|
||||
# Install ca-certificates for Alpine
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./server/package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY ./server/ ./
|
||||
|
||||
# Copy your custom CA certificate
|
||||
COPY ./certs/custom-ca.crt /usr/local/share/ca-certificates/
|
||||
|
||||
# Update CA certificates
|
||||
RUN update-ca-certificates
|
||||
|
||||
EXPOSE 52345
|
||||
|
||||
CMD ["node", "src/index.js"]
|
||||
```
|
||||
|
||||
### Docker Compose Override
|
||||
|
||||
Create `docker/dev/docker-compose.custom-ca.yaml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
server:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: server-custom-ca.Dockerfile
|
||||
restart: always
|
||||
ports:
|
||||
- "52345:52345"
|
||||
env_file:
|
||||
- server.env
|
||||
depends_on:
|
||||
- redis
|
||||
- mongodb
|
||||
```
|
||||
|
||||
Run with: `docker-compose -f docker-compose.yaml -f docker-compose.custom-ca.yaml up`
|
||||
|
||||
## Alpine Linux Considerations
|
||||
|
||||
Since Checkmate uses Alpine Linux as the base image, you need to install the `ca-certificates` package:
|
||||
|
||||
```dockerfile
|
||||
# Install ca-certificates for Alpine
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
# Copy and update CA certificates
|
||||
COPY ./certs/custom-ca.crt /usr/local/share/ca-certificates/
|
||||
RUN update-ca-certificates
|
||||
```
|
||||
|
||||
## Smallstep CA Configuration
|
||||
|
||||
If you're using Smallstep as your internal CA, you can export the root CA certificate:
|
||||
|
||||
### Export Smallstep Root CA
|
||||
|
||||
```bash
|
||||
# Export the root CA certificate
|
||||
step certificate inspect --format pem step-ca/root_ca.crt > custom-ca.pem
|
||||
|
||||
# Or if you have the CA URL configured
|
||||
step certificate inspect --format pem $(step path)/certs/root_ca.crt > custom-ca.pem
|
||||
```
|
||||
|
||||
### Using the Exported Certificate
|
||||
|
||||
1. Copy the exported `custom-ca.pem` to your `docker/dev/certs/` directory
|
||||
2. Use either the Node-level or OS-level approach above
|
||||
3. Restart your Checkmate server container
|
||||
|
||||
## Security Considerations
|
||||
|
||||
⚠️ **Important Security Warning**: Only trust CAs that you control or explicitly trust. Adding untrusted CAs can compromise the security of your monitoring system.
|
||||
|
||||
- **Private CAs**: Only trust CAs from your organization's PKI infrastructure
|
||||
- **Self-signed certificates**: Consider using proper CA infrastructure instead
|
||||
- **Certificate validation**: Ensure your CA certificates are valid and not expired
|
||||
- **Access control**: Limit access to the CA certificate files in production
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Verify CA Trust
|
||||
|
||||
Test if your CA is trusted by the container:
|
||||
|
||||
```bash
|
||||
# Enter the running container
|
||||
docker exec -it <container_name> sh
|
||||
|
||||
# Check if the CA is in the trust store
|
||||
ls -la /usr/local/share/ca-certificates/
|
||||
cat /etc/ssl/certs/ca-certificates.crt | grep -A 5 -B 5 "YOUR_CA_NAME"
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Permission denied**: Ensure the CA certificate file has proper read permissions
|
||||
2. **Certificate format**: Use PEM format (.pem, .crt) for best compatibility
|
||||
3. **Container restart**: Always restart the container after adding new CA certificates
|
||||
4. **Path issues**: Verify the certificate path in your volume mounts
|
||||
|
||||
## Example: Complete Working Setup
|
||||
|
||||
Here's a complete example for a Smallstep CA setup. This demonstrates both the baseline failure (expected) and the custom CA success:
|
||||
|
||||
### Baseline Test (Should Fail)
|
||||
1. **Start Checkmate without custom CA trust:**
|
||||
```bash
|
||||
cd docker/dev
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
2. **Test connection to internal HTTPS endpoint:**
|
||||
```bash
|
||||
# This should fail with TLS error (unknown CA)
|
||||
docker-compose exec server node -e "
|
||||
const https = require('https');
|
||||
https.get('https://your-internal-site.com', res => {
|
||||
console.log('STATUS:', res.statusCode);
|
||||
}).on('error', e => {
|
||||
console.error('ERR:', e.message);
|
||||
});
|
||||
"
|
||||
```
|
||||
**Expected result**: TLS error due to unknown CA
|
||||
|
||||
### Custom CA Test (Should Succeed)
|
||||
1. **Export Smallstep Root CA:**
|
||||
```bash
|
||||
step certificate inspect --format pem step-ca/root_ca.crt > docker/dev/certs/smallstep-root-ca.pem
|
||||
```
|
||||
|
||||
2. **Update docker-compose.yaml with custom CA trust:**
|
||||
```yaml
|
||||
services:
|
||||
server:
|
||||
environment:
|
||||
NODE_EXTRA_CA_CERTS: /certs/smallstep-root-ca.pem
|
||||
volumes:
|
||||
- ./certs:/certs:ro
|
||||
```
|
||||
|
||||
3. **Restart with custom CA:**
|
||||
```bash
|
||||
docker-compose down
|
||||
docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml up -d
|
||||
```
|
||||
|
||||
4. **Test the same connection:**
|
||||
```bash
|
||||
# This should now succeed
|
||||
docker-compose exec server node -e "
|
||||
const https = require('https');
|
||||
https.get('https://your-internal-site.com', res => {
|
||||
console.log('STATUS:', res.statusCode);
|
||||
}).on('error', e => {
|
||||
console.error('ERR:', e.message);
|
||||
});
|
||||
"
|
||||
```
|
||||
**Expected result**: HTTP 200 OK
|
||||
|
||||
### Verification
|
||||
- **Baseline**: TLS failure proves default behavior (unknown CA)
|
||||
- **Custom CA**: TLS success proves custom CA trust is working
|
||||
- **Both tests must pass** to confirm the feature works correctly
|
||||
|
||||
Your Checkmate server should now trust certificates issued by your Smallstep CA, allowing you to monitor internal HTTPS endpoints without disabling SSL validation.
|
||||
143
scripts/dev/setup-custom-ca.sh
Executable file
143
scripts/dev/setup-custom-ca.sh
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Dev/Test only: Not required in production
|
||||
# This script generates test certificates for development and testing purposes
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
CERTS_DIR="$REPO_ROOT/docker/dev/certs"
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
local status=$1
|
||||
local message=$2
|
||||
case $status in
|
||||
"PASS")
|
||||
echo -e "${GREEN}[PASS]${NC} $message"
|
||||
;;
|
||||
"INFO")
|
||||
echo -e "${BLUE}[INFO]${NC} $message"
|
||||
;;
|
||||
"WARN")
|
||||
echo -e "${YELLOW}[WARN]${NC} $message"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Function to check if a command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Function to generate certificates using mkcert
|
||||
generate_certs_mkcert() {
|
||||
print_status "INFO" "Generating certificates using mkcert..."
|
||||
|
||||
# Install mkcert CA (ignore if already done)
|
||||
mkcert -install 2>/dev/null || true
|
||||
|
||||
# Copy root CA
|
||||
local ca_root
|
||||
ca_root=$(mkcert -CAROOT)
|
||||
cp "$ca_root/rootCA.pem" "$CERTS_DIR/custom-ca.pem"
|
||||
|
||||
# Generate server certificate
|
||||
mkcert -key-file "$CERTS_DIR/host-int-key.pem" \
|
||||
-cert-file "$CERTS_DIR/host-int-cert.pem" \
|
||||
host.docker.internal
|
||||
|
||||
print_status "PASS" "Certificates generated using mkcert"
|
||||
}
|
||||
|
||||
# Function to generate certificates using OpenSSL
|
||||
generate_certs_openssl() {
|
||||
print_status "INFO" "Generating certificates using OpenSSL..."
|
||||
|
||||
# Create CA private key
|
||||
openssl genrsa -out "$CERTS_DIR/ca.key" 2048
|
||||
|
||||
# Create CA certificate
|
||||
openssl req -new -x509 -days 365 -key "$CERTS_DIR/ca.key" \
|
||||
-out "$CERTS_DIR/custom-ca.pem" \
|
||||
-subj "/C=US/ST=Test/L=Test/O=Test CA/CN=Test Root CA"
|
||||
|
||||
# Create server private key
|
||||
openssl genrsa -out "$CERTS_DIR/host-int-key.pem" 2048
|
||||
|
||||
# Create server certificate signing request
|
||||
openssl req -new -key "$CERTS_DIR/host-int-key.pem" \
|
||||
-out "$CERTS_DIR/host-int-cert.csr" \
|
||||
-subj "/C=US/ST=Test/L=Test/O=Test/CN=host.docker.internal"
|
||||
|
||||
# Create extfile for SAN
|
||||
cat > "$CERTS_DIR/san.ext" << EOF
|
||||
subjectAltName=DNS:host.docker.internal,IP:127.0.0.1
|
||||
EOF
|
||||
|
||||
# Sign server certificate with CA
|
||||
openssl x509 -req -days 365 \
|
||||
-in "$CERTS_DIR/host-int-cert.csr" \
|
||||
-CA "$CERTS_DIR/custom-ca.pem" \
|
||||
-CAkey "$CERTS_DIR/ca.key" \
|
||||
-CAcreateserial \
|
||||
-out "$CERTS_DIR/host-int-cert.pem" \
|
||||
-extfile "$CERTS_DIR/san.ext"
|
||||
|
||||
# Clean up temporary files
|
||||
rm -f "$CERTS_DIR/ca.key" "$CERTS_DIR/host-int-cert.csr" "$CERTS_DIR/san.ext" "$CERTS_DIR/.srl"
|
||||
|
||||
print_status "PASS" "Certificates generated using OpenSSL"
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
print_status "INFO" "Setting up custom CA certificates for Checkmate testing"
|
||||
echo "================================================================"
|
||||
|
||||
# Create certs directory if missing
|
||||
print_status "INFO" "Creating certificates directory..."
|
||||
mkdir -p "$CERTS_DIR"
|
||||
|
||||
# Generate certificates
|
||||
if command_exists mkcert; then
|
||||
generate_certs_mkcert
|
||||
else
|
||||
print_status "WARN" "mkcert not found, falling back to OpenSSL"
|
||||
generate_certs_openssl
|
||||
fi
|
||||
|
||||
# Create duplicate for compatibility with existing overrides
|
||||
print_status "INFO" "Creating duplicate CA file for compatibility..."
|
||||
cp "$CERTS_DIR/custom-ca.pem" "$CERTS_DIR/smallstep-root-ca.pem"
|
||||
|
||||
# Verify certificates exist
|
||||
if [ ! -f "$CERTS_DIR/custom-ca.pem" ] || [ ! -f "$CERTS_DIR/host-int-cert.pem" ] || [ ! -f "$CERTS_DIR/host-int-key.pem" ]; then
|
||||
echo "Error: Failed to generate required certificates"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Print summary
|
||||
echo ""
|
||||
print_status "PASS" "All required certificates generated successfully"
|
||||
echo ""
|
||||
echo "Certificate summary:"
|
||||
echo "===================="
|
||||
ls -l "$CERTS_DIR"
|
||||
echo ""
|
||||
echo "CA certificate preview:"
|
||||
echo "======================"
|
||||
head -3 "$CERTS_DIR/custom-ca.pem"
|
||||
echo ""
|
||||
print_status "INFO" "Certificates are ready for use with Checkmate custom CA trust"
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
454
scripts/test-custom-ca.sh
Executable file
454
scripts/test-custom-ca.sh
Executable file
@@ -0,0 +1,454 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Dev/Test only: Not required in production
|
||||
# This script tests the custom CA trust functionality in development environment
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DEV_DIR="${ROOT_DIR}/docker/dev"
|
||||
CERT_DIR="${DEV_DIR}/certs"
|
||||
NGINX_DIR="${DEV_DIR}/nginx-test"
|
||||
COMPOSE_BASELINE="${DEV_DIR}/docker-compose.yaml"
|
||||
COMPOSE_CUSTOM_CA="${DEV_DIR}/docker-compose.custom-ca-example.yaml"
|
||||
COMPOSE_NGINX_TEST="${NGINX_DIR}/docker-compose.nginx-test.yaml"
|
||||
|
||||
# Test configuration
|
||||
NGINX_PORT=8443
|
||||
CHECKMATE_PORT=52345
|
||||
HEALTH_ENDPOINT="http://localhost:$CHECKMATE_PORT/api/v1/health"
|
||||
TEST_URL="https://host.docker.internal:$NGINX_PORT"
|
||||
MAX_WAIT=60
|
||||
WAIT_INTERVAL=2
|
||||
|
||||
# Global flag for cleanup behavior
|
||||
CLEAN_CERTS=false
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
local status=$1
|
||||
local message=$2
|
||||
case $status in
|
||||
"PASS")
|
||||
echo -e "${GREEN}[PASS]${NC} $message"
|
||||
;;
|
||||
"FAIL")
|
||||
echo -e "${RED}[FAIL]${NC} $message"
|
||||
;;
|
||||
"INFO")
|
||||
echo -e "${BLUE}[INFO]${NC} $message"
|
||||
;;
|
||||
"WARN")
|
||||
echo -e "${YELLOW}[WARN]${NC} $message"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Function to check if a command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Function to check if certificates exist and are valid
|
||||
check_certificates() {
|
||||
local ca_file="${CERT_DIR}/custom-ca.pem"
|
||||
local cert_file="${CERT_DIR}/host-int-cert.pem"
|
||||
local key_file="${CERT_DIR}/host-int-key.pem"
|
||||
|
||||
if [ ! -f "$ca_file" ] || [ ! -f "$cert_file" ] || [ ! -f "$key_file" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if files are not empty
|
||||
if [ ! -s "$ca_file" ] || [ ! -s "$cert_file" ] || [ ! -s "$key_file" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if CA file starts with BEGIN CERTIFICATE
|
||||
if ! head -1 "$ca_file" | grep -q "BEGIN CERTIFICATE"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to wait for service to be ready
|
||||
wait_for_service() {
|
||||
local url=$1
|
||||
local service_name=$2
|
||||
local elapsed=0
|
||||
|
||||
print_status "INFO" "Waiting for $service_name to be ready..."
|
||||
|
||||
while [ $elapsed -lt $MAX_WAIT ]; do
|
||||
if curl -s -f "$url" >/dev/null 2>&1; then
|
||||
print_status "PASS" "$service_name is ready after ${elapsed}s"
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep $WAIT_INTERVAL
|
||||
elapsed=$((elapsed + WAIT_INTERVAL))
|
||||
echo -n "."
|
||||
done
|
||||
|
||||
print_status "FAIL" "$service_name failed to start within ${MAX_WAIT}s"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to get container name by image
|
||||
get_container_name() {
|
||||
local image_name=$1
|
||||
docker ps --filter "ancestor=$image_name" --format "{{.Names}}" | head -n1
|
||||
}
|
||||
|
||||
# Function to run Node.js probe test
|
||||
run_probe_test() {
|
||||
local container_name=$1
|
||||
local test_name=$2
|
||||
local expected_exit_code=$3
|
||||
|
||||
print_status "INFO" "Running $test_name probe test..."
|
||||
|
||||
local probe_script="
|
||||
const https = require('https');
|
||||
https.get('https://host.docker.internal:8443', res => {
|
||||
console.log('STATUS', res.statusCode);
|
||||
process.exit(res.statusCode===200?0:1);
|
||||
}).on('error', e => {
|
||||
console.error('ERR', e.code||e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
"
|
||||
|
||||
local exit_code
|
||||
if docker exec -i "$container_name" node -e "$probe_script" 2>/dev/null; then
|
||||
exit_code=$?
|
||||
else
|
||||
exit_code=$?
|
||||
fi
|
||||
|
||||
if [ $exit_code -eq $expected_exit_code ]; then
|
||||
print_status "PASS" "$test_name probe test completed with expected exit code $expected_exit_code"
|
||||
return 0
|
||||
else
|
||||
print_status "FAIL" "$test_name probe test failed with exit code $exit_code (expected $expected_exit_code)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to setup certificates if needed
|
||||
setup_certificates_if_needed() {
|
||||
if check_certificates; then
|
||||
print_status "INFO" "Certificates already exist and are valid, skipping generation"
|
||||
return 0
|
||||
fi
|
||||
|
||||
print_status "INFO" "Certificates missing or invalid, generating new ones..."
|
||||
if [ -x "${ROOT_DIR}/scripts/dev/setup-custom-ca.sh" ]; then
|
||||
"${ROOT_DIR}/scripts/dev/setup-custom-ca.sh"
|
||||
else
|
||||
print_status "FAIL" "Certificate setup script not found at scripts/dev/setup-custom-ca.sh"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to create nginx test configuration
|
||||
create_nginx_test_config() {
|
||||
print_status "INFO" "Setting up nginx test configuration..."
|
||||
|
||||
mkdir -p "$NGINX_DIR"
|
||||
|
||||
# Create nginx.conf if it doesn't exist
|
||||
if [ ! -f "${NGINX_DIR}/nginx.conf" ]; then
|
||||
cat > "${NGINX_DIR}/nginx.conf" << 'EOF'
|
||||
events {}
|
||||
http {
|
||||
server {
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/nginx/certs/server.crt;
|
||||
ssl_certificate_key /etc/nginx/certs/server.key;
|
||||
location / {
|
||||
return 200 "hello from tls\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Create docker-compose.nginx-test.yaml if it doesn't exist
|
||||
if [ ! -f "$COMPOSE_NGINX_TEST" ]; then
|
||||
cat > "$COMPOSE_NGINX_TEST" << EOF
|
||||
services:
|
||||
nginx-test:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "$NGINX_PORT:443"
|
||||
volumes:
|
||||
- ../certs/host-int-cert.pem:/etc/nginx/certs/server.crt:ro
|
||||
- ../certs/host-int-key.pem:/etc/nginx/certs/server.key:ro
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
restart: unless-stopped
|
||||
EOF
|
||||
fi
|
||||
|
||||
print_status "PASS" "Nginx test configuration created"
|
||||
}
|
||||
|
||||
# Function to start nginx test service
|
||||
start_nginx_test() {
|
||||
print_status "INFO" "Starting nginx test service..."
|
||||
|
||||
cd "$NGINX_DIR"
|
||||
docker-compose -f docker-compose.nginx-test.yaml up -d
|
||||
|
||||
# Wait for nginx to be ready
|
||||
local elapsed=0
|
||||
while [ $elapsed -lt $MAX_WAIT ]; do
|
||||
if curl -s -f -k "https://localhost:$NGINX_PORT" >/dev/null 2>&1; then
|
||||
print_status "PASS" "Nginx test service is ready"
|
||||
cd "$ROOT_DIR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
sleep $WAIT_INTERVAL
|
||||
elapsed=$((elapsed + WAIT_INTERVAL))
|
||||
echo -n "."
|
||||
done
|
||||
|
||||
print_status "FAIL" "Nginx test service failed to start"
|
||||
cd "$ROOT_DIR"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to stop nginx test service
|
||||
stop_nginx_test() {
|
||||
print_status "INFO" "Stopping nginx test service..."
|
||||
cd "$NGINX_DIR"
|
||||
docker-compose -f docker-compose.nginx-test.yaml down 2>/dev/null || true
|
||||
cd "$ROOT_DIR"
|
||||
}
|
||||
|
||||
# Function to run baseline test
|
||||
run_baseline_test() {
|
||||
print_status "INFO" "Running baseline test (should fail due to unknown CA)..."
|
||||
|
||||
# Start baseline Checkmate
|
||||
cd "$DEV_DIR"
|
||||
docker-compose -f docker-compose.yaml up -d --build
|
||||
|
||||
# Wait for Checkmate to be ready
|
||||
if ! wait_for_service "$HEALTH_ENDPOINT" "Checkmate baseline"; then
|
||||
cd "$ROOT_DIR"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get container name
|
||||
local container_name
|
||||
container_name=$(get_container_name "uptime_server")
|
||||
if [ -z "$container_name" ]; then
|
||||
print_status "FAIL" "Could not find Checkmate server container"
|
||||
cd "$ROOT_DIR"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Run probe test (should fail)
|
||||
if run_probe_test "$container_name" "baseline" 1; then
|
||||
print_status "PASS" "Baseline test completed - TLS failure as expected"
|
||||
cd "$ROOT_DIR"
|
||||
return 0
|
||||
else
|
||||
print_status "FAIL" "Baseline test failed - unexpected behavior"
|
||||
cd "$ROOT_DIR"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to run custom CA test
|
||||
run_custom_ca_test() {
|
||||
print_status "INFO" "Running custom CA test (should succeed)..."
|
||||
|
||||
# Stop baseline Checkmate
|
||||
cd "$DEV_DIR"
|
||||
docker-compose -f docker-compose.yaml down
|
||||
|
||||
# Start Checkmate with custom CA
|
||||
docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml up -d --build
|
||||
|
||||
# Wait for Checkmate to be ready
|
||||
if ! wait_for_service "$HEALTH_ENDPOINT" "Checkmate custom CA"; then
|
||||
cd "$ROOT_DIR"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get container name
|
||||
local container_name
|
||||
container_name=$(get_container_name "uptime_server")
|
||||
if [ -z "$container_name" ]; then
|
||||
print_status "FAIL" "Could not find Checkmate server container"
|
||||
cd "$ROOT_DIR"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Run probe test (should succeed)
|
||||
if run_probe_test "$container_name" "custom CA" 0; then
|
||||
print_status "PASS" "Custom CA test completed - TLS success as expected"
|
||||
cd "$ROOT_DIR"
|
||||
return 0
|
||||
else
|
||||
print_status "FAIL" "Custom CA test failed - unexpected behavior"
|
||||
cd "$ROOT_DIR"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to cleanup everything
|
||||
cleanup() {
|
||||
print_status "INFO" "Cleaning up test environment..."
|
||||
|
||||
# Stop all services
|
||||
cd "$DEV_DIR"
|
||||
docker-compose -f docker-compose.yaml down 2>/dev/null || true
|
||||
docker-compose -f docker-compose.yaml -f docker-compose.custom-ca-example.yaml down 2>/dev/null || true
|
||||
|
||||
stop_nginx_test
|
||||
|
||||
# Only remove certificates if --clean was specified
|
||||
if [ "$CLEAN_CERTS" = true ]; then
|
||||
print_status "INFO" "Removing certificates as requested with --clean"
|
||||
rm -rf "${CERT_DIR:?}"/*
|
||||
else
|
||||
print_status "INFO" "Preserving certificates for future test runs"
|
||||
fi
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
print_status "PASS" "Cleanup completed"
|
||||
}
|
||||
|
||||
# Function to show usage
|
||||
show_usage() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --clean Clean up all test containers and certificates"
|
||||
echo " --help Show this help message"
|
||||
echo ""
|
||||
echo "This script tests the custom CA trust functionality in Checkmate."
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
local clean_only=false
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--clean)
|
||||
clean_only=true
|
||||
CLEAN_CERTS=true
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
show_usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
print_status "INFO" "Starting Checkmate Custom CA Trust Test"
|
||||
echo "=================================================="
|
||||
|
||||
if [ "$clean_only" = true ]; then
|
||||
cleanup
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check prerequisites
|
||||
if ! command_exists docker; then
|
||||
print_status "FAIL" "Docker is not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command_exists docker-compose; then
|
||||
print_status "FAIL" "docker-compose is not installed or not in PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if required files exist
|
||||
if [ ! -f "$COMPOSE_BASELINE" ]; then
|
||||
print_status "FAIL" "Baseline docker-compose.yaml not found at $COMPOSE_BASELINE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$COMPOSE_CUSTOM_CA" ]; then
|
||||
print_status "FAIL" "Custom CA docker-compose override not found at $COMPOSE_CUSTOM_CA"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Setup test environment
|
||||
setup_certificates_if_needed
|
||||
create_nginx_test_config
|
||||
start_nginx_test
|
||||
|
||||
# Run tests
|
||||
local baseline_result=false
|
||||
local custom_ca_result=false
|
||||
|
||||
if run_baseline_test; then
|
||||
baseline_result=true
|
||||
fi
|
||||
|
||||
if run_custom_ca_test; then
|
||||
custom_ca_result=true
|
||||
fi
|
||||
|
||||
# Print summary
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
print_status "INFO" "Test Summary"
|
||||
echo "=================================================="
|
||||
|
||||
if [ "$baseline_result" = true ]; then
|
||||
print_status "PASS" "Baseline: TLS failure as expected"
|
||||
else
|
||||
print_status "FAIL" "Baseline: Unexpected behavior"
|
||||
fi
|
||||
|
||||
if [ "$custom_ca_result" = true ]; then
|
||||
print_status "PASS" "Custom CA: TLS success (STATUS 200)"
|
||||
else
|
||||
print_status "FAIL" "Custom CA: Unexpected behavior"
|
||||
fi
|
||||
|
||||
if [ "$baseline_result" = true ] && [ "$custom_ca_result" = true ]; then
|
||||
echo ""
|
||||
print_status "PASS" "All tests passed! Custom CA trust is working correctly."
|
||||
echo ""
|
||||
print_status "INFO" "To clean up, run: $0 --clean"
|
||||
exit 0
|
||||
else
|
||||
echo ""
|
||||
print_status "FAIL" "Some tests failed. Custom CA trust may not be working correctly."
|
||||
echo ""
|
||||
print_status "INFO" "To clean up, run: $0 --clean"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Trap cleanup on script exit
|
||||
trap cleanup EXIT
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user