roster/SENSITIVE_VARIABLES_USAGE.md

272 lines
7.1 KiB
Markdown

# Sensitive Variables - Usage Guide
This document explains how to use the GNOME Keyring integration for storing sensitive variable values securely.
## Overview
Roster now supports marking variables as **sensitive**, which stores their values encrypted in GNOME Keyring instead of plain text in the JSON file.
**Use sensitive variables for:**
- API keys (`api_key`, `secret_key`)
- Authentication tokens (`bearer_token`, `oauth_token`)
- Passwords (`password`, `db_password`)
- Any secret credentials
**Use regular variables for:**
- Base URLs (`base_url`, `api_endpoint`)
- Environment names (`env`, `region`)
- Non-sensitive configuration values
## Storage
### Regular Variables
Stored in: `~/.local/share/cz.bugsy.roster/requests.json`
```json
{
"projects": [{
"variable_names": ["base_url", "api_key"],
"sensitive_variables": [], // empty = not sensitive
"environments": [{
"variables": {
"base_url": "https://api.example.com", // visible in JSON
"api_key": "secret-key-123" // visible (NOT SAFE!)
}
}]
}]
}
```
### Sensitive Variables
Stored in: GNOME Keyring (encrypted)
```json
{
"projects": [{
"variable_names": ["base_url", "api_key"],
"sensitive_variables": ["api_key"], // marked as sensitive
"environments": [{
"variables": {
"base_url": "https://api.example.com", // visible in JSON
"api_key": "" // empty placeholder
}
}]
}]
}
```
The actual value of `api_key` is stored encrypted in GNOME Keyring and automatically retrieved when needed.
## Code Examples
### Basic Usage
```python
from roster.project_manager import ProjectManager
pm = ProjectManager()
project_id = "your-project-id"
env_id = "your-environment-id"
# Mark a variable as sensitive (moves existing values to keyring)
pm.mark_variable_as_sensitive(project_id, "api_key")
# Set a sensitive variable value (goes to keyring)
pm.set_variable_value(project_id, env_id, "api_key", "my-secret-key-123")
# Get a sensitive variable value (retrieved from keyring)
value = pm.get_variable_value(project_id, env_id, "api_key")
# Returns: "my-secret-key-123"
# Check if variable is sensitive
is_sensitive = pm.is_variable_sensitive(project_id, "api_key")
# Returns: True
```
### Using in Request Substitution
```python
# Get environment with ALL values (including secrets from keyring)
environment = pm.get_environment_with_secrets(project_id, env_id)
# Now use this environment for variable substitution
from roster.variable_substitution import VariableSubstitution
vs = VariableSubstitution()
request = HttpRequest(
method="GET",
url="{{base_url}}/users",
headers={"Authorization": "Bearer {{api_key}}"},
body=""
)
substituted_request, undefined = vs.substitute_request(request, environment)
# Result:
# url = "https://api.example.com/users"
# headers = {"Authorization": "Bearer my-secret-key-123"}
```
### Mark Variable as Sensitive
```python
# Existing variable with values in all environments
pm.mark_variable_as_sensitive(project_id, "token")
# This will:
# 1. Add "token" to project.sensitive_variables list
# 2. Move all environment values to GNOME Keyring
# 3. Clear values in JSON (replaced with empty strings)
```
### Mark Variable as Non-Sensitive
```python
# Move back from keyring to JSON
pm.mark_variable_as_nonsensitive(project_id, "base_url")
# This will:
# 1. Remove "base_url" from project.sensitive_variables list
# 2. Move all environment values from keyring to JSON
# 3. Delete secrets from keyring
```
### Variable Operations (Automatic Secret Handling)
```python
# Rename a sensitive variable
pm.rename_variable(project_id, "old_token", "new_token")
# Automatically renames in both JSON AND keyring
# Delete a sensitive variable
pm.delete_variable(project_id, "api_key")
# Automatically deletes from both JSON AND keyring
# Delete an environment
pm.delete_environment(project_id, env_id)
# Automatically deletes all secrets for that environment
# Delete a project
pm.delete_project(project_id)
# Automatically deletes ALL secrets for that project
```
## Integration with UI
When implementing UI for sensitive variables:
### Variable List Display
```python
# Show lock icon for sensitive variables
for var_name in project.variable_names:
is_sensitive = var_name in project.sensitive_variables
icon = "🔒" if is_sensitive else ""
label = f"{icon} {var_name}"
```
### Variable Value Entry
```python
# Use password entry for sensitive variables
entry = Gtk.Entry()
if var_name in project.sensitive_variables:
entry.set_visibility(False) # Show bullets instead of text
entry.set_input_purpose(Gtk.InputPurpose.PASSWORD)
```
### Context Menu
```python
# Right-click menu on variable
menu = Gio.Menu()
if pm.is_variable_sensitive(project_id, var_name):
menu.append("Mark as Non-Sensitive", f"app.mark-nonsensitive::{var_name}")
else:
menu.append("Mark as Sensitive", f"app.mark-sensitive::{var_name}")
```
## How It Works
### GNOME Keyring Schema
Each secret is stored with these attributes:
```python
{
"project_id": "uuid-of-project",
"environment_id": "uuid-of-environment",
"variable_name": "api_key"
}
```
Label shown in Seahorse (Passwords and Keys app):
```
"Roster: ProjectName/EnvironmentName/variable_name"
```
### Viewing Secrets in Seahorse
1. Open "Passwords and Keys" application
2. Look under "Login" keyring
3. Find entries labeled "Roster: ..."
4. These are your sensitive variable values
### Security
- Encrypted at rest with your login password
- Automatically unlocked when you log in
- Protected by OS-level security
- Uses the same secure backend as browser passwords, WiFi passwords, SSH keys
## Migration Guide
If you have existing variables with sensitive data:
```python
project_id = "your-project-id"
sensitive_vars = ["api_key", "token", "password", "secret"]
for var_name in sensitive_vars:
pm.mark_variable_as_sensitive(project_id, var_name)
# Done! All values are now encrypted in GNOME Keyring
```
## Error Handling
```python
from roster.secret_manager import get_secret_manager
sm = get_secret_manager()
# store_secret returns bool
success = sm.store_secret(project_id, env_id, "api_key", "value")
if not success:
# Handle error (check logs)
print("Failed to store secret")
# retrieve_secret returns None on failure
value = sm.retrieve_secret(project_id, env_id, "api_key")
if value is None:
# Secret not found or error occurred
print("Secret not found")
```
## Best Practices
1. **Mark variables as sensitive BEFORE entering values**
- This ensures values never touch the JSON file
2. **Use descriptive variable names**
- `github_token` instead of `token`
- `stripe_api_key` instead of `key`
3. **Don't commit the JSON file with sensitive data**
- If you accidentally stored secrets in JSON, mark as sensitive to move them
4. **Regular variables are fine for most things**
- Only use sensitive variables for actual secrets
- Base URLs, regions, etc. don't need encryption
5. **Backup your GNOME Keyring**
- Sensitive variables are stored in your system keyring
- Backup `~/.local/share/keyrings/` if needed