From c08e95bdb8c2defe352f95711824ad1fa980a2d4 Mon Sep 17 00:00:00 2001 From: Manuel Bluhm Date: Mon, 25 Nov 2024 12:52:07 +0400 Subject: [PATCH] Enable TLS - add tls module for certificate generation, which can be enabled where needed - add TLS certificate generation script, allowing multiple IP entries - add key copy service to allow user access in /run/givc, by default keys and certificates are stored in /etc/givc with root access only - remove TLS data name dependencies Signed-off-by: Manuel Bluhm --- .github/pull_request_template.md | 2 +- flake.lock | 12 +-- flake.nix | 1 + internal/pkgs/localelistener/transport.go | 2 +- internal/pkgs/types/types.go | 6 -- nixos/modules/appvm.nix | 19 ++++ nixos/modules/definitions.nix | 8 +- nixos/modules/host.nix | 42 ++++---- nixos/modules/sysvm.nix | 14 +++ nixos/modules/tls.nix | 109 +++++++++++++++++++ nixos/packages/givc-gen-certs.nix | 126 +++++++++++++--------- nixos/tests/admin.nix | 6 +- nixos/tests/dbus.nix | 6 +- nixos/tests/netvm.nix | 6 +- 14 files changed, 263 insertions(+), 96 deletions(-) create mode 100644 nixos/modules/tls.nix diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f8051f5..b4fbd04 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -21,7 +21,7 @@ Please check, [X], to all that applies. Leave [ ] if an item does not apply but - [ ] Commits are squashed into relevant entities - avoid a lot of minimal dev time commits in the PR - [ ] [Contribution guidelines](https://github.com/tiiuae/ghaf/blob/main/CONTRIBUTING.md) followed - [ ] Test procedure added to nixos/tests -- [ ] Author has run `nix flake check --accept-flake-config` and it passes +- [ ] Author has run `nix flake check` and it passes - [ ] All automatic Github Action checks pass - see [actions](https://github.com/tiiuae/ghaf-givc/actions) - [ ] Author has added reviewers and removed PR draft status diff --git a/flake.lock b/flake.lock index c13405c..ae1e4a6 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "crane": { "locked": { - "lastModified": 1731974733, - "narHash": "sha256-enYSSZVVl15FI5p+0Y5/Ckf5DZAvXe6fBrHxyhA/njc=", + "lastModified": 1732407143, + "narHash": "sha256-qJOGDT6PACoX+GbNH2PPx2ievlmtT1NVeTB80EkRLys=", "owner": "ipetkov", "repo": "crane", - "rev": "3cb338ce81076ce5e461cf77f7824476addb0e1c", + "rev": "f2b4b472983817021d9ffb60838b2b36b9376b20", "type": "github" }, "original": { @@ -165,11 +165,11 @@ ] }, "locked": { - "lastModified": 1732187120, - "narHash": "sha256-XdW2mYXvPHYtZ8oQqO3tRYtxx7kI0Hs3NU64IwAtD68=", + "lastModified": 1732292307, + "narHash": "sha256-5WSng844vXt8uytT5djmqBCkopyle6ciFgteuA9bJpw=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "37f8f47cb618eddee0c0dd31a582b1cd3013c7f6", + "rev": "705df92694af7093dfbb27109ce16d828a79155f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index bf7b41b..3ab23a7 100644 --- a/flake.nix +++ b/flake.nix @@ -84,6 +84,7 @@ sysvm = import ./nixos/modules/sysvm.nix { inherit self; }; appvm = import ./nixos/modules/appvm.nix { inherit self; }; dbus = import ./nixos/modules/dbus.nix { inherit self; }; + tls = import ./nixos/modules/tls.nix { inherit self; }; }; # Overlays diff --git a/internal/pkgs/localelistener/transport.go b/internal/pkgs/localelistener/transport.go index 6a8533a..569aa5a 100644 --- a/internal/pkgs/localelistener/transport.go +++ b/internal/pkgs/localelistener/transport.go @@ -57,7 +57,7 @@ func (s *LocaleServer) TimezoneSet(ctx context.Context, req *locale_api.Timezone err := s.Controller.SetTimezone(context.Background(), req.Timezone) if err != nil { - log.Infof("[SetLocale] Error setting timezone: %v\n", err) + log.Infof("[SetTimezone] Error setting timezone: %v\n", err) return nil, fmt.Errorf("Cannot set timezone") } diff --git a/internal/pkgs/types/types.go b/internal/pkgs/types/types.go index 56cccb7..57085ac 100644 --- a/internal/pkgs/types/types.go +++ b/internal/pkgs/types/types.go @@ -85,12 +85,6 @@ const ( UNIT_TYPE_APPVM_APP ) -const ( - PROXY_NULL uint32 = 0 - PROXY_SERVER_CONNECTED uint32 = 1 - PROXY_CLIENT_CONNECTED uint32 = 2 -) - const ( APP_ARG_FLAG = "flag" APP_ARG_URL = "url" diff --git a/nixos/modules/appvm.nix b/nixos/modules/appvm.nix index 994f5da..20da6ea 100644 --- a/nixos/modules/appvm.nix +++ b/nixos/modules/appvm.nix @@ -116,6 +116,25 @@ in ''; }; + # Copy givc keys and certificates for user access + systemd.services.givc-user-key-setup = { + description = "Prepare givc keys and certificates for user access"; + enable = true; + wantedBy = [ "local-fs.target" ]; + after = [ "local-fs.target" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.rsync}/bin/rsync -r --chown=root:users --chmod=g+rx /etc/givc /run"; + Restart = "no"; + }; + }; + givc.appvm.tls = { + caCertPath = "/run/givc/ca-cert.pem"; + certPath = "/run/givc/cert.pem"; + keyPath = "/run/givc/key.pem"; + }; + + # User agent systemd.user.services."givc-${cfg.agent.name}" = { description = "GIVC remote service manager for application VMs"; enable = true; diff --git a/nixos/modules/definitions.nix b/nixos/modules/definitions.nix index 83cc3ac..ed53e13 100644 --- a/nixos/modules/definitions.nix +++ b/nixos/modules/definitions.nix @@ -8,7 +8,7 @@ let transportSubmodule = types.submodule { options = { name = mkOption { - description = "Network host and TLS name"; + description = "Identifier for network, host, and/or TLS name"; type = types.str; default = "localhost"; }; @@ -89,17 +89,17 @@ in caCertPath = mkOption { description = "Path to the CA certificate file."; type = types.str; - default = ""; + default = "/etc/givc/ca-cert.pem"; }; certPath = mkOption { description = "Path to the service certificate file."; type = types.str; - default = ""; + default = "/etc/givc/cert.pem"; }; keyPath = mkOption { description = "Path to the service key file."; type = types.str; - default = ""; + default = "/etc/givc/key.pem"; }; }; }; diff --git a/nixos/modules/host.nix b/nixos/modules/host.nix index 1b0d3ce..e47c871 100644 --- a/nixos/modules/host.nix +++ b/nixos/modules/host.nix @@ -77,26 +77,28 @@ in } ]; - systemd.services."givc-${cfg.agent.name}" = { - description = "GIVC remote service manager for the host."; - enable = true; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "exec"; - ExecStart = "${givc-agent}/bin/givc-agent"; - Restart = "always"; - RestartSec = 1; - }; - environment = { - "AGENT" = "${toJSON cfg.agent}"; - "DEBUG" = "${trivial.boolToString cfg.debug}"; - "TYPE" = "0"; - "SUBTYPE" = "1"; - "SERVICES" = "${concatStringsSep " " cfg.services}"; - "ADMIN_SERVER" = "${toJSON cfg.admin}"; - "TLS_CONFIG" = "${toJSON cfg.tls}"; + systemd.services = { + "givc-${cfg.agent.name}" = { + description = "GIVC remote service manager for the host."; + enable = true; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "exec"; + ExecStart = "${givc-agent}/bin/givc-agent"; + Restart = "always"; + RestartSec = 1; + }; + environment = { + "AGENT" = "${toJSON cfg.agent}"; + "DEBUG" = "${trivial.boolToString cfg.debug}"; + "TYPE" = "0"; + "SUBTYPE" = "1"; + "SERVICES" = "${concatStringsSep " " cfg.services}"; + "ADMIN_SERVER" = "${toJSON cfg.admin}"; + "TLS_CONFIG" = "${toJSON cfg.tls}"; + }; }; }; networking.firewall.allowedTCPPorts = diff --git a/nixos/modules/sysvm.nix b/nixos/modules/sysvm.nix index 4efbed8..fa1f167 100644 --- a/nixos/modules/sysvm.nix +++ b/nixos/modules/sysvm.nix @@ -21,6 +21,7 @@ let concatStringsSep optionalString optionals + optionalAttrs ; inherit (builtins) toJSON; inherit (import ./definitions.nix { inherit config lib; }) @@ -32,6 +33,7 @@ in { options.givc.sysvm = { enable = mkEnableOption "Enable givc-sysvm module."; + enableUserTlsAccess = mkEnableOption "Enable users to access TLS keys to run client."; agent = mkOption { description = '' @@ -134,6 +136,18 @@ in wantedBy = [ "network-online.target" ]; }; + systemd.services.givc-user-key-setup = optionalAttrs cfg.enableUserTlsAccess { + description = "Prepare givc keys and certificates for user access"; + enable = true; + wantedBy = [ "local-fs.target" ]; + after = [ "local-fs.target" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.rsync}/bin/rsync -r --chown=root:users --chmod=g+rx /etc/givc /run"; + Restart = "no"; + }; + }; + systemd.services."givc-${cfg.agent.name}" = { description = "GIVC remote service manager for system VMs"; enable = true; diff --git a/nixos/modules/tls.nix b/nixos/modules/tls.nix new file mode 100644 index 0000000..49cacc2 --- /dev/null +++ b/nixos/modules/tls.nix @@ -0,0 +1,109 @@ +# Copyright 2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ self }: +{ + config, + pkgs, + lib, + ... +}: +let + cfg = config.givc.tls; + inherit (lib) + mkOption + mkEnableOption + mkIf + types + ; + inherit (import ./definitions.nix { inherit config lib; }) + transportSubmodule + ; +in +{ + options.givc.tls = { + enable = mkEnableOption "Enable givc-tls module. This module generates keys and certificates for givc's mTLS in /etc/givc."; + + agents = mkOption { + description = "List of agents to generate TLS certificates for. Requires a list of 'transportSubmodule'."; + type = types.listOf transportSubmodule; + }; + + adminTlsName = mkOption { + description = "TLS host name of admin server."; + type = types.str; + }; + + adminAddresses = mkOption { + description = "List of addresses for the admin service to listen on. Requires a list of 'transportSubmodule'."; + type = types.listOf transportSubmodule; + }; + + generatorHostName = mkOption { + description = "Host name of the certificate generator. This will prevent to write the TLS data into the storage path."; + type = types.str; + }; + + storagePath = mkOption { + description = "Storage path for generated keys and certificates. Will use subdirectories for each agent by name."; + type = types.str; + }; + + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.agents != [ ]; + message = "The TLS module requires a list of agents to generate keys and certificates for."; + } + { + assertion = cfg.adminTlsName != ""; + message = "The TLS module requires a TLS host name for the admin server."; + } + { + assertion = cfg.adminAddresses != [ ]; + message = "The TLS module requires a list of addresses for the admin service to listen on."; + } + { + assertion = cfg.generatorHostName != ""; + message = "The TLS module requires a host name for the certificate generator."; + } + { + assertion = cfg.storagePath != ""; + message = "The TLS module requires a storage path for generated keys and certificates."; + } + ]; + + systemd.services = { + givc-key-setup = + let + givcCertGenerator = pkgs.callPackage ../packages/givc-gen-certs.nix { + inherit lib pkgs; + inherit (cfg) + agents + adminTlsName + adminAddresses + generatorHostName + ; + }; + in + { + enable = true; + description = "Generate keys and certificates for givc"; + path = [ givcCertGenerator ]; + wantedBy = [ "local-fs.target" ]; + after = [ "local-fs.target" ]; + unitConfig.ConditionPathExists = "!/etc/givc/tls.lock"; + serviceConfig = { + Type = "notify"; + NotifyAccess = "all"; + Restart = "no"; + StandardOutput = "journal"; + StandardError = "journal"; + ExecStart = "${givcCertGenerator}/bin/givc-gen-certs ${cfg.storagePath}"; + ExecStartPost = "${pkgs.coreutils}/bin/install -m 000 /dev/null /etc/givc/tls.lock"; + }; + }; + }; + }; +} diff --git a/nixos/packages/givc-gen-certs.nix b/nixos/packages/givc-gen-certs.nix index a64955a..184d08a 100644 --- a/nixos/packages/givc-gen-certs.nix +++ b/nixos/packages/givc-gen-certs.nix @@ -1,65 +1,93 @@ -{ pkgs }: +{ + lib, + pkgs, + agents, + adminTlsName, + adminAddresses, + generatorHostName, +}: pkgs.writeShellScriptBin "givc-gen-certs" '' set -xeuo pipefail if [ $# -eq 1 ]; then - target="$1" + STORAGE_DIR="$1" else - echo "Usage: $0 " >&2 + echo "Usage: $0 " >&2 exit 1 fi - acl_prefix="otherName.1:1.2.3.4.5.6;UTF8" + # acl_prefix="otherName.1:1.2.3.4.5.6;UTF8" + + # Parameters + VALIDITY=3650 + EXT_KEY_USAGE="extendedKeyUsage=serverAuth,clientAuth" + CA_NAME="GivcCA" + CA_CONSTRAINTS="basicConstraints=critical,CA:true,pathlen:1" + CA_DIRECTORY="/tmp/ca" # Function to create key/cert based on IP and/or DNS gen_cert(){ + + # Initialize name and storage path name="$1" - path="$target"/"$name" + case "$name" in + ${generatorHostName}) + path="/etc/givc/" + ;; + *) + path="''${STORAGE_DIR}/''${name}/etc/givc/" + esac + [[ -d "$path" ]] && rm -r "$path" mkdir -p "$path" - usage="extendedKeyUsage=serverAuth,clientAuth" - if [ $# -ge 2 ]; then - ip1="$2" - alttext="subjectAltName=IP.1:''${ip1},DNS.1:''${name}" - else - alttext="subjectAltName=DNS.1:''${name}" - fi - ${pkgs.openssl}/bin/openssl genpkey -algorithm ED25519 -out "$path"/"$name"-key.pem - ${pkgs.openssl}/bin/openssl req -new -key "$path"/"$name"-key.pem -out "$path"/"$name"-csr.pem -subj "/CN=''${name}" -addext "$alttext" -addext "$usage" - ${pkgs.openssl}/bin/openssl x509 -req -in "$path"/"$name"-csr.pem -CA $ca_dir/ca-cert.pem -CAkey $ca_dir/ca-key.pem -CAcreateserial -out "$path"/"$name"-cert.pem -extfile <(printf "%s" "$alttext") -days $VALIDITY - cp $ca_dir/ca-cert.pem "$path"/ca-cert.pem - if [ "$(whoami)" == "root" ]; then - if [ "$name" == "ghaf-host.ghaf" ]; then - chown -R root:root "$path" - chmod -R 400 "$path" - else - chown -R microvm:kvm "$path" - chmod -R 770 "$path" - fi - rm "$path"/"$name"-csr.pem - else - echo "Insecure mode! Certificate for ''${name} have wrong permissions. Call as root on host to be secure" - fi + + # Initialize DNS and IP entry + alttext="subjectAltName=DNS.1:''${name}" + shift + count=1 + while [[ $# -gt 0 ]]; do + case "$1" in + *) + alttext+=",IP.''$count:''$1" + count=$((count+1)) + shift + ;; + esac + done + + # Generate and sign key-cert pair + ${pkgs.openssl}/bin/openssl genpkey -algorithm ED25519 -out "$path"/key.pem + ${pkgs.openssl}/bin/openssl req -new -key "$path"/key.pem -out "$path"/"$name"-csr.pem -subj "/CN=''${name}" -addext "$alttext" -addext "$EXT_KEY_USAGE" + ${pkgs.openssl}/bin/openssl x509 -req -in "$path"/"$name"-csr.pem -CA $CA_DIRECTORY/ca-cert.pem -CAkey $CA_DIRECTORY/ca-key.pem -CAcreateserial -out "$path"/cert.pem -extfile <(printf "%s" "$alttext") -days $VALIDITY + + # Copy CA certificate + cp $CA_DIRECTORY/ca-cert.pem "$path"/ca-cert.pem + + # Set permissions + chown -R root:root "$path" + chmod -R 500 "$path" + + # Cleanup + rm "$path"/"$name"-csr.pem } + # Create CA - VALIDITY=3650 - CONSTRAINTS="basicConstraints=critical,CA:true,pathlen:1" - ca_dir="$target/ca.ghaf" - mkdir -p $ca_dir - ${pkgs.openssl}/bin/openssl genpkey -algorithm ED25519 -out $ca_dir/ca-key.pem - ${pkgs.openssl}/bin/openssl req -x509 -new -key $ca_dir/ca-key.pem -out $ca_dir/ca-cert.pem -subj "/CN=GivcCA" -addext $CONSTRAINTS -days $VALIDITY - if [ "$(whoami)" == "root" ]; then - chmod -R 400 $ca_dir - fi - # Generate keys/certificates - gen_cert "ghaf-host" "192.168.101.2" "$acl_prefix:host,acl_prefix:agent" - gen_cert "admin-vm" "192.168.101.10" - gen_cert "net-vm" "192.168.101.1" - gen_cert "gui-vm" "192.168.101.3" - gen_cert "ids-vm" "192.168.101.4" - gen_cert "audio-vm" "192.168.101.5" - gen_cert "element-vm" "192.168.100.253" - gen_cert "chromium-vm" - gen_cert "gala-vm" - gen_cert "zathura-vm" - gen_cert "appflowy-vm" + mkdir -p $CA_DIRECTORY + ${pkgs.openssl}/bin/openssl genpkey -algorithm ED25519 -out $CA_DIRECTORY/ca-key.pem + ${pkgs.openssl}/bin/openssl req -x509 -new -key $CA_DIRECTORY/ca-key.pem -out $CA_DIRECTORY/ca-cert.pem -subj "/CN=$CA_NAME" -addext $CA_CONSTRAINTS -days $VALIDITY + chmod -R 400 $CA_DIRECTORY + + # Generate agent keys/certificates + ${lib.concatStringsSep "\n" ( + map (entry: "gen_cert ${entry.name} ${entry.addr}") ( + lib.filter (agent: agent.name != adminTlsName) agents + ) + )} + + # Generate admin key/certificate + gen_cert ${adminTlsName} ${lib.concatMapStringsSep " " (e: e.addr) adminAddresses} + + # Cleanup + rm -r $CA_DIRECTORY + + /run/current-system/systemd/bin/systemd-notify --ready '' diff --git a/nixos/tests/admin.nix b/nixos/tests/admin.nix index 1a173c9..a2bb150 100644 --- a/nixos/tests/admin.nix +++ b/nixos/tests/admin.nix @@ -29,9 +29,9 @@ let admin = lib.head adminConfig.addresses; mkTls = name: { enable = tls; - caCertPath = "${snakeoil}/${name}/ca-cert.pem"; - certPath = "${snakeoil}/${name}/${name}-cert.pem"; - keyPath = "${snakeoil}/${name}/${name}-key.pem"; + caCertPath = lib.mkForce "${snakeoil}/${name}/ca-cert.pem"; + certPath = lib.mkForce "${snakeoil}/${name}/${name}-cert.pem"; + keyPath = lib.mkForce "${snakeoil}/${name}/${name}-key.pem"; }; in { diff --git a/nixos/tests/dbus.nix b/nixos/tests/dbus.nix index e874209..e555b65 100644 --- a/nixos/tests/dbus.nix +++ b/nixos/tests/dbus.nix @@ -30,9 +30,9 @@ let admin = lib.head adminConfig.addresses; mkTls = name: { enable = tls; - caCertPath = "${snakeoil}/${name}/ca-cert.pem"; - certPath = "${snakeoil}/${name}/${name}-cert.pem"; - keyPath = "${snakeoil}/${name}/${name}-key.pem"; + caCertPath = lib.mkForce "${snakeoil}/${name}/ca-cert.pem"; + certPath = lib.mkForce "${snakeoil}/${name}/${name}-cert.pem"; + keyPath = lib.mkForce "${snakeoil}/${name}/${name}-key.pem"; }; in { diff --git a/nixos/tests/netvm.nix b/nixos/tests/netvm.nix index a4d8fed..eff22ea 100644 --- a/nixos/tests/netvm.nix +++ b/nixos/tests/netvm.nix @@ -27,9 +27,9 @@ let admin = lib.head adminConfig.addresses; mkTls = name: { enable = tls; - caCertPath = "${snakeoil}/${name}/ca-cert.pem"; - certPath = "${snakeoil}/${name}/${name}-cert.pem"; - keyPath = "${snakeoil}/${name}/${name}-key.pem"; + caCertPath = lib.mkForce "${snakeoil}/${name}/ca-cert.pem"; + certPath = lib.mkForce "${snakeoil}/${name}/${name}-cert.pem"; + keyPath = lib.mkForce "${snakeoil}/${name}/${name}-key.pem"; }; in {