tlacacoca 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tlacacoca-0.1.3/PKG-INFO +193 -0
- tlacacoca-0.1.3/README.md +180 -0
- tlacacoca-0.1.3/pyproject.toml +35 -0
- tlacacoca-0.1.3/src/tlacacoca/__init__.py +74 -0
- tlacacoca-0.1.3/src/tlacacoca/logging/__init__.py +9 -0
- tlacacoca-0.1.3/src/tlacacoca/logging/structured.py +144 -0
- tlacacoca-0.1.3/src/tlacacoca/middleware/__init__.py +29 -0
- tlacacoca-0.1.3/src/tlacacoca/middleware/access_control.py +145 -0
- tlacacoca-0.1.3/src/tlacacoca/middleware/base.py +147 -0
- tlacacoca-0.1.3/src/tlacacoca/middleware/certificate_auth.py +183 -0
- tlacacoca-0.1.3/src/tlacacoca/middleware/rate_limit.py +178 -0
- tlacacoca-0.1.3/src/tlacacoca/py.typed +0 -0
- tlacacoca-0.1.3/src/tlacacoca/security/__init__.py +32 -0
- tlacacoca-0.1.3/src/tlacacoca/security/certificates.py +262 -0
- tlacacoca-0.1.3/src/tlacacoca/security/tls.py +130 -0
- tlacacoca-0.1.3/src/tlacacoca/security/tofu.py +518 -0
tlacacoca-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: tlacacoca
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Shared foundation library for TLS-based protocol implementations
|
|
5
|
+
Author: Alan Velasco
|
|
6
|
+
Author-email: Alan Velasco <alanvelasco.a@gmail.com>
|
|
7
|
+
Requires-Dist: cryptography>=46.0.3
|
|
8
|
+
Requires-Dist: structlog>=25.5.0
|
|
9
|
+
Requires-Dist: tomli>=2.0.0 ; python_full_version < '3.11'
|
|
10
|
+
Requires-Dist: tomli-w>=1.2.0
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# Tlacacoca - Shared Foundation Library for TLS-Based Protocols
|
|
15
|
+
|
|
16
|
+
[](https://www.python.org/downloads/)
|
|
17
|
+
[](https://opensource.org/licenses/MIT)
|
|
18
|
+
[](https://github.com/astral-sh/ruff)
|
|
19
|
+
|
|
20
|
+
A protocol-agnostic foundation library providing shared components for building secure TLS-based network protocol implementations in Python. Tlacacoca (pronounced "tla-ka-KO-ka", from Nahuatl meaning "secure/safe") provides security, middleware, and logging utilities that can be shared across multiple protocol implementations.
|
|
21
|
+
|
|
22
|
+
## Key Features
|
|
23
|
+
|
|
24
|
+
- **Security First** - TLS context creation, TOFU certificate validation, certificate utilities
|
|
25
|
+
- **Middleware System** - Rate limiting, IP access control, certificate authentication
|
|
26
|
+
- **Structured Logging** - Privacy-preserving logging with IP hashing
|
|
27
|
+
- **Protocol Agnostic** - Abstract interfaces that any TLS-based protocol can build upon
|
|
28
|
+
- **Modern Python** - Full type hints, async/await support, and modern tooling with `uv`
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# As a library
|
|
36
|
+
uv add tlacacoca
|
|
37
|
+
|
|
38
|
+
# From source (for development)
|
|
39
|
+
git clone https://github.com/alanbato/tlacacoca.git
|
|
40
|
+
cd tlacacoca && uv sync
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Basic Usage
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import ssl
|
|
47
|
+
from tlacacoca import (
|
|
48
|
+
create_client_context,
|
|
49
|
+
create_server_context,
|
|
50
|
+
TOFUDatabase,
|
|
51
|
+
RateLimiter,
|
|
52
|
+
RateLimitConfig,
|
|
53
|
+
AccessControl,
|
|
54
|
+
AccessControlConfig,
|
|
55
|
+
MiddlewareChain,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Create TLS contexts
|
|
59
|
+
client_ctx = create_client_context(verify_mode=ssl.CERT_REQUIRED)
|
|
60
|
+
server_ctx = create_server_context("cert.pem", "key.pem")
|
|
61
|
+
|
|
62
|
+
# Set up TOFU certificate validation
|
|
63
|
+
async with TOFUDatabase(app_name="myapp") as tofu:
|
|
64
|
+
# First connection - certificate is stored
|
|
65
|
+
await tofu.verify_or_trust("example.com", 1965, cert_fingerprint)
|
|
66
|
+
|
|
67
|
+
# Subsequent connections - certificate is verified
|
|
68
|
+
await tofu.verify_or_trust("example.com", 1965, cert_fingerprint)
|
|
69
|
+
|
|
70
|
+
# Configure middleware chain
|
|
71
|
+
rate_config = RateLimitConfig(capacity=10, refill_rate=1.0)
|
|
72
|
+
access_config = AccessControlConfig(
|
|
73
|
+
allow_list=["192.168.1.0/24"],
|
|
74
|
+
default_allow=False
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
chain = MiddlewareChain([
|
|
78
|
+
AccessControl(access_config),
|
|
79
|
+
RateLimiter(rate_config),
|
|
80
|
+
])
|
|
81
|
+
|
|
82
|
+
# Process requests through middleware
|
|
83
|
+
result = await chain.process_request(
|
|
84
|
+
request_url="protocol://example.com/path",
|
|
85
|
+
client_ip="192.168.1.100"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if result.allowed:
|
|
89
|
+
# Handle request
|
|
90
|
+
pass
|
|
91
|
+
else:
|
|
92
|
+
# Map denial_reason to protocol-specific response
|
|
93
|
+
if result.denial_reason == "rate_limit":
|
|
94
|
+
# e.g., Gemini: "44 SLOW DOWN\r\n"
|
|
95
|
+
pass
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Components
|
|
99
|
+
|
|
100
|
+
### Security
|
|
101
|
+
|
|
102
|
+
| Component | Description |
|
|
103
|
+
|-----------|-------------|
|
|
104
|
+
| `create_client_context()` | Create TLS context for client connections |
|
|
105
|
+
| `create_server_context()` | Create TLS context for server connections |
|
|
106
|
+
| `TOFUDatabase` | Trust-On-First-Use certificate validation |
|
|
107
|
+
| `generate_self_signed_cert()` | Generate self-signed certificates |
|
|
108
|
+
| `get_certificate_fingerprint()` | Get SHA-256 fingerprint of certificate |
|
|
109
|
+
| `load_certificate()` | Load certificate from PEM file |
|
|
110
|
+
|
|
111
|
+
### Middleware
|
|
112
|
+
|
|
113
|
+
| Component | Description |
|
|
114
|
+
|-----------|-------------|
|
|
115
|
+
| `MiddlewareChain` | Chain multiple middleware components |
|
|
116
|
+
| `RateLimiter` | Token bucket rate limiting per IP |
|
|
117
|
+
| `AccessControl` | IP-based allow/deny lists with CIDR support |
|
|
118
|
+
| `CertificateAuth` | Client certificate authentication |
|
|
119
|
+
|
|
120
|
+
### Logging
|
|
121
|
+
|
|
122
|
+
| Component | Description |
|
|
123
|
+
|-----------|-------------|
|
|
124
|
+
| `configure_logging()` | Configure structured logging |
|
|
125
|
+
| `get_logger()` | Get a logger instance |
|
|
126
|
+
| `hash_ip_processor()` | Privacy-preserving IP hashing |
|
|
127
|
+
|
|
128
|
+
## Protocol Implementations Using Tlacacoca
|
|
129
|
+
|
|
130
|
+
Tlacacoca is designed to be used by protocol-specific implementations:
|
|
131
|
+
|
|
132
|
+
- **nauyaca** - Gemini protocol server & client
|
|
133
|
+
- **amatl** - Scroll protocol implementation (planned)
|
|
134
|
+
|
|
135
|
+
## Documentation
|
|
136
|
+
|
|
137
|
+
### Middleware Return Types
|
|
138
|
+
|
|
139
|
+
Middleware components return `MiddlewareResult` with protocol-agnostic denial reasons:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from tlacacoca import MiddlewareResult, DenialReason
|
|
143
|
+
|
|
144
|
+
# Allowed request
|
|
145
|
+
result = MiddlewareResult(allowed=True)
|
|
146
|
+
|
|
147
|
+
# Denied request
|
|
148
|
+
result = MiddlewareResult(
|
|
149
|
+
allowed=False,
|
|
150
|
+
denial_reason=DenialReason.RATE_LIMIT,
|
|
151
|
+
retry_after=30
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Protocol implementations map these to their specific status codes:
|
|
156
|
+
|
|
157
|
+
| Denial Reason | Gemini Status | Description |
|
|
158
|
+
|--------------|---------------|-------------|
|
|
159
|
+
| `RATE_LIMIT` | 44 SLOW DOWN | Rate limit exceeded |
|
|
160
|
+
| `ACCESS_DENIED` | 53 PROXY REFUSED | IP not allowed |
|
|
161
|
+
| `CERT_REQUIRED` | 60 CLIENT CERT REQUIRED | Need client certificate |
|
|
162
|
+
| `CERT_NOT_AUTHORIZED` | 61 CERT NOT AUTHORIZED | Certificate not in allowed list |
|
|
163
|
+
|
|
164
|
+
## Contributing
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Setup
|
|
168
|
+
git clone https://github.com/alanbato/tlacacoca.git
|
|
169
|
+
cd tlacacoca && uv sync
|
|
170
|
+
|
|
171
|
+
# Test
|
|
172
|
+
uv run pytest
|
|
173
|
+
|
|
174
|
+
# Lint & Type Check
|
|
175
|
+
uv run ruff check src/ tests/
|
|
176
|
+
uv run ty check src/
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
184
|
+
|
|
185
|
+
## Resources
|
|
186
|
+
|
|
187
|
+
- [SECURITY.md](SECURITY.md) - Security documentation
|
|
188
|
+
- [GitHub Issues](https://github.com/alanbato/tlacacoca/issues) - Bug reports
|
|
189
|
+
- [GitHub Discussions](https://github.com/alanbato/tlacacoca/discussions) - Questions and ideas
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
**Status**: Active development (pre-1.0). Core security and middleware features are stable.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Tlacacoca - Shared Foundation Library for TLS-Based Protocols
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/astral-sh/ruff)
|
|
6
|
+
|
|
7
|
+
A protocol-agnostic foundation library providing shared components for building secure TLS-based network protocol implementations in Python. Tlacacoca (pronounced "tla-ka-KO-ka", from Nahuatl meaning "secure/safe") provides security, middleware, and logging utilities that can be shared across multiple protocol implementations.
|
|
8
|
+
|
|
9
|
+
## Key Features
|
|
10
|
+
|
|
11
|
+
- **Security First** - TLS context creation, TOFU certificate validation, certificate utilities
|
|
12
|
+
- **Middleware System** - Rate limiting, IP access control, certificate authentication
|
|
13
|
+
- **Structured Logging** - Privacy-preserving logging with IP hashing
|
|
14
|
+
- **Protocol Agnostic** - Abstract interfaces that any TLS-based protocol can build upon
|
|
15
|
+
- **Modern Python** - Full type hints, async/await support, and modern tooling with `uv`
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# As a library
|
|
23
|
+
uv add tlacacoca
|
|
24
|
+
|
|
25
|
+
# From source (for development)
|
|
26
|
+
git clone https://github.com/alanbato/tlacacoca.git
|
|
27
|
+
cd tlacacoca && uv sync
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Basic Usage
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import ssl
|
|
34
|
+
from tlacacoca import (
|
|
35
|
+
create_client_context,
|
|
36
|
+
create_server_context,
|
|
37
|
+
TOFUDatabase,
|
|
38
|
+
RateLimiter,
|
|
39
|
+
RateLimitConfig,
|
|
40
|
+
AccessControl,
|
|
41
|
+
AccessControlConfig,
|
|
42
|
+
MiddlewareChain,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Create TLS contexts
|
|
46
|
+
client_ctx = create_client_context(verify_mode=ssl.CERT_REQUIRED)
|
|
47
|
+
server_ctx = create_server_context("cert.pem", "key.pem")
|
|
48
|
+
|
|
49
|
+
# Set up TOFU certificate validation
|
|
50
|
+
async with TOFUDatabase(app_name="myapp") as tofu:
|
|
51
|
+
# First connection - certificate is stored
|
|
52
|
+
await tofu.verify_or_trust("example.com", 1965, cert_fingerprint)
|
|
53
|
+
|
|
54
|
+
# Subsequent connections - certificate is verified
|
|
55
|
+
await tofu.verify_or_trust("example.com", 1965, cert_fingerprint)
|
|
56
|
+
|
|
57
|
+
# Configure middleware chain
|
|
58
|
+
rate_config = RateLimitConfig(capacity=10, refill_rate=1.0)
|
|
59
|
+
access_config = AccessControlConfig(
|
|
60
|
+
allow_list=["192.168.1.0/24"],
|
|
61
|
+
default_allow=False
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
chain = MiddlewareChain([
|
|
65
|
+
AccessControl(access_config),
|
|
66
|
+
RateLimiter(rate_config),
|
|
67
|
+
])
|
|
68
|
+
|
|
69
|
+
# Process requests through middleware
|
|
70
|
+
result = await chain.process_request(
|
|
71
|
+
request_url="protocol://example.com/path",
|
|
72
|
+
client_ip="192.168.1.100"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if result.allowed:
|
|
76
|
+
# Handle request
|
|
77
|
+
pass
|
|
78
|
+
else:
|
|
79
|
+
# Map denial_reason to protocol-specific response
|
|
80
|
+
if result.denial_reason == "rate_limit":
|
|
81
|
+
# e.g., Gemini: "44 SLOW DOWN\r\n"
|
|
82
|
+
pass
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Components
|
|
86
|
+
|
|
87
|
+
### Security
|
|
88
|
+
|
|
89
|
+
| Component | Description |
|
|
90
|
+
|-----------|-------------|
|
|
91
|
+
| `create_client_context()` | Create TLS context for client connections |
|
|
92
|
+
| `create_server_context()` | Create TLS context for server connections |
|
|
93
|
+
| `TOFUDatabase` | Trust-On-First-Use certificate validation |
|
|
94
|
+
| `generate_self_signed_cert()` | Generate self-signed certificates |
|
|
95
|
+
| `get_certificate_fingerprint()` | Get SHA-256 fingerprint of certificate |
|
|
96
|
+
| `load_certificate()` | Load certificate from PEM file |
|
|
97
|
+
|
|
98
|
+
### Middleware
|
|
99
|
+
|
|
100
|
+
| Component | Description |
|
|
101
|
+
|-----------|-------------|
|
|
102
|
+
| `MiddlewareChain` | Chain multiple middleware components |
|
|
103
|
+
| `RateLimiter` | Token bucket rate limiting per IP |
|
|
104
|
+
| `AccessControl` | IP-based allow/deny lists with CIDR support |
|
|
105
|
+
| `CertificateAuth` | Client certificate authentication |
|
|
106
|
+
|
|
107
|
+
### Logging
|
|
108
|
+
|
|
109
|
+
| Component | Description |
|
|
110
|
+
|-----------|-------------|
|
|
111
|
+
| `configure_logging()` | Configure structured logging |
|
|
112
|
+
| `get_logger()` | Get a logger instance |
|
|
113
|
+
| `hash_ip_processor()` | Privacy-preserving IP hashing |
|
|
114
|
+
|
|
115
|
+
## Protocol Implementations Using Tlacacoca
|
|
116
|
+
|
|
117
|
+
Tlacacoca is designed to be used by protocol-specific implementations:
|
|
118
|
+
|
|
119
|
+
- **nauyaca** - Gemini protocol server & client
|
|
120
|
+
- **amatl** - Scroll protocol implementation (planned)
|
|
121
|
+
|
|
122
|
+
## Documentation
|
|
123
|
+
|
|
124
|
+
### Middleware Return Types
|
|
125
|
+
|
|
126
|
+
Middleware components return `MiddlewareResult` with protocol-agnostic denial reasons:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from tlacacoca import MiddlewareResult, DenialReason
|
|
130
|
+
|
|
131
|
+
# Allowed request
|
|
132
|
+
result = MiddlewareResult(allowed=True)
|
|
133
|
+
|
|
134
|
+
# Denied request
|
|
135
|
+
result = MiddlewareResult(
|
|
136
|
+
allowed=False,
|
|
137
|
+
denial_reason=DenialReason.RATE_LIMIT,
|
|
138
|
+
retry_after=30
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Protocol implementations map these to their specific status codes:
|
|
143
|
+
|
|
144
|
+
| Denial Reason | Gemini Status | Description |
|
|
145
|
+
|--------------|---------------|-------------|
|
|
146
|
+
| `RATE_LIMIT` | 44 SLOW DOWN | Rate limit exceeded |
|
|
147
|
+
| `ACCESS_DENIED` | 53 PROXY REFUSED | IP not allowed |
|
|
148
|
+
| `CERT_REQUIRED` | 60 CLIENT CERT REQUIRED | Need client certificate |
|
|
149
|
+
| `CERT_NOT_AUTHORIZED` | 61 CERT NOT AUTHORIZED | Certificate not in allowed list |
|
|
150
|
+
|
|
151
|
+
## Contributing
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Setup
|
|
155
|
+
git clone https://github.com/alanbato/tlacacoca.git
|
|
156
|
+
cd tlacacoca && uv sync
|
|
157
|
+
|
|
158
|
+
# Test
|
|
159
|
+
uv run pytest
|
|
160
|
+
|
|
161
|
+
# Lint & Type Check
|
|
162
|
+
uv run ruff check src/ tests/
|
|
163
|
+
uv run ty check src/
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
171
|
+
|
|
172
|
+
## Resources
|
|
173
|
+
|
|
174
|
+
- [SECURITY.md](SECURITY.md) - Security documentation
|
|
175
|
+
- [GitHub Issues](https://github.com/alanbato/tlacacoca/issues) - Bug reports
|
|
176
|
+
- [GitHub Discussions](https://github.com/alanbato/tlacacoca/discussions) - Questions and ideas
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
**Status**: Active development (pre-1.0). Core security and middleware features are stable.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tlacacoca"
|
|
3
|
+
version = "0.1.3"
|
|
4
|
+
description = "Shared foundation library for TLS-based protocol implementations"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Alan Velasco", email = "alanvelasco.a@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"cryptography>=46.0.3",
|
|
12
|
+
"structlog>=25.5.0",
|
|
13
|
+
"tomli>=2.0.0 ; python_full_version < '3.11'",
|
|
14
|
+
"tomli-w>=1.2.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["uv_build>=0.9.10,<0.10.0"]
|
|
19
|
+
build-backend = "uv_build"
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"pre-commit>=4.5.1",
|
|
24
|
+
"pytest>=9.0.2",
|
|
25
|
+
"pytest-asyncio>=0.24.0",
|
|
26
|
+
"pytest-cov>=4.0.0",
|
|
27
|
+
"ruff>=0.14.11",
|
|
28
|
+
"ty>=0.0.10",
|
|
29
|
+
]
|
|
30
|
+
docs = [
|
|
31
|
+
"mkdocs-git-revision-date-localized-plugin>=1.5.0",
|
|
32
|
+
"mkdocs-material>=9.7.1",
|
|
33
|
+
"mkdocstrings[python]>=1.0.0",
|
|
34
|
+
"pymdown-extensions>=10.20",
|
|
35
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""tlacacoca - Shared foundation library for TLS-based protocol implementations.
|
|
2
|
+
|
|
3
|
+
This library provides protocol-agnostic components for building secure
|
|
4
|
+
network protocol clients and servers:
|
|
5
|
+
|
|
6
|
+
- Security: TLS context creation, certificate utilities, TOFU validation
|
|
7
|
+
- Middleware: Rate limiting, access control, certificate authentication
|
|
8
|
+
- Logging: Structured logging with privacy-preserving IP hashing
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Security
|
|
12
|
+
from .security.certificates import (
|
|
13
|
+
generate_self_signed_cert,
|
|
14
|
+
get_certificate_fingerprint,
|
|
15
|
+
get_certificate_fingerprint_from_path,
|
|
16
|
+
get_certificate_info,
|
|
17
|
+
is_certificate_expired,
|
|
18
|
+
is_certificate_valid_for_hostname,
|
|
19
|
+
load_certificate,
|
|
20
|
+
validate_certificate_file,
|
|
21
|
+
)
|
|
22
|
+
from .security.tls import create_client_context, create_server_context
|
|
23
|
+
from .security.tofu import CertificateChangedError, TOFUDatabase
|
|
24
|
+
|
|
25
|
+
# Middleware
|
|
26
|
+
from .middleware.access_control import AccessControl, AccessControlConfig
|
|
27
|
+
from .middleware.base import DenialReason, Middleware, MiddlewareChain, MiddlewareResult
|
|
28
|
+
from .middleware.certificate_auth import (
|
|
29
|
+
CertificateAuth,
|
|
30
|
+
CertificateAuthConfig,
|
|
31
|
+
CertificateAuthPathRule,
|
|
32
|
+
)
|
|
33
|
+
from .middleware.rate_limit import RateLimitConfig, RateLimiter, TokenBucket
|
|
34
|
+
|
|
35
|
+
# Logging
|
|
36
|
+
from .logging.structured import configure_logging, get_logger, hash_ip_processor
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
# Security - TLS
|
|
40
|
+
"create_client_context",
|
|
41
|
+
"create_server_context",
|
|
42
|
+
# Security - Certificates
|
|
43
|
+
"generate_self_signed_cert",
|
|
44
|
+
"load_certificate",
|
|
45
|
+
"get_certificate_fingerprint",
|
|
46
|
+
"get_certificate_fingerprint_from_path",
|
|
47
|
+
"is_certificate_expired",
|
|
48
|
+
"is_certificate_valid_for_hostname",
|
|
49
|
+
"get_certificate_info",
|
|
50
|
+
"validate_certificate_file",
|
|
51
|
+
# Security - TOFU
|
|
52
|
+
"TOFUDatabase",
|
|
53
|
+
"CertificateChangedError",
|
|
54
|
+
# Middleware - Base
|
|
55
|
+
"DenialReason",
|
|
56
|
+
"MiddlewareResult",
|
|
57
|
+
"Middleware",
|
|
58
|
+
"MiddlewareChain",
|
|
59
|
+
# Middleware - Rate limiting
|
|
60
|
+
"TokenBucket",
|
|
61
|
+
"RateLimiter",
|
|
62
|
+
"RateLimitConfig",
|
|
63
|
+
# Middleware - Access control
|
|
64
|
+
"AccessControl",
|
|
65
|
+
"AccessControlConfig",
|
|
66
|
+
# Middleware - Certificate auth
|
|
67
|
+
"CertificateAuth",
|
|
68
|
+
"CertificateAuthConfig",
|
|
69
|
+
"CertificateAuthPathRule",
|
|
70
|
+
# Logging
|
|
71
|
+
"configure_logging",
|
|
72
|
+
"get_logger",
|
|
73
|
+
"hash_ip_processor",
|
|
74
|
+
]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Structured logging configuration using structlog.
|
|
2
|
+
|
|
3
|
+
This module provides structured logging configuration with privacy-preserving
|
|
4
|
+
features like IP address hashing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import structlog
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def hash_ip_processor(
|
|
16
|
+
logger: Any, method_name: str, event_dict: dict[str, Any]
|
|
17
|
+
) -> dict[str, Any]:
|
|
18
|
+
"""Hash IP addresses in log events for privacy.
|
|
19
|
+
|
|
20
|
+
IP addresses should be hashed in logs to protect user privacy while
|
|
21
|
+
still allowing abuse detection.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
logger: The logger instance.
|
|
25
|
+
method_name: The logging method name.
|
|
26
|
+
event_dict: The event dictionary.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
The event dictionary with client_ip replaced by client_ip_hash.
|
|
30
|
+
"""
|
|
31
|
+
if "client_ip" in event_dict:
|
|
32
|
+
ip = event_dict["client_ip"]
|
|
33
|
+
if ip and ip != "unknown":
|
|
34
|
+
# SHA256 hash, truncated to 12 chars for readability
|
|
35
|
+
hashed = hashlib.sha256(ip.encode()).hexdigest()[:12]
|
|
36
|
+
event_dict["client_ip_hash"] = hashed
|
|
37
|
+
del event_dict["client_ip"]
|
|
38
|
+
return event_dict
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def configure_logging(
|
|
42
|
+
log_level: str = "INFO",
|
|
43
|
+
log_file: Path | None = None,
|
|
44
|
+
json_logs: bool = False,
|
|
45
|
+
hash_ips: bool = True,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Configure structured logging for the application.
|
|
48
|
+
|
|
49
|
+
This function should be called once at application startup. When using
|
|
50
|
+
a log file, the file handle remains open for the application lifetime.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
log_level: Logging level (DEBUG, INFO, WARNING, ERROR).
|
|
54
|
+
log_file: Optional path to log file. If None, logs to stdout.
|
|
55
|
+
The file is opened in append mode and remains open.
|
|
56
|
+
json_logs: If True, output logs in JSON format. Otherwise, use
|
|
57
|
+
human-readable format.
|
|
58
|
+
hash_ips: If True (default), hash client IP addresses in logs
|
|
59
|
+
for privacy.
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
>>> # Configure for development (human-readable console output)
|
|
63
|
+
>>> configure_logging(log_level="DEBUG")
|
|
64
|
+
|
|
65
|
+
>>> # Configure for production (JSON logs to file, hashed IPs)
|
|
66
|
+
>>> configure_logging(
|
|
67
|
+
... log_level="INFO",
|
|
68
|
+
... log_file=Path("/var/log/myapp.log"),
|
|
69
|
+
... json_logs=True,
|
|
70
|
+
... hash_ips=True
|
|
71
|
+
... )
|
|
72
|
+
"""
|
|
73
|
+
# Determine output stream
|
|
74
|
+
if log_file:
|
|
75
|
+
output_stream = open(log_file, "a")
|
|
76
|
+
else:
|
|
77
|
+
output_stream = sys.stdout
|
|
78
|
+
|
|
79
|
+
# Build base processors
|
|
80
|
+
base_processors: list[Any] = [
|
|
81
|
+
structlog.contextvars.merge_contextvars,
|
|
82
|
+
structlog.processors.add_log_level,
|
|
83
|
+
structlog.processors.TimeStamper(
|
|
84
|
+
fmt="iso" if json_logs else "%Y-%m-%d %H:%M:%S"
|
|
85
|
+
),
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
# Add IP hashing processor if enabled (for privacy)
|
|
89
|
+
if hash_ips:
|
|
90
|
+
base_processors.append(hash_ip_processor)
|
|
91
|
+
|
|
92
|
+
# Configure output format
|
|
93
|
+
if json_logs:
|
|
94
|
+
# JSON format for production/structured logging
|
|
95
|
+
processors = base_processors + [structlog.processors.JSONRenderer()]
|
|
96
|
+
else:
|
|
97
|
+
# Human-readable format for development
|
|
98
|
+
processors = base_processors + [
|
|
99
|
+
structlog.dev.ConsoleRenderer(colors=output_stream.isatty())
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
# Configure structlog
|
|
103
|
+
structlog.configure(
|
|
104
|
+
processors=processors,
|
|
105
|
+
wrapper_class=structlog.make_filtering_bound_logger(_level_to_int(log_level)),
|
|
106
|
+
context_class=dict,
|
|
107
|
+
logger_factory=structlog.PrintLoggerFactory(file=output_stream),
|
|
108
|
+
cache_logger_on_first_use=True,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _level_to_int(level: str) -> int:
|
|
113
|
+
"""Convert string log level to integer.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
level: Log level string (DEBUG, INFO, WARNING, ERROR, CRITICAL).
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Integer log level.
|
|
120
|
+
"""
|
|
121
|
+
levels = {
|
|
122
|
+
"DEBUG": 10,
|
|
123
|
+
"INFO": 20,
|
|
124
|
+
"WARNING": 30,
|
|
125
|
+
"ERROR": 40,
|
|
126
|
+
"CRITICAL": 50,
|
|
127
|
+
}
|
|
128
|
+
return levels.get(level.upper(), 20)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_logger(name: str) -> structlog.BoundLogger:
|
|
132
|
+
"""Get a logger instance for a module.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
name: Logger name (typically __name__).
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
A structlog BoundLogger instance.
|
|
139
|
+
|
|
140
|
+
Examples:
|
|
141
|
+
>>> logger = get_logger(__name__)
|
|
142
|
+
>>> logger.info("server_started", host="localhost", port=1965)
|
|
143
|
+
"""
|
|
144
|
+
return structlog.get_logger(name)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Middleware module for request processing, rate limiting, and access control."""
|
|
2
|
+
|
|
3
|
+
from .access_control import AccessControl, AccessControlConfig
|
|
4
|
+
from .base import DenialReason, Middleware, MiddlewareChain, MiddlewareResult
|
|
5
|
+
from .certificate_auth import (
|
|
6
|
+
CertificateAuth,
|
|
7
|
+
CertificateAuthConfig,
|
|
8
|
+
CertificateAuthPathRule,
|
|
9
|
+
)
|
|
10
|
+
from .rate_limit import RateLimitConfig, RateLimiter, TokenBucket
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
# Base
|
|
14
|
+
"DenialReason",
|
|
15
|
+
"MiddlewareResult",
|
|
16
|
+
"Middleware",
|
|
17
|
+
"MiddlewareChain",
|
|
18
|
+
# Rate limiting
|
|
19
|
+
"TokenBucket",
|
|
20
|
+
"RateLimiter",
|
|
21
|
+
"RateLimitConfig",
|
|
22
|
+
# Access control
|
|
23
|
+
"AccessControl",
|
|
24
|
+
"AccessControlConfig",
|
|
25
|
+
# Certificate auth
|
|
26
|
+
"CertificateAuth",
|
|
27
|
+
"CertificateAuthConfig",
|
|
28
|
+
"CertificateAuthPathRule",
|
|
29
|
+
]
|