Skip to content

feat(ssh): add configuration#175

Open
Sporif wants to merge 1 commit intonix-community:mainfrom
Sporif:ssh-feat-configuration
Open

feat(ssh): add configuration#175
Sporif wants to merge 1 commit intonix-community:mainfrom
Sporif:ssh-feat-configuration

Conversation

@Sporif
Copy link
Contributor

@Sporif Sporif commented Feb 12, 2026

  • Add a setting to specify the path to the known hosts file
    I need this since I use programs.ssh.knownHosts to set up known hosts, which stores them in /etc/ssh/ssh_known_hosts, and extra known hosts files can be set with programs.ssh.knownHostsFiles. This setting could be improved by making it a list of files that are checked in order, but for now I think it's enough.

  • Allow obtaining private keys via a command instead of the ssh agent
    Instead of an ssh agent, I use Bitwarden to store my ssh keys and rbw to access them on the command line. I've used memfd_create to store the key in a volatile anonymous file for automatic cleanup.

  • Add partial support for the NIX_SSHOPTS environment variable
    Setting this is necessary to support passing the private keys obtained via ssh.private_key_cmd to the ssh command run by nix-copy-closure. So adding support for users to set it directly seems natural. It's not full support since it's only used with nix-copy-closure.

 - Add a setting to specify the path to the known hosts file
 - Allow obtaining private keys via a command instead of the ssh agent
 - Add partial support for the `NIX_SSHOPTS` environment variable
@Sporif Sporif force-pushed the ssh-feat-configuration branch from f3220f3 to d56d8ea Compare February 12, 2026 19:51
Comment on lines +213 to +235
var memFd int
memFd, err = unix.MemfdCreate("nixos-cli-ssh-key", unix.MFD_CLOEXEC|unix.MFD_ALLOW_SEALING|unix.MFD_NOEXEC_SEAL)
if err != nil {
return nil, "", fmt.Errorf("failed to create memfd: %v", err)
}

if _, err = unix.Write(memFd, stdout.Bytes()); err != nil {
return nil, "", fmt.Errorf("failed to write to memfd: %v", err)
}

_, err = unix.FcntlInt(uintptr(memFd), unix.F_ADD_SEALS, unix.F_SEAL_SEAL|unix.F_SEAL_SHRINK|unix.F_SEAL_GROW|unix.F_SEAL_WRITE)
if err != nil {
return nil, "", fmt.Errorf("failed to add seals to memfd: %v", err)
}

if err = unix.Fchmod(memFd, 0o400); err != nil {
return nil, "", fmt.Errorf("failed to change permissions of memfd: %v", err)
}

keyPath := fmt.Sprintf("/proc/%v/fd/%v", os.Getpid(), memFd)
auth := ssh.PublicKeys(signer)

return auth, keyPath, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to have a fallback mechanism that doesn't use memfd on non-Linux platforms?

Although rather niche, I would like to preserve the ability to deploy a NixOS system from a non-NixOS one such as macOS.

Comment on lines +108 to 112
knownHostsFile := cfg.Ssh.KnownHostsFile
if knownHostsFile == "" {
knownHostsFile = filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts")
}
knownHostsKeyCallback, err := knownhosts.New(knownHostsFile)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to pass multiple files to knownhosts.New(), so this conditional isn't really needed.

We can keep a list of well-known paths, such as /etc/ssh/known_hosts, the current $HOME/.ssh/known-hosts, concat that with the ssh.known_hosts_file setting, and pass that to this function most likely.

Following from that, ssh.known_hosts_file could be made plural, and a user is free to specify multiple sources of this if they would like.

log.Warnf("failed to obtain private key: %v", keyErr)
log.Warnf("falling back to password auth")
}
} else if sock := os.Getenv("SSH_AUTH_SOCK"); sock != "" {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, multiple auth methods should be supported, since SSH runs them in order with all the keys it manages to find, so this shouldn't be gated behind an else if.

Suggested change
} else if sock := os.Getenv("SSH_AUTH_SOCK"); sock != "" {
}
if sock := os.Getenv("SSH_AUTH_SOCK"); sock != "" {

log.CmdArray(argv)

cmd := NewCommand(argv[0], argv[1:]...)
sshoptsEnv := strings.TrimSpace(strings.Join(sshopts, " "))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use shlex.Join() for proper shell quoting.

}

argv := []string{"nix-copy-closure"}
sshopts := []string{os.Getenv("NIX_SSHOPTS")}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use shlex.Split() on NIX_SSHOPTS first, since these are shell args and can be quoted as such, and end up getting joined later.

address: address,
port: port,
password: password,
sshopts: sshopts,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: might be better to name this nixSSHOpts or something along those lines, since this only applies to the Nix command for now.

return fmt.Sprintf("%s@%s:%d", s.user, s.address, s.port)
}

func (s *SSHSystem) Sshopts() []string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: use the uppercase naming for the acronym SSH

Suggested change
func (s *SSHSystem) Sshopts() []string {
func (s *SSHSystem) NixSSHOpts() []string {

speed up activations at the cost of risking data loss if interrupted.

*NIX_SSHOPTS*
Additional ssh options to be passed to _nixos-copy-closure_ on the command line.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Additional ssh options to be passed to _nixos-copy-closure_ on the command line.
Additional ssh options to be passed to _nix-copy-closure_ on the command line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants