access

package
v1.3.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package access implements the business logic behind `nself access grant / revoke / list` — managing SSH public keys in the authorized_keys file of an already-deployed nself host. It never touches private key material: every entry point here accepts or produces public keys and fingerprints only.

Purpose: fill the gap where hcloud only injects SSH keys at server-creation time, leaving no CLI path to grant or revoke access on a running box without hand-editing authorized_keys over a raw ssh session. Inputs: a Transport (SSHTransport in production, LocalFileTransport in tests) plus a parsed PublicKey and request options. Outputs: GrantResult / RevokeResult / ListResult describing what changed, including the key fingerprint for verification. Constraints: idempotent grant, timestamped backup before every mutation, a lockout guard on revoke, and an audit line per mutation. See manager.go, transport.go, and audit.go for the pieces of that contract.

Index

Constants

This section is empty.

Variables

View Source
var ErrLastKey = errors.New("refusing to remove the last remaining authorized key: this would lock out all SSH access to the host; pass --force to override")

ErrLastKey is returned by Revoke when removing the requested key would leave the host with zero authorized keys — the lockout failure mode this command exists to prevent.

Functions

func SetAuditLogPathForTest

func SetAuditLogPathForTest(path string) func()

SetAuditLogPathForTest redirects the audit log to path for the duration of a test and returns a restore function to call in a defer.

Types

type Entry

type Entry struct {
	// User is the label identifying whose key this is (e.g. a teammate's
	// name), not necessarily a Unix account — grant/revoke key entries by
	// this label, independent of which OS account authorized_keys belongs to.
	User string
	Key  PublicKey

	// Sudo and Docker record the intended privilege level for this grant.
	// They are audit/inventory metadata only: this package does not itself
	// modify OS group membership. See the access wiki page for the reasoning.
	Sudo   bool
	Docker bool

	Expires *time.Time
	Granted time.Time
}

Entry is one nself-managed authorized_keys line: a single public key granted to a named person, with optional privilege metadata and expiry.

func (Entry) Expired

func (e Entry) Expired(now time.Time) bool

Expired reports whether e carries an expiry that has passed as of now.

func (Entry) Fingerprint

func (e Entry) Fingerprint() string

Fingerprint is a convenience wrapper around Key.Fingerprint. A managed entry's key has already been validated once at grant time, so a parse error here (which should not happen) degrades to an empty string rather than panicking a caller that only wants a display value.

type GrantRequest

type GrantRequest struct {
	User    string
	Key     PublicKey
	Sudo    bool
	Docker  bool
	Expires *time.Time
	DryRun  bool
}

GrantRequest describes one `nself access grant` invocation.

type GrantResult

type GrantResult struct {
	AlreadyGranted bool
	Fingerprint    string
	BackupPath     string
	Diff           string
}

GrantResult reports what Grant did or would do.

func Grant

func Grant(ctx context.Context, t Transport, req GrantRequest) (GrantResult, error)

Grant adds or updates req.User's managed key on t. Re-granting the exact same key with the same --sudo/--docker/--expires for the same user is a no-op (AlreadyGranted=true) — it never duplicates a line. Granting a different key, or different metadata, for an existing user replaces that user's single managed line in place.

type ListResult

type ListResult struct {
	Entries      []Entry
	ForeignCount int
}

ListResult reports every nself-managed entry found, plus a count of foreign (non-nself-managed) key lines sharing the file.

func List

func List(ctx context.Context, t Transport) (ListResult, error)

List reads t's authorized_keys and returns every nself-managed entry, sorted by user label.

type LocalFileTransport

type LocalFileTransport struct {
	Path string
	// contains filtered or unexported fields
}

LocalFileTransport implements Transport against a file on the local filesystem.

func NewLocalFileTransport

func NewLocalFileTransport(path string) *LocalFileTransport

NewLocalFileTransport returns a Transport rooted at path.

func (*LocalFileTransport) Backup

func (t *LocalFileTransport) Backup(ctx context.Context) (string, error)

Backup copies the current file to "<path>.bak.<UTC timestamp>" and returns that path, or "" if there was nothing to back up.

func (*LocalFileTransport) Describe

func (t *LocalFileTransport) Describe() string

func (*LocalFileTransport) Read

func (t *LocalFileTransport) Read(ctx context.Context) ([]byte, error)

Read returns the file's content, or (nil, nil) if it does not exist yet.

func (*LocalFileTransport) Write

func (t *LocalFileTransport) Write(ctx context.Context, content []byte) error

Write replaces the file's content, creating its parent directory (0700) if needed, and leaves the file at 0600.

type PublicKey

type PublicKey struct {
	Type string
	Data string // base64, undecoded
}

PublicKey is a parsed SSH public key: its algorithm identifier and the base64-encoded key blob. A PublicKey never carries private key material.

func LoadPublicKeyArg

func LoadPublicKeyArg(arg string) (PublicKey, error)

LoadPublicKeyArg resolves the `--key <pubkey|@file>` convention: a leading '@' means read the key from the given file path, otherwise arg is the key material itself.

func ParsePublicKey

func ParsePublicKey(input string) (PublicKey, error)

ParsePublicKey parses a single authorized_keys-style public key line ("<type> <base64> [comment...]"), ignoring any comment. It refuses input that looks like a private key without echoing that input anywhere, so a pasted private key is never reflected back to the terminal or a log line.

func (PublicKey) Fingerprint

func (k PublicKey) Fingerprint() (string, error)

Fingerprint returns the key's fingerprint in the same format `ssh-keygen -lf` prints: "SHA256:<unpadded base64 of sha256(key blob)>".

func (PublicKey) Line

func (k PublicKey) Line() string

Line renders "<type> <data>" with no comment. Callers append their own nself-managed tag comment (see entry.go).

type RevokeRequest

type RevokeRequest struct {
	User   string
	Force  bool
	DryRun bool
}

RevokeRequest describes one `nself access revoke` invocation.

type RevokeResult

type RevokeResult struct {
	Fingerprint string
	BackupPath  string
	Diff        string
}

RevokeResult reports what Revoke did or would do.

func Revoke

Revoke removes req.User's managed key from t. It refuses to proceed (ErrLastKey) when the target host would be left with zero authorized keys, unless req.Force is set.

type SSHTransport

type SSHTransport struct {
	// Host is "[user@]host" — the ssh connection target, e.g. "root@5.75.235.42".
	Host string

	// IdentityPath is the local private key used to authenticate the SSH
	// connection (the operator's own key, distinct from any key being
	// granted or revoked).
	IdentityPath string

	// RemotePath is the authorized_keys path on the remote host. Defaults to
	// "~/.ssh/authorized_keys" (relative to whichever account Host connects
	// as) when empty.
	RemotePath string
}

SSHTransport implements Transport by running the ssh binary against a real host.

func (*SSHTransport) Backup

func (t *SSHTransport) Backup(ctx context.Context) (string, error)

Backup copies the remote file to a timestamped sibling and returns that remote path, or "" if there was nothing to back up.

func (*SSHTransport) Describe

func (t *SSHTransport) Describe() string

func (*SSHTransport) Read

func (t *SSHTransport) Read(ctx context.Context) ([]byte, error)

Read returns the remote authorized_keys content, or (nil, nil) if the file does not exist on the remote host.

func (*SSHTransport) Write

func (t *SSHTransport) Write(ctx context.Context, content []byte) error

Write replaces the remote authorized_keys content, creating its parent directory (0700) first, and leaves the file at 0600.

type Transport

type Transport interface {
	// Describe returns a short human-readable label for error messages and
	// audit lines, e.g. "root@5.75.235.42" or a fixture's file path.
	Describe() string

	// Read returns the current authorized_keys content, or (nil, nil) if the
	// file does not exist yet — a fresh host with no managed keys is not an
	// error.
	Read(ctx context.Context) ([]byte, error)

	// Backup copies the current file to a timestamped sibling before any
	// mutation and returns its path. If the file does not exist yet, Backup
	// is a no-op that returns "".
	Backup(ctx context.Context) (string, error)

	// Write replaces the authorized_keys content and ensures the result is
	// readable only by its owner (0600), matching the CLI's env-file
	// permission rule.
	Write(ctx context.Context, content []byte) error
}

Transport reads, backs up, and writes one remote (or fixture) authorized_keys file.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL