# 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