Skip to content

Commit 4ff504c

Browse files
committed
style: fix formatting
1 parent c891335 commit 4ff504c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+4818
-11
lines changed

ROADMAP_SECRETS.md

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
# NORA Secrets Management Roadmap
2+
3+
## Overview
4+
5+
Trait-based secrets architecture for secure credential management.
6+
7+
```
8+
┌─────────────────────────────────────────────────────────────┐
9+
│ SecretsProvider Trait │
10+
├─────────────────────────────────────────────────────────────┤
11+
│ get_secret(key) -> ProtectedString │
12+
│ get_secret_optional(key) -> Option<ProtectedString> │
13+
│ provider_name() -> &'static str │
14+
└─────────────────────────────────────────────────────────────┘
15+
16+
┌────────────────────┼────────────────────┐
17+
│ │ │
18+
▼ ▼ ▼
19+
┌──────────┐ ┌──────────┐ ┌──────────┐
20+
│ ENV │ │ AWS │ │ Vault │
21+
│ Provider │ │ Secrets │ │ Provider │
22+
│ v0.3.0 │ │ v0.4.0 │ │ v0.5.0 │
23+
└──────────┘ └──────────┘ └──────────┘
24+
```
25+
26+
---
27+
28+
## Phase 1: ENV Provider (v0.3.0) ✅ DONE
29+
30+
### Features
31+
- [x] SecretsProvider trait
32+
- [x] EnvProvider implementation
33+
- [x] ProtectedString with zeroize
34+
- [x] Redacted Debug impl
35+
- [x] SecretsConfig in config.toml
36+
- [x] ENV overrides
37+
- [x] 16 unit tests
38+
39+
### Files
40+
```
41+
nora-registry/src/secrets/
42+
├── mod.rs # Trait + factory
43+
├── env.rs # EnvProvider
44+
└── protected.rs # ProtectedString, S3Credentials
45+
```
46+
47+
### Config
48+
```toml
49+
[secrets]
50+
provider = "env"
51+
clear_env = false
52+
```
53+
54+
### ENV Variables
55+
```bash
56+
NORA_SECRETS_PROVIDER=env
57+
NORA_SECRETS_CLEAR_ENV=false
58+
```
59+
60+
---
61+
62+
## Phase 2: AWS + K8s (v0.4.0)
63+
64+
### AWS Secrets Manager
65+
66+
```rust
67+
// src/secrets/aws.rs
68+
pub struct AwsSecretsProvider {
69+
client: SecretsManagerClient,
70+
secret_name: String,
71+
region: String,
72+
}
73+
74+
#[async_trait]
75+
impl SecretsProvider for AwsSecretsProvider {
76+
async fn get_secret(&self, key: &str) -> Result<ProtectedString, SecretsError> {
77+
let response = self.client
78+
.get_secret_value()
79+
.secret_id(&self.secret_name)
80+
.send()
81+
.await?;
82+
83+
let secrets: HashMap<String, String> =
84+
serde_json::from_str(&response.secret_string.unwrap())?;
85+
86+
secrets.get(key)
87+
.map(|v| ProtectedString::new(v.clone()))
88+
.ok_or_else(|| SecretsError::NotFound(key.to_string()))
89+
}
90+
}
91+
```
92+
93+
### Config
94+
```toml
95+
[secrets]
96+
provider = "aws-secrets"
97+
98+
[secrets.aws]
99+
secret_name = "nora/production"
100+
region = "us-east-1"
101+
```
102+
103+
### Kubernetes Secrets
104+
105+
```rust
106+
// src/secrets/k8s.rs
107+
pub struct K8sSecretsProvider {
108+
mount_path: PathBuf,
109+
}
110+
111+
#[async_trait]
112+
impl SecretsProvider for K8sSecretsProvider {
113+
async fn get_secret(&self, key: &str) -> Result<ProtectedString, SecretsError> {
114+
let path = self.mount_path.join(key);
115+
let value = tokio::fs::read_to_string(&path)
116+
.await
117+
.map_err(|_| SecretsError::NotFound(key.to_string()))?;
118+
Ok(ProtectedString::new(value.trim().to_string()))
119+
}
120+
}
121+
```
122+
123+
### Config
124+
```toml
125+
[secrets]
126+
provider = "k8s"
127+
128+
[secrets.k8s]
129+
mount_path = "/var/run/secrets/nora"
130+
```
131+
132+
### Kubernetes Deployment
133+
```yaml
134+
apiVersion: v1
135+
kind: Secret
136+
metadata:
137+
name: nora-secrets
138+
type: Opaque
139+
data:
140+
AWS_ACCESS_KEY_ID: QUtJQS4uLg==
141+
AWS_SECRET_ACCESS_KEY: d0phbHIuLi4=
142+
---
143+
apiVersion: apps/v1
144+
kind: Deployment
145+
spec:
146+
template:
147+
spec:
148+
containers:
149+
- name: nora
150+
volumeMounts:
151+
- name: secrets
152+
mountPath: /var/run/secrets/nora
153+
readOnly: true
154+
volumes:
155+
- name: secrets
156+
secret:
157+
secretName: nora-secrets
158+
```
159+
160+
### Tasks
161+
- [ ] Add `aws-sdk-secretsmanager` dependency
162+
- [ ] Implement AwsSecretsProvider
163+
- [ ] Implement K8sSecretsProvider
164+
- [ ] Add auto-refresh for rotation
165+
- [ ] Integration tests with localstack
166+
- [ ] Update factory function
167+
- [ ] Documentation
168+
169+
---
170+
171+
## Phase 3: HashiCorp Vault (v0.5.0+)
172+
173+
### Vault Provider
174+
175+
```rust
176+
// src/secrets/vault.rs
177+
pub struct VaultProvider {
178+
client: VaultClient,
179+
mount_path: String,
180+
secret_path: String,
181+
}
182+
183+
#[async_trait]
184+
impl SecretsProvider for VaultProvider {
185+
async fn get_secret(&self, key: &str) -> Result<ProtectedString, SecretsError> {
186+
let secret: HashMap<String, String> = self.client
187+
.kv2(&self.mount_path)
188+
.read(&format!("{}/{}", self.secret_path, key))
189+
.await?;
190+
191+
secret.get("value")
192+
.map(|v| ProtectedString::new(v.clone()))
193+
.ok_or_else(|| SecretsError::NotFound(key.to_string()))
194+
}
195+
}
196+
```
197+
198+
### Auth Methods
199+
200+
**Kubernetes Auth:**
201+
```rust
202+
let jwt = tokio::fs::read_to_string(
203+
"/var/run/secrets/kubernetes.io/serviceaccount/token"
204+
).await?;
205+
206+
client.auth().kubernetes(&role, &jwt).await?;
207+
```
208+
209+
**AppRole Auth:**
210+
```rust
211+
let role_id = env::var("VAULT_ROLE_ID")?;
212+
let secret_id = env::var("VAULT_SECRET_ID")?;
213+
214+
client.auth().approle(&role_id, &secret_id).await?;
215+
```
216+
217+
### Config
218+
```toml
219+
[secrets]
220+
provider = "vault"
221+
222+
[secrets.vault]
223+
address = "https://vault.company.com"
224+
mount_path = "secret"
225+
secret_path = "nora/production"
226+
auth_method = "kubernetes" # or "approle", "token"
227+
role = "nora-production"
228+
```
229+
230+
### Tasks
231+
- [ ] Add `vaultrs` dependency
232+
- [ ] Implement VaultProvider
233+
- [ ] Kubernetes auth
234+
- [ ] AppRole auth
235+
- [ ] Token auth
236+
- [ ] Lease renewal
237+
- [ ] Integration tests
238+
- [ ] Documentation
239+
240+
---
241+
242+
## Security Checklist
243+
244+
### Code
245+
- [x] Zeroize for all secrets (ProtectedString)
246+
- [x] Redacted Debug impl
247+
- [x] No secret logging
248+
- [x] Clear ENV after read option
249+
- [ ] TLS verification for Vault/AWS
250+
251+
### Deployment
252+
- [x] .gitignore for secrets
253+
- [ ] Kubernetes Secrets encrypted at rest
254+
- [ ] AWS KMS for Secrets Manager
255+
- [ ] Least privilege IAM/RBAC
256+
- [ ] Audit logging
257+
258+
---
259+
260+
## Comparison
261+
262+
| Provider | Complexity | Security | Use Case |
263+
|----------|------------|----------|----------|
264+
| ENV | Low | Medium | Dev, small prod |
265+
| AWS Secrets | Medium | High | AWS infrastructure |
266+
| K8s Secrets | Medium | Good | Kubernetes |
267+
| Vault | High | Maximum | Enterprise |
268+
269+
---
270+
271+
## NOT Implementing
272+
273+
### Vault Sidecar Pattern
274+
- Too complex (90% teams do it wrong)
275+
- NORA uses native Vault client instead
276+
- Simpler deployment
277+
278+
### Custom Secrets Manager (СЕВА)
279+
- Focus on NORA core first
280+
- May add later if demand exists
281+
282+
---
283+
284+
## Timeline
285+
286+
| Phase | Version | ETA |
287+
|-------|---------|-----|
288+
| ENV Provider | v0.3.0 | ✅ Done |
289+
| AWS + K8s | v0.4.0 | 1-2 weeks |
290+
| Vault | v0.5.0 | 2-3 weeks |
291+
292+
---
293+
294+
## Expert Panel Recommendations
295+
296+
**Linus Torvalds:**
297+
> "ENV variables — правильно. Это UNIX way. Но zeroize обязателен."
298+
299+
**Werner Vogels (AWS):**
300+
> "AWS Secrets Manager интеграция критична для AWS workloads."
301+
302+
**Kelsey Hightower:**
303+
> "K8s Secrets через volume mount — проще чем sidecar."
304+
305+
**Mitchell Hashimoto:**
306+
> "Native Vault client лучше sidecar. Проще деплоить."
307+
308+
**DHH:**
309+
> "95% пользователей используют ENV. Не делайте Vault обязательным."

0 commit comments

Comments
 (0)