Network & Security
Cloudillo’s network architecture emphasizes security, privacy, and ease of deployment. The system uses modern TLS with automatic certificate management via ACME (Let’s Encrypt) and supports both HTTP/1.1 and HTTP/2.
TLS/HTTPS Architecture
Rustls Integration
Cloudillo uses Rustls, a modern, memory-safe TLS library written in pure Rust:
Advantages:
- ✅ Memory-safe: No buffer overflows or memory corruption
- ✅ No unsafe code: Aligns with Cloudillo’s
#![forbid(unsafe_code)] - ✅ Modern protocols: TLS 1.2 and TLS 1.3
- ✅ High performance: Zero-copy design
- ✅ Small footprint: Minimal dependencies
Version:
[dependencies]
rustls = "0.23"
tokio-rustls = "0.26"HTTP/2 Support
Cloudillo supports HTTP/2 via ALPN (Application-Layer Protocol Negotiation):
TLS Configuration Steps:
1. Create ServerConfig builder with no client auth
2. Set certificate resolver
3. Configure ALPN protocols:
- "h2" (HTTP/2, preferred)
- "http/1.1" (fallback for compatibility)
4. Client selects protocol during TLS handshakeBenefits:
- Multiplexing (multiple requests over single connection)
- Server push (future feature)
- Header compression
- Binary protocol efficiency
SNI (Server Name Indication)
SNI enables hosting multiple domains on one IP address:
CertResolver Structure:
certs: RwLock - In-memory cache of domain → CertifiedKeyauth_adapter: Reference to persistent certificate storage
Resolution Algorithm:
- Extract domain from ClientHello SNI field
- Look up domain in in-memory cache
- Return cached certificate if found
- (On-demand loading if not cached - see Dynamic Loading)
Flow:
Client → TLS ClientHello with SNI="alice.example.com"
↓
CertResolver extracts "alice.example.com"
↓
Looks up certificate for alice.example.com
↓
Returns appropriate certificate
↓
TLS handshake completesACME (Let’s Encrypt) Integration
Automatic Certificate Management
Cloudillo uses instant-acme for automatic certificate provisioning:
Algorithm: Request ACME Certificate
1. Create ACME account:
- Contact email: mailto:{email}
- Agree to terms of service
- Connect to Let's Encrypt production server
2. Create order for domain
- Specify domain identifiers
3. Get authorization challenges
- Find HTTP-01 challenge (not DNS-01)
- Extract challenge token
4. Store challenge response in AuthAdapter
- key: challenge.token
- value: key_authorization(challenge)
5. Validate challenge
- ACME server verifies HTTP endpoint
6. Poll for order completion (max 10 attempts, 5s intervals)
- Retry until OrderStatus::Ready
7. Generate Certificate Signing Request (CSR)
- Create certificate parameters for domain
- Serialize to DER format
8. Finalize order with CSR
9. Download signed certificate chain
10. Store certificate in AuthAdapter
- Persist for reuse and renewalHTTP-01 Challenge
Cloudillo uses HTTP-01 challenge for domain validation:
Challenge Endpoint:
HTTP Route: GET /.well-known/acme-challenge/{token}
Handler Algorithm:
1. Extract token from URL path
2. Query AuthAdapter for stored challenge response
3. Return challenge value (plain text)
This endpoint must be publicly accessible over HTTP (port 80)
for ACME validation.Challenge Flow:
ACME server → GET http://example.com/.well-known/acme-challenge/{token}
↓
Cloudillo → Lookup token in AuthAdapter
↓
Cloudillo → Return challenge response
↓
ACME server validates
↓
Domain ownership confirmed
↓
Certificate issuedCertificate Renewal
Certificates auto-renew before expiration:
Algorithm: Auto-Renew Certificates (runs every 12 hours)
1. Loop continuously:
a. Sleep 12 hours
b. Get all domains from AuthAdapter
c. For each domain:
- Read certificate from storage
- Parse expiration date
- Calculate days until expiry
- If < 30 days:
* Request new certificate via ACME
* On success:
- Log certificate renewed
- Update in-memory certificate cache
* On error:
- Log error
- Continue to next domainCertificate Storage
Certificates stored in AuthAdapter via trait interface:
AuthAdapter Methods:
create_cert(domain, cert_chain)- Store new certificate chainread_cert(domain)- Retrieve certificate (returns Option<Vec>)delete_cert(domain)- Remove certificatelist_domains()- Get all registered domains
SQLite Schema:
CREATE TABLE certificates (
domain TEXT PRIMARY KEY,
cert_chain BLOB NOT NULL,
private_key BLOB NOT NULL,
created_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL
);
CREATE INDEX idx_certs_expiry ON certificates(expires_at);Certificate Resolver
In-Memory Cache
Certificates cached in memory for performance:
Data Structures:
App.certs: RwLock<HashMap<domain, CertifiedKey»CertResolver.certs: RwLock<HashMap<domain, CertifiedKey»CertResolver.auth_adapter: Persistent storage reference
load_certificate(domain) Algorithm:
- Query AuthAdapter for certificate bytes
- Parse certificate chain and private key
- Acquire write lock on certs
- Insert domain → CertifiedKey into cache
reload_all() Algorithm:
- Get all domains from AuthAdapter
- For each domain, call load_certificate
- Repopulate entire in-memory cache
Dynamic Loading
Certificates loaded on-demand if not in cache:
Algorithm: ResolvesServerCert::resolve(client_hello)
1. Extract domain from ClientHello SNI
2. Try read lock on cache:
- If domain found: Return cached certificate
- If not found: Continue to step 3
3. Load from storage (async I/O in blocking task):
a. Read certificate bytes from AuthAdapter
b. Parse certificate and key
c. Acquire write lock on cache
d. Insert into cache
e. Return certificate
This pattern ensures:
- Fast path (cached): Lock-free read
- Slow path (on-demand): Blocking async I/O off async runtimeServer Architecture
Dual-Server Setup
Cloudillo runs two servers:
HTTPS Server (primary, always running):
Algorithm: HTTPS Server
1. Configure TLS with Rustls:
- No client authentication
- Dynamic certificate resolver (SNI-based)
2. Bind TCP listener to configured address (e.g., 0.0.0.0:443)
3. Accept connections in loop:
a. Accept TCP stream
b. Upgrade to TLS connection
c. Spawn async task to handle connectionHTTP Server (conditional, for ACME only):
Algorithm: HTTP Server (if ACME enabled)
1. Bind TCP listener to 0.0.0.0:80
2. Spawn separate async task to run in parallel
3. Accept connections in loop:
a. Accept TCP stream
b. Parse HTTP request
c. If path is /.well-known/acme-challenge/{token}:
- Handle ACME challenge request
d. Else:
- Send HTTP 301 redirect to HTTPSGraceful Shutdown
Algorithm: Server Shutdown
1. Create oneshot channel for shutdown signal (tx, rx)
2. Spawn server task (runs in background)
3. Wait for either:
a. Ctrl+C signal (SIGINT), or
b. Shutdown signal sent to tx
4. On shutdown trigger:
a. Log "Shutting down gracefully..."
b. Abort server task
c. Allow in-flight connections to complete
d. Return success
This allows clean shutdown without dropping active requests.Cryptography
Algorithms
TLS:
- TLS 1.2 (minimum)
- TLS 1.3 (preferred)
- Cipher suites: Modern, secure defaults from Rustls
Action Signatures:
- Algorithm: ES384 (ECDSA with P-384 and SHA-384)
- Key size: 384 bits
- Signature size: ~96 bytes
Content Hashing:
- Algorithm: SHA256
- Used for: File IDs, action IDs, content addressing
- Output: 256 bits (32 bytes)
Password Hashing:
- Algorithm: bcrypt
- Work factor: 12 (default)
- Salt: Random, unique per password
Key Management
Profile Signing Keys (ES384):
Algorithm: Generate and Store Profile Key
1. Generate random key pair using P-384 curve
- Use OS random number generator
- Both signing_key (private) and verifying_key (public)
2. Store in AuthAdapter:
- tenant_id: Identifies user/organization
- key_id: Identifies specific key version
- public_key: Shared publicly, used for verification
- private_key: Kept secret, used for signingTLS Certificates (RSA or ECDSA):
- Generated by Let’s Encrypt
- Stored in AuthAdapter
- Auto-renewed before expiration
Secure Random Generation
Algorithm: Generate Secure Session ID
1. Request 32 random bytes from OS entropy source (OsRng)
2. Encode bytes as URL-safe base64 string
3. Return base64-encoded session ID
This produces 256-bit random session identifiers suitable for
authentication tokens and nonces.Security Best Practices
No Unsafe Code
Cloudillo enforces memory safety:
#![forbid(unsafe_code)]All dependencies must also be free of unsafe code where possible.
Input Validation
URL Validation Algorithm:
- Parse URL string
- Verify scheme is “https” (reject “http”, “ftp”, etc.)
- Extract domain/host from URL
- Validate domain format (valid characters, not IP, proper TLD)
- Return error if any check fails
JWT Validation Algorithm:
- Decode token (checks base64 format)
- Verify signature using ES384 algorithm with public key
- Validate expiration time (exp claim < current time)
- Extract and return claims
Size Limits:
- MAX_REQUEST_SIZE: 100 MB (entire HTTP request)
- MAX_JSON_SIZE: 10 MB (JSON request body)
- MAX_ACTION_SIZE: 1 MB (individual action token)
Rate Limiting
Per-IP Rate Limiter Algorithm:
Data: RateLimiter with HashMap<IpAddr → VecDeque<Instant>>
check(ip) Algorithm:
1. Acquire write lock on limits map
2. Get or create rate limit entry for IP
3. Get current time
4. Remove all requests older than 1 minute
5. If requests >= max_per_minute: return false (rate limited)
6. Record new request timestamp
7. Return true (request allowed)
This sliding-window approach counts requests in the last 60 seconds.Per-User Rate Limits (hardcoded):
MAX_ACTIONS_PER_HOUR: 100 actions (limit posting/interactions)MAX_FILE_UPLOADS_PER_HOUR: 50 uploads (limit bandwidth)MAX_DB_QUERIES_PER_MINUTE: 1000 queries (limit database load)
CORS Configuration
CORS Layer Configuration:
1. Allow Origin: Mirror request (echo Origin header)
- Allows any origin to make requests
- Each response includes Access-Control-Allow-Origin: <requesting origin>
2. Allowed Methods: GET, POST, PATCH, DELETE
- Other methods (PUT, HEAD) are rejected
3. Allowed Headers: Authorization, Content-Type
- Custom headers must be explicitly allowed
4. Credentials: Enabled
- Allows cookies and credentials in cross-origin requests
5. Max Age: 3600 seconds (1 hour)
- Browsers cache CORS preflight responses for 1 hour
6. Apply to API routes
- All routes inherit CORS configurationMonitoring & Logging
Structured Logging
Tracing Library Format:
- Each log entry includes message + contextual fields
- Fields use structured format (field = value)
- Levels: debug, info, warn, error
Request Logging Example:
- method: HTTP verb
- path: Request URI
- remote_addr: Client IP
- Message: “Handling request”
Error Logging Example:
- error: Exception message
- context: Category (e.g., “certificate_renewal”)
- domain: Relevant domain
Security Events
Key security-related events logged with structured context:
Rate Limit Hit:
- ip: Client IP address
- reason: “rate_limit_exceeded”
Token Rejection:
- token_issuer: Who signed the token
- reason: “signature_verification_failed”
Authorization Failure:
- ip: Client IP
- path: Request URL
- reason: “permission_denied”
Metrics
SecurityMetrics Structure:
failed_auth_attempts(AtomicU64) - Failed login attemptsrate_limit_hits(AtomicU64) - Rate limit violationscert_renewals(AtomicU64) - Successful certificate renewalssignature_failures(AtomicU64) - Token signature verification failuresblocked_ips(AtomicUsize) - Currently rate-limited IP addresses
Deployment Considerations
Firewall Configuration
Required Ports:
- 443 (HTTPS) - Primary server
- 80 (HTTP) - ACME challenges only (optional redirect to HTTPS)
Firewall Rules:
# Allow HTTPS
sudo ufw allow 443/tcp
# Allow HTTP for ACME
sudo ufw allow 80/tcp
# Deny all other inbound
sudo ufw default deny incoming
sudo ufw default allow outgoingReverse Proxy
If using nginx or similar:
server {
listen 443 ssl http2;
server_name *.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass https://localhost:8443;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket support
location /ws/ {
proxy_pass https://localhost:8443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}Docker Deployment
FROM rust:1.75 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates
COPY --from=builder /app/target/release/basic-server /usr/local/bin/
EXPOSE 8443 80
CMD ["basic-server"]Docker Compose:
version: '3.8'
services:
cloudillo:
build: .
ports:
- "443:8443"
- "80:80"
environment:
MODE: standalone
BASE_ID_TAG: example.com
ACME_EMAIL: admin@example.com
DB_DIR: /data/db
DATA_DIR: /data/blobs
volumes:
- ./data:/data
restart: unless-stoppedSecurity Checklist
Before Deployment
- TLS certificates configured (ACME or manual)
- Firewall rules in place
- Rate limiting enabled
- CORS properly configured
- Logging enabled
- Backups configured
- Password policies enforced
- Security headers set
Ongoing Maintenance
- Monitor certificate expiration
- Review security logs weekly
- Update dependencies monthly
- Backup data daily
- Test disaster recovery quarterly
- Audit access controls quarterly
- Review rate limits as needed
See Also
- Federation - Inter-instance communication
- Identity System - Cryptographic keys
- [Access Control](/architecture/data-layer/access-control/access - JWT validation
- System Architecture - Overall architecture