The ssh install and ssh to commands (closes #2)
All checks were successful
check / check (push) Successful in 21s
All checks were successful
check / check (push) Successful in 21s
install runs the system ssh and hands the host a short shell script to run, with the public key line on the connection's standard input rather than on a command line, where anyone else on the host could read it out of the process list. The script makes ~/.ssh and authorized_keys if they are missing, adds the line unless the same line is already there, and says which of the two it did. to serves the key from an agent inside the tool, on a unix socket in a temporary directory only its owner can enter, and points ssh at it with -o IdentityAgent. The socket and directory go when the command ends and the private key is never written to disk. Only this command hands back the status ssh ended with instead of ending with status 1. The tests put a stand-in ssh on the path: for install it runs the script the tool sends against a directory standing in for the host's home directory, so the file, the modes and the second run that changes nothing are all watched happening. Model: opus-5
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package sshkey_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -8,8 +11,16 @@ import (
|
||||
"git.eeqj.de/sneak/keyfunc/internal/sshkey"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
// agentDirectoryMode is what the directory holding the agent socket
|
||||
// has to be: nobody but its owner may enter it.
|
||||
const agentDirectoryMode = 0o700
|
||||
|
||||
// exampleIndex is the key index every test here derives at.
|
||||
const exampleIndex = 0
|
||||
|
||||
// example returns the mnemonic every BIP-39 document uses to show its
|
||||
// test vectors: eleven abandons and about.
|
||||
func example() string {
|
||||
@@ -26,7 +37,7 @@ func TestTooFewBytesAreRefused(t *testing.T) {
|
||||
func TestTheCommentIsPutAtTheEndOfTheLine(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key := forIndex(t, 0)
|
||||
key := exampleKey(t)
|
||||
|
||||
line, err := key.Line("hello")
|
||||
require.NoError(t, err)
|
||||
@@ -37,7 +48,7 @@ func TestTheCommentIsPutAtTheEndOfTheLine(t *testing.T) {
|
||||
func TestThePrivateKeyCarriesTheSamePublicKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key := forIndex(t, 0)
|
||||
key := exampleKey(t)
|
||||
|
||||
line, err := key.Line("")
|
||||
require.NoError(t, err)
|
||||
@@ -54,11 +65,59 @@ func TestThePrivateKeyCarriesTheSamePublicKey(t *testing.T) {
|
||||
require.Equal(t, line, back)
|
||||
}
|
||||
|
||||
// forIndex derives the key for one index.
|
||||
func forIndex(t *testing.T, index uint32) *sshkey.Key {
|
||||
func TestTheAgentServesTheOneKeyAndNothingElse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key := exampleKey(t)
|
||||
|
||||
served, err := key.Serve(t.Context(), "a comment")
|
||||
require.NoError(t, err)
|
||||
|
||||
directory, err := os.Stat(filepath.Dir(served.Socket()))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t,
|
||||
os.FileMode(agentDirectoryMode), directory.Mode().Perm(),
|
||||
)
|
||||
|
||||
var dialer net.Dialer
|
||||
|
||||
connection, err := dialer.DialContext(t.Context(), "unix", served.Socket())
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() { _ = connection.Close() }()
|
||||
|
||||
held, err := agent.NewClient(connection).List()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, held, 1)
|
||||
|
||||
line, err := key.Line("a comment")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, line, held[0].String())
|
||||
}
|
||||
|
||||
func TestStoppingTheAgentLeavesNothingBehind(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
served, err := exampleKey(t).Serve(t.Context(), "a comment")
|
||||
require.NoError(t, err)
|
||||
|
||||
directory := filepath.Dir(served.Socket())
|
||||
require.DirExists(t, directory)
|
||||
|
||||
served.Stop()
|
||||
require.NoDirExists(t, directory)
|
||||
|
||||
var dialer net.Dialer
|
||||
|
||||
_, err = dialer.DialContext(t.Context(), "unix", served.Socket())
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// exampleKey derives the key the example mnemonic gives.
|
||||
func exampleKey(t *testing.T) *sshkey.Key {
|
||||
t.Helper()
|
||||
|
||||
material, err := derive.Bytes(example(), sshkey.Application, index)
|
||||
material, err := derive.Bytes(example(), sshkey.Application, exampleIndex)
|
||||
require.NoError(t, err)
|
||||
|
||||
key, err := sshkey.New(material)
|
||||
|
||||
Reference in New Issue
Block a user