The Secrets Service provides secure access to secrets stored in the Kubiya Control Plane, enabling agents and integrations to retrieve credentials, API keys, and other sensitive data at runtime.
Overview
Secrets in Kubiya are securely stored credentials, tokens, API keys, and other sensitive data used by agents and integrations. The Secrets Service allows you to:
List Secrets : View secret metadata (names, types, scopes) without exposing values
Retrieve Values : Securely access secret values when needed by agents or workflows
The Secrets Service provides read-only access via the SDK. Secret creation, updates, and deletion must be performed through the Kubiya Dashboard or CLI to ensure proper security controls.
Quick Start
from kubiya import ControlPlaneClient
# Initialize the client
client = ControlPlaneClient( api_key = "your-api-key" )
# List all secrets (metadata only)
secrets = client.secrets.list()
for secret in secrets:
print ( f "Secret: { secret[ 'name' ] } - Type: { secret[ 'type' ] } " )
# Get specific secret value (use with caution)
secret_value = client.secrets.get_value( name = "github-api-token" )
print ( f "Token: { secret_value[ 'value' ] } " ) # Handle securely!
Core Concepts
Secret Types
Kubiya supports various secret types:
API Keys : Third-party service API keys (GitHub, AWS, etc.)
Tokens : OAuth tokens, personal access tokens
Credentials : Username/password pairs
Certificates : TLS certificates and private keys
Environment Variables : Sensitive configuration values
Secret Scopes
Secrets can have different scopes:
Organization : Available to all agents and users in the organization
Team : Available to specific teams
User : Personal secrets for individual users
Security Best Practices
Always handle secret values with care:
Never log secret values
Never store secrets in version control
Use secrets only when necessary
Clear sensitive data from memory after use
Basic Usage
List All Secrets
from kubiya import ControlPlaneClient
client = ControlPlaneClient( api_key = "your-api-key" )
# List all accessible secrets (metadata only)
secrets = client.secrets.list()
print ( f "Total secrets: { len (secrets) } " )
for secret in secrets:
print ( f "Name: { secret[ 'name' ] } " )
print ( f "Type: { secret[ 'type' ] } " )
print ( f "Scope: { secret[ 'scope' ] } " )
print ( f "Created: { secret[ 'created_at' ] } " )
print ( f "Description: { secret.get( 'description' , 'N/A' ) } " )
print ( "---" )
[
{
"name" : "github-api-token" ,
"type" : "token" ,
"scope" : "organization" ,
"description" : "GitHub API token for repository access" ,
"created_at" : "2025-11-15T10:00:00Z" ,
"created_by" : "[email protected] " ,
"last_used" : "2025-12-11T09:30:00Z" ,
"integration" : "github"
},
{
"name" : "aws-credentials" ,
"type" : "credentials" ,
"scope" : "team" ,
"description" : "AWS access credentials for DevOps team" ,
"created_at" : "2025-10-20T14:22:00Z" ,
"created_by" : "[email protected] " ,
"last_used" : "2025-12-10T16:45:00Z" ,
"integration" : "aws"
},
{
"name" : "slack-webhook-url" ,
"type" : "url" ,
"scope" : "organization" ,
"description" : "Slack webhook for notifications" ,
"created_at" : "2025-09-05T08:15:00Z" ,
"created_by" : "[email protected] " ,
"last_used" : "2025-12-11T10:15:00Z" ,
"integration" : "slack"
}
]
The list() method returns secret metadata only . Actual secret values are not included in list responses for security reasons.
Get Secret Value
from kubiya import ControlPlaneClient
client = ControlPlaneClient( api_key = "your-api-key" )
# Retrieve actual secret value
secret = client.secrets.get_value( name = "github-api-token" )
# Access the secret value
token = secret[ 'value' ]
# Use the secret (example: API call)
import requests
headers = { "Authorization" : f "Bearer { token } " }
response = requests.get( "https://api.github.com/user" , headers = headers)
# Clear sensitive data from memory after use
token = None
secret = None
Security Warning : Always clear secret values from memory after use and never log, print, or store them insecurely.
{
"name" : "github-api-token" ,
"value" : "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ,
"type" : "token" ,
"scope" : "organization" ,
"retrieved_at" : "2025-12-11T10:30:00Z"
}
Practical Examples
1. Secret Inventory
List all secrets with categorization:
from kubiya import ControlPlaneClient
from collections import defaultdict
def generate_secret_inventory ( client : ControlPlaneClient):
"""Generate comprehensive secret inventory."""
secrets = client.secrets.list()
# Categorize by type
by_type = defaultdict( list )
by_scope = defaultdict( list )
by_integration = defaultdict( list )
for secret in secrets:
by_type[secret[ 'type' ]].append(secret)
by_scope[secret[ 'scope' ]].append(secret)
if secret.get( 'integration' ):
by_integration[secret[ 'integration' ]].append(secret)
print ( "Secret Inventory Report" )
print ( "=" * 60 )
print ( f "Total Secrets: { len (secrets) } \n " )
print ( "By Type:" )
for secret_type, items in by_type.items():
print ( f " { secret_type } : { len (items) } " )
print ( " \n By Scope:" )
for scope, items in by_scope.items():
print ( f " { scope } : { len (items) } " )
print ( " \n By Integration:" )
for integration, items in by_integration.items():
print ( f " { integration } : { len (items) } " )
return {
"total" : len (secrets),
"by_type" : dict (by_type),
"by_scope" : dict (by_scope),
"by_integration" : dict (by_integration)
}
# Usage
client = ControlPlaneClient( api_key = "your-api-key" )
inventory = generate_secret_inventory(client)
2. Secret Usage Validator
Check if required secrets exist:
from kubiya import ControlPlaneClient
from kubiya.resources.exceptions import ControlPlaneError
from typing import List
def validate_required_secrets (
client : ControlPlaneClient,
required_secrets : List[ str ]
) -> dict :
"""Validate that all required secrets are available."""
try :
# Get all available secrets
available_secrets = client.secrets.list()
available_names = {s[ 'name' ] for s in available_secrets}
# Check for missing secrets
missing = []
present = []
for secret_name in required_secrets:
if secret_name in available_names:
present.append(secret_name)
else :
missing.append(secret_name)
# Report results
if missing:
print ( f "❌ Missing { len (missing) } required secrets:" )
for name in missing:
print ( f " - { name } " )
else :
print ( f "✅ All { len (required_secrets) } required secrets are available" )
return {
"valid" : len (missing) == 0 ,
"present" : present,
"missing" : missing,
"total_required" : len (required_secrets)
}
except ControlPlaneError as e:
print ( f "Failed to validate secrets: { e } " )
return None
# Usage
client = ControlPlaneClient( api_key = "your-api-key" )
required = [
"github-api-token" ,
"aws-credentials" ,
"slack-webhook-url" ,
"datadog-api-key"
]
validation = validate_required_secrets(client, required)
if not validation[ 'valid' ]:
print ( f " \n Please create the missing secrets in Kubiya Dashboard" )
3. Secure Secret Accessor
Safely retrieve and use secrets with automatic cleanup:
from kubiya import ControlPlaneClient
from kubiya.resources.exceptions import ControlPlaneError
from contextlib import contextmanager
import gc
@contextmanager
def secure_secret ( client : ControlPlaneClient, secret_name : str ):
"""Context manager for secure secret access with automatic cleanup."""
secret_value = None
try :
# Retrieve secret
secret = client.secrets.get_value( name = secret_name)
secret_value = secret[ 'value' ]
yield secret_value
except ControlPlaneError as e:
print ( f "Failed to access secret ' { secret_name } ': { e } " )
raise
finally :
# Clear secret from memory
if secret_value:
secret_value = None
gc.collect() # Force garbage collection
# Usage
client = ControlPlaneClient( api_key = "your-api-key" )
with secure_secret(client, "github-api-token" ) as token:
# Use the token
import requests
headers = { "Authorization" : f "Bearer { token } " }
response = requests.get( "https://api.github.com/user" , headers = headers)
print ( f "GitHub user: { response.json()[ 'login' ] } " )
# Token is automatically cleared from memory here
4. Secret Rotation Checker
Identify secrets that may need rotation:
from kubiya import ControlPlaneClient
from datetime import datetime, timedelta
def check_secret_rotation_needed ( client : ControlPlaneClient, days_threshold : int = 90 ):
"""Identify secrets that may need rotation based on age."""
secrets = client.secrets.list()
now = datetime.utcnow()
needs_rotation = []
recently_rotated = []
for secret in secrets:
created_at = datetime.fromisoformat(secret[ 'created_at' ].replace( 'Z' , '+00:00' ))
age_days = (now - created_at).days
if age_days > days_threshold:
needs_rotation.append({
"name" : secret[ 'name' ],
"age_days" : age_days,
"created_at" : secret[ 'created_at' ],
"type" : secret[ 'type' ]
})
elif age_days < 30 :
recently_rotated.append(secret[ 'name' ])
print ( f "Secret Rotation Analysis (threshold: { days_threshold } days)" )
print ( "=" * 60 )
if needs_rotation:
print ( f " \n ⚠️ { len (needs_rotation) } secrets may need rotation:" )
for item in sorted (needs_rotation, key = lambda x : x[ 'age_days' ], reverse = True ):
print ( f " - { item[ 'name' ] } : { item[ 'age_days' ] } days old ( { item[ 'type' ] } )" )
else :
print ( " \n ✅ No secrets exceed rotation threshold" )
if recently_rotated:
print ( f " \n ✅ { len (recently_rotated) } secrets rotated recently:" )
for name in recently_rotated:
print ( f " - { name } " )
return {
"needs_rotation" : needs_rotation,
"recently_rotated" : recently_rotated,
"threshold_days" : days_threshold
}
# Usage
client = ControlPlaneClient( api_key = "your-api-key" )
rotation_report = check_secret_rotation_needed(client, days_threshold = 90 )
5. Integration Secrets Checker
Verify secrets for specific integrations:
from kubiya import ControlPlaneClient
from typing import Dict, List
def check_integration_secrets (
client : ControlPlaneClient,
integration_requirements : Dict[ str , List[ str ]]
) -> dict :
"""Check if required secrets exist for each integration."""
secrets = client.secrets.list()
# Build secret name to integration mapping
available_by_integration = {}
for secret in secrets:
integration = secret.get( 'integration' )
if integration:
if integration not in available_by_integration:
available_by_integration[integration] = []
available_by_integration[integration].append(secret[ 'name' ])
# Check requirements
results = {}
for integration, required_secrets in integration_requirements.items():
available = available_by_integration.get(integration, [])
missing = [s for s in required_secrets if s not in available]
results[integration] = {
"required" : required_secrets,
"available" : available,
"missing" : missing,
"complete" : len (missing) == 0
}
# Print report
print ( "Integration Secrets Report" )
print ( "=" * 60 )
for integration, result in results.items():
status = "✅" if result[ 'complete' ] else "❌"
print ( f " \n { status } { integration } :" )
print ( f " Required: { len (result[ 'required' ]) } " )
print ( f " Available: { len (result[ 'available' ]) } " )
if result[ 'missing' ]:
print ( f " Missing: { ', ' .join(result[ 'missing' ]) } " )
return results
# Usage
client = ControlPlaneClient( api_key = "your-api-key" )
requirements = {
"github" : [ "github-api-token" , "github-webhook-secret" ],
"aws" : [ "aws-credentials" , "aws-session-token" ],
"slack" : [ "slack-webhook-url" , "slack-bot-token" ],
"datadog" : [ "datadog-api-key" , "datadog-app-key" ]
}
integration_report = check_integration_secrets(client, requirements)
Error Handling
from kubiya import ControlPlaneClient
from kubiya.resources.exceptions import ControlPlaneError
from kubiya.core.exceptions import (
APIError as KubiyaAPIError,
AuthenticationError as KubiyaAuthenticationError,
TimeoutError as KubiyaTimeoutError
)
client = ControlPlaneClient( api_key = "your-api-key" )
# Handle secret access errors
try :
secrets = client.secrets.list()
except ControlPlaneError as e:
print ( f "Failed to list secrets: { e } " )
except KubiyaAuthenticationError as e:
print ( f "Authentication failed: { e } " )
except KubiyaAPIError as e:
print ( f "API error: { e.status_code } - { e.message } " )
# Handle secret not found
try :
secret = client.secrets.get_value( name = "non-existent-secret" )
except ControlPlaneError as e:
if "not found" in str (e).lower():
print ( "Secret not found" )
elif "permission denied" in str (e).lower():
print ( "Insufficient permissions to access secret" )
else :
print ( f "Error accessing secret: { e } " )
# Handle permission errors
try :
secret = client.secrets.get_value( name = "admin-only-secret" )
except ControlPlaneError as e:
print ( f "Access denied: { e } " )
# Log audit trail or alert security team
Best Practices
1. Never Log Secret Values
from kubiya import ControlPlaneClient
import logging
client = ControlPlaneClient( api_key = "your-api-key" )
# ❌ BAD - Never do this
secret = client.secrets.get_value( name = "api-token" )
logging.info( f "Token: { secret[ 'value' ] } " ) # NEVER LOG SECRET VALUES!
# ✅ GOOD - Log metadata only
secrets = client.secrets.list()
logging.info( f "Found { len (secrets) } secrets" )
logging.info( f "Secret names: { [s[ 'name' ] for s in secrets] } " )
2. Use Context Managers for Secret Access
from contextlib import contextmanager
from kubiya import ControlPlaneClient
@contextmanager
def get_secret_safely ( client : ControlPlaneClient, name : str ):
"""Safely retrieve and cleanup secrets."""
secret_value = None
try :
secret = client.secrets.get_value( name = name)
yield secret[ 'value' ]
finally :
secret_value = None # Clear from memory
# Usage
client = ControlPlaneClient( api_key = "your-api-key" )
with get_secret_safely(client, "api-token" ) as token:
# Use token here
pass
# Token is cleared automatically
3. Validate Secret Availability Before Use
from kubiya import ControlPlaneClient
def ensure_secrets_available ( client : ControlPlaneClient, required : list ) -> bool :
"""Check secrets exist before attempting to use them."""
available = {s[ 'name' ] for s in client.secrets.list()}
missing = [name for name in required if name not in available]
if missing:
raise ValueError ( f "Missing required secrets: { ', ' .join(missing) } " )
return True
# Usage
client = ControlPlaneClient( api_key = "your-api-key" )
ensure_secrets_available(client, [ "github-token" , "aws-key" ])
4. Implement Secret Caching Carefully
from kubiya import ControlPlaneClient
from datetime import datetime, timedelta
from typing import Optional
class SecretCache :
"""In-memory secret cache with TTL (use with caution!)."""
def __init__ ( self , client : ControlPlaneClient, ttl_seconds : int = 300 ):
self .client = client
self .ttl_seconds = ttl_seconds
self ._cache = {}
def get ( self , name : str ) -> str :
"""Get secret with caching."""
now = datetime.utcnow()
# Check cache
if name in self ._cache:
value, expiry = self ._cache[name]
if now < expiry:
return value
# Fetch and cache
secret = self .client.secrets.get_value( name = name)
value = secret[ 'value' ]
expiry = now + timedelta( seconds = self .ttl_seconds)
self ._cache[name] = (value, expiry)
return value
def clear ( self ):
"""Clear all cached secrets."""
self ._cache = {}
# Usage (use only when absolutely necessary)
cache = SecretCache(client, ttl_seconds = 300 )
token = cache.get( "api-token" )
Security Note : Only cache secrets when absolutely necessary and always use short TTLs. Cached secrets in memory are a security risk if the process is compromised.
API Reference
Methods
Method Description Parameters Returns list()List all secrets (metadata only) None List[Dict[str, Any]]get_value(name)Retrieve secret value name: strDict[str, Any]
Secret creation, updates, and deletion must be performed through the Kubiya Dashboard or CLI for security reasons.
{
"name" : "github-api-token" ,
"type" : "token" , # token, credentials, url, certificate, env_var
"scope" : "organization" , # organization, team, user
"description" : "GitHub API token for repository access" ,
"created_at" : "2025-11-15T10:00:00Z" ,
"created_by" : "[email protected] " ,
"last_used" : "2025-12-11T09:30:00Z" ,
"integration" : "github" # Optional: associated integration
}
Secret Value Structure
{
"name" : "github-api-token" ,
"value" : "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" , # Actual secret value
"type" : "token" ,
"scope" : "organization" ,
"retrieved_at" : "2025-12-11T10:30:00Z"
}
Security Considerations
Secret Access Logging
All secret access is logged by the Control Plane for security auditing:
Who accessed which secret
When the secret was accessed
From which service or agent
Permissions
Secret access is governed by:
Organization roles : Admins have full access
Team membership : Team secrets require team membership
User ownership : User-scoped secrets are personal
Rotation Recommendations
API Keys : Rotate every 90 days
Tokens : Rotate every 60 days
Credentials : Rotate every 90 days
Certificates : Follow certificate expiry dates
Next Steps