mirror of
https://github.com/nix-community/home-manager
synced 2024-09-12 06:16:33 +02:00
These (and the `*MD` functions apart from `literalMD`) are now no-ops in nixpkgs and serve no purpose other than to add additional noise and potentially mislead people into thinking unmarked DocBook documentation will still be accepted. Note that if backporting changes including documentation to 23.05, the `mdDoc` calls will need to be re-added. To reproduce this commit, run: $ NIX_PATH=nixpkgs=flake:nixpkgs/e7e69199f0372364a6106a1e735f68604f4c5a25 \ nix shell nixpkgs#coreutils \ -c find . -name '*.nix' \ -exec nix run -- github:emilazy/nix-doc-munge/98dadf1f77351c2ba5dcb709a2a171d655f15099 \ --strip {} + $ ./format
548 lines
16 KiB
Nix
548 lines
16 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
|
|
cfg = config.programs.ssh;
|
|
|
|
isPath = x: builtins.substring 0 1 (toString x) == "/";
|
|
|
|
addressPort = entry:
|
|
if isPath entry.address
|
|
then " ${entry.address}"
|
|
else " [${entry.address}]:${toString entry.port}";
|
|
|
|
unwords = builtins.concatStringsSep " ";
|
|
|
|
mkSetEnvStr = envStr: unwords
|
|
(mapAttrsToList
|
|
(name: value: ''${name}="${escape [ "\"" "\\" ] (toString value)}"'')
|
|
envStr
|
|
);
|
|
|
|
bindOptions = {
|
|
address = mkOption {
|
|
type = types.str;
|
|
default = "localhost";
|
|
example = "example.org";
|
|
description = "The address where to bind the port.";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.nullOr types.port;
|
|
default = null;
|
|
example = 8080;
|
|
description = "Specifies port number to bind on bind address.";
|
|
};
|
|
};
|
|
|
|
dynamicForwardModule = types.submodule {
|
|
options = bindOptions;
|
|
};
|
|
|
|
forwardModule = types.submodule {
|
|
options = {
|
|
bind = bindOptions;
|
|
|
|
host = {
|
|
address = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
example = "example.org";
|
|
description = "The address where to forward the traffic to.";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.nullOr types.port;
|
|
default = null;
|
|
example = 80;
|
|
description = "Specifies port number to forward the traffic to.";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
matchBlockModule = types.submodule ({ dagName, ... }: {
|
|
options = {
|
|
host = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
example = "*.example.org";
|
|
description = ''
|
|
`Host` pattern used by this conditional block.
|
|
See
|
|
{manpage}`ssh_config(5)`
|
|
for `Host` block details.
|
|
This option is ignored if
|
|
{option}`ssh.matchBlocks.*.match`
|
|
if defined.
|
|
'';
|
|
};
|
|
|
|
match = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
example = "host <hostname> canonical\nhost <hostname> exec \"ping -c1 -q 192.168.17.1\"";
|
|
description = ''
|
|
`Match` block conditions used by this block. See
|
|
{manpage}`ssh_config(5)`
|
|
for `Match` block details.
|
|
This option takes precedence over
|
|
{option}`ssh.matchBlocks.*.host`
|
|
if defined.
|
|
'';
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.nullOr types.port;
|
|
default = null;
|
|
description = "Specifies port number to connect on remote host.";
|
|
};
|
|
|
|
forwardAgent = mkOption {
|
|
default = null;
|
|
type = types.nullOr types.bool;
|
|
description = ''
|
|
Whether the connection to the authentication agent (if any)
|
|
will be forwarded to the remote machine.
|
|
'';
|
|
};
|
|
|
|
forwardX11 = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Specifies whether X11 connections will be automatically redirected
|
|
over the secure channel and {env}`DISPLAY` set.
|
|
'';
|
|
};
|
|
|
|
forwardX11Trusted = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Specifies whether remote X11 clients will have full access to the
|
|
original X11 display.
|
|
'';
|
|
};
|
|
|
|
identitiesOnly = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Specifies that ssh should only use the authentication
|
|
identity explicitly configured in the
|
|
{file}`~/.ssh/config` files or passed on the
|
|
ssh command-line, even if {command}`ssh-agent`
|
|
offers more identities.
|
|
'';
|
|
};
|
|
|
|
identityFile = mkOption {
|
|
type = with types; either (listOf str) (nullOr str);
|
|
default = [];
|
|
apply = p:
|
|
if p == null then []
|
|
else if isString p then [p]
|
|
else p;
|
|
description = ''
|
|
Specifies files from which the user identity is read.
|
|
Identities will be tried in the given order.
|
|
'';
|
|
};
|
|
|
|
user = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Specifies the user to log in as.";
|
|
};
|
|
|
|
hostname = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Specifies the real host name to log into.";
|
|
};
|
|
|
|
serverAliveInterval = mkOption {
|
|
type = types.int;
|
|
default = 0;
|
|
description =
|
|
"Set timeout in seconds after which response will be requested.";
|
|
};
|
|
|
|
serverAliveCountMax = mkOption {
|
|
type = types.ints.positive;
|
|
default = 3;
|
|
description = ''
|
|
Sets the number of server alive messages which may be sent
|
|
without SSH receiving any messages back from the server.
|
|
'';
|
|
};
|
|
|
|
sendEnv = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = ''
|
|
Environment variables to send from the local host to the
|
|
server.
|
|
'';
|
|
};
|
|
|
|
setEnv = mkOption {
|
|
type = with types; attrsOf (oneOf [ str path int float ]);
|
|
default = {};
|
|
description = ''
|
|
Environment variables and their value to send to the server.
|
|
'';
|
|
};
|
|
|
|
compression = mkOption {
|
|
type = types.nullOr types.bool;
|
|
default = null;
|
|
description = ''
|
|
Specifies whether to use compression. Omitted from the host
|
|
block when `null`.
|
|
'';
|
|
};
|
|
|
|
checkHostIP = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''
|
|
Check the host IP address in the
|
|
{file}`known_hosts` file.
|
|
'';
|
|
};
|
|
|
|
proxyCommand = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "The command to use to connect to the server.";
|
|
};
|
|
|
|
proxyJump = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "The proxy host to use to connect to the server.";
|
|
};
|
|
|
|
certificateFile = mkOption {
|
|
type = with types; either (listOf str) (nullOr str);
|
|
default = [];
|
|
apply = p:
|
|
if p == null then []
|
|
else if isString p then [p]
|
|
else p;
|
|
description = ''
|
|
Specifies files from which the user certificate is read.
|
|
'';
|
|
};
|
|
|
|
addressFamily = mkOption {
|
|
default = null;
|
|
type = types.nullOr (types.enum ["any" "inet" "inet6"]);
|
|
description = ''
|
|
Specifies which address family to use when connecting.
|
|
'';
|
|
};
|
|
|
|
localForwards = mkOption {
|
|
type = types.listOf forwardModule;
|
|
default = [];
|
|
example = literalExpression ''
|
|
[
|
|
{
|
|
bind.port = 8080;
|
|
host.address = "10.0.0.13";
|
|
host.port = 80;
|
|
}
|
|
];
|
|
'';
|
|
description = ''
|
|
Specify local port forwardings. See
|
|
{manpage}`ssh_config(5)` for `LocalForward`.
|
|
'';
|
|
};
|
|
|
|
remoteForwards = mkOption {
|
|
type = types.listOf forwardModule;
|
|
default = [];
|
|
example = literalExpression ''
|
|
[
|
|
{
|
|
bind.port = 8080;
|
|
host.address = "10.0.0.13";
|
|
host.port = 80;
|
|
}
|
|
];
|
|
'';
|
|
description = ''
|
|
Specify remote port forwardings. See
|
|
{manpage}`ssh_config(5)` for `RemoteForward`.
|
|
'';
|
|
};
|
|
|
|
dynamicForwards = mkOption {
|
|
type = types.listOf dynamicForwardModule;
|
|
default = [];
|
|
example = literalExpression ''
|
|
[ { port = 8080; } ];
|
|
'';
|
|
description = ''
|
|
Specify dynamic port forwardings. See
|
|
{manpage}`ssh_config(5)` for `DynamicForward`.
|
|
'';
|
|
};
|
|
|
|
extraOptions = mkOption {
|
|
type = types.attrsOf types.str;
|
|
default = {};
|
|
description = "Extra configuration options for the host.";
|
|
};
|
|
};
|
|
|
|
# config.host = mkDefault dagName;
|
|
});
|
|
|
|
matchBlockStr = key: cf: concatStringsSep "\n" (
|
|
let
|
|
hostOrDagName = if cf.host != null then cf.host else key;
|
|
matchHead = if cf.match != null
|
|
then "Match ${cf.match}"
|
|
else "Host ${hostOrDagName}";
|
|
in [ "${matchHead}" ]
|
|
++ optional (cf.port != null) " Port ${toString cf.port}"
|
|
++ optional (cf.forwardAgent != null) " ForwardAgent ${lib.hm.booleans.yesNo cf.forwardAgent}"
|
|
++ optional cf.forwardX11 " ForwardX11 yes"
|
|
++ optional cf.forwardX11Trusted " ForwardX11Trusted yes"
|
|
++ optional cf.identitiesOnly " IdentitiesOnly yes"
|
|
++ optional (cf.user != null) " User ${cf.user}"
|
|
++ optional (cf.hostname != null) " HostName ${cf.hostname}"
|
|
++ optional (cf.addressFamily != null) " AddressFamily ${cf.addressFamily}"
|
|
++ optional (cf.sendEnv != []) " SendEnv ${unwords cf.sendEnv}"
|
|
++ optional (cf.setEnv != {}) " SetEnv ${mkSetEnvStr cf.setEnv}"
|
|
++ optional (cf.serverAliveInterval != 0)
|
|
" ServerAliveInterval ${toString cf.serverAliveInterval}"
|
|
++ optional (cf.serverAliveCountMax != 3)
|
|
" ServerAliveCountMax ${toString cf.serverAliveCountMax}"
|
|
++ optional (cf.compression != null) " Compression ${lib.hm.booleans.yesNo cf.compression}"
|
|
++ optional (!cf.checkHostIP) " CheckHostIP no"
|
|
++ optional (cf.proxyCommand != null) " ProxyCommand ${cf.proxyCommand}"
|
|
++ optional (cf.proxyJump != null) " ProxyJump ${cf.proxyJump}"
|
|
++ map (file: " IdentityFile ${file}") cf.identityFile
|
|
++ map (file: " CertificateFile ${file}") cf.certificateFile
|
|
++ map (f: " LocalForward" + addressPort f.bind + addressPort f.host) cf.localForwards
|
|
++ map (f: " RemoteForward" + addressPort f.bind + addressPort f.host) cf.remoteForwards
|
|
++ map (f: " DynamicForward" + addressPort f) cf.dynamicForwards
|
|
++ mapAttrsToList (n: v: " ${n} ${v}") cf.extraOptions
|
|
);
|
|
|
|
in
|
|
|
|
{
|
|
meta.maintainers = [ maintainers.rycee ];
|
|
|
|
options.programs.ssh = {
|
|
enable = mkEnableOption "SSH client configuration";
|
|
|
|
package = mkPackageOption pkgs "openssh" {
|
|
nullable = true;
|
|
default = null;
|
|
extraDescription = "By default, the client provided by your system is used.";
|
|
};
|
|
|
|
forwardAgent = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = ''
|
|
Whether the connection to the authentication agent (if any)
|
|
will be forwarded to the remote machine.
|
|
'';
|
|
};
|
|
|
|
compression = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = "Specifies whether to use compression.";
|
|
};
|
|
|
|
serverAliveInterval = mkOption {
|
|
type = types.int;
|
|
default = 0;
|
|
description = ''
|
|
Set default timeout in seconds after which response will be requested.
|
|
'';
|
|
};
|
|
|
|
serverAliveCountMax = mkOption {
|
|
type = types.ints.positive;
|
|
default = 3;
|
|
description = ''
|
|
Sets the default number of server alive messages which may be
|
|
sent without SSH receiving any messages back from the server.
|
|
'';
|
|
};
|
|
|
|
hashKnownHosts = mkOption {
|
|
default = false;
|
|
type = types.bool;
|
|
description = ''
|
|
Indicates that
|
|
{manpage}`ssh(1)`
|
|
should hash host names and addresses when they are added to
|
|
the known hosts file.
|
|
'';
|
|
};
|
|
|
|
userKnownHostsFile = mkOption {
|
|
type = types.str;
|
|
default = "~/.ssh/known_hosts";
|
|
description = ''
|
|
Specifies one or more files to use for the user host key
|
|
database, separated by whitespace. The default is
|
|
{file}`~/.ssh/known_hosts`.
|
|
'';
|
|
};
|
|
|
|
controlMaster = mkOption {
|
|
default = "no";
|
|
type = types.enum ["yes" "no" "ask" "auto" "autoask"];
|
|
description = ''
|
|
Configure sharing of multiple sessions over a single network connection.
|
|
'';
|
|
};
|
|
|
|
controlPath = mkOption {
|
|
type = types.str;
|
|
default = "~/.ssh/master-%r@%n:%p";
|
|
description = ''
|
|
Specify path to the control socket used for connection sharing.
|
|
'';
|
|
};
|
|
|
|
controlPersist = mkOption {
|
|
type = types.str;
|
|
default = "no";
|
|
example = "10m";
|
|
description = ''
|
|
Whether control socket should remain open in the background.
|
|
'';
|
|
};
|
|
|
|
extraConfig = mkOption {
|
|
type = types.lines;
|
|
default = "";
|
|
description = ''
|
|
Extra configuration.
|
|
'';
|
|
};
|
|
|
|
extraOptionOverrides = mkOption {
|
|
type = types.attrsOf types.str;
|
|
default = {};
|
|
description = ''
|
|
Extra SSH configuration options that take precedence over any
|
|
host specific configuration.
|
|
'';
|
|
};
|
|
|
|
includes = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = ''
|
|
File globs of ssh config files that should be included via the
|
|
`Include` directive.
|
|
|
|
See
|
|
{manpage}`ssh_config(5)`
|
|
for more information.
|
|
'';
|
|
};
|
|
|
|
matchBlocks = mkOption {
|
|
type = hm.types.dagOf matchBlockModule;
|
|
default = {};
|
|
example = literalExpression ''
|
|
{
|
|
"john.example.com" = {
|
|
hostname = "example.com";
|
|
user = "john";
|
|
};
|
|
foo = lib.hm.dag.entryBefore ["john.example.com"] {
|
|
hostname = "example.com";
|
|
identityFile = "/home/john/.ssh/foo_rsa";
|
|
};
|
|
};
|
|
'';
|
|
description = ''
|
|
Specify per-host settings. Note, if the order of rules matter
|
|
then use the DAG functions to express the dependencies as
|
|
shown in the example.
|
|
|
|
See
|
|
{manpage}`ssh_config(5)`
|
|
for more information.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion =
|
|
let
|
|
# `builtins.any`/`lib.lists.any` does not return `true` if there are no elements.
|
|
any' = pred: items: if items == [] then true else any pred items;
|
|
# Check that if `entry.address` is defined, and is a path, that `entry.port` has not
|
|
# been defined.
|
|
noPathWithPort = entry: entry.address != null && isPath entry.address -> entry.port == null;
|
|
checkDynamic = block: any' noPathWithPort block.dynamicForwards;
|
|
checkBindAndHost = fwd: noPathWithPort fwd.bind && noPathWithPort fwd.host;
|
|
checkLocal = block: any' checkBindAndHost block.localForwards;
|
|
checkRemote = block: any' checkBindAndHost block.remoteForwards;
|
|
checkMatchBlock = block: all (fn: fn block) [ checkLocal checkRemote checkDynamic ];
|
|
in any' checkMatchBlock (map (block: block.data) (builtins.attrValues cfg.matchBlocks));
|
|
message = "Forwarded paths cannot have ports.";
|
|
}
|
|
];
|
|
|
|
home.packages = optional (cfg.package != null) cfg.package;
|
|
|
|
home.file.".ssh/config".text =
|
|
let
|
|
sortedMatchBlocks = hm.dag.topoSort cfg.matchBlocks;
|
|
sortedMatchBlocksStr = builtins.toJSON sortedMatchBlocks;
|
|
matchBlocks =
|
|
if sortedMatchBlocks ? result
|
|
then sortedMatchBlocks.result
|
|
else abort "Dependency cycle in SSH match blocks: ${sortedMatchBlocksStr}";
|
|
in ''
|
|
${concatStringsSep "\n" (
|
|
(mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides)
|
|
++ (optional (cfg.includes != [ ]) ''
|
|
Include ${concatStringsSep " " cfg.includes}
|
|
'')
|
|
++ (map (block: matchBlockStr block.name block.data) matchBlocks)
|
|
)}
|
|
|
|
Host *
|
|
ForwardAgent ${lib.hm.booleans.yesNo cfg.forwardAgent}
|
|
Compression ${lib.hm.booleans.yesNo cfg.compression}
|
|
ServerAliveInterval ${toString cfg.serverAliveInterval}
|
|
ServerAliveCountMax ${toString cfg.serverAliveCountMax}
|
|
HashKnownHosts ${lib.hm.booleans.yesNo cfg.hashKnownHosts}
|
|
UserKnownHostsFile ${cfg.userKnownHostsFile}
|
|
ControlMaster ${cfg.controlMaster}
|
|
ControlPath ${cfg.controlPath}
|
|
ControlPersist ${cfg.controlPersist}
|
|
|
|
${replaceStrings ["\n"] ["\n "] cfg.extraConfig}
|
|
'';
|
|
|
|
warnings = mapAttrsToList
|
|
(n: v: "The SSH config match block `programs.ssh.matchBlocks.${n}` sets both of the host and match options.\nThe match option takes precedence.")
|
|
(filterAttrs (n: v: v.data.host != null && v.data.match != null) cfg.matchBlocks);
|
|
};
|
|
}
|