armature-framework 0.2.2

A modern, type-safe HTTP framework for Rust inspired by Angular and NestJS. Features dependency injection, decorators, middleware, authentication (JWT/OAuth2/SAML), validation, OpenAPI/Swagger, caching, job queues, and observability.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# HTTPS/TLS Guide

Complete guide to adding HTTPS/TLS support to your Armature applications.

## Table of Contents

- [Overview]#overview
- [Quick Start]#quick-start
- [Certificate Management]#certificate-management
- [Production Deployment]#production-deployment
- [HTTP to HTTPS Redirect]#http-to-https-redirect
- [Best Practices]#best-practices
- [Troubleshooting]#troubleshooting

## Overview

Armature provides built-in HTTPS/TLS support using `rustls`, a modern TLS library written in Rust. This enables secure communication between clients and your server.

### Features

- ✅ TLS 1.2 and TLS 1.3 support
- ✅ HTTP/2 and HTTP/1.1 ALPN
- ✅ Certificate loading from PEM files
- ✅ Self-signed certificates for development
- ✅ HTTP to HTTPS automatic redirect
- ✅ Zero-copy TLS with tokio-rustls

## Quick Start

### Development (Self-Signed Certificates)

For local development, you can use automatically generated self-signed certificates:

```rust
use armature_framework::prelude::*;

#[module()]
#[derive(Default)]
struct AppModule;

#[tokio::main]
async fn main() -> Result<()> {
    let app = Application::create::<AppModule>().await;

    // Generate self-signed certificate (development only!)
    let tls_config = TlsConfig::self_signed(&["localhost", "127.0.0.1"])?;

    // Start HTTPS server
    app.listen_https(8443, tls_config).await?;

    Ok(())
}
```

**Build with the `self-signed-certs` feature:**

```bash
cargo run --features self-signed-certs
```

**Test:**

```bash
curl -k https://localhost:8443/
```

> ⚠️ **Warning**: Self-signed certificates should NEVER be used in production!

### Production (Real Certificates)

For production, use certificates from a trusted Certificate Authority:

```rust
use armature_framework::prelude::*;

#[module()]
#[derive(Default)]
struct AppModule;

#[tokio::main]
async fn main() -> Result<()> {
    let app = Application::create::<AppModule>().await;

    // Load real certificates
    let tls_config = TlsConfig::from_pem_files(
        "/etc/ssl/certs/your-cert.pem",
        "/etc/ssl/private/your-key.pem"
    )?;

    // Start HTTPS server
    app.listen_https(443, tls_config).await?;

    Ok(())
}
```

## Certificate Management

### Loading from Files

The most common approach is to load certificates from PEM files:

```rust
use armature_core::TlsConfig;

// Load from file paths
let tls_config = TlsConfig::from_pem_files("cert.pem", "key.pem")?;
```

### Loading from Memory

You can also load certificates from byte arrays:

```rust
use armature_core::TlsConfig;

let cert_pem = include_bytes!("../certs/cert.pem");
let key_pem = include_bytes!("../certs/key.pem");

let tls_config = TlsConfig::from_pem_bytes(cert_pem, key_pem)?;
```

### Certificate Formats

Armature accepts certificates in **PEM format**:

- **Certificate**: `cert.pem` or `fullchain.pem`
- **Private Key**: `key.pem` or `privkey.pem`

**Example PEM Certificate:**

```
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKJ...
...
-----END CERTIFICATE-----
```

**Example PEM Private Key:**

```
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0...
...
-----END PRIVATE KEY-----
```

### Generating Development Certificates

#### Using OpenSSL

```bash
openssl req -x509 -newkey rsa:4096 \
  -keyout key.pem -out cert.pem \
  -days 365 -nodes \
  -subj "/CN=localhost"
```

#### Using mkcert (Recommended for Development)

[mkcert](https://github.com/FiloSottile/mkcert) automatically creates and installs a local CA:

```bash
# Install mkcert
brew install mkcert  # macOS
# or: choco install mkcert  # Windows
# or: apt install mkcert  # Ubuntu

# Install local CA
mkcert -install

# Generate certificate
mkcert localhost 127.0.0.1 ::1
```

This creates `localhost+2.pem` and `localhost+2-key.pem`.

## Production Deployment

### Let's Encrypt (Recommended)

[Let's Encrypt](https://letsencrypt.org/) provides free, automated TLS certificates.

#### Using Certbot

```bash
# Install certbot
sudo apt install certbot  # Ubuntu/Debian

# Get certificate
sudo certbot certonly --standalone -d yourdomain.com

# Certificates will be in:
# /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# /etc/letsencrypt/live/yourdomain.com/privkey.pem
```

#### Using Armature

```rust
use armature_framework::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let app = Application::create::<AppModule>().await;

    let tls_config = TlsConfig::from_pem_files(
        "/etc/letsencrypt/live/yourdomain.com/fullchain.pem",
        "/etc/letsencrypt/live/yourdomain.com/privkey.pem"
    )?;

    app.listen_https(443, tls_config).await?;

    Ok(())
}
```

### Certificate Renewal

Let's Encrypt certificates expire every 90 days. Set up automatic renewal:

```bash
# Test renewal
sudo certbot renew --dry-run

# Add to crontab for automatic renewal
sudo crontab -e
# Add: 0 0 * * * certbot renew --quiet && systemctl restart your-app
```

### File Permissions

Ensure proper permissions for certificate files:

```bash
# Certificate (public) - readable by all
chmod 644 /etc/ssl/certs/your-cert.pem

# Private key - readable only by owner
chmod 600 /etc/ssl/private/your-key.pem
chown root:root /etc/ssl/private/your-key.pem
```

## HTTP to HTTPS Redirect

Automatically redirect HTTP traffic to HTTPS:

```rust
use armature_framework::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let app = Application::create::<AppModule>().await;

    let tls_config = TlsConfig::from_pem_files("cert.pem", "key.pem")?;

    // Configure HTTPS with HTTP redirect
    let https_config = HttpsConfig::new("0.0.0.0:443", tls_config)
        .with_http_redirect("0.0.0.0:80");

    // This starts both:
    // - HTTPS server on port 443
    // - HTTP server on port 80 (redirects to HTTPS)
    app.listen_with_config(https_config).await?;

    Ok(())
}
```

**How it works:**

1. HTTP server listens on port 80
2. All requests receive a `301 Moved Permanently` response
3. `Location` header points to the HTTPS URL
4. Client automatically follows redirect to HTTPS

**Example redirect response:**

```http
HTTP/1.1 301 Moved Permanently
Location: https://example.com/path
```

## Best Practices

### Security

1. **Never Use Self-Signed Certs in Production**
   ```rust
   #[cfg(debug_assertions)]
   let tls = TlsConfig::self_signed(&["localhost"])?;

   #[cfg(not(debug_assertions))]
   let tls = TlsConfig::from_pem_files("cert.pem", "key.pem")?;
   ```

2. **Protect Private Keys**
   - Store in secure locations (e.g., `/etc/ssl/private/`)
   - Use `chmod 600` to restrict access
   - Never commit to version control
   - Consider using secrets management (Vault, AWS Secrets Manager)

3. **Use Strong Certificates**
   - RSA 2048-bit minimum (4096-bit recommended)
   - Or ECDSA P-256 or P-384
   - From trusted Certificate Authorities

4. **Keep Certificates Updated**
   - Monitor expiration dates
   - Automate renewal
   - Test renewal process

### Configuration

1. **Environment-Based Config**
   ```rust
   use std::env;

   let cert_path = env::var("TLS_CERT_PATH")
       .unwrap_or_else(|_| "/etc/ssl/certs/cert.pem".to_string());

   let key_path = env::var("TLS_KEY_PATH")
       .unwrap_or_else(|_| "/etc/ssl/private/key.pem".to_string());

   let tls_config = TlsConfig::from_pem_files(cert_path, key_path)?;
   ```

2. **Graceful Error Handling**
   ```rust
   let tls_config = match TlsConfig::from_pem_files("cert.pem", "key.pem") {
       Ok(config) => config,
       Err(e) => {
           eprintln!("Failed to load TLS certificates: {}", e);
           eprintln!("Make sure cert.pem and key.pem exist and are readable");
           return Err(e);
       }
   };
   ```

3. **Use Standard Ports**
   - HTTPS: port 443
   - HTTP: port 80 (for redirects only)

### Performance

1. **HTTP/2 is Enabled by Default**
   - Armature automatically negotiates HTTP/2 via ALPN
   - Falls back to HTTP/1.1 if needed

2. **TLS Session Resumption**
   - rustls handles session resumption automatically
   - Reduces handshake overhead for repeat connections

3. **Connection Pooling**
   - Use keep-alive connections
   - Let clients reuse TLS sessions

## Troubleshooting

### Certificate Errors

**Problem**: `Failed to create TLS config: invalid certificate`

**Solutions:**
- Verify certificate is in PEM format
- Check certificate is not expired: `openssl x509 -in cert.pem -noout -dates`
- Ensure certificate matches private key:
  ```bash
  openssl x509 -noout -modulus -in cert.pem | openssl md5
  openssl rsa -noout -modulus -in key.pem | openssl md5
  # MD5 hashes should match
  ```

### Permission Errors

**Problem**: `Failed to open key file: Permission denied`

**Solutions:**
```bash
# Check file permissions
ls -l key.pem

# Fix permissions
chmod 600 key.pem

# Or run with appropriate user
sudo -u www-data ./your-app
```

### Port Binding Errors

**Problem**: `Address already in use (os error 98)`

**Solutions:**
```bash
# Check what's using the port
sudo lsof -i :443

# Kill the process
sudo kill <PID>

# Or use a different port for testing
# app.listen_https(8443, tls_config).await?;
```

### Browser Warnings

**Problem**: Browser shows "Your connection is not private"

**Solutions:**
- **Development**: Expected with self-signed certs, click "Advanced" → "Proceed"
- **Production**: Use certificates from a trusted CA (Let's Encrypt)
- **Testing**: Import self-signed cert into browser's trusted certificates

### TLS Handshake Failures

**Problem**: `TLS handshake failed: ...`

**Solutions:**
- Check client supports TLS 1.2/1.3
- Verify certificate chain is complete (use `fullchain.pem`, not just `cert.pem`)
- Test with OpenSSL:
  ```bash
  openssl s_client -connect localhost:443 -servername localhost
  ```

## Examples

### Basic HTTPS Server

```rust
use armature_framework::prelude::*;

#[derive(Default)]
pub struct ApiService;

#[injectable]
impl ApiService {
    pub fn get_data(&self) -> String {
        "Secure data".to_string()
    }
}

pub struct ApiController {
    api_service: std::sync::Arc<ApiService>,
}

#[controller("/api")]
impl ApiController {
    pub fn new(api_service: std::sync::Arc<ApiService>) -> Self {
        Self { api_service }
    }

    #[get("/data")]
    pub async fn get_data(&self, _req: HttpRequest) -> Result<HttpResponse> {
        let data = self.api_service.get_data();
        Ok(HttpResponse::ok().with_json(&serde_json::json!({
            "data": data,
            "secure": true
        }))?)
    }
}

#[module({
    providers: [ApiService],
    controllers: [ApiController],
})]
pub struct AppModule {}

#[tokio::main]
async fn main() -> Result<()> {
    let app = Application::create::<AppModule>().await;

    #[cfg(feature = "self-signed-certs")]
    let tls_config = TlsConfig::self_signed(&["localhost"])?;

    #[cfg(not(feature = "self-signed-certs"))]
    let tls_config = TlsConfig::from_pem_files("cert.pem", "key.pem")?;

    app.listen_https(8443, tls_config).await?;

    Ok(())
}
```

### HTTPS with Environment Config

```rust
use armature_framework::prelude::*;
use std::env;

#[tokio::main]
async fn main() -> Result<()> {
    let app = Application::create::<AppModule>().await;

    // Load config from environment
    let cert_path = env::var("TLS_CERT_PATH")?;
    let key_path = env::var("TLS_KEY_PATH")?;
    let port: u16 = env::var("HTTPS_PORT")
        .unwrap_or_else(|_| "443".to_string())
        .parse()?;

    let tls_config = TlsConfig::from_pem_files(cert_path, key_path)?;

    println!("Starting HTTPS server on port {}", port);
    app.listen_https(port, tls_config).await?;

    Ok(())
}
```

### Full Production Setup

```rust
use armature_framework::prelude::*;
use std::env;

#[tokio::main]
async fn main() -> Result<()> {
    // Load environment variables
    dotenv::dotenv().ok();

    let app = Application::create::<AppModule>().await;

    let domain = env::var("DOMAIN")?;
    let cert_dir = env::var("CERT_DIR").unwrap_or_else(|_| "/etc/letsencrypt/live".to_string());

    let cert_path = format!("{}/{}/fullchain.pem", cert_dir, domain);
    let key_path = format!("{}/{}/privkey.pem", cert_dir, domain);

    let tls_config = TlsConfig::from_pem_files(cert_path, key_path)?;

    let https_config = HttpsConfig::new("0.0.0.0:443", tls_config)
        .with_http_redirect("0.0.0.0:80");

    println!("🔒 Starting production HTTPS server");
    println!("   Domain: {}", domain);
    println!("   HTTPS: https://{}", domain);
    println!("   HTTP redirect enabled");

    app.listen_with_config(https_config).await?;

    Ok(())
}
```

## Summary

**Key Takeaways:**

1. ✅ Use `TlsConfig::self_signed()` for development
2. ✅ Use `TlsConfig::from_pem_files()` for production
3. ✅ Get free certificates from Let's Encrypt
4. ✅ Enable HTTP to HTTPS redirect with `HttpsConfig`
5. ✅ Protect private keys with proper permissions
6. ✅ Automate certificate renewal
7. ✅ Use environment variables for configuration

**Never:**
- ❌ Use self-signed certificates in production
- ❌ Commit private keys to version control
- ❌ Use weak keys (< 2048 bits)
- ❌ Ignore certificate expiration

HTTPS is essential for modern web applications. With Armature's built-in support, securing your application is straightforward and follows Rust best practices.