roster/SENSITIVE_VARIABLES_USAGE.md

7.1 KiB

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

{
  "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)

{
  "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

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

# 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

# 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

# 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)

# 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

# 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

# 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

# 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:

{
    "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:

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

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