|
| 1 | +# SSH Key Retention for Git Proxy |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This document describes the SSH key retention feature that allows Git Proxy to securely store and reuse user SSH keys during the approval process, eliminating the need for users to re-authenticate when their push is approved. |
| 6 | + |
| 7 | +## Problem Statement |
| 8 | + |
| 9 | +Previously, when a user pushes code via SSH to Git Proxy: |
| 10 | + |
| 11 | +1. User authenticates with their SSH key |
| 12 | +2. Push is intercepted and requires approval |
| 13 | +3. After approval, the system loses the user's SSH key |
| 14 | +4. User must manually re-authenticate or the system falls back to proxy's SSH key |
| 15 | + |
| 16 | +## Solution Architecture |
| 17 | + |
| 18 | +### Components |
| 19 | + |
| 20 | +1. **SSHKeyManager** (`src/security/SSHKeyManager.ts`) |
| 21 | + - Handles secure encryption/decryption of SSH keys |
| 22 | + - Manages key expiration (24 hours by default) |
| 23 | + - Provides cleanup mechanisms for expired keys |
| 24 | + |
| 25 | +2. **SSHAgent** (`src/security/SSHAgent.ts`) |
| 26 | + - In-memory SSH key store with automatic expiration |
| 27 | + - Provides signing capabilities for SSH authentication |
| 28 | + - Singleton pattern for system-wide access |
| 29 | + |
| 30 | +3. **SSH Key Capture Processor** (`src/proxy/processors/push-action/captureSSHKey.ts`) |
| 31 | + - Captures SSH key information during push processing |
| 32 | + - Stores key securely when approval is required |
| 33 | + |
| 34 | +4. **SSH Key Forwarding Service** (`src/service/SSHKeyForwardingService.ts`) |
| 35 | + - Handles approved pushes using retained SSH keys |
| 36 | + - Provides fallback mechanisms for expired/missing keys |
| 37 | + |
| 38 | +### Security Features |
| 39 | + |
| 40 | +- **Encryption**: All stored SSH keys are encrypted using AES-256-GCM |
| 41 | +- **Expiration**: Keys automatically expire after 24 hours |
| 42 | +- **Secure Cleanup**: Memory is securely cleared when keys are removed |
| 43 | +- **Environment-based Keys**: Encryption keys can be provided via environment variables |
| 44 | + |
| 45 | +## Implementation Details |
| 46 | + |
| 47 | +### SSH Key Capture Flow |
| 48 | + |
| 49 | +1. User connects via SSH and authenticates with their public key |
| 50 | +2. SSH server captures key information and stores it on the client connection |
| 51 | +3. When a push is processed, the `captureSSHKey` processor: |
| 52 | + - Checks if this is an SSH push requiring approval |
| 53 | + - Stores SSH key information in the action for later use |
| 54 | + |
| 55 | +### Approval and Push Flow |
| 56 | + |
| 57 | +1. Push is approved via web interface or API |
| 58 | +2. `SSHKeyForwardingService.executeApprovedPush()` is called |
| 59 | +3. Service attempts to retrieve the user's SSH key from the agent |
| 60 | +4. If key is available and valid: |
| 61 | + - Creates temporary SSH key file |
| 62 | + - Executes git push with user's credentials |
| 63 | + - Cleans up temporary files |
| 64 | +5. If key is not available: |
| 65 | + - Falls back to proxy's SSH key |
| 66 | + - Logs the fallback for audit purposes |
| 67 | + |
| 68 | +### Database Schema Changes |
| 69 | + |
| 70 | +The `Push` type has been extended with: |
| 71 | + |
| 72 | +```typescript |
| 73 | +{ |
| 74 | + encryptedSSHKey?: string; // Encrypted SSH private key |
| 75 | + sshKeyExpiry?: Date; // Key expiration timestamp |
| 76 | + protocol?: 'https' | 'ssh'; // Protocol used for the push |
| 77 | + userId?: string; // User ID for the push |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +## Configuration |
| 82 | + |
| 83 | +### Environment Variables |
| 84 | + |
| 85 | +- `SSH_KEY_ENCRYPTION_KEY`: 32-byte hex string for SSH key encryption |
| 86 | +- If not provided, keys are derived from the SSH host key |
| 87 | + |
| 88 | +### SSH Configuration |
| 89 | + |
| 90 | +Enable SSH support in `proxy.config.json`: |
| 91 | + |
| 92 | +```json |
| 93 | +{ |
| 94 | + "ssh": { |
| 95 | + "enabled": true, |
| 96 | + "port": 2222, |
| 97 | + "hostKey": { |
| 98 | + "privateKeyPath": "./.ssh/host_key", |
| 99 | + "publicKeyPath": "./.ssh/host_key.pub" |
| 100 | + } |
| 101 | + } |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +## Security Considerations |
| 106 | + |
| 107 | +### Encryption Key Management |
| 108 | + |
| 109 | +- **Production**: Use `SSH_KEY_ENCRYPTION_KEY` environment variable with a securely generated 32-byte key |
| 110 | +- **Development**: System derives keys from SSH host key (less secure but functional) |
| 111 | + |
| 112 | +### Key Rotation |
| 113 | + |
| 114 | +- SSH keys are automatically rotated every 24 hours |
| 115 | +- Manual cleanup can be triggered via `SSHKeyManager.cleanupExpiredKeys()` |
| 116 | + |
| 117 | +### Memory Security |
| 118 | + |
| 119 | +- Private keys are stored in Buffer objects that are securely cleared |
| 120 | +- Temporary files are created with restrictive permissions (0600) |
| 121 | +- All temporary files are automatically cleaned up |
| 122 | + |
| 123 | +## API Usage |
| 124 | + |
| 125 | +### Adding SSH Key to Agent |
| 126 | + |
| 127 | +```typescript |
| 128 | +import { SSHKeyForwardingService } from './service/SSHKeyForwardingService'; |
| 129 | + |
| 130 | +// Add SSH key for a push |
| 131 | +SSHKeyForwardingService.addSSHKeyForPush( |
| 132 | + pushId, |
| 133 | + privateKeyBuffer, |
| 134 | + publicKeyBuffer, |
| 135 | + 'user@example.com', |
| 136 | +); |
| 137 | +``` |
| 138 | + |
| 139 | +### Executing Approved Push |
| 140 | + |
| 141 | +```typescript |
| 142 | +// Execute approved push with retained SSH key |
| 143 | +const success = await SSHKeyForwardingService.executeApprovedPush(pushId); |
| 144 | +``` |
| 145 | + |
| 146 | +### Cleanup |
| 147 | + |
| 148 | +```typescript |
| 149 | +// Manual cleanup of expired keys |
| 150 | +await SSHKeyForwardingService.cleanupExpiredKeys(); |
| 151 | +``` |
| 152 | + |
| 153 | +## Monitoring and Logging |
| 154 | + |
| 155 | +The system provides comprehensive logging for: |
| 156 | + |
| 157 | +- SSH key capture and storage |
| 158 | +- Key expiration and cleanup |
| 159 | +- Push execution with user keys |
| 160 | +- Fallback to proxy keys |
| 161 | + |
| 162 | +Log prefixes: |
| 163 | + |
| 164 | +- `[SSH Key Manager]`: Key encryption/decryption operations |
| 165 | +- `[SSH Agent]`: In-memory key management |
| 166 | +- `[SSH Forwarding]`: Push execution and key usage |
| 167 | + |
| 168 | +## Future Enhancements |
| 169 | + |
| 170 | +1. **SSH Agent Forwarding**: Implement true SSH agent forwarding instead of key storage |
| 171 | +2. **Key Derivation**: Support for different key types (Ed25519, ECDSA, etc.) |
| 172 | +3. **Audit Logging**: Enhanced audit trail for SSH key usage |
| 173 | +4. **Key Rotation**: Automatic key rotation based on push frequency |
| 174 | +5. **Integration**: Integration with external SSH key management systems |
| 175 | + |
| 176 | +## Troubleshooting |
| 177 | + |
| 178 | +### Common Issues |
| 179 | + |
| 180 | +1. **Key Not Found**: Check if key has expired or was not properly captured |
| 181 | +2. **Permission Denied**: Verify SSH key permissions and proxy configuration |
| 182 | +3. **Fallback to Proxy Key**: Normal behavior when user key is unavailable |
| 183 | + |
| 184 | +### Debug Commands |
| 185 | + |
| 186 | +```bash |
| 187 | +# Check SSH agent status |
| 188 | +curl -X GET http://localhost:8080/api/v1/ssh/agent/status |
| 189 | + |
| 190 | +# List active SSH keys |
| 191 | +curl -X GET http://localhost:8080/api/v1/ssh/agent/keys |
| 192 | + |
| 193 | +# Trigger cleanup |
| 194 | +curl -X POST http://localhost:8080/api/v1/ssh/agent/cleanup |
| 195 | +``` |
| 196 | + |
| 197 | +## Conclusion |
| 198 | + |
| 199 | +The SSH key retention feature provides a seamless experience for users while maintaining security through encryption, expiration, and proper cleanup mechanisms. It eliminates the need for re-authentication while ensuring that SSH keys are not permanently stored or exposed. |
0 commit comments