/
agent.go
101 lines (91 loc) 路 2.75 KB
/
agent.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package keys
import (
"fmt"
"net"
"golang.org/x/crypto/ssh/agent"
)
// Agent is a connection to an SSH agent, along with some supplementray data for
// manageing the connection.
type Agent struct {
conn net.Conn
sshAgent agent.ExtendedAgent
path string
passphrase string
}
// AgentOpt is a configuration option for an SSH agent connection.
type AgentOpt func(*Agent)
// NewSSHAgent establishes a connection to a unix socket at the given path
// representing an SSH agent. Options can be given to configure the connection
// and how the agent is used. Options are executed in the order provided.
// Returned is an SSH agent connection or an error if unsuccessful.
func NewSSHAgent(path string, opts ...AgentOpt) (*Agent, error) {
conn, err := net.Dial("unix", path)
if err != nil {
return nil, fmt.Errorf("failed to connect to agent socket: %w", err)
}
client := agent.NewClient(conn)
if client == nil {
return nil, fmt.Errorf("socket could not be as ssh agent: %w", err)
}
sshAgent := Agent{
conn: conn,
sshAgent: client,
path: path,
passphrase: "",
}
for _, opt := range opts {
opt(&sshAgent)
}
return &sshAgent, nil
}
// AgentPassphraseOpt should be provided when creating a new agent if the SSH
// agent is locked by a passphrase.
func AgentPassphraseOpt(passphrase string) AgentOpt {
return func(a *Agent) {
a.passphrase = passphrase
}
}
// Close closes the underlying socket connection used to communicate with the
// SSH agent.
func (a *Agent) Close() error {
return a.conn.Close()
}
// GetKeys uses the SSH agent connection to retreive all the keys present in the
// agent in the authorized key format. If the agent is locked and the correct
// passphrase option has been provided, the agent will be unlocked and re-locked
// as required. Returned are the keys present in the agent or an error if
// unsuccessful.
func (a *Agent) GetKeys() ([]*Key, error) {
if a.passphrase != "" {
if err := a.unlock(); err != nil {
return nil, err
}
//TODO: Check for errors when re-locking the agent
defer a.lock()
}
agentKeys, err := a.sshAgent.List()
if err != nil {
return nil, fmt.Errorf("failed to get keys from agent: %w", err)
}
keys := make([]*Key, len(agentKeys))
for idx, k := range agentKeys {
key, err := NewKey(k.String(), k.Comment)
if err != nil {
return nil, fmt.Errorf("failed to parse key from agent: %w", err)
}
keys[idx] = key
}
return keys, nil
}
func (a *Agent) unlock() error {
if err := a.sshAgent.Unlock([]byte(a.passphrase)); err != nil {
return fmt.Errorf("failed to unlock ssh agent: %w", err)
}
return nil
}
func (a *Agent) lock() error {
if err := a.sshAgent.Lock([]byte(a.passphrase)); err != nil {
return fmt.Errorf("failed to lock ssh agent: %w", err)
}
return nil
}