a73x

internal/server/api/injectedkey.go

Ref:   Size: 2.6 KiB   History

package api

import (
	"strings"

	"github.com/a73x/eitri/internal/server/api/types"
	"github.com/a73x/eitri/internal/server/store"
	"golang.org/x/crypto/ssh"
)

// maxKeyCommentLen bounds the comment kept from a user-supplied key. It is
// free text that ends up rendered in the console, and an authorized_keys
// comment is conventionally a short "user@host" — anything longer is either a
// mistake or an attempt to fill a column.
const maxKeyCommentLen = 128

// describeKey summarises the authorized key eitri is about to install, for the
// record kept on the VM row. It returns the key type, its SHA256 fingerprint in
// OpenSSH's own spelling, and the key's comment.
//
// It DESCRIBES rather than validates. Create deliberately accepts any
// single-line key — the guest's sshd is the thing that decides what it will
// honour, and refusing a key here on cryptographic grounds would be eitri
// second-guessing it. So a key that cannot be parsed still yields whatever can
// be said about it: the leading token as its type, and no fingerprint. An empty
// fingerprint is the signal that the key was unreadable, not that none exists.
//
// A key eitri never installed returns three empty strings, which is a different
// statement entirely: nothing was injected.
func describeKey(line string) (keyType, fingerprint, comment string) {
	line = strings.TrimSpace(line)
	if line == "" {
		return "", "", ""
	}
	pub, comment, _, _, err := ssh.ParseAuthorizedKey([]byte(line))
	if err != nil {
		// Unreadable: say what it claims to be and nothing more. The first
		// field of an authorized_keys line is its type by convention, and a
		// claim is still worth showing next to "we could not read this".
		return firstField(line), "", ""
	}
	if len(comment) > maxKeyCommentLen {
		comment = comment[:maxKeyCommentLen]
	}
	return pub.Type(), ssh.FingerprintSHA256(pub), comment
}

// firstField returns the first whitespace-separated token of s, bounded so an
// unparseable line cannot contribute an unbounded "type".
func firstField(s string) string {
	if i := strings.IndexAny(s, " \t"); i >= 0 {
		s = s[:i]
	}
	if len(s) > 32 {
		s = s[:32]
	}
	return s
}

// injectedKey renders a VM row's recorded key for the wire, or nil when eitri
// installed none. It is derived and read-only: the key itself stays write-only,
// like every other field a create accepts and no response echoes.
func injectedKey(vm store.VM) *types.InjectedKey {
	if vm.InjectedKeyType == "" && vm.InjectedKeyFP == "" {
		return nil
	}
	return &types.InjectedKey{
		Type:        vm.InjectedKeyType,
		Fingerprint: vm.InjectedKeyFP,
		Comment:     vm.InjectedKeyComment,
	}
}