From 0f46188ca10c2112e4af826233d203165ead17f4 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Wed, 12 Dec 2018 13:59:08 +0100 Subject: nixos/tests: add google-oslogin test --- nixos/tests/all-tests.nix | 1 + nixos/tests/google-oslogin/default.nix | 52 ++++++++++++++++++ nixos/tests/google-oslogin/server.nix | 29 ++++++++++ nixos/tests/google-oslogin/server.py | 96 ++++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 nixos/tests/google-oslogin/default.nix create mode 100644 nixos/tests/google-oslogin/server.nix create mode 100644 nixos/tests/google-oslogin/server.py (limited to 'nixos') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 1e8e2213fac0..860262eeb6cd 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -81,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/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() -- cgit 1.4.1