feat(M38): SSH target connector for agentless deployment via SSH/SFTP

Adds a new target connector enabling certificate deployment to any
Linux/Unix server without installing the certctl agent binary. Uses the
proxy agent pattern — a single agent in the same network zone deploys
certs to remote servers over SSH/SFTP.

Key additions:
- SSH/SFTP connector with key auth (file/inline) + password auth
- Injectable SSHClient interface for cross-platform testing (25 tests)
- Shell injection prevention via validation.ValidateShellCommand()
- Configurable cert/key/chain paths with octal permissions
- GUI: 11 SSH config fields in target create wizard

Also fixes pre-existing frontend bug where all target type strings
(nginx, apache, etc.) were sent as lowercase but the backend expects
proper-case (NGINX, Apache, etc.), breaking GUI-created targets.
Adds missing TargetTypeSSH to validTargetTypes service map.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
shankar0123
2026-04-05 12:36:01 -04:00
parent 8f146e08d6
commit 697c0be9f3
13 changed files with 1448 additions and 60 deletions
+11 -10
View File
@@ -11,16 +11,17 @@ import { formatDateTime } from '../api/utils';
import type { Job } from '../api/types';
const typeLabels: Record<string, string> = {
nginx: 'NGINX',
apache: 'Apache',
haproxy: 'HAProxy',
traefik: 'Traefik',
caddy: 'Caddy',
f5_bigip: 'F5 BIG-IP',
iis: 'IIS',
envoy: 'Envoy',
postfix: 'Postfix',
dovecot: 'Dovecot',
NGINX: 'NGINX',
Apache: 'Apache',
HAProxy: 'HAProxy',
Traefik: 'Traefik',
Caddy: 'Caddy',
F5: 'F5 BIG-IP',
IIS: 'IIS',
Envoy: 'Envoy',
Postfix: 'Postfix',
Dovecot: 'Dovecot',
SSH: 'SSH',
};
function InfoRow({ label, value }: { label: string; value: React.ReactNode }) {
+45 -30
View File
@@ -11,83 +11,85 @@ import { formatDateTime } from '../api/utils';
import type { Target } from '../api/types';
const typeLabels: Record<string, string> = {
nginx: 'NGINX',
apache: 'Apache',
haproxy: 'HAProxy',
traefik: 'Traefik',
caddy: 'Caddy',
envoy: 'Envoy',
postfix: 'Postfix',
dovecot: 'Dovecot',
f5_bigip: 'F5 BIG-IP',
iis: 'IIS',
NGINX: 'NGINX',
Apache: 'Apache',
HAProxy: 'HAProxy',
Traefik: 'Traefik',
Caddy: 'Caddy',
Envoy: 'Envoy',
Postfix: 'Postfix',
Dovecot: 'Dovecot',
F5: 'F5 BIG-IP',
IIS: 'IIS',
SSH: 'SSH',
};
const TARGET_TYPES = [
{ value: 'nginx', label: 'NGINX', description: 'Deploy to NGINX web server via file write + config validation + reload' },
{ value: 'apache', label: 'Apache httpd', description: 'Separate cert/chain/key files, apachectl configtest, graceful reload' },
{ value: 'haproxy', label: 'HAProxy', description: 'Combined PEM file (cert+chain+key), optional validate, reload' },
{ value: 'traefik', label: 'Traefik', description: 'File provider deployment — writes cert/key to watched directory, auto-reload' },
{ value: 'caddy', label: 'Caddy', description: 'Admin API hot-reload or file-based deployment with configurable mode' },
{ value: 'envoy', label: 'Envoy', description: 'File-based deployment — writes cert/key to watched directory. Optional SDS file generation.' },
{ value: 'postfix', label: 'Postfix', description: 'Postfix MTA — file write + postfix reload' },
{ value: 'dovecot', label: 'Dovecot', description: 'Dovecot IMAP/POP3 — file write + doveadm reload' },
{ value: 'f5_bigip', label: 'F5 BIG-IP', description: 'iControl REST — cert upload, SSL profile update via proxy agent' },
{ value: 'iis', label: 'IIS', description: 'Windows IIS via agent-local PowerShell or remote WinRM proxy agent' },
{ value: 'NGINX', label: 'NGINX', description: 'Deploy to NGINX web server via file write + config validation + reload' },
{ value: 'Apache', label: 'Apache httpd', description: 'Separate cert/chain/key files, apachectl configtest, graceful reload' },
{ value: 'HAProxy', label: 'HAProxy', description: 'Combined PEM file (cert+chain+key), optional validate, reload' },
{ value: 'Traefik', label: 'Traefik', description: 'File provider deployment — writes cert/key to watched directory, auto-reload' },
{ value: 'Caddy', label: 'Caddy', description: 'Admin API hot-reload or file-based deployment with configurable mode' },
{ value: 'Envoy', label: 'Envoy', description: 'File-based deployment — writes cert/key to watched directory. Optional SDS file generation.' },
{ value: 'Postfix', label: 'Postfix', description: 'Postfix MTA — file write + postfix reload' },
{ value: 'Dovecot', label: 'Dovecot', description: 'Dovecot IMAP/POP3 — file write + doveadm reload' },
{ value: 'F5', label: 'F5 BIG-IP', description: 'iControl REST — cert upload, SSL profile update via proxy agent' },
{ value: 'IIS', label: 'IIS', description: 'Windows IIS via agent-local PowerShell or remote WinRM proxy agent' },
{ value: 'SSH', label: 'SSH', description: 'Agentless deployment via SSH/SFTP — deploy to any Linux/Unix server without installing an agent' },
];
const CONFIG_FIELDS: Record<string, { key: string; label: string; placeholder: string; required?: boolean }[]> = {
nginx: [
NGINX: [
{ key: 'cert_path', label: 'Certificate Path', placeholder: '/etc/nginx/ssl/cert.pem', required: true },
{ key: 'key_path', label: 'Key Path', placeholder: '/etc/nginx/ssl/key.pem', required: true },
{ key: 'chain_path', label: 'Chain Path', placeholder: '/etc/nginx/ssl/chain.pem' },
{ key: 'reload_cmd', label: 'Reload Command', placeholder: 'nginx -t && systemctl reload nginx' },
],
apache: [
Apache: [
{ key: 'cert_path', label: 'Certificate Path', placeholder: '/etc/apache2/ssl/cert.pem', required: true },
{ key: 'key_path', label: 'Key Path', placeholder: '/etc/apache2/ssl/key.pem', required: true },
{ key: 'chain_path', label: 'Chain Path', placeholder: '/etc/apache2/ssl/chain.pem' },
{ key: 'reload_cmd', label: 'Reload Command', placeholder: 'apachectl configtest && apachectl graceful' },
],
haproxy: [
HAProxy: [
{ key: 'pem_path', label: 'Combined PEM Path', placeholder: '/etc/haproxy/certs/combined.pem', required: true },
{ key: 'reload_cmd', label: 'Reload Command', placeholder: 'systemctl reload haproxy' },
{ key: 'validate_cmd', label: 'Validate Command (optional)', placeholder: 'haproxy -c -f /etc/haproxy/haproxy.cfg' },
],
traefik: [
Traefik: [
{ key: 'cert_dir', label: 'Certificate Directory', placeholder: '/etc/traefik/certs', required: true },
{ key: 'cert_file', label: 'Certificate Filename', placeholder: 'cert.pem (default)' },
{ key: 'key_file', label: 'Key Filename', placeholder: 'key.pem (default)' },
],
caddy: [
Caddy: [
{ key: 'mode', label: 'Deployment Mode', placeholder: 'api (default) or file', required: true },
{ key: 'admin_api', label: 'Admin API URL', placeholder: 'http://localhost:2019 (default)' },
{ key: 'cert_dir', label: 'Certificate Directory (file mode)', placeholder: '/etc/caddy/certs' },
{ key: 'cert_file', label: 'Certificate Filename', placeholder: 'cert.pem (default)' },
{ key: 'key_file', label: 'Key Filename', placeholder: 'key.pem (default)' },
],
envoy: [
Envoy: [
{ key: 'cert_dir', label: 'Certificate Directory', placeholder: '/etc/envoy/certs', required: true },
{ key: 'cert_filename', label: 'Certificate Filename', placeholder: 'cert.pem (default)' },
{ key: 'key_filename', label: 'Key Filename', placeholder: 'key.pem (default)' },
{ key: 'chain_filename', label: 'Chain Filename (optional)', placeholder: 'chain.pem (leave empty to append to cert)' },
{ key: 'sds_config', label: 'Generate SDS Config', placeholder: 'true or false' },
],
postfix: [
Postfix: [
{ key: 'cert_path', label: 'Certificate Path', placeholder: '/etc/postfix/certs/cert.pem' },
{ key: 'key_path', label: 'Key Path', placeholder: '/etc/postfix/certs/key.pem' },
{ key: 'chain_path', label: 'Chain Path (optional)', placeholder: '/etc/postfix/certs/chain.pem' },
{ key: 'reload_command', label: 'Reload Command', placeholder: 'postfix reload' },
{ key: 'validate_command', label: 'Validate Command', placeholder: 'postfix check' },
],
dovecot: [
Dovecot: [
{ key: 'cert_path', label: 'Certificate Path', placeholder: '/etc/dovecot/certs/cert.pem' },
{ key: 'key_path', label: 'Key Path', placeholder: '/etc/dovecot/certs/key.pem' },
{ key: 'chain_path', label: 'Chain Path (optional)', placeholder: '/etc/dovecot/certs/chain.pem' },
{ key: 'reload_command', label: 'Reload Command', placeholder: 'doveadm reload' },
{ key: 'validate_command', label: 'Validate Command', placeholder: 'doveconf -n' },
],
f5_bigip: [
F5: [
{ key: 'host', label: 'Management Host', placeholder: 'f5.internal.example.com', required: true },
{ key: 'port', label: 'Management Port', placeholder: '443' },
{ key: 'username', label: 'Username', placeholder: 'admin', required: true },
@@ -97,7 +99,7 @@ const CONFIG_FIELDS: Record<string, { key: string; label: string; placeholder: s
{ key: 'insecure', label: 'Skip TLS Verify', placeholder: 'true (default)' },
{ key: 'timeout', label: 'Timeout (seconds)', placeholder: '30' },
],
iis: [
IIS: [
{ key: 'site_name', label: 'IIS Site Name', placeholder: 'Default Web Site', required: true },
{ key: 'cert_store', label: 'Certificate Store', placeholder: 'My', required: true },
{ key: 'port', label: 'HTTPS Port', placeholder: '443' },
@@ -112,6 +114,19 @@ const CONFIG_FIELDS: Record<string, { key: string; label: string; placeholder: s
{ key: 'winrm.winrm_https', label: 'WinRM Use HTTPS', placeholder: 'true or false' },
{ key: 'winrm.winrm_insecure', label: 'WinRM Skip TLS Verify', placeholder: 'false' },
],
SSH: [
{ key: 'host', label: 'SSH Host', placeholder: '192.168.1.100 or server.example.com', required: true },
{ key: 'port', label: 'SSH Port', placeholder: '22 (default)' },
{ key: 'user', label: 'SSH Username', placeholder: 'root or certctl', required: true },
{ key: 'auth_method', label: 'Auth Method', placeholder: 'key (default) or password' },
{ key: 'private_key_path', label: 'Private Key Path', placeholder: '/home/certctl/.ssh/id_ed25519' },
{ key: 'password', label: 'SSH Password', placeholder: 'Leave empty for key auth' },
{ key: 'cert_path', label: 'Remote Certificate Path', placeholder: '/etc/ssl/certs/cert.pem', required: true },
{ key: 'key_path', label: 'Remote Key Path', placeholder: '/etc/ssl/private/key.pem', required: true },
{ key: 'chain_path', label: 'Remote Chain Path (optional)', placeholder: '/etc/ssl/certs/chain.pem' },
{ key: 'reload_command', label: 'Reload Command (optional)', placeholder: 'systemctl reload nginx' },
{ key: 'timeout', label: 'Connection Timeout (seconds)', placeholder: '30 (default)' },
],
};
function CreateTargetWizard({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) {