# 6.2 credentials.yml
**Purpose:** Configure SSH authentication methods, manage secrets, and define credential resolution for mcp-ssh-orchestrator.
## Overview
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.
## File Structure
```yaml
# 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"
```
## Configuration Fields
### Required Fields
| Field | Type | Description | Example |
|-------|------|-------------|---------|
| `name` | string | Unique credential identifier | `"prod_admin"` |
| `username` | string | SSH username | `"ubuntu"` |
### Optional Fields
| 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"` |
## Authentication Methods
### Key-Based Authentication (Recommended)
**SSH Key Authentication:**
```yaml
entries:
- name: "prod_admin"
username: "ubuntu"
key_path: "id_ed25519"
key_passphrase_secret: "prod_key_passphrase"
password_secret: "" # Empty = no password auth
```
**Key without Passphrase:**
```yaml
entries:
- name: "dev_admin"
username: "developer"
key_path: "dev_key"
key_passphrase_secret: "" # Empty = no passphrase
password_secret: ""
```
### Password-Based Authentication
**Password Authentication:**
```yaml
entries:
- name: "legacy_admin"
username: "admin"
key_path: "" # Empty = no key auth
password_secret: "legacy_password"
```
**Mixed Authentication (Key + Password):**
```yaml
entries:
- name: "mixed_auth"
username: "ubuntu"
key_path: "id_ed25519"
key_passphrase_secret: "key_passphrase"
password_secret: "user_password"
```
## Secret Resolution
### Secret Resolution Order
Secrets are resolved in this order:
1. **Direct environment variable** (Docker MCP Gateway): `<SECRET_NAME>` (uppercase)
- Docker MCP Gateway injects secrets as env vars matching the `env:` field in `server.yml`
- Example: `server.yml` has `env: SSH_KEY_PASSPHRASE_01` → container gets `SSH_KEY_PASSPHRASE_01`
- Secrets are ONLY injected when Docker MCP Gateway runs the container (not when running manually)
2. **Prefixed environment variable** (standalone/backward compatibility): `MCP_SSH_SECRET_<NAME>` (uppercase)
- Supports existing standalone deployments using prefixed env vars
3. **`.env` file**: `/app/secrets/.env`
- Consolidated secrets file with `KEY=value` format
- Supports comments (lines starting with `#`) and empty lines
- Supports quoted values (single or double quotes)
- Values can contain `=` characters
- Case-insensitive key lookup
4. **Docker secret file**: `/app/secrets/<name>`
- Individual file-based secret storage for standalone deployments
- Maintains backward compatibility with existing setups
5. **Empty string**: If not found
### Docker MCP Gateway Secret Resolution
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, matches `env:` field, ALL CAPS)
- The `env:` field in `server.yml` determines the environment variable name in the container
**Example:**
```yaml
# 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.yml
```
**Verification:**
```bash
# 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-password
```
### Using .env File for Secrets
The `.env` file provides a convenient way to manage all secrets in a single consolidated file, making it easier to organize and maintain credentials.
#### Creating a .env File
1. Copy the example file:
```bash
cp secrets/.env.example secrets/.env
```
2. Edit `secrets/.env` and add your secrets:
```bash
# 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
```
3. Set secure file permissions:
```bash
chmod 600 secrets/.env
```
#### .env File Format
- **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:**
```bash
# 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
```
#### Security Best Practices
- **File permissions**: Always set `.env` file to `600` (owner read/write only)
```bash
chmod 600 secrets/.env
```
- **Never commit**: Ensure `.env` is in `.gitignore` (it is by default)
- **Location**: Keep `.env` file in the `secrets/` directory
- **Backup**: Store backups securely, never in version control
#### Migration from Individual Files
To migrate from individual secret files to `.env`:
1. List all your secret files:
```bash
ls -1 secrets/
```
2. Read each secret and add to `.env`:
```bash
echo "SECRET_NAME=$(cat secrets/SECRET_NAME)" >> secrets/.env
```
3. Verify the `.env` file:
```bash
cat secrets/.env
```
4. Test that secrets still resolve correctly
5. Optionally remove individual files (after verifying everything works)
#### .env File Caching
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).
### Path Traversal Protection
**Security Feature:** Secret resolution includes path traversal protection to prevent unauthorized file access.
### Input Length Limits
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
- **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.
#### Secret Name Validation
- **Allowed characters**: Alphanumeric (a-z, A-Z, 0-9), dash (-), underscore (_)
- **Rejected**: Special characters, path separators, traversal patterns
- **Examples**:
```yaml
# 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
```
#### Path Resolution Security
1. **Relative paths only**: Secrets must use relative paths (no absolute paths)
2. **Directory confinement**: All resolved paths must stay within `/app/secrets`
3. **Normalization**: Paths are normalized to handle encoded traversal attempts
4. **File type validation**: Only regular files are accepted (directories and symlinks rejected)
5. **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.
### Environment Variables
**Setting secrets via environment:**
```bash
# 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:latest
```
**Claude Desktop configuration:**
```json
{
"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"
]
}
}
}
```
### Docker Secrets
**Creating Docker secrets:**
```bash
# 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: true
```
**File-based secrets:**
```bash
# 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/*
```
## SSH Key Management
### Key Generation
**Ed25519 Keys (Recommended):**
```bash
# 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.pub
```
**RSA Keys (Legacy):**
```bash
# 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.pub
```
### Key Deployment
**Deploy public key to target hosts:**
```bash
# Copy public key to target host
ssh-copy-id -i ~/mcp-ssh/keys/id_ed25519.pub ubuntu@10.0.0.11
# Or manually add to authorized_keys
cat ~/mcp-ssh/keys/id_ed25519.pub | ssh ubuntu@10.0.0.11 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
```
### Key Rotation
**Rotate SSH keys:**
```bash
# 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
```
## Environment-Specific Configurations
### Development Environment
```yaml
# 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: ""
```
### Staging Environment
```yaml
# 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: ""
```
### Production Environment
```yaml
# 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: ""
```
## Multi-User Configurations
### Different Users per Host Type
```yaml
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: ""
```
### Service Account Credentials
```yaml
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: ""
```
## Path Traversal Protection for Keys
**Security Feature:** SSH key path resolution includes path traversal protection.
### Key Path Validation
- **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**:
```yaml
# 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 file
```
**File 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.
## Security Best Practices
### Key Security
1. **Use Ed25519 keys** for new deployments
2. **Set private key permissions** to 0400
3. **Use passphrases** for private keys
4. **Rotate keys quarterly**
5. **Use separate keys** for different environments
### Secret Security
1. **Never hardcode secrets** in YAML files
2. **Use Docker secrets** for production
3. **Use environment variables** for development
4. **Rotate secrets regularly**
5. **Use strong, unique secrets**
### Access Control
1. **Use least-privilege usernames**
2. **Separate service accounts** from admin accounts
3. **Use different credentials** per environment
4. **Monitor credential usage**
5. **Revoke unused credentials**
## Validation and Testing
### Configuration Validation
```bash
# 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())
"
```
### Authentication Testing
```bash
# Test SSH key authentication
ssh -i keys/id_ed25519 ubuntu@10.0.0.11 "echo 'Key auth successful'"
# Test password authentication
ssh ubuntu@10.0.0.11 "echo 'Password auth successful'"
# Test via mcp-ssh-orchestrator
ssh_ping # Health check
ssh_describe_host --alias "web1" # Get host details
```
### Secret Resolution Testing
```bash
# 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)
"
```
## Troubleshooting
### Common Issues
1. **Invalid YAML syntax**
```bash
# Check syntax
python -c "import yaml; yaml.safe_load(open('config/credentials.yml'))"
```
2. **Missing secret files**
```bash
# Check secret files
ls -la ~/mcp-ssh/secrets/
```
3. **Wrong key permissions**
```bash
# Fix key permissions
chmod 0400 ~/mcp-ssh/keys/*
```
4. **SSH connection failures**
```bash
# Test SSH connection
ssh -v -i keys/id_ed25519 ubuntu@10.0.0.11
```
### Authentication Issues
1. **Key authentication failed**
```bash
# Check key format
ssh-keygen -l -f keys/id_ed25519
# Test key manually
ssh -i keys/id_ed25519 ubuntu@10.0.0.11
```
2. **Password authentication failed**
```bash
# Check password secret
echo $MCP_SSH_SECRET_ADMIN_PASSWORD
# Test password manually
ssh ubuntu@10.0.0.11
```
3. **Passphrase authentication failed**
```bash
# Check passphrase secret
echo $MCP_SSH_SECRET_KEY_PASSPHRASE
# Test passphrase manually
ssh -i keys/id_ed25519 ubuntu@10.0.0.11
```
## Next Steps
- **[policy.yml](06.3-policy.yml)** - Security policy configuration
- **[Usage Cookbook](08-Usage-Cookbook)** - Practical credential examples
- **[Deployment](09-Deployment)** - Production credential management
- **[Troubleshooting](12-Troubleshooting)** - Common credential issues