diff options
Diffstat (limited to 'nixos')
23 files changed, 459 insertions, 65 deletions
diff --git a/nixos/doc/manual/release-notes/rl-1903.xml b/nixos/doc/manual/release-notes/rl-1903.xml index 69e94fbccc5c..d99c88817279 100644 --- a/nixos/doc/manual/release-notes/rl-1903.xml +++ b/nixos/doc/manual/release-notes/rl-1903.xml @@ -43,6 +43,15 @@ <literal>./programs/nm-applet.nix</literal> </para> </listitem> + <listitem> + <para> + There is a new <varname>security.googleOsLogin</varname> module for using + <link xlink:href="https://cloud.google.com/compute/docs/instances/managing-instance-access">OS Login</link> + to manage SSH access to Google Compute Engine instances, which supersedes + the imperative and broken <literal>google-accounts-daemon</literal> used + in <literal>nixos/modules/virtualisation/google-compute-config.nix</literal>. + </para> + </listitem> </itemizedlist> </section> @@ -318,6 +327,22 @@ case. </para> </listitem> + <listitem> + <para> + The <literal>pam_unix</literal> account module is now loaded with its + control field set to <literal>required</literal> instead of + <literal>sufficient</literal>, so that later pam account modules that + might do more extensive checks are being executed. + Previously, the whole account module verification was exited prematurely + in case a nss module provided the account name to + <literal>pam_unix</literal>. + The LDAP and SSSD NixOS modules already add their NSS modules when + enabled. In case your setup breaks due to some later pam account module + previosuly shadowed, or failing NSS lookups, please file a bug. You can + get back the old behaviour by manually setting + <literal><![CDATA[security.pam.services.<name?>.text]]></literal>. + </para> + </listitem> </itemizedlist> </section> diff --git a/nixos/lib/testing.nix b/nixos/lib/testing.nix index 57b4412d9bb7..c0b4041d7e30 100644 --- a/nixos/lib/testing.nix +++ b/nixos/lib/testing.nix @@ -116,7 +116,7 @@ in rec { vms = map (m: m.config.system.build.vm) (lib.attrValues nodes); - ocrProg = tesseract_4.override { enableLanguages = [ "eng" ]; }; + ocrProg = tesseract4.override { enableLanguages = [ "eng" ]; }; imagemagick_tiff = imagemagick_light.override { inherit libtiff; }; diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix index 1ef915d40612..b68e55a40b90 100644 --- a/nixos/lib/utils.nix +++ b/nixos/lib/utils.nix @@ -7,9 +7,8 @@ rec { || elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ]; # Check whenever `b` depends on `a` as a fileSystem - # FIXME: it's incorrect to simply use hasPrefix here: "/dev/a" is not a parent of "/dev/ab" - fsBefore = a: b: ((any (x: elem x [ "bind" "move" ]) b.options) && (a.mountPoint == b.device)) - || (hasPrefix a.mountPoint b.mountPoint); + fsBefore = a: b: a.mountPoint == b.device + || hasPrefix "${a.mountPoint}${optionalString (!(hasSuffix "/" a.mountPoint)) "/"}" b.mountPoint; # Escape a path according to the systemd rules, e.g. /dev/xyzzy # becomes dev-xyzzy. FIXME: slow. diff --git a/nixos/modules/config/nsswitch.nix b/nixos/modules/config/nsswitch.nix index a74d551f50df..b601e908e49f 100644 --- a/nixos/modules/config/nsswitch.nix +++ b/nixos/modules/config/nsswitch.nix @@ -1,6 +1,6 @@ # Configuration for the Name Service Switch (/etc/nsswitch.conf). -{ config, lib, ... }: +{ config, lib, pkgs, ... }: with lib; @@ -15,6 +15,7 @@ let ldap = canLoadExternalModules && (config.users.ldap.enable && config.users.ldap.nsswitch); sssd = canLoadExternalModules && config.services.sssd.enable; resolved = canLoadExternalModules && config.services.resolved.enable; + googleOsLogin = canLoadExternalModules && config.security.googleOsLogin.enable; hostArray = [ "files" ] ++ optional mymachines "mymachines" @@ -29,6 +30,7 @@ let ++ optional sssd "sss" ++ optional ldap "ldap" ++ optional mymachines "mymachines" + ++ optional googleOsLogin "cache_oslogin oslogin" ++ [ "systemd" ]; shadowArray = [ "files" ] @@ -97,7 +99,7 @@ in { # configured IP addresses, or ::1 and 127.0.0.2 as # fallbacks. Systemd also provides nss-mymachines to return IP # addresses of local containers. - system.nssModules = optionals canLoadExternalModules [ config.systemd.package.out ]; - + system.nssModules = (optionals canLoadExternalModules [ config.systemd.package.out ]) + ++ optional googleOsLogin pkgs.google-compute-engine-oslogin.out; }; } diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 8fda7ee0b0a9..4a392b6f5c9c 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -154,6 +154,7 @@ ./security/chromium-suid-sandbox.nix ./security/dhparams.nix ./security/duosec.nix + ./security/google_oslogin.nix ./security/hidepid.nix ./security/lock-kernel-modules.nix ./security/misc.nix @@ -303,6 +304,7 @@ ./services/hardware/usbmuxd.nix ./services/hardware/thermald.nix ./services/hardware/undervolt.nix + ./services/hardware/vdr.nix ./services/logging/SystemdJournal2Gelf.nix ./services/logging/awstats.nix ./services/logging/fluentd.nix diff --git a/nixos/modules/programs/adb.nix b/nixos/modules/programs/adb.nix index 942572cef9d5..250d8c252a3b 100644 --- a/nixos/modules/programs/adb.nix +++ b/nixos/modules/programs/adb.nix @@ -16,7 +16,6 @@ with lib; To grant access to a user, it must be part of adbusers group: <code>users.users.alice.extraGroups = ["adbusers"];</code> ''; - relatedPackages = [ ["androidenv" "platformTools"] ]; }; }; }; @@ -24,7 +23,7 @@ with lib; ###### implementation config = mkIf config.programs.adb.enable { services.udev.packages = [ pkgs.android-udev-rules ]; - environment.systemPackages = [ pkgs.androidenv.platformTools ]; + environment.systemPackages = [ pkgs.androidenv.androidPkgs_9_0.platform-tools ]; users.groups.adbusers = {}; }; } diff --git a/nixos/modules/security/google_oslogin.nix b/nixos/modules/security/google_oslogin.nix new file mode 100644 index 000000000000..246419b681af --- /dev/null +++ b/nixos/modules/security/google_oslogin.nix @@ -0,0 +1,68 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.security.googleOsLogin; + package = pkgs.google-compute-engine-oslogin; + +in + +{ + + options = { + + security.googleOsLogin.enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable Google OS Login + + The OS Login package enables the following components: + AuthorizedKeysCommand to query valid SSH keys from the user's OS Login + profile during ssh authentication phase. + NSS Module to provide user and group information + PAM Module for the sshd service, providing authorization and + authentication support, allowing the system to use data stored in + Google Cloud IAM permissions to control both, the ability to log into + an instance, and to perform operations as root (sudo). + ''; + }; + + }; + + config = mkIf cfg.enable { + security.pam.services.sshd = { + makeHomeDir = true; + googleOsLoginAccountVerification = true; + # disabled for now: googleOsLoginAuthentication = true; + }; + + security.sudo.extraConfig = '' + #includedir /run/google-sudoers.d + ''; + systemd.tmpfiles.rules = [ + "d /run/google-sudoers.d 750 root root -" + "d /var/google-users.d 750 root root -" + ]; + + # enable the nss module, so user lookups etc. work + system.nssModules = [ package ]; + + # Ugly: sshd refuses to start if a store path is given because /nix/store is group-writable. + # So indirect by a symlink. + environment.etc."ssh/authorized_keys_command_google_oslogin" = { + mode = "0755"; + text = '' + #!/bin/sh + exec ${package}/bin/google_authorized_keys "$@" + ''; + }; + services.openssh.extraConfig = '' + AuthorizedKeysCommand /etc/ssh/authorized_keys_command_google_oslogin %u + AuthorizedKeysCommandUser nobody + ''; + }; + +} diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix index 926c6d77d3bb..b1a0eff98c20 100644 --- a/nixos/modules/security/pam.nix +++ b/nixos/modules/security/pam.nix @@ -77,6 +77,30 @@ let ''; }; + googleOsLoginAccountVerification = mkOption { + default = false; + type = types.bool; + description = '' + If set, will use the Google OS Login PAM modules + (<literal>pam_oslogin_login</literal>, + <literal>pam_oslogin_admin</literal>) to verify possible OS Login + users and set sudoers configuration accordingly. + This only makes sense to enable for the <literal>sshd</literal> PAM + service. + ''; + }; + + googleOsLoginAuthentication = mkOption { + default = false; + type = types.bool; + description = '' + If set, will use the <literal>pam_oslogin_login</literal>'s user + authentication methods to authenticate users using 2FA. + This only makes sense to enable for the <literal>sshd</literal> PAM + service. + ''; + }; + fprintAuth = mkOption { default = config.services.fprintd.enable; type = types.bool; @@ -269,7 +293,7 @@ let text = mkDefault ('' # Account management. - account ${if cfg.sssdStrictAccess then "required" else "sufficient"} pam_unix.so + account required pam_unix.so ${optionalString use_ldap "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"} ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess==false) @@ -278,8 +302,14 @@ let "account [default=bad success=ok user_unknown=ignore] ${pkgs.sssd}/lib/security/pam_sss.so"} ${optionalString config.krb5.enable "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"} + ${optionalString cfg.googleOsLoginAccountVerification '' + account [success=ok ignore=ignore default=die] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so + account [success=ok default=ignore] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_admin.so + ''} # Authentication management. + ${optionalString cfg.googleOsLoginAuthentication + "auth [success=done perm_denied=bad default=ignore] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so"} ${optionalString cfg.rootOK "auth sufficient pam_rootok.so"} ${optionalString cfg.requireWheel diff --git a/nixos/modules/services/databases/aerospike.nix b/nixos/modules/services/databases/aerospike.nix index 5f33164998be..4b905f90529d 100644 --- a/nixos/modules/services/databases/aerospike.nix +++ b/nixos/modules/services/databases/aerospike.nix @@ -43,6 +43,7 @@ in package = mkOption { default = pkgs.aerospike; + defaultText = "pkgs.aerospike"; type = types.package; description = "Which Aerospike derivation to use"; }; diff --git a/nixos/modules/services/databases/clickhouse.nix b/nixos/modules/services/databases/clickhouse.nix index 1b8771cec391..21e0cee34151 100644 --- a/nixos/modules/services/databases/clickhouse.nix +++ b/nixos/modules/services/databases/clickhouse.nix @@ -70,6 +70,11 @@ with lib; }; }; + environment.systemPackages = [ pkgs.clickhouse ]; + + # startup requires a `/etc/localtime` which only if exists if `time.timeZone != null` + time.timeZone = mkDefault "UTC"; + }; } diff --git a/nixos/modules/services/hardware/vdr.nix b/nixos/modules/services/hardware/vdr.nix new file mode 100644 index 000000000000..75136a2f7964 --- /dev/null +++ b/nixos/modules/services/hardware/vdr.nix @@ -0,0 +1,71 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.vdr; + libDir = "/var/lib/vdr"; +in { + + ###### interface + + options = { + + services.vdr = { + enable = mkEnableOption "enable VDR. Please put config into ${libDir}."; + + package = mkOption { + type = types.package; + default = pkgs.vdr; + defaultText = "pkgs.vdr"; + example = literalExample "pkgs.wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }"; + description = "Package to use."; + }; + + videoDir = mkOption { + type = types.path; + default = "/srv/vdr/video"; + description = "Recording directory"; + }; + + extraArguments = mkOption { + type = types.listOf types.str; + default = []; + description = "Additional command line arguments to pass to VDR."; + }; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + systemd.tmpfiles.rules = [ + "d ${cfg.videoDir} 0755 vdr vdr -" + "Z ${cfg.videoDir} - vdr vdr -" + ]; + + systemd.services.vdr = { + description = "VDR"; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = '' + ${cfg.package}/bin/vdr \ + --video="${cfg.videoDir}" \ + --config="${libDir}" \ + ${escapeShellArgs cfg.extraArguments} + ''; + User = "vdr"; + CacheDirectory = "vdr"; + StateDirectory = "vdr"; + Restart = "on-failure"; + }; + }; + + users.users.vdr = { + group = "vdr"; + home = libDir; + }; + + users.groups.vdr = {}; + }; +} diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix index 412d57b27b82..602cd50d8f55 100644 --- a/nixos/modules/services/network-filesystems/ipfs.nix +++ b/nixos/modules/services/network-filesystems/ipfs.nix @@ -74,7 +74,7 @@ in { services.ipfs = { - enable = mkEnableOption "Interplanetary File System"; + enable = mkEnableOption "Interplanetary File System (WARNING: may cause severe network degredation)"; user = mkOption { type = types.str; diff --git a/nixos/modules/services/networking/shairport-sync.nix b/nixos/modules/services/networking/shairport-sync.nix index 36ecb74ffc95..90c0689dc7bf 100644 --- a/nixos/modules/services/networking/shairport-sync.nix +++ b/nixos/modules/services/networking/shairport-sync.nix @@ -27,7 +27,7 @@ in }; arguments = mkOption { - default = "-v -d pulse"; + default = "-v pulse"; description = '' Arguments to pass to the daemon. Defaults to a local pulseaudio server. diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix index 035029150c81..047321bd9495 100644 --- a/nixos/modules/services/x11/display-managers/default.nix +++ b/nixos/modules/services/x11/display-managers/default.nix @@ -194,9 +194,12 @@ let ${xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions '') cfg.displayManager.extraSessionFilePackages} - mkdir -p "$out/share/wayland-sessions" + ${concatMapStrings (pkg: '' - ${xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions + if test -d ${pkg}/share/wayland-sessions; then + mkdir -p "$out/share/wayland-sessions" + ${xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions + fi '') cfg.displayManager.extraSessionFilePackages} ''; diff --git a/nixos/modules/virtualisation/google-compute-config.nix b/nixos/modules/virtualisation/google-compute-config.nix index 1f8485b274fc..8c7331fe4d2b 100644 --- a/nixos/modules/virtualisation/google-compute-config.nix +++ b/nixos/modules/virtualisation/google-compute-config.nix @@ -65,33 +65,7 @@ in # GC has 1460 MTU networking.interfaces.eth0.mtu = 1460; - # allow the google-accounts-daemon to manage users - users.mutableUsers = true; - # and allow users to sudo without password - security.sudo.enable = true; - security.sudo.extraConfig = '' - %google-sudoers ALL=(ALL:ALL) NOPASSWD:ALL - ''; - - # NOTE: google-accounts tries to write to /etc/sudoers.d but the folder doesn't exist - # FIXME: not such file or directory on dynamic SSH provisioning - systemd.services.google-accounts-daemon = { - description = "Google Compute Engine Accounts Daemon"; - # This daemon creates dynamic users - enable = config.users.mutableUsers; - after = [ - "network.target" - "google-instance-setup.service" - "google-network-setup.service" - ]; - requires = ["network.target"]; - wantedBy = ["multi-user.target"]; - path = with pkgs; [ shadow ]; - serviceConfig = { - Type = "simple"; - ExecStart = "${gce}/bin/google_accounts_daemon --debug"; - }; - }; + security.googleOsLogin.enable = true; systemd.services.google-clock-skew-daemon = { description = "Google Compute Engine Clock Skew Daemon"; diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix index 66b253c230f1..ea8b92e94f01 100644 --- a/nixos/release-combined.nix +++ b/nixos/release-combined.nix @@ -5,7 +5,7 @@ { nixpkgs ? { outPath = (import ../lib).cleanSource ./..; revCount = 56789; shortRev = "gfedcba"; } , stableBranch ? false , supportedSystems ? [ "x86_64-linux" ] -, limitedSupportedSystems ? [ "i686-linux" ] +, limitedSupportedSystems ? [ "i686-linux" "aarch64-linux" ] }: let @@ -46,16 +46,20 @@ in rec { }; constituents = let - all = x: map (system: x.${system}) supportedSystems; + # Except for the given systems, return the system-specific constituent + except = systems: x: map (system: x.${system}) (pkgs.lib.subtractLists systems supportedSystems); + all = x: except [] x; in [ nixos.channel (all nixos.dummy) (all nixos.manual) - nixos.iso_minimal.x86_64-linux or [] - nixos.iso_minimal.i686-linux or [] nixos.iso_graphical.x86_64-linux or [] + nixos.iso_minimal.aarch64-linux or [] + nixos.iso_minimal.i686-linux or [] + nixos.iso_minimal.x86_64-linux or [] nixos.ova.x86_64-linux or [] + nixos.sd_image.aarch64-linux or [] #(all nixos.tests.containers) (all nixos.tests.containers-imperative) @@ -63,24 +67,24 @@ in rec { nixos.tests.chromium.x86_64-linux or [] (all nixos.tests.firefox) (all nixos.tests.firewall) - (all nixos.tests.gnome3) + (except ["aarch64-linux"] nixos.tests.gnome3) nixos.tests.installer.zfsroot.x86_64-linux or [] # ZFS is 64bit only - (all nixos.tests.installer.lvm) - (all nixos.tests.installer.luksroot) - (all nixos.tests.installer.separateBoot) - (all nixos.tests.installer.separateBootFat) - (all nixos.tests.installer.simple) - (all nixos.tests.installer.simpleLabels) - (all nixos.tests.installer.simpleProvided) - (all nixos.tests.installer.simpleUefiSystemdBoot) - (all nixos.tests.installer.swraid) - (all nixos.tests.installer.btrfsSimple) - (all nixos.tests.installer.btrfsSubvols) - (all nixos.tests.installer.btrfsSubvolDefault) - (all nixos.tests.boot.biosCdrom) - #(all nixos.tests.boot.biosUsb) # disabled due to issue #15690 - (all nixos.tests.boot.uefiCdrom) - (all nixos.tests.boot.uefiUsb) + (except ["aarch64-linux"] nixos.tests.installer.lvm) + (except ["aarch64-linux"] nixos.tests.installer.luksroot) + (except ["aarch64-linux"] nixos.tests.installer.separateBoot) + (except ["aarch64-linux"] nixos.tests.installer.separateBootFat) + (except ["aarch64-linux"] nixos.tests.installer.simple) + (except ["aarch64-linux"] nixos.tests.installer.simpleLabels) + (except ["aarch64-linux"] nixos.tests.installer.simpleProvided) + (except ["aarch64-linux"] nixos.tests.installer.simpleUefiSystemdBoot) + (except ["aarch64-linux"] nixos.tests.installer.swraid) + (except ["aarch64-linux"] nixos.tests.installer.btrfsSimple) + (except ["aarch64-linux"] nixos.tests.installer.btrfsSubvols) + (except ["aarch64-linux"] nixos.tests.installer.btrfsSubvolDefault) + (except ["aarch64-linux"] nixos.tests.boot.biosCdrom) + #(except ["aarch64-linux"] nixos.tests.boot.biosUsb) # disabled due to issue #15690 + (except ["aarch64-linux"] nixos.tests.boot.uefiCdrom) + (except ["aarch64-linux"] nixos.tests.boot.uefiUsb) (all nixos.tests.boot-stage1) (all nixos.tests.hibernate) nixos.tests.docker.x86_64-linux or [] @@ -132,7 +136,8 @@ in rec { nixpkgs.tarball (all allSupportedNixpkgs.emacs) - (all allSupportedNixpkgs.jdk) + # The currently available aarch64 JDK is unfree + (except ["aarch64-linux"] allSupportedNixpkgs.jdk) ]; }); diff --git a/nixos/release.nix b/nixos/release.nix index ec3f411e8e7b..e7952b33de6b 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -157,7 +157,7 @@ in rec { # A variant with a more recent (but possibly less stable) kernel # that might support more hardware. - iso_minimal_new_kernel = forMatchingSystems [ "x86_64-linux" ] (system: makeIso { + iso_minimal_new_kernel = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system: makeIso { module = ./modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix; type = "minimal-new-kernel"; inherit system; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index ca5015ded3e8..860262eeb6cd 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -39,6 +39,7 @@ in cfssl = handleTestOn ["x86_64-linux"] ./cfssl.nix {}; chromium = (handleTestOn ["x86_64-linux"] ./chromium.nix {}).stable or {}; cjdns = handleTest ./cjdns.nix {}; + clickhouse = handleTest ./clickhouse.nix {}; cloud-init = handleTest ./cloud-init.nix {}; codimd = handleTest ./codimd.nix {}; containers-bridge = handleTest ./containers-bridge.nix {}; @@ -80,6 +81,7 @@ in gitlab = handleTest ./gitlab.nix {}; gitolite = handleTest ./gitolite.nix {}; gjs = handleTest ./gjs.nix {}; + google-oslogin = handleTest ./google-oslogin {}; gnome3 = handleTestOn ["x86_64-linux"] ./gnome3.nix {}; # libsmbios is unsupported on aarch64 gnome3-gdm = handleTestOn ["x86_64-linux"] ./gnome3-gdm.nix {}; # libsmbios is unsupported on aarch64 gocd-agent = handleTest ./gocd-agent.nix {}; diff --git a/nixos/tests/clickhouse.nix b/nixos/tests/clickhouse.nix new file mode 100644 index 000000000000..7d835069ec4d --- /dev/null +++ b/nixos/tests/clickhouse.nix @@ -0,0 +1,25 @@ +import ./make-test.nix ({ pkgs, ... }: { + name = "clickhouse"; + meta.maintainers = with pkgs.stdenv.lib.maintainers; [ ma27 ]; + + machine = { + services.clickhouse.enable = true; + }; + + testScript = + let + # work around quote/substitution complexity by Nix, Perl, bash and SQL. + tableDDL = pkgs.writeText "ddl.sql" "CREATE TABLE `demo` (`value` FixedString(10)) engine = MergeTree PARTITION BY value ORDER BY tuple();"; + insertQuery = pkgs.writeText "insert.sql" "INSERT INTO `demo` (`value`) VALUES ('foo');"; + selectQuery = pkgs.writeText "select.sql" "SELECT * from `demo`"; + in + '' + $machine->start(); + $machine->waitForUnit("clickhouse.service"); + $machine->waitForOpenPort(9000); + + $machine->succeed("cat ${tableDDL} | clickhouse-client"); + $machine->succeed("cat ${insertQuery} | clickhouse-client"); + $machine->succeed("cat ${selectQuery} | clickhouse-client | grep foo"); + ''; +}) diff --git a/nixos/tests/google-oslogin/default.nix b/nixos/tests/google-oslogin/default.nix new file mode 100644 index 000000000000..3b84bba3f985 --- /dev/null +++ b/nixos/tests/google-oslogin/default.nix @@ -0,0 +1,52 @@ +import ../make-test.nix ({ pkgs, ... } : +let + inherit (import ./../ssh-keys.nix pkgs) + snakeOilPrivateKey snakeOilPublicKey; +in { + name = "google-oslogin"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ adisbladis flokli ]; + }; + + nodes = { + # the server provides both the the mocked google metadata server and the ssh server + server = (import ./server.nix pkgs); + + client = { ... }: {}; + }; + testScript = '' + startAll; + + $server->waitForUnit("mock-google-metadata.service"); + $server->waitForOpenPort(80); + + # mockserver should return a non-expired ssh key for both mockuser and mockadmin + $server->succeed('${pkgs.google-compute-engine-oslogin}/bin/google_authorized_keys mockuser | grep -q "${snakeOilPublicKey}"'); + $server->succeed('${pkgs.google-compute-engine-oslogin}/bin/google_authorized_keys mockadmin | grep -q "${snakeOilPublicKey}"'); + + # install snakeoil ssh key on the client + $client->succeed("mkdir -p ~/.ssh"); + $client->succeed("cat ${snakeOilPrivateKey} > ~/.ssh/id_snakeoil"); + $client->succeed("chmod 600 ~/.ssh/id_snakeoil"); + + $client->waitForUnit("network.target"); + $server->waitForUnit("sshd.service"); + + # we should not be able to connect as non-existing user + $client->fail("ssh -o User=ghost -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil 'true'"); + + # we should be able to connect as mockuser + $client->succeed("ssh -o User=mockuser -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil 'true'"); + # but we shouldn't be able to sudo + $client->fail("ssh -o User=mockuser -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil '/run/wrappers/bin/sudo /run/current-system/sw/bin/id' | grep -q 'root'"); + + # we should also be able to log in as mockadmin + $client->succeed("ssh -o User=mockadmin -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil 'true'"); + # pam_oslogin_admin.so should now have generated a sudoers file + $server->succeed("find /run/google-sudoers.d | grep -q '/run/google-sudoers.d/mockadmin'"); + + # and we should be able to sudo + $client->succeed("ssh -o User=mockadmin -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil '/run/wrappers/bin/sudo /run/current-system/sw/bin/id' | grep -q 'root'"); + ''; + }) + diff --git a/nixos/tests/google-oslogin/server.nix b/nixos/tests/google-oslogin/server.nix new file mode 100644 index 000000000000..fdb7141da317 --- /dev/null +++ b/nixos/tests/google-oslogin/server.nix @@ -0,0 +1,29 @@ +{ pkgs, ... }: +let + inherit (import ./../ssh-keys.nix pkgs) + snakeOilPrivateKey snakeOilPublicKey; +in { + networking.firewall.allowedTCPPorts = [ 80 ]; + + systemd.services.mock-google-metadata = { + description = "Mock Google metadata service"; + serviceConfig.Type = "simple"; + serviceConfig.ExecStart = "${pkgs.python3}/bin/python ${./server.py}"; + environment = { + SNAKEOIL_PUBLIC_KEY = snakeOilPublicKey; + }; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + }; + + services.openssh.enable = true; + services.openssh.challengeResponseAuthentication = false; + services.openssh.passwordAuthentication = false; + + security.googleOsLogin.enable = true; + + # Mock google service + networking.extraHosts = '' + 127.0.0.1 metadata.google.internal + ''; +} diff --git a/nixos/tests/google-oslogin/server.py b/nixos/tests/google-oslogin/server.py new file mode 100644 index 000000000000..bfc527cb97d3 --- /dev/null +++ b/nixos/tests/google-oslogin/server.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +import json +import sys +import time +import os +import hashlib +import base64 + +from http.server import BaseHTTPRequestHandler, HTTPServer +from typing import Dict + +SNAKEOIL_PUBLIC_KEY = os.environ['SNAKEOIL_PUBLIC_KEY'] + + +def w(msg): + sys.stderr.write(f"{msg}\n") + sys.stderr.flush() + + +def gen_fingerprint(pubkey): + decoded_key = base64.b64decode(pubkey.encode("ascii").split()[1]) + return hashlib.sha256(decoded_key).hexdigest() + +def gen_email(username): + """username seems to be a 21 characters long number string, so mimic that in a reproducible way""" + return str(int(hashlib.sha256(username.encode()).hexdigest(), 16))[0:21] + +def gen_mockuser(username: str, uid: str, gid: str, home_directory: str, snakeoil_pubkey: str) -> Dict: + snakeoil_pubkey_fingerprint = gen_fingerprint(snakeoil_pubkey) + # seems to be a 21 characters long numberstring, so mimic that in a reproducible way + email = gen_email(username) + return { + "loginProfiles": [ + { + "name": email, + "posixAccounts": [ + { + "primary": True, + "username": username, + "uid": uid, + "gid": gid, + "homeDirectory": home_directory, + "operatingSystemType": "LINUX" + } + ], + "sshPublicKeys": { + snakeoil_pubkey_fingerprint: { + "key": snakeoil_pubkey, + "expirationTimeUsec": str((time.time() + 600) * 1000000), # 10 minutes in the future + "fingerprint": snakeoil_pubkey_fingerprint + } + } + } + ] + } + + +class ReqHandler(BaseHTTPRequestHandler): + def _send_json_ok(self, data): + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + out = json.dumps(data).encode() + w(out) + self.wfile.write(out) + + def do_GET(self): + p = str(self.path) + # mockuser and mockadmin are allowed to login, both use the same snakeoil public key + if p == '/computeMetadata/v1/oslogin/users?username=mockuser' \ + or p == '/computeMetadata/v1/oslogin/users?uid=1009719690': + self._send_json_ok(gen_mockuser(username='mockuser', uid='1009719690', gid='1009719690', + home_directory='/home/mockuser', snakeoil_pubkey=SNAKEOIL_PUBLIC_KEY)) + elif p == '/computeMetadata/v1/oslogin/users?username=mockadmin' \ + or p == '/computeMetadata/v1/oslogin/users?uid=1009719691': + self._send_json_ok(gen_mockuser(username='mockadmin', uid='1009719691', gid='1009719691', + home_directory='/home/mockadmin', snakeoil_pubkey=SNAKEOIL_PUBLIC_KEY)) + + # mockuser is allowed to login + elif p == f"/computeMetadata/v1/oslogin/authorize?email={gen_email('mockuser')}&policy=login": + self._send_json_ok({'success': True}) + + # mockadmin may also become root + elif p == f"/computeMetadata/v1/oslogin/authorize?email={gen_email('mockadmin')}&policy=login" or p == f"/computeMetadata/v1/oslogin/authorize?email={gen_email('mockadmin')}&policy=adminLogin": + self._send_json_ok({'success': True}) + else: + sys.stderr.write(f"Unhandled path: {p}\n") + sys.stderr.flush() + self.send_response(501) + self.end_headers() + self.wfile.write(b'') + + +if __name__ == '__main__': + s = HTTPServer(('0.0.0.0', 80), ReqHandler) + s.serve_forever() diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix index 7627bb07901d..73c1e71eb516 100644 --- a/nixos/tests/home-assistant.nix +++ b/nixos/tests/home-assistant.nix @@ -4,6 +4,7 @@ let configDir = "/var/lib/foobar"; apiPassword = "some_secret"; mqttPassword = "another_secret"; + hassCli = "hass-cli --server http://hass:8123 --password '${apiPassword}'"; in { name = "home-assistant"; @@ -16,7 +17,7 @@ in { { pkgs, ... }: { environment.systemPackages = with pkgs; [ - mosquitto + mosquitto home-assistant-cli ]; services.home-assistant = { inherit configDir; @@ -71,6 +72,11 @@ in { $hass->waitUntilSucceeds("mosquitto_pub -V mqttv311 -t home-assistant/test -u homeassistant -P '${mqttPassword}' -m let_there_be_light"); $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"on\"'"); + # Toggle a binary sensor using hass-cli + $hass->succeed("${hassCli} entity get binary_sensor.mqtt_binary_sensor | grep -qF '\"state\": \"on\"'"); + $hass->succeed("${hassCli} entity edit binary_sensor.mqtt_binary_sensor --json='{\"state\": \"off\"}'"); + $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"off\"'"); + # Print log to ease debugging my $log = $hass->succeed("cat ${configDir}/home-assistant.log"); print "\n### home-assistant.log ###\n"; |