...
This commit is contained in:
65
examples/osal/sshagent.vsh
Normal file
65
examples/osal/sshagent.vsh
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.osal.sshagent
|
||||
import freeflowuniverse.herolib.builder
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
console.print_header('SSH Agent Management Example')
|
||||
|
||||
// Create SSH agent with single instance guarantee
|
||||
mut agent := sshagent.new_single()!
|
||||
println('SSH Agent initialized and ensured single instance')
|
||||
|
||||
// Show diagnostics
|
||||
diag := agent.diagnostics()
|
||||
console.print_header('SSH Agent Diagnostics:')
|
||||
for key, value in diag {
|
||||
console.print_item('${key}: ${value}')
|
||||
}
|
||||
|
||||
// Show current agent status
|
||||
println(agent)
|
||||
|
||||
// Example: Generate a test key if no keys exist
|
||||
if agent.keys.len == 0 {
|
||||
console.print_header('No keys found, generating example key...')
|
||||
mut key := agent.generate('example_key', '')!
|
||||
console.print_debug('Generated key: ${key}')
|
||||
|
||||
// Load the generated key
|
||||
key.load()!
|
||||
console.print_debug('Key loaded into agent')
|
||||
}
|
||||
|
||||
// Example: Push key to remote node (uncomment and modify for actual use)
|
||||
/*
|
||||
console.print_header('Testing remote node key deployment...')
|
||||
mut b := builder.new()!
|
||||
|
||||
// Create connection to remote node
|
||||
mut node := b.node_new(
|
||||
ipaddr: 'root@192.168.1.100:22' // Replace with actual remote host
|
||||
name: 'test_node'
|
||||
)!
|
||||
|
||||
if agent.keys.len > 0 {
|
||||
key_name := agent.keys[0].name
|
||||
console.print_debug('Pushing key "${key_name}" to remote node...')
|
||||
|
||||
// Push the key
|
||||
agent.push_key_to_node(mut node, key_name)!
|
||||
|
||||
// Verify access
|
||||
if agent.verify_key_access(mut node, key_name)! {
|
||||
console.print_debug('✓ SSH key access verified')
|
||||
} else {
|
||||
console.print_debug('✗ SSH key access verification failed')
|
||||
}
|
||||
|
||||
// Optional: Remove key from remote (for testing)
|
||||
// agent.remove_key_from_node(mut node, key_name)!
|
||||
// console.print_debug('Key removed from remote node')
|
||||
}
|
||||
*/
|
||||
|
||||
console.print_header('SSH Agent example completed successfully')
|
||||
100
lib/osal/sshagent/builder_integration.v
Normal file
100
lib/osal/sshagent/builder_integration.v
Normal file
@@ -0,0 +1,100 @@
|
||||
module sshagent
|
||||
|
||||
import freeflowuniverse.herolib.builder
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
// push SSH public key to a remote node's authorized_keys
|
||||
pub fn (mut agent SSHAgent) push_key_to_node(mut node builder.Node, key_name string) ! {
|
||||
// Verify this is an SSH node
|
||||
node_info := node.info()
|
||||
if node_info['category'] != 'ssh' {
|
||||
return error('Can only push keys to SSH nodes, got: ${node_info['category']}')
|
||||
}
|
||||
|
||||
// Find the key
|
||||
mut key := agent.get(name: key_name) or {
|
||||
return error('SSH key "${key_name}" not found in agent')
|
||||
}
|
||||
|
||||
// Get public key content
|
||||
pubkey_content := key.keypub()!
|
||||
|
||||
// Check if authorized_keys file exists on remote
|
||||
home_dir := node.environ_get()!['HOME'] or {
|
||||
return error('Could not determine HOME directory on remote node')
|
||||
}
|
||||
|
||||
ssh_dir := '${home_dir}/.ssh'
|
||||
authorized_keys_path := '${ssh_dir}/authorized_keys'
|
||||
|
||||
// Ensure .ssh directory exists with correct permissions
|
||||
node.exec_silent('mkdir -p ${ssh_dir}')!
|
||||
node.exec_silent('chmod 700 ${ssh_dir}')!
|
||||
|
||||
// Check if key already exists
|
||||
if node.file_exists(authorized_keys_path) {
|
||||
existing_keys := node.file_read(authorized_keys_path)!
|
||||
if existing_keys.contains(pubkey_content.trim_space()) {
|
||||
console.print_debug('SSH key already exists on remote node')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Add key to authorized_keys
|
||||
node.exec_silent('echo "${pubkey_content}" >> ${authorized_keys_path}')!
|
||||
node.exec_silent('chmod 600 ${authorized_keys_path}')!
|
||||
|
||||
console.print_debug('SSH key "${key_name}" successfully pushed to node')
|
||||
}
|
||||
|
||||
// remove SSH public key from a remote node's authorized_keys
|
||||
pub fn (mut agent SSHAgent) remove_key_from_node(mut node builder.Node, key_name string) ! {
|
||||
// Verify this is an SSH node
|
||||
node_info := node.info()
|
||||
if node_info['category'] != 'ssh' {
|
||||
return error('Can only remove keys from SSH nodes, got: ${node_info['category']}')
|
||||
}
|
||||
|
||||
// Find the key
|
||||
mut key := agent.get(name: key_name) or {
|
||||
return error('SSH key "${key_name}" not found in agent')
|
||||
}
|
||||
|
||||
// Get public key content
|
||||
pubkey_content := key.keypub()!
|
||||
|
||||
// Get authorized_keys path
|
||||
home_dir := node.environ_get()!['HOME'] or {
|
||||
return error('Could not determine HOME directory on remote node')
|
||||
}
|
||||
|
||||
authorized_keys_path := '${home_dir}/.ssh/authorized_keys'
|
||||
|
||||
if !node.file_exists(authorized_keys_path) {
|
||||
console.print_debug('authorized_keys file does not exist on remote node')
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the key line from authorized_keys
|
||||
escaped_key := pubkey_content.replace('/', '\\/')
|
||||
node.exec_silent('sed -i "\\|${escaped_key}|d" ${authorized_keys_path}')!
|
||||
|
||||
console.print_debug('SSH key "${key_name}" removed from remote node')
|
||||
}
|
||||
|
||||
// verify SSH key access to remote node
|
||||
pub fn (mut agent SSHAgent) verify_key_access(mut node builder.Node, key_name string) !bool {
|
||||
// This would attempt to connect with the specific key
|
||||
// For now, we'll do a simple connectivity test
|
||||
node_info := node.info()
|
||||
if node_info['category'] != 'ssh' {
|
||||
return error('Can only verify access to SSH nodes')
|
||||
}
|
||||
|
||||
// Test basic connectivity
|
||||
result := node.exec_silent('echo "SSH key verification successful"') or {
|
||||
return false
|
||||
}
|
||||
|
||||
return result.contains('SSH key verification successful')
|
||||
}
|
||||
@@ -30,3 +30,16 @@ pub fn loaded() bool {
|
||||
mut agent := new() or { panic(err) }
|
||||
return agent.active
|
||||
}
|
||||
|
||||
// create new SSH agent with single instance guarantee
|
||||
pub fn new_single(args_ SSHAgentNewArgs) !SSHAgent {
|
||||
mut agent := new(args_)!
|
||||
agent.ensure_single_agent()!
|
||||
return agent
|
||||
}
|
||||
|
||||
// check if SSH agent is properly configured and running
|
||||
pub fn agent_status() !map[string]string {
|
||||
mut agent := new()!
|
||||
return agent.diagnostics()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ module sshagent
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
// import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
|
||||
@[heap]
|
||||
pub struct SSHAgent {
|
||||
@@ -12,9 +12,139 @@ pub mut:
|
||||
homepath pathlib.Path
|
||||
}
|
||||
|
||||
// ensure only one SSH agent is running for the current user
|
||||
pub fn (mut agent SSHAgent) ensure_single_agent() ! {
|
||||
user := os.getenv('USER')
|
||||
socket_path := get_agent_socket_path(user)
|
||||
|
||||
// Check if we have a valid agent already
|
||||
if agent.is_agent_responsive() {
|
||||
console.print_debug('SSH agent already running and responsive')
|
||||
return
|
||||
}
|
||||
|
||||
// Kill any orphaned agents
|
||||
agent.cleanup_orphaned_agents()!
|
||||
|
||||
// Start new agent with consistent socket
|
||||
agent.start_agent_with_socket(socket_path)!
|
||||
|
||||
// Set environment variables
|
||||
os.setenv('SSH_AUTH_SOCK', socket_path, true)
|
||||
agent.active = true
|
||||
}
|
||||
|
||||
// get consistent socket path per user
|
||||
fn get_agent_socket_path(user string) string {
|
||||
return '/tmp/ssh-agent-${user}.sock'
|
||||
}
|
||||
|
||||
// check if current agent is responsive
|
||||
pub fn (mut agent SSHAgent) is_agent_responsive() bool {
|
||||
if os.getenv('SSH_AUTH_SOCK') == '' {
|
||||
return false
|
||||
}
|
||||
|
||||
res := os.execute('ssh-add -l 2>/dev/null')
|
||||
return res.exit_code == 0 || res.exit_code == 1 // 1 means no keys, but agent is running
|
||||
}
|
||||
|
||||
// cleanup orphaned ssh-agent processes
|
||||
pub fn (mut agent SSHAgent) cleanup_orphaned_agents() ! {
|
||||
user := os.getenv('USER')
|
||||
|
||||
// Find ssh-agent processes for current user
|
||||
res := os.execute('pgrep -u ${user} ssh-agent')
|
||||
if res.exit_code == 0 && res.output.len > 0 {
|
||||
pids := res.output.trim_space().split('\n')
|
||||
|
||||
for pid in pids {
|
||||
if pid.trim_space() != '' {
|
||||
// Check if this agent has a valid socket
|
||||
if !agent.is_agent_pid_valid(pid.int()) {
|
||||
console.print_debug('Killing orphaned ssh-agent PID: ${pid}')
|
||||
os.execute('kill ${pid}')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if specific agent PID is valid and responsive
|
||||
fn (mut agent SSHAgent) is_agent_pid_valid(pid int) bool {
|
||||
// Try to find socket for this PID
|
||||
res := os.execute('find /tmp -name "agent.*" -user ${os.getenv('USER')} 2>/dev/null | head -10')
|
||||
if res.exit_code != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for socket_path in res.output.split('\n') {
|
||||
if socket_path.trim_space() != '' {
|
||||
// Test if this socket responds
|
||||
old_sock := os.getenv('SSH_AUTH_SOCK')
|
||||
os.setenv('SSH_AUTH_SOCK', socket_path, true)
|
||||
test_res := os.execute('ssh-add -l 2>/dev/null')
|
||||
os.setenv('SSH_AUTH_SOCK', old_sock, true)
|
||||
|
||||
if test_res.exit_code == 0 || test_res.exit_code == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// start new ssh-agent with specific socket path
|
||||
pub fn (mut agent SSHAgent) start_agent_with_socket(socket_path string) ! {
|
||||
// Remove existing socket if it exists
|
||||
if os.exists(socket_path) {
|
||||
os.rm(socket_path)!
|
||||
}
|
||||
|
||||
// Start ssh-agent with specific socket
|
||||
cmd := 'ssh-agent -a ${socket_path}'
|
||||
res := os.execute(cmd)
|
||||
if res.exit_code != 0 {
|
||||
return error('Failed to start ssh-agent: ${res.output}')
|
||||
}
|
||||
|
||||
// Verify socket was created
|
||||
if !os.exists(socket_path) {
|
||||
return error('SSH agent socket was not created at ${socket_path}')
|
||||
}
|
||||
|
||||
// Set environment variable
|
||||
os.setenv('SSH_AUTH_SOCK', socket_path, true)
|
||||
|
||||
// Verify agent is responsive
|
||||
if !agent.is_agent_responsive() {
|
||||
return error('SSH agent started but is not responsive')
|
||||
}
|
||||
|
||||
console.print_debug('SSH agent started with socket: ${socket_path}')
|
||||
}
|
||||
|
||||
// get agent status and diagnostics
|
||||
pub fn (mut agent SSHAgent) diagnostics() map[string]string {
|
||||
mut diag := map[string]string{}
|
||||
|
||||
diag['socket_path'] = os.getenv('SSH_AUTH_SOCK')
|
||||
diag['socket_exists'] = os.exists(diag['socket_path']).str()
|
||||
diag['agent_responsive'] = agent.is_agent_responsive().str()
|
||||
diag['loaded_keys_count'] = agent.keys.filter(it.loaded).len.str()
|
||||
diag['total_keys_count'] = agent.keys.len.str()
|
||||
|
||||
// Count running ssh-agent processes
|
||||
user := os.getenv('USER')
|
||||
res := os.execute('pgrep -u ${user} ssh-agent | wc -l')
|
||||
diag['agent_processes'] = if res.exit_code == 0 { res.output.trim_space() } else { '0' }
|
||||
|
||||
return diag
|
||||
}
|
||||
|
||||
// get all keys from sshagent and from the local .ssh dir
|
||||
pub fn (mut agent SSHAgent) init() ! {
|
||||
// first get keys out of ssh-add
|
||||
// first get keys out of ssh-add
|
||||
agent.keys = []SSHKey{}
|
||||
res := os.execute('ssh-add -L')
|
||||
if res.exit_code == 0 {
|
||||
|
||||
Reference in New Issue
Block a user