06.2 credentials.yml - samerfarida/mcp-ssh-orchestrator GitHub Wiki
Purpose: Configure SSH authentication methods, manage secrets, and define credential resolution for mcp-ssh-orchestrator.
The credentials.yml file defines SSH authentication methods and secret management for connecting to target hosts. It supports both key-based and password-based authentication with secure secret resolution.
# credentials.yml
entries:
- name: "prod_admin"
username: "ubuntu"
key_path: "id_ed25519"
key_passphrase_secret: "prod_key_passphrase"
password_secret: ""
- name: "legacy_password"
username: "admin"
key_path: ""
password_secret: "legacy_password"| Field | Type | Description | Example |
|---|---|---|---|
name |
string | Unique credential identifier | "prod_admin" |
username |
string | SSH username | "ubuntu" |
| Field | Type | Default | Description | Example |
|---|---|---|---|---|
key_path |
string | "" |
SSH private key file path | "id_ed25519" |
key_passphrase_secret |
string | "" |
Passphrase secret name | "prod_key_passphrase" |
password_secret |
string | "" |
Password secret name | "admin_password" |
SSH Key Authentication:
entries:
- name: "prod_admin"
username: "ubuntu"
key_path: "id_ed25519"
key_passphrase_secret: "prod_key_passphrase"
password_secret: "" # Empty = no password authKey without Passphrase:
entries:
- name: "dev_admin"
username: "developer"
key_path: "dev_key"
key_passphrase_secret: "" # Empty = no passphrase
password_secret: ""Password Authentication:
entries:
- name: "legacy_admin"
username: "admin"
key_path: "" # Empty = no key auth
password_secret: "legacy_password"Mixed Authentication (Key + Password):
entries:
- name: "mixed_auth"
username: "ubuntu"
key_path: "id_ed25519"
key_passphrase_secret: "key_passphrase"
password_secret: "user_password"Secrets are resolved in this order:
-
Direct environment variable (Docker MCP Gateway):
<SECRET_NAME>(uppercase)- Docker MCP Gateway injects secrets as env vars matching the
env:field inserver.yml - Example:
server.ymlhasenv: SSH_KEY_PASSPHRASE_01→ container getsSSH_KEY_PASSPHRASE_01 - Secrets are ONLY injected when Docker MCP Gateway runs the container (not when running manually)
- Docker MCP Gateway injects secrets as env vars matching the
-
Prefixed environment variable (standalone/backward compatibility):
MCP_SSH_SECRET_<NAME>(uppercase)- Supports existing standalone deployments using prefixed env vars
-
.envfile:/app/secrets/.env- Consolidated secrets file with
KEY=valueformat - Supports comments (lines starting with
#) and empty lines - Supports quoted values (single or double quotes)
- Values can contain
=characters - Case-insensitive key lookup
- Consolidated secrets file with
-
Docker secret file:
/app/secrets/<name>- Individual file-based secret storage for standalone deployments
- Maintains backward compatibility with existing setups
- Empty string: If not found
When using mcp-ssh-orchestrator with Docker MCP Gateway, secrets are injected as environment variables matching the env: field in server.yml.
Important Notes:
- Secrets are ONLY injected when Docker MCP Gateway runs the container (not when running manually)
- Docker MCP secret name:
mcp-ssh-orchestrator.ssh_password_secret_01(stored in Docker MCP) - Container env var:
SSH_PASSWORD_SECRET_01(injected by gateway, matchesenv:field, ALL CAPS) - The
env:field inserver.ymldetermines the environment variable name in the container
Example:
# server.yml (Docker MCP Registry)
secrets:
- name: mcp-ssh-orchestrator.ssh_password_secret_01
env: SSH_PASSWORD_SECRET_01
example: <SSH_PASSWORD_SECRET_01>
# credentials.yml
entries:
- name: "prod_admin"
username: "ubuntu"
key_path: "id_ed25519"
key_passphrase_secret: "SSH_KEY_PASSPHRASE_01" # Matches env: in server.yml
password_secret: "SSH_PASSWORD_SECRET_01" # Matches env: in server.ymlVerification:
# Set secret in Docker MCP
docker mcp secret set mcp-ssh-orchestrator.ssh_password_secret_01="my-password"
# When gateway runs the container, verify env var exists:
docker exec <gateway-container> env | grep SSH_PASSWORD_SECRET_01
# Should output: SSH_PASSWORD_SECRET_01=my-passwordThe .env file provides a convenient way to manage all secrets in a single consolidated file, making it easier to organize and maintain credentials.
-
Copy the example file:
cp secrets/.env.example secrets/.env
-
Edit
secrets/.envand add your secrets:# SSH Passwords SSH_PASSWORD_SECRET_01=your-actual-password lab_maint_password=your-lab-password # SSH Key Passphrases SSH_KEY_PASSPHRASE_01=your-actual-passphrase prod_admin_passphrase=your-prod-passphrase
-
Set secure file permissions:
chmod 600 secrets/.env
-
Basic format:
KEY=value(one per line) -
Comments: Lines starting with
#are ignored - Empty lines: Ignored
-
Quoted values: Supports single (
'value') and double ("value") quotes -
Values with
=: Values can contain=characters (split on first=) - Whitespace: Keys and values are automatically stripped of leading/trailing whitespace
Example .env file:
# SSH Passwords
SSH_PASSWORD_SECRET_01=my-password-123
lab_maint_password="password with spaces"
# SSH Key Passphrases
SSH_KEY_PASSPHRASE_01=my-passphrase-456
prod_admin_passphrase='another passphrase'
# Values with equals signs
COMPLEX_VALUE=key1=value1&key2=value2-
File permissions: Always set
.envfile to600(owner read/write only)chmod 600 secrets/.env
-
Never commit: Ensure
.envis in.gitignore(it is by default) -
Location: Keep
.envfile in thesecrets/directory -
Backup: Store backups securely, never in version control
To migrate from individual secret files to .env:
-
List all your secret files:
ls -1 secrets/
-
Read each secret and add to
.env:echo "SECRET_NAME=$(cat secrets/SECRET_NAME)" >> secrets/.env
-
Verify the
.envfile:cat secrets/.env
-
Test that secrets still resolve correctly
-
Optionally remove individual files (after verifying everything works)
The .env file is cached after first load to improve performance. The cache is keyed by secrets_dir, so different directories maintain separate caches. If you modify the .env file, you may need to restart the application for changes to take effect (cache is not automatically invalidated).
Security Feature: Secret resolution includes path traversal protection to prevent unauthorized file access.
All string parameters in credentials configuration have length limits:
-
Secret Names: Maximum 100 characters
- Validated when resolving secrets via
password_secret,key_passphrase_secret - Names exceeding limit are rejected with security event logging
- Validated when resolving secrets via
-
SSH Key Paths: Maximum 500 characters
- Validated when resolving key paths
- Paths exceeding limit are rejected with security event logging
Security: Length limits prevent resource exhaustion attacks via oversized inputs.
-
Allowed characters: Alphanumeric (a-z, A-Z, 0-9), dash (-), underscore (_)
-
Rejected: Special characters, path separators, traversal patterns
-
Examples:
# Valid password_secret: "prod_password" password_secret: "key-passphrase" # Invalid (rejected) password_secret: "../etc/passwd" # Path traversal password_secret: "/absolute/path" # Absolute path password_secret: "secret.name" # Special characters
- Relative paths only: Secrets must use relative paths (no absolute paths)
-
Directory confinement: All resolved paths must stay within
/app/secrets - Normalization: Paths are normalized to handle encoded traversal attempts
- File type validation: Only regular files are accepted (directories and symlinks rejected)
- Security logging: Path traversal and file validation failures are logged as security events
File Type Requirements:
- Regular files within secrets directory
- Directories (must be files, not directories)
- Symbolic links (rejected for security)
Effect: Prevents accessing files outside the secrets directory via path traversal attacks and blocks access to directories or symlinks that could pose security risks.
Setting secrets via environment:
# Set secret via environment variable
export MCP_SSH_SECRET_PROD_KEY_PASSPHRASE="my-passphrase"
export MCP_SSH_SECRET_ADMIN_PASSWORD="admin-password"
# Use in Docker
docker run -i --rm \
-e MCP_SSH_SECRET_PROD_KEY_PASSPHRASE="my-passphrase" \
-e MCP_SSH_SECRET_ADMIN_PASSWORD="admin-password" \
ghcr.io/samerfarida/mcp-ssh-orchestrator:latestClaude Desktop configuration:
{
"mcpServers": {
"ssh-orchestrator": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_SSH_SECRET_PROD_PASSWORD=your-password",
"-v", "/Users/YOUR_USERNAME/mcp-ssh/config:/app/config:ro",
"-v", "/Users/YOUR_USERNAME/mcp-ssh/keys:/app/keys:ro",
"ghcr.io/samerfarida/mcp-ssh-orchestrator:latest"
]
}
}
}Creating Docker secrets:
# Create secret
echo "my-passphrase" | docker secret create ssh_key_passphrase -
# Use in Docker Compose
services:
mcp-ssh:
image: ghcr.io/samerfarida/mcp-ssh-orchestrator:latest
secrets:
- ssh_key_passphrase
volumes:
- ./config:/app/config:ro
- ./keys:/app/keys:ro
secrets:
ssh_key_passphrase:
external: trueFile-based secrets:
# Create secrets directory
mkdir -p ~/mcp-ssh/secrets
chmod 0700 ~/mcp-ssh/secrets
# Add secret files
echo "passphrase" > ~/mcp-ssh/secrets/key_passphrase
echo "password" > ~/mcp-ssh/secrets/admin_password
# Set permissions
chmod 0400 ~/mcp-ssh/secrets/*Ed25519 Keys (Recommended):
# Generate Ed25519 key pair
ssh-keygen -t ed25519 -f ~/.ssh/mcp_orchestrator -C "mcp-ssh-orchestrator"
# Copy to keys directory
cp ~/.ssh/mcp_orchestrator ~/mcp-ssh/keys/id_ed25519
cp ~/.ssh/mcp_orchestrator.pub ~/mcp-ssh/keys/id_ed25519.pub
# Set permissions
chmod 0400 ~/mcp-ssh/keys/id_ed25519
chmod 0444 ~/mcp-ssh/keys/id_ed25519.pubRSA Keys (Legacy):
# Generate RSA 4096-bit key pair
ssh-keygen -t rsa -b 4096 -f ~/.ssh/mcp_orchestrator_rsa -C "mcp-ssh-orchestrator"
# Copy to keys directory
cp ~/.ssh/mcp_orchestrator_rsa ~/mcp-ssh/keys/id_rsa
cp ~/.ssh/mcp_orchestrator_rsa.pub ~/mcp-ssh/keys/id_rsa.pub
# Set permissions
chmod 0400 ~/mcp-ssh/keys/id_rsa
chmod 0444 ~/mcp-ssh/keys/id_rsa.pubDeploy public key to target hosts:
# Copy public key to target host
ssh-copy-id -i ~/mcp-ssh/keys/id_ed25519.pub [email protected]
# Or manually add to authorized_keys
cat ~/mcp-ssh/keys/id_ed25519.pub | ssh [email protected] "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"Rotate SSH keys:
# Generate new key pair
ssh-keygen -t ed25519 -f ~/.ssh/mcp_orchestrator_new -C "mcp-ssh-orchestrator-new"
# Deploy new public key to all hosts
for host in $(ssh_list_hosts); do
ssh-copy-id -i ~/.ssh/mcp_orchestrator_new.pub ubuntu@$host
done
# Update configuration
cp ~/.ssh/mcp_orchestrator_new ~/mcp-ssh/keys/id_ed25519
chmod 0400 ~/mcp-ssh/keys/id_ed25519
# Remove old key from hosts
for host in $(ssh_list_hosts); do
ssh ubuntu@$host "sed -i '/mcp-ssh-orchestrator/d' ~/.ssh/authorized_keys"
done# credentials.yml - Development
entries:
- name: "dev_admin"
username: "developer"
key_path: "dev_key"
key_passphrase_secret: "" # No passphrase for dev
password_secret: ""
- name: "dev_root"
username: "root"
key_path: "dev_root_key"
key_passphrase_secret: ""
password_secret: ""# credentials.yml - Staging
entries:
- name: "staging_admin"
username: "ubuntu"
key_path: "staging_key"
key_passphrase_secret: "staging_key_passphrase"
password_secret: ""
- name: "staging_service"
username: "service"
key_path: "staging_service_key"
key_passphrase_secret: "staging_service_passphrase"
password_secret: ""# credentials.yml - Production
entries:
- name: "prod_admin"
username: "ubuntu"
key_path: "prod_key"
key_passphrase_secret: "prod_key_passphrase"
password_secret: ""
- name: "prod_service"
username: "service"
key_path: "prod_service_key"
key_passphrase_secret: "prod_service_passphrase"
password_secret: ""
- name: "prod_monitoring"
username: "monitoring"
key_path: "prod_monitoring_key"
key_passphrase_secret: "prod_monitoring_passphrase"
password_secret: ""entries:
# Web server credentials
- name: "web_admin"
username: "ubuntu"
key_path: "web_key"
key_passphrase_secret: "web_key_passphrase"
password_secret: ""
# Database server credentials
- name: "db_admin"
username: "postgres"
key_path: "db_key"
key_passphrase_secret: "db_key_passphrase"
password_secret: ""
# Monitoring server credentials
- name: "monitor_admin"
username: "monitoring"
key_path: "monitor_key"
key_passphrase_secret: "monitor_key_passphrase"
password_secret: ""entries:
# Application service account
- name: "app_service"
username: "app"
key_path: "app_service_key"
key_passphrase_secret: "app_service_passphrase"
password_secret: ""
# Backup service account
- name: "backup_service"
username: "backup"
key_path: "backup_service_key"
key_passphrase_secret: "backup_service_passphrase"
password_secret: ""
# Monitoring service account
- name: "monitoring_service"
username: "monitoring"
key_path: "monitoring_service_key"
key_passphrase_secret: "monitoring_service_passphrase"
password_secret: ""Security Feature: SSH key path resolution includes path traversal protection.
-
Relative paths: Resolved relative to
keys_dir, validated to stay within directory -
Absolute paths: Must be within
keys_dir(rejected if outside) -
Traversal patterns: Paths containing
..or..\\are rejected - File type validation: Only regular files accepted (directories and symlinks rejected)
- Security logging: Path traversal and file validation failures are logged
Examples:
# Valid key paths
key_path: "id_ed25519" # Relative, within keys_dir
key_path: "/app/keys/id_ed25519" # Absolute, within keys_dir
# Invalid (rejected)
key_path: "../outside_key" # Path traversal
key_path: "/etc/passwd" # Outside keys_dir
key_path: "../../root/.ssh/id_rsa" # Traversal attempt
key_path: "subdirectory" # Points to directory, not file
key_path: "symlink_key" # Points to symlink, not regular fileFile Type Requirements:
- Regular files within keys_dir (or non-existent files - validated when used)
- Directories
- Symbolic links (rejected for security)
Effect: Prevents accessing SSH keys from unauthorized locations and blocks access to directories or symlinks that could pose security risks.
- Use Ed25519 keys for new deployments
- Set private key permissions to 0400
- Use passphrases for private keys
- Rotate keys quarterly
- Use separate keys for different environments
- Never hardcode secrets in YAML files
- Use Docker secrets for production
- Use environment variables for development
- Rotate secrets regularly
- Use strong, unique secrets
- Use least-privilege usernames
- Separate service accounts from admin accounts
- Use different credentials per environment
- Monitor credential usage
- Revoke unused credentials
# Validate credentials.yml syntax
python -c "import yaml; yaml.safe_load(open('config/credentials.yml'))"
# Validate credential references
python -c "
from mcp_ssh.config import Config
config = Config('config/')
print('Credential validation:', config.validate_credentials())
"# Test SSH key authentication
ssh -i keys/id_ed25519 [email protected] "echo 'Key auth successful'"
# Test password authentication
ssh [email protected] "echo 'Password auth successful'"
# Test via mcp-ssh-orchestrator
ssh_ping # Health check
ssh_describe_host --alias "web1" # Get host details# Test secret resolution
python -c "
from mcp_ssh.config import Config
config = Config('config/')
cred = config.get_credentials('prod_admin')
print('Key path:', cred.key_path)
print('Username:', cred.username)
"-
Invalid YAML syntax
# Check syntax python -c "import yaml; yaml.safe_load(open('config/credentials.yml'))"
-
Missing secret files
# Check secret files ls -la ~/mcp-ssh/secrets/
-
Wrong key permissions
# Fix key permissions chmod 0400 ~/mcp-ssh/keys/*
-
SSH connection failures
# Test SSH connection ssh -v -i keys/id_ed25519 [email protected]
-
Key authentication failed
# Check key format ssh-keygen -l -f keys/id_ed25519 # Test key manually ssh -i keys/id_ed25519 [email protected]
-
Password authentication failed
# Check password secret echo $MCP_SSH_SECRET_ADMIN_PASSWORD # Test password manually ssh [email protected]
-
Passphrase authentication failed
# Check passphrase secret echo $MCP_SSH_SECRET_KEY_PASSPHRASE # Test passphrase manually ssh -i keys/id_ed25519 [email protected]
- policy.yml - Security policy configuration
- Usage Cookbook - Practical credential examples
- Deployment - Production credential management
- Troubleshooting - Common credential issues