699 lines
29 KiB
HTML
699 lines
29 KiB
HTML
|
|
<!DOCTYPE HTML>
|
|||
|
|
<html lang="en" class="ayu sidebar-visible" dir="ltr">
|
|||
|
|
<head>
|
|||
|
|
<!-- Book generated using mdBook -->
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<title>SSH Temporal Keys User Guide - Provisioning Platform Documentation</title>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- Custom HTML head -->
|
|||
|
|
|
|||
|
|
<meta name="description" content="Complete documentation for the Provisioning Platform - Infrastructure automation with Nushell, KCL, and Rust">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|||
|
|
<meta name="theme-color" content="#ffffff">
|
|||
|
|
|
|||
|
|
<link rel="icon" href="../favicon.svg">
|
|||
|
|
<link rel="shortcut icon" href="../favicon.png">
|
|||
|
|
<link rel="stylesheet" href="../css/variables.css">
|
|||
|
|
<link rel="stylesheet" href="../css/general.css">
|
|||
|
|
<link rel="stylesheet" href="../css/chrome.css">
|
|||
|
|
<link rel="stylesheet" href="../css/print.css" media="print">
|
|||
|
|
|
|||
|
|
<!-- Fonts -->
|
|||
|
|
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
|||
|
|
<link rel="stylesheet" href="../fonts/fonts.css">
|
|||
|
|
|
|||
|
|
<!-- Highlight.js Stylesheets -->
|
|||
|
|
<link rel="stylesheet" id="highlight-css" href="../highlight.css">
|
|||
|
|
<link rel="stylesheet" id="tomorrow-night-css" href="../tomorrow-night.css">
|
|||
|
|
<link rel="stylesheet" id="ayu-highlight-css" href="../ayu-highlight.css">
|
|||
|
|
|
|||
|
|
<!-- Custom theme stylesheets -->
|
|||
|
|
|
|||
|
|
|
|||
|
|
<!-- Provide site root and default themes to javascript -->
|
|||
|
|
<script>
|
|||
|
|
const path_to_root = "../";
|
|||
|
|
const default_light_theme = "ayu";
|
|||
|
|
const default_dark_theme = "navy";
|
|||
|
|
</script>
|
|||
|
|
<!-- Start loading toc.js asap -->
|
|||
|
|
<script src="../toc.js"></script>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div id="mdbook-help-container">
|
|||
|
|
<div id="mdbook-help-popup">
|
|||
|
|
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
|
|||
|
|
<div>
|
|||
|
|
<p>Press <kbd>←</kbd> or <kbd>→</kbd> to navigate between chapters</p>
|
|||
|
|
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
|
|||
|
|
<p>Press <kbd>?</kbd> to show this help</p>
|
|||
|
|
<p>Press <kbd>Esc</kbd> to hide this help</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div id="body-container">
|
|||
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|||
|
|
<script>
|
|||
|
|
try {
|
|||
|
|
let theme = localStorage.getItem('mdbook-theme');
|
|||
|
|
let sidebar = localStorage.getItem('mdbook-sidebar');
|
|||
|
|
|
|||
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|||
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|||
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|||
|
|
}
|
|||
|
|
} catch (e) { }
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|||
|
|
<script>
|
|||
|
|
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
|
|||
|
|
let theme;
|
|||
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|||
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|||
|
|
const html = document.documentElement;
|
|||
|
|
html.classList.remove('ayu')
|
|||
|
|
html.classList.add(theme);
|
|||
|
|
html.classList.add("js");
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
|||
|
|
|
|||
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|||
|
|
<script>
|
|||
|
|
let sidebar = null;
|
|||
|
|
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
|||
|
|
if (document.body.clientWidth >= 1080) {
|
|||
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|||
|
|
sidebar = sidebar || 'visible';
|
|||
|
|
} else {
|
|||
|
|
sidebar = 'hidden';
|
|||
|
|
}
|
|||
|
|
sidebar_toggle.checked = sidebar === 'visible';
|
|||
|
|
html.classList.remove('sidebar-visible');
|
|||
|
|
html.classList.add("sidebar-" + sidebar);
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|||
|
|
<!-- populated by js -->
|
|||
|
|
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
|||
|
|
<noscript>
|
|||
|
|
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
|
|||
|
|
</noscript>
|
|||
|
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
|||
|
|
<div class="sidebar-resize-indicator"></div>
|
|||
|
|
</div>
|
|||
|
|
</nav>
|
|||
|
|
|
|||
|
|
<div id="page-wrapper" class="page-wrapper">
|
|||
|
|
|
|||
|
|
<div class="page">
|
|||
|
|
<div id="menu-bar-hover-placeholder"></div>
|
|||
|
|
<div id="menu-bar" class="menu-bar sticky">
|
|||
|
|
<div class="left-buttons">
|
|||
|
|
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|||
|
|
<i class="fa fa-bars"></i>
|
|||
|
|
</label>
|
|||
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|||
|
|
<i class="fa fa-paint-brush"></i>
|
|||
|
|
</button>
|
|||
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|||
|
|
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
|
|||
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
|||
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|||
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|||
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|||
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|||
|
|
</ul>
|
|||
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
|
|||
|
|
<i class="fa fa-search"></i>
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h1 class="menu-title">Provisioning Platform Documentation</h1>
|
|||
|
|
|
|||
|
|
<div class="right-buttons">
|
|||
|
|
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
|||
|
|
<i id="print-button" class="fa fa-print"></i>
|
|||
|
|
</a>
|
|||
|
|
<a href="https://github.com/provisioning/provisioning-platform" title="Git repository" aria-label="Git repository">
|
|||
|
|
<i id="git-repository-button" class="fa fa-github"></i>
|
|||
|
|
</a>
|
|||
|
|
<a href="https://github.com/provisioning/provisioning-platform/edit/main/provisioning/docs/src/user/SSH_TEMPORAL_KEYS_USER_GUIDE.md" title="Suggest an edit" aria-label="Suggest an edit">
|
|||
|
|
<i id="git-edit-button" class="fa fa-edit"></i>
|
|||
|
|
</a>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div id="search-wrapper" class="hidden">
|
|||
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|||
|
|
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|||
|
|
</form>
|
|||
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|||
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|||
|
|
<ul id="searchresults">
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|||
|
|
<script>
|
|||
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|||
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|||
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|||
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<div id="content" class="content">
|
|||
|
|
<main>
|
|||
|
|
<h1 id="ssh-temporal-keys---user-guide"><a class="header" href="#ssh-temporal-keys---user-guide">SSH Temporal Keys - User Guide</a></h1>
|
|||
|
|
<h2 id="quick-start"><a class="header" href="#quick-start">Quick Start</a></h2>
|
|||
|
|
<h3 id="generate-and-connect-with-temporary-key"><a class="header" href="#generate-and-connect-with-temporary-key">Generate and Connect with Temporary Key</a></h3>
|
|||
|
|
<p>The fastest way to use temporal SSH keys:</p>
|
|||
|
|
<pre><code class="language-bash"># Auto-generate, deploy, and connect (key auto-revoked after disconnect)
|
|||
|
|
ssh connect server.example.com
|
|||
|
|
|
|||
|
|
# Connect with custom user and TTL
|
|||
|
|
ssh connect server.example.com --user deploy --ttl 30min
|
|||
|
|
|
|||
|
|
# Keep key active after disconnect
|
|||
|
|
ssh connect server.example.com --keep
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="manual-key-management"><a class="header" href="#manual-key-management">Manual Key Management</a></h3>
|
|||
|
|
<p>For more control over the key lifecycle:</p>
|
|||
|
|
<pre><code class="language-bash"># 1. Generate key
|
|||
|
|
ssh generate-key server.example.com --user root --ttl 1hr
|
|||
|
|
|
|||
|
|
# Output:
|
|||
|
|
# ✓ SSH key generated successfully
|
|||
|
|
# Key ID: abc-123-def-456
|
|||
|
|
# Type: dynamickeypair
|
|||
|
|
# User: root
|
|||
|
|
# Server: server.example.com
|
|||
|
|
# Expires: 2024-01-01T13:00:00Z
|
|||
|
|
# Fingerprint: SHA256:...
|
|||
|
|
#
|
|||
|
|
# Private Key (save securely):
|
|||
|
|
# -----BEGIN OPENSSH PRIVATE KEY-----
|
|||
|
|
# ...
|
|||
|
|
# -----END OPENSSH PRIVATE KEY-----
|
|||
|
|
|
|||
|
|
# 2. Deploy key to server
|
|||
|
|
ssh deploy-key abc-123-def-456
|
|||
|
|
|
|||
|
|
# 3. Use the private key to connect
|
|||
|
|
ssh -i /path/to/private/key root@server.example.com
|
|||
|
|
|
|||
|
|
# 4. Revoke when done
|
|||
|
|
ssh revoke-key abc-123-def-456
|
|||
|
|
</code></pre>
|
|||
|
|
<h2 id="key-features"><a class="header" href="#key-features">Key Features</a></h2>
|
|||
|
|
<h3 id="automatic-expiration"><a class="header" href="#automatic-expiration">Automatic Expiration</a></h3>
|
|||
|
|
<p>All keys expire automatically after their TTL:</p>
|
|||
|
|
<ul>
|
|||
|
|
<li><strong>Default TTL</strong>: 1 hour</li>
|
|||
|
|
<li><strong>Configurable</strong>: From 5 minutes to 24 hours</li>
|
|||
|
|
<li><strong>Background Cleanup</strong>: Automatic removal from servers every 5 minutes</li>
|
|||
|
|
</ul>
|
|||
|
|
<h3 id="multiple-key-types"><a class="header" href="#multiple-key-types">Multiple Key Types</a></h3>
|
|||
|
|
<p>Choose the right key type for your use case:</p>
|
|||
|
|
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Description</th><th>Use Case</th></tr></thead><tbody>
|
|||
|
|
<tr><td><strong>dynamic</strong> (default)</td><td>Generated Ed25519 keys</td><td>Quick SSH access</td></tr>
|
|||
|
|
<tr><td><strong>ca</strong></td><td>Vault CA-signed certificate</td><td>Enterprise with SSH CA</td></tr>
|
|||
|
|
<tr><td><strong>otp</strong></td><td>Vault one-time password</td><td>Single-use access</td></tr>
|
|||
|
|
</tbody></table>
|
|||
|
|
</div>
|
|||
|
|
<h3 id="security-benefits"><a class="header" href="#security-benefits">Security Benefits</a></h3>
|
|||
|
|
<p>✅ No static SSH keys to manage
|
|||
|
|
✅ Short-lived credentials (1 hour default)
|
|||
|
|
✅ Automatic cleanup on expiration
|
|||
|
|
✅ Audit trail for all operations
|
|||
|
|
✅ Private keys never stored on disk</p>
|
|||
|
|
<h2 id="common-usage-patterns"><a class="header" href="#common-usage-patterns">Common Usage Patterns</a></h2>
|
|||
|
|
<h3 id="development-workflow"><a class="header" href="#development-workflow">Development Workflow</a></h3>
|
|||
|
|
<pre><code class="language-bash"># Quick SSH for debugging
|
|||
|
|
ssh connect dev-server.local --ttl 30min
|
|||
|
|
|
|||
|
|
# Execute commands
|
|||
|
|
ssh root@dev-server.local "systemctl status nginx"
|
|||
|
|
|
|||
|
|
# Connection closes, key auto-revokes
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="production-deployment"><a class="header" href="#production-deployment">Production Deployment</a></h3>
|
|||
|
|
<pre><code class="language-bash"># Generate key with longer TTL for deployment
|
|||
|
|
ssh generate-key prod-server.example.com --ttl 2hr
|
|||
|
|
|
|||
|
|
# Deploy to server
|
|||
|
|
ssh deploy-key <key-id>
|
|||
|
|
|
|||
|
|
# Run deployment script
|
|||
|
|
ssh -i /tmp/deploy-key root@prod-server.example.com < deploy.sh
|
|||
|
|
|
|||
|
|
# Manual revoke when done
|
|||
|
|
ssh revoke-key <key-id>
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="multi-server-access"><a class="header" href="#multi-server-access">Multi-Server Access</a></h3>
|
|||
|
|
<pre><code class="language-bash"># Generate one key
|
|||
|
|
ssh generate-key server01.example.com --ttl 1hr
|
|||
|
|
|
|||
|
|
# Use the same private key for multiple servers (if you have provisioning access)
|
|||
|
|
# Note: Currently each key is server-specific, multi-server support coming soon
|
|||
|
|
</code></pre>
|
|||
|
|
<h2 id="command-reference"><a class="header" href="#command-reference">Command Reference</a></h2>
|
|||
|
|
<h3 id="ssh-generate-key"><a class="header" href="#ssh-generate-key">ssh generate-key</a></h3>
|
|||
|
|
<p>Generate a new temporal SSH key.</p>
|
|||
|
|
<p><strong>Syntax</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh generate-key <server> [options]
|
|||
|
|
</code></pre>
|
|||
|
|
<p><strong>Options</strong>:</p>
|
|||
|
|
<ul>
|
|||
|
|
<li><code>--user <name></code>: SSH user (default: root)</li>
|
|||
|
|
<li><code>--ttl <duration></code>: Key lifetime (default: 1hr)</li>
|
|||
|
|
<li><code>--type <ca|otp|dynamic></code>: Key type (default: dynamic)</li>
|
|||
|
|
<li><code>--ip <address></code>: Allowed IP (OTP mode only)</li>
|
|||
|
|
<li><code>--principal <name></code>: Principal (CA mode only)</li>
|
|||
|
|
</ul>
|
|||
|
|
<p><strong>Examples</strong>:</p>
|
|||
|
|
<pre><code class="language-bash"># Basic usage
|
|||
|
|
ssh generate-key server.example.com
|
|||
|
|
|
|||
|
|
# Custom user and TTL
|
|||
|
|
ssh generate-key server.example.com --user deploy --ttl 30min
|
|||
|
|
|
|||
|
|
# Vault CA mode
|
|||
|
|
ssh generate-key server.example.com --type ca --principal admin
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="ssh-deploy-key"><a class="header" href="#ssh-deploy-key">ssh deploy-key</a></h3>
|
|||
|
|
<p>Deploy a generated key to the target server.</p>
|
|||
|
|
<p><strong>Syntax</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh deploy-key <key-id>
|
|||
|
|
</code></pre>
|
|||
|
|
<p><strong>Example</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh deploy-key abc-123-def-456
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="ssh-list-keys"><a class="header" href="#ssh-list-keys">ssh list-keys</a></h3>
|
|||
|
|
<p>List all active SSH keys.</p>
|
|||
|
|
<p><strong>Syntax</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh list-keys [--expired]
|
|||
|
|
</code></pre>
|
|||
|
|
<p><strong>Examples</strong>:</p>
|
|||
|
|
<pre><code class="language-bash"># List active keys
|
|||
|
|
ssh list-keys
|
|||
|
|
|
|||
|
|
# Show only deployed keys
|
|||
|
|
ssh list-keys | where deployed == true
|
|||
|
|
|
|||
|
|
# Include expired keys
|
|||
|
|
ssh list-keys --expired
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="ssh-get-key"><a class="header" href="#ssh-get-key">ssh get-key</a></h3>
|
|||
|
|
<p>Get detailed information about a specific key.</p>
|
|||
|
|
<p><strong>Syntax</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh get-key <key-id>
|
|||
|
|
</code></pre>
|
|||
|
|
<p><strong>Example</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh get-key abc-123-def-456
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="ssh-revoke-key"><a class="header" href="#ssh-revoke-key">ssh revoke-key</a></h3>
|
|||
|
|
<p>Immediately revoke a key (removes from server and tracking).</p>
|
|||
|
|
<p><strong>Syntax</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh revoke-key <key-id>
|
|||
|
|
</code></pre>
|
|||
|
|
<p><strong>Example</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh revoke-key abc-123-def-456
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="ssh-connect"><a class="header" href="#ssh-connect">ssh connect</a></h3>
|
|||
|
|
<p>Auto-generate, deploy, connect, and revoke (all-in-one).</p>
|
|||
|
|
<p><strong>Syntax</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh connect <server> [options]
|
|||
|
|
</code></pre>
|
|||
|
|
<p><strong>Options</strong>:</p>
|
|||
|
|
<ul>
|
|||
|
|
<li><code>--user <name></code>: SSH user (default: root)</li>
|
|||
|
|
<li><code>--ttl <duration></code>: Key lifetime (default: 1hr)</li>
|
|||
|
|
<li><code>--type <ca|otp|dynamic></code>: Key type (default: dynamic)</li>
|
|||
|
|
<li><code>--keep</code>: Don’t revoke after disconnect</li>
|
|||
|
|
</ul>
|
|||
|
|
<p><strong>Examples</strong>:</p>
|
|||
|
|
<pre><code class="language-bash"># Quick connection
|
|||
|
|
ssh connect server.example.com
|
|||
|
|
|
|||
|
|
# Custom user
|
|||
|
|
ssh connect server.example.com --user deploy
|
|||
|
|
|
|||
|
|
# Keep key active after disconnect
|
|||
|
|
ssh connect server.example.com --keep
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="ssh-stats"><a class="header" href="#ssh-stats">ssh stats</a></h3>
|
|||
|
|
<p>Show SSH key statistics.</p>
|
|||
|
|
<p><strong>Syntax</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh stats
|
|||
|
|
</code></pre>
|
|||
|
|
<p><strong>Example Output</strong>:</p>
|
|||
|
|
<pre><code>SSH Key Statistics:
|
|||
|
|
Total generated: 42
|
|||
|
|
Active keys: 10
|
|||
|
|
Expired keys: 32
|
|||
|
|
|
|||
|
|
Keys by type:
|
|||
|
|
dynamic: 35
|
|||
|
|
otp: 5
|
|||
|
|
certificate: 2
|
|||
|
|
|
|||
|
|
Last cleanup: 2024-01-01T12:00:00Z
|
|||
|
|
Cleaned keys: 5
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="ssh-cleanup"><a class="header" href="#ssh-cleanup">ssh cleanup</a></h3>
|
|||
|
|
<p>Manually trigger cleanup of expired keys.</p>
|
|||
|
|
<p><strong>Syntax</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh cleanup
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="ssh-test"><a class="header" href="#ssh-test">ssh test</a></h3>
|
|||
|
|
<p>Run a quick test of the SSH key system.</p>
|
|||
|
|
<p><strong>Syntax</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh test <server> [--user <name>]
|
|||
|
|
</code></pre>
|
|||
|
|
<p><strong>Example</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh test server.example.com --user root
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="ssh-help"><a class="header" href="#ssh-help">ssh help</a></h3>
|
|||
|
|
<p>Show help information.</p>
|
|||
|
|
<p><strong>Syntax</strong>:</p>
|
|||
|
|
<pre><code class="language-bash">ssh help
|
|||
|
|
</code></pre>
|
|||
|
|
<h2 id="duration-formats"><a class="header" href="#duration-formats">Duration Formats</a></h2>
|
|||
|
|
<p>The <code>--ttl</code> option accepts various duration formats:</p>
|
|||
|
|
<div class="table-wrapper"><table><thead><tr><th>Format</th><th>Example</th><th>Meaning</th></tr></thead><tbody>
|
|||
|
|
<tr><td>Minutes</td><td><code>30min</code></td><td>30 minutes</td></tr>
|
|||
|
|
<tr><td>Hours</td><td><code>2hr</code></td><td>2 hours</td></tr>
|
|||
|
|
<tr><td>Mixed</td><td><code>1hr 30min</code></td><td>1.5 hours</td></tr>
|
|||
|
|
<tr><td>Seconds</td><td><code>3600sec</code></td><td>1 hour</td></tr>
|
|||
|
|
</tbody></table>
|
|||
|
|
</div>
|
|||
|
|
<h2 id="working-with-private-keys"><a class="header" href="#working-with-private-keys">Working with Private Keys</a></h2>
|
|||
|
|
<h3 id="saving-private-keys"><a class="header" href="#saving-private-keys">Saving Private Keys</a></h3>
|
|||
|
|
<p>When you generate a key, save the private key immediately:</p>
|
|||
|
|
<pre><code class="language-bash"># Generate and save to file
|
|||
|
|
ssh generate-key server.example.com | get private_key | save -f ~/.ssh/temp_key
|
|||
|
|
chmod 600 ~/.ssh/temp_key
|
|||
|
|
|
|||
|
|
# Use the key
|
|||
|
|
ssh -i ~/.ssh/temp_key root@server.example.com
|
|||
|
|
|
|||
|
|
# Cleanup
|
|||
|
|
rm ~/.ssh/temp_key
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="using-ssh-agent"><a class="header" href="#using-ssh-agent">Using SSH Agent</a></h3>
|
|||
|
|
<p>Add the temporary key to your SSH agent:</p>
|
|||
|
|
<pre><code class="language-bash"># Generate key and extract private key
|
|||
|
|
ssh generate-key server.example.com | get private_key | save -f /tmp/temp_key
|
|||
|
|
chmod 600 /tmp/temp_key
|
|||
|
|
|
|||
|
|
# Add to agent
|
|||
|
|
ssh-add /tmp/temp_key
|
|||
|
|
|
|||
|
|
# Connect (agent provides the key automatically)
|
|||
|
|
ssh root@server.example.com
|
|||
|
|
|
|||
|
|
# Remove from agent
|
|||
|
|
ssh-add -d /tmp/temp_key
|
|||
|
|
rm /tmp/temp_key
|
|||
|
|
</code></pre>
|
|||
|
|
<h2 id="troubleshooting"><a class="header" href="#troubleshooting">Troubleshooting</a></h2>
|
|||
|
|
<h3 id="key-deployment-fails"><a class="header" href="#key-deployment-fails">Key Deployment Fails</a></h3>
|
|||
|
|
<p><strong>Problem</strong>: <code>ssh deploy-key</code> returns error</p>
|
|||
|
|
<p><strong>Solutions</strong>:</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>
|
|||
|
|
<p>Check SSH connectivity to server:</p>
|
|||
|
|
<pre><code class="language-bash">ssh root@server.example.com
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>Verify provisioning key is configured:</p>
|
|||
|
|
<pre><code class="language-bash">echo $PROVISIONING_SSH_KEY
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>Check server SSH daemon:</p>
|
|||
|
|
<pre><code class="language-bash">ssh root@server.example.com "systemctl status sshd"
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
</ol>
|
|||
|
|
<h3 id="private-key-not-working"><a class="header" href="#private-key-not-working">Private Key Not Working</a></h3>
|
|||
|
|
<p><strong>Problem</strong>: SSH connection fails with “Permission denied (publickey)”</p>
|
|||
|
|
<p><strong>Solutions</strong>:</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>
|
|||
|
|
<p>Verify key was deployed:</p>
|
|||
|
|
<pre><code class="language-bash">ssh list-keys | where id == "<key-id>"
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>Check key hasn’t expired:</p>
|
|||
|
|
<pre><code class="language-bash">ssh get-key <key-id> | get expires_at
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>Verify private key permissions:</p>
|
|||
|
|
<pre><code class="language-bash">chmod 600 /path/to/private/key
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
</ol>
|
|||
|
|
<h3 id="cleanup-not-running"><a class="header" href="#cleanup-not-running">Cleanup Not Running</a></h3>
|
|||
|
|
<p><strong>Problem</strong>: Expired keys not being removed</p>
|
|||
|
|
<p><strong>Solutions</strong>:</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>
|
|||
|
|
<p>Check orchestrator is running:</p>
|
|||
|
|
<pre><code class="language-bash">curl http://localhost:9090/health
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>Trigger manual cleanup:</p>
|
|||
|
|
<pre><code class="language-bash">ssh cleanup
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p>Check orchestrator logs:</p>
|
|||
|
|
<pre><code class="language-bash">tail -f ./data/orchestrator.log | grep SSH
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
</ol>
|
|||
|
|
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
|
|||
|
|
<h3 id="security"><a class="header" href="#security">Security</a></h3>
|
|||
|
|
<ol>
|
|||
|
|
<li>
|
|||
|
|
<p><strong>Short TTLs</strong>: Use the shortest TTL that works for your task</p>
|
|||
|
|
<pre><code class="language-bash">ssh connect server.example.com --ttl 30min
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p><strong>Immediate Revocation</strong>: Revoke keys when you’re done</p>
|
|||
|
|
<pre><code class="language-bash">ssh revoke-key <key-id>
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p><strong>Private Key Handling</strong>: Never share or commit private keys</p>
|
|||
|
|
<pre><code class="language-bash"># Save to temp location, delete after use
|
|||
|
|
ssh generate-key server.example.com | get private_key | save -f /tmp/key
|
|||
|
|
# ... use key ...
|
|||
|
|
rm /tmp/key
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
</ol>
|
|||
|
|
<h3 id="workflow-integration"><a class="header" href="#workflow-integration">Workflow Integration</a></h3>
|
|||
|
|
<ol>
|
|||
|
|
<li>
|
|||
|
|
<p><strong>Automated Deployments</strong>: Generate key in CI/CD</p>
|
|||
|
|
<pre><code class="language-bash">#!/bin/bash
|
|||
|
|
KEY_ID=$(ssh generate-key prod.example.com --ttl 1hr | get id)
|
|||
|
|
ssh deploy-key $KEY_ID
|
|||
|
|
# Run deployment
|
|||
|
|
ansible-playbook deploy.yml
|
|||
|
|
ssh revoke-key $KEY_ID
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p><strong>Interactive Use</strong>: Use <code>ssh connect</code> for quick access</p>
|
|||
|
|
<pre><code class="language-bash">ssh connect dev.example.com
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
<li>
|
|||
|
|
<p><strong>Monitoring</strong>: Check statistics regularly</p>
|
|||
|
|
<pre><code class="language-bash">ssh stats
|
|||
|
|
</code></pre>
|
|||
|
|
</li>
|
|||
|
|
</ol>
|
|||
|
|
<h2 id="advanced-usage"><a class="header" href="#advanced-usage">Advanced Usage</a></h2>
|
|||
|
|
<h3 id="vault-integration"><a class="header" href="#vault-integration">Vault Integration</a></h3>
|
|||
|
|
<p>If your organization uses HashiCorp Vault:</p>
|
|||
|
|
<h4 id="ca-mode-recommended"><a class="header" href="#ca-mode-recommended">CA Mode (Recommended)</a></h4>
|
|||
|
|
<pre><code class="language-bash"># Generate CA-signed certificate
|
|||
|
|
ssh generate-key server.example.com --type ca --principal admin --ttl 1hr
|
|||
|
|
|
|||
|
|
# Vault signs your public key
|
|||
|
|
# Server must trust Vault CA certificate
|
|||
|
|
</code></pre>
|
|||
|
|
<p><strong>Setup</strong> (one-time):</p>
|
|||
|
|
<pre><code class="language-bash"># On servers, add to /etc/ssh/sshd_config:
|
|||
|
|
TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem
|
|||
|
|
|
|||
|
|
# Get Vault CA public key:
|
|||
|
|
vault read -field=public_key ssh/config/ca | \
|
|||
|
|
sudo tee /etc/ssh/trusted-user-ca-keys.pem
|
|||
|
|
|
|||
|
|
# Restart SSH:
|
|||
|
|
sudo systemctl restart sshd
|
|||
|
|
</code></pre>
|
|||
|
|
<h4 id="otp-mode"><a class="header" href="#otp-mode">OTP Mode</a></h4>
|
|||
|
|
<pre><code class="language-bash"># Generate one-time password
|
|||
|
|
ssh generate-key server.example.com --type otp --ip 192.168.1.100
|
|||
|
|
|
|||
|
|
# Use the OTP to connect (single use only)
|
|||
|
|
</code></pre>
|
|||
|
|
<h3 id="scripting"><a class="header" href="#scripting">Scripting</a></h3>
|
|||
|
|
<p>Use in scripts for automated operations:</p>
|
|||
|
|
<pre><code class="language-nushell"># deploy.nu
|
|||
|
|
def deploy [target: string] {
|
|||
|
|
let key = (ssh generate-key $target --ttl 1hr)
|
|||
|
|
ssh deploy-key $key.id
|
|||
|
|
|
|||
|
|
# Run deployment
|
|||
|
|
try {
|
|||
|
|
ssh $"root@($target)" "bash /path/to/deploy.sh"
|
|||
|
|
} catch {
|
|||
|
|
print "Deployment failed"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Always cleanup
|
|||
|
|
ssh revoke-key $key.id
|
|||
|
|
}
|
|||
|
|
</code></pre>
|
|||
|
|
<h2 id="api-integration"><a class="header" href="#api-integration">API Integration</a></h2>
|
|||
|
|
<p>For programmatic access, use the REST API:</p>
|
|||
|
|
<pre><code class="language-bash"># Generate key
|
|||
|
|
curl -X POST http://localhost:9090/api/v1/ssh/generate \
|
|||
|
|
-H "Content-Type: application/json" \
|
|||
|
|
-d '{
|
|||
|
|
"key_type": "dynamickeypair",
|
|||
|
|
"user": "root",
|
|||
|
|
"target_server": "server.example.com",
|
|||
|
|
"ttl_seconds": 3600
|
|||
|
|
}'
|
|||
|
|
|
|||
|
|
# Deploy key
|
|||
|
|
curl -X POST http://localhost:9090/api/v1/ssh/{key_id}/deploy
|
|||
|
|
|
|||
|
|
# List keys
|
|||
|
|
curl http://localhost:9090/api/v1/ssh/keys
|
|||
|
|
|
|||
|
|
# Get stats
|
|||
|
|
curl http://localhost:9090/api/v1/ssh/stats
|
|||
|
|
</code></pre>
|
|||
|
|
<h2 id="faq"><a class="header" href="#faq">FAQ</a></h2>
|
|||
|
|
<p><strong>Q: Can I use the same key for multiple servers?</strong>
|
|||
|
|
A: Currently, each key is tied to a specific server. Multi-server support is planned.</p>
|
|||
|
|
<p><strong>Q: What happens if the orchestrator crashes?</strong>
|
|||
|
|
A: Keys in memory are lost, but keys already deployed to servers remain until their expiration time.</p>
|
|||
|
|
<p><strong>Q: Can I extend the TTL of an existing key?</strong>
|
|||
|
|
A: No, you must generate a new key. This is by design for security.</p>
|
|||
|
|
<p><strong>Q: What’s the maximum TTL?</strong>
|
|||
|
|
A: Configurable by admin, default maximum is 24 hours.</p>
|
|||
|
|
<p><strong>Q: Are private keys stored anywhere?</strong>
|
|||
|
|
A: Private keys exist only in memory during generation and are shown once to the user. They are never written to disk by the system.</p>
|
|||
|
|
<p><strong>Q: What happens if cleanup fails?</strong>
|
|||
|
|
A: The key remains in authorized_keys until the next cleanup run. You can trigger manual cleanup with <code>ssh cleanup</code>.</p>
|
|||
|
|
<p><strong>Q: Can I use this with non-root users?</strong>
|
|||
|
|
A: Yes, use <code>--user <username></code> when generating the key.</p>
|
|||
|
|
<p><strong>Q: How do I know when my key will expire?</strong>
|
|||
|
|
A: Use <code>ssh get-key <key-id></code> to see the exact expiration timestamp.</p>
|
|||
|
|
<h2 id="support"><a class="header" href="#support">Support</a></h2>
|
|||
|
|
<p>For issues or questions:</p>
|
|||
|
|
<ol>
|
|||
|
|
<li>Check orchestrator logs: <code>tail -f ./data/orchestrator.log</code></li>
|
|||
|
|
<li>Run diagnostics: <code>ssh stats</code></li>
|
|||
|
|
<li>Test connectivity: <code>ssh test server.example.com</code></li>
|
|||
|
|
<li>Review documentation: <code>SSH_KEY_MANAGEMENT.md</code></li>
|
|||
|
|
</ol>
|
|||
|
|
<h2 id="see-also"><a class="header" href="#see-also">See Also</a></h2>
|
|||
|
|
<ul>
|
|||
|
|
<li><strong>Architecture</strong>: <code>SSH_KEY_MANAGEMENT.md</code></li>
|
|||
|
|
<li><strong>Implementation</strong>: <code>SSH_IMPLEMENTATION_SUMMARY.md</code></li>
|
|||
|
|
<li><strong>Configuration</strong>: <code>config/ssh-config.toml.example</code></li>
|
|||
|
|
</ul>
|
|||
|
|
|
|||
|
|
</main>
|
|||
|
|
|
|||
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|||
|
|
<!-- Mobile navigation buttons -->
|
|||
|
|
<a rel="prev" href="../user/DYNAMIC_SECRETS_QUICK_REFERENCE.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|||
|
|
<i class="fa fa-angle-left"></i>
|
|||
|
|
</a>
|
|||
|
|
|
|||
|
|
<a rel="next prefetch" href="../user/RUSTYVAULT_KMS_GUIDE.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|||
|
|
<i class="fa fa-angle-right"></i>
|
|||
|
|
</a>
|
|||
|
|
|
|||
|
|
<div style="clear: both"></div>
|
|||
|
|
</nav>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|||
|
|
<a rel="prev" href="../user/DYNAMIC_SECRETS_QUICK_REFERENCE.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|||
|
|
<i class="fa fa-angle-left"></i>
|
|||
|
|
</a>
|
|||
|
|
|
|||
|
|
<a rel="next prefetch" href="../user/RUSTYVAULT_KMS_GUIDE.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|||
|
|
<i class="fa fa-angle-right"></i>
|
|||
|
|
</a>
|
|||
|
|
</nav>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- Livereload script (if served using the cli tool) -->
|
|||
|
|
<script>
|
|||
|
|
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|||
|
|
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
|
|||
|
|
const socket = new WebSocket(wsAddress);
|
|||
|
|
socket.onmessage = function (event) {
|
|||
|
|
if (event.data === "reload") {
|
|||
|
|
socket.close();
|
|||
|
|
location.reload();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
window.onbeforeunload = function() {
|
|||
|
|
socket.close();
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
window.playground_copyable = true;
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
|
|||
|
|
<script src="../elasticlunr.min.js"></script>
|
|||
|
|
<script src="../mark.min.js"></script>
|
|||
|
|
<script src="../searcher.js"></script>
|
|||
|
|
|
|||
|
|
<script src="../clipboard.min.js"></script>
|
|||
|
|
<script src="../highlight.js"></script>
|
|||
|
|
<script src="../book.js"></script>
|
|||
|
|
|
|||
|
|
<!-- Custom JS scripts -->
|
|||
|
|
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
</body>
|
|||
|
|
</html>
|