Skip to content

Commit 11c498b

Browse files
committed
refactor: update metadata handling and fix path_data behavior
- Add ConfigMetadata class for handling configuration metadata - Fix PathData.get_value() to maintain original behavior of returning None - Update tests to match expected behavior - Add ADR for sensitive value storage - Update schema and documentation
1 parent caa1b62 commit 11c498b

File tree

12 files changed

+297
-262
lines changed

12 files changed

+297
-262
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# ADR 0002: Sensitive Value Storage
2+
3+
## Status
4+
Proposed
5+
6+
## Context
7+
The helm-values-manager needs to handle both sensitive and non-sensitive configuration values. While non-sensitive values can be stored directly in the configuration files, sensitive values require special handling to ensure security.
8+
9+
## Decision
10+
We will store sensitive values using the existing configuration structure, with sensitive values using a special reference format.
11+
The existing schema already supports this through its `sensitive` flag and flexible string values.
12+
13+
1. When a value is marked as sensitive (`sensitive: true`):
14+
- The actual value will be stored in a secure backend (AWS Secrets Manager, Azure Key Vault, etc.)
15+
- Only a reference to the secret will be stored in our configuration files
16+
- The reference will use a URI-like format: `secret://<backend-type>/<secret-path>`
17+
18+
2. Example configuration showing both sensitive and non-sensitive values:
19+
```json
20+
{
21+
"version": "1.0",
22+
"release": "my-app",
23+
"deployments": {
24+
"prod": {
25+
"backend": "gcp",
26+
"auth": {
27+
"type": "managed_identity"
28+
},
29+
"backend_config": {
30+
"region": "us-central1"
31+
}
32+
}
33+
},
34+
"config": [
35+
{
36+
"path": "app.replicas",
37+
"description": "Number of application replicas",
38+
"required": true,
39+
"sensitive": false,
40+
"values": {
41+
"dev": "3",
42+
"prod": "5"
43+
}
44+
},
45+
{
46+
"path": "app.database.password",
47+
"description": "Database password",
48+
"required": true,
49+
"sensitive": true,
50+
"values": {
51+
"dev": "secret://gcp-secrets/my-app/dev/db-password",
52+
"prod": "secret://gcp-secrets/my-app/prod/db-password"
53+
}
54+
}
55+
]
56+
}
57+
```
58+
59+
This approach:
60+
1. Leverages the existing schema without modifications:
61+
- Uses the `sensitive` flag to identify sensitive values
62+
- Uses the flexible string type in `values` to store references
63+
- Maintains backward compatibility
64+
2. Provides a clear and consistent format for secret references
65+
3. Supports versioning through the secret path (e.g., `secret://gcp-secrets/my-app/prod/db-password/v1`)
66+
67+
The validation and resolution of secret references will be handled by:
68+
1. The backend implementation (parsing and resolving references)
69+
2. The `Value` class (determining whether to treat a value as a reference based on the `sensitive` flag)
70+
71+
## Implementation Notes
72+
1. Secret references will be parsed and validated by the backend implementation
73+
2. The `Value` class will check the `sensitive` flag to determine how to handle the value:
74+
- If `sensitive: false`, use the value as-is
75+
- If `sensitive: true`, parse the value as a secret reference and resolve it
76+
3. Each secure backend will implement its own reference resolution logic
77+
4. Future enhancement: Add commands to manage secrets directly through the tool
78+
79+
## Consequences
80+
81+
### Positive
82+
- Security: Sensitive values never leave the secure backend
83+
- Traceability: Easy to track which secrets are used where
84+
- Versioning: Support for secret rotation via version references
85+
- Flexibility: Different backends can implement their own reference formats
86+
- Auditability: References are human-readable for easier debugging
87+
88+
### Negative
89+
- Additional Setup: Users need to create secrets separately (until we add direct creation support)
90+
- Complexity: Need to manage both direct values and secret references
91+
- Dependencies: Requires access to secure backends
92+
93+
## Related
94+
- ADR 0001 (if it exists, about general value storage)
95+
- Future ADR about secret management commands

docs/ADRs/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ An Architecture Decision Record (ADR) is a document that captures an important a
4848
- **Impact**: Ensures consistent user experience and debugging
4949
- **Dependencies**: ADR-001
5050

51+
### [ADR-007: Sensitive Value Storage](007-sensitive-value-storage.md)
52+
- **Status**: Proposed
53+
- **Context**: Need for secure handling of sensitive configuration values
54+
- **Decision**: Store sensitive values using reference-based approach in secure backends
55+
- **Impact**: Ensures security while maintaining flexibility and traceability
56+
- **Dependencies**: ADR-005
57+
5158
## ADR Template
5259
For new ADRs, use this template:
5360
```markdown

docs/Design/low-level-design.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ classDiagram
2323
class PathData {
2424
+str path
2525
+Dict metadata
26-
+Dict~str,Value~ values
26+
-Dict~str,Value~ _values
2727
+validate() None
28-
+add_value(environment: str, value: Value) None
28+
+set_value(environment: str, value: Value) None
2929
+get_value(environment: str) Optional[Value]
30+
+get_environments() Iterator[str]
3031
+to_dict() Dict
3132
+from_dict(data: Dict, create_value_fn) PathData
3233
}
@@ -212,12 +213,12 @@ The configuration follows the v1 schema:
212213
"release": "my-app",
213214
"deployments": {
214215
"prod": {
215-
"backend": "aws",
216+
"backend": "gcp",
216217
"auth": {
217218
"type": "managed_identity"
218219
},
219220
"backend_config": {
220-
"region": "us-west-2"
221+
"region": "us-central1"
221222
}
222223
}
223224
},
@@ -231,6 +232,16 @@ The configuration follows the v1 schema:
231232
"dev": "3",
232233
"prod": "5"
233234
}
235+
},
236+
{
237+
"path": "app.database.password",
238+
"description": "Database password",
239+
"required": true,
240+
"sensitive": true,
241+
"values": {
242+
"dev": "secret://gcp-secrets/my-app/dev/db-password",
243+
"prod": "secret://gcp-secrets/my-app/prod/db-password"
244+
}
234245
}
235246
]
236247
}

helm_values_manager/backends/base.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def __init__(self, auth_config: Dict[str, str]) -> None:
3535
ValueError: If the auth_config is invalid
3636
"""
3737
self._validate_auth_config(auth_config)
38+
self.backend_type = self.__class__.__name__.lower().replace("backend", "")
3839

3940
def _validate_auth_config(self, auth_config: Dict[str, str]) -> None:
4041
"""Validate the authentication configuration.
@@ -59,20 +60,18 @@ def _validate_auth_config(self, auth_config: Dict[str, str]) -> None:
5960
raise ValueError(f"Invalid auth type: {auth_config['type']}. " f"Must be one of: {', '.join(valid_types)}")
6061

6162
@abstractmethod
62-
def get_value(self, path: str, environment: str) -> str:
63-
"""Get a value from the storage backend.
63+
def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
64+
"""
65+
Get a value from storage.
6466
6567
Args:
66-
path: The configuration path (e.g., "app.replicas")
67-
environment: The environment name (e.g., "dev", "prod")
68+
path: The configuration path
69+
environment: The environment name
70+
resolve: If True, resolve any secret references to their actual values.
71+
If False, return the raw value which may be a secret reference.
6872
6973
Returns:
70-
str: The value stored in the backend.
71-
72-
Raises:
73-
ValueError: If the value doesn't exist
74-
ConnectionError: If there's an error connecting to the backend
75-
PermissionError: If there's an authentication or authorization error
74+
str: The value (resolved or raw depending on resolve parameter)
7675
"""
7776
pass
7877

helm_values_manager/backends/simple.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,18 @@ def _get_storage_key(self, path: str, environment: str) -> str:
2525
"""Generate a unique storage key."""
2626
return f"{path}:{environment}"
2727

28-
def get_value(self, path: str, environment: str) -> str:
28+
def get_value(self, path: str, environment: str, resolve: bool = False) -> str:
2929
"""
3030
Get a value from the in-memory storage.
3131
3232
Args:
3333
path: The configuration path (e.g., "app.replicas")
3434
environment: The environment name (e.g., "dev", "prod")
35+
resolve: If True, resolve any secret references to their actual values.
36+
If False, return the raw value which may be a secret reference.
3537
3638
Returns:
37-
str: The stored value
39+
str: The value (resolved or raw depending on resolve parameter)
3840
3941
Raises:
4042
ValueError: If the value doesn't exist
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Simple dataclass for configuration metadata."""
2+
3+
from dataclasses import asdict, dataclass
4+
from typing import Any, Dict, Optional
5+
6+
7+
@dataclass
8+
class ConfigMetadata:
9+
"""
10+
Represents metadata for a configuration path.
11+
12+
Attributes:
13+
description (Optional[str]): Description of the configuration path.
14+
required (bool): Whether the configuration path is required. Defaults to False.
15+
sensitive (bool): Whether the configuration path is sensitive. Defaults to False.
16+
"""
17+
18+
description: Optional[str] = None
19+
required: bool = False
20+
sensitive: bool = False
21+
22+
def to_dict(self) -> Dict[str, Any]:
23+
"""Convert metadata to dictionary."""
24+
return asdict(self)
25+
26+
@classmethod
27+
def from_dict(cls, data: Dict[str, Any]) -> "ConfigMetadata":
28+
"""Create a ConfigMetadata instance from a dictionary."""
29+
return cls(
30+
description=data.get("description"),
31+
required=data.get("required", False),
32+
sensitive=data.get("sensitive", False),
33+
)

0 commit comments

Comments
 (0)