From 8c7feb2e4d7972d592f3c01ff6263c8d4db8f474 Mon Sep 17 00:00:00 2001 From: Yuri Nesterov Date: Fri, 29 Nov 2024 16:43:51 +0200 Subject: [PATCH] Refactor VM and application definitions Signed-off-by: Yuri Nesterov --- modules/common/services/default.nix | 1 - .../microvm/virtualization/microvm/appvm.nix | 106 ++++- .../microvm/virtualization/microvm/guivm.nix | 55 +++ .../virtualization/microvm/modules.nix | 6 - modules/reference/appvms/appflowy.nix | 33 -- modules/reference/appvms/business.nix | 434 ++++++------------ modules/reference/appvms/chromium.nix | 59 ++- modules/reference/appvms/comms.nix | 116 ++--- modules/reference/appvms/gala.nix | 23 +- modules/reference/appvms/google-chrome.nix | 59 ++- modules/reference/appvms/zathura.nix | 41 +- modules/reference/desktop/applications.nix | 83 ++++ modules/reference/desktop/default.nix | 7 + modules/reference/profiles/laptop-x86.nix | 3 + modules/reference/programs/chromium.nix | 35 +- modules/reference/programs/default.nix | 3 +- .../reference/programs/element-desktop.nix | 60 +++ modules/reference/programs/google-chrome.nix | 64 ++- .../reference/services/firewall/firewall.nix | 71 +++ modules/reference/services/pac/pac.nix | 154 +++++++ .../services/proxy-server/3proxy-config.nix | 7 +- 21 files changed, 861 insertions(+), 559 deletions(-) delete mode 100644 modules/reference/appvms/appflowy.nix create mode 100644 modules/reference/desktop/applications.nix create mode 100644 modules/reference/desktop/default.nix create mode 100644 modules/reference/programs/element-desktop.nix create mode 100644 modules/reference/services/firewall/firewall.nix create mode 100644 modules/reference/services/pac/pac.nix diff --git a/modules/common/services/default.nix b/modules/common/services/default.nix index c5bff9b7a..18f920387 100644 --- a/modules/common/services/default.nix +++ b/modules/common/services/default.nix @@ -6,7 +6,6 @@ ./audio.nix ./wifi.nix ./firmware.nix - ./desktop.nix ./xdgopener.nix ./xdghandlers.nix ./namespaces.nix diff --git a/modules/microvm/virtualization/microvm/appvm.nix b/modules/microvm/virtualization/microvm/appvm.nix index c0d40dc14..6e912a174 100644 --- a/modules/microvm/virtualization/microvm/appvm.nix +++ b/modules/microvm/virtualization/microvm/appvm.nix @@ -13,20 +13,35 @@ let configHost = config; cfg = config.ghaf.virtualization.microvm.appvm; - sshKeysHelper = pkgs.callPackage ../../../../packages/ssh-keys-helper { - inherit pkgs; - config = configHost; - }; - makeVm = { vm, vmIndex }: let vmName = "${vm.name}-vm"; cid = if vm.cid > 0 then vm.cid else cfg.vsockBaseCID + vmIndex; + # A list of applications for the GIVC service + givcApplications = map (app: { + name = app.givcName; + command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/${app.command}"; + args = app.givcArgs; + }) vm.applications; + # Packages and extra modules from all applications defined in the appvm + appPackages = builtins.concatLists (map (app: app.packages) vm.applications); + appExtraModules = builtins.concatLists (map (app: app.extraModules) vm.applications); + sshKeysHelper = pkgs.callPackage ../../../../packages/ssh-keys-helper { + inherit pkgs; + config = configHost; + }; appvmConfiguration = { imports = [ inputs.impermanence.nixosModules.impermanence inputs.self.nixosModules.givc-appvm + { + ghaf.givc.appvm = { + enable = true; + name = lib.mkForce vmName; + applications = givcApplications; + }; + } (import ./common/vm-networking.nix { inherit config lib vmName; inherit (vm) macAddress; @@ -126,7 +141,7 @@ let pkgs.tpm2-tools pkgs.opensc pkgs.givc-cli - ]; + ] ++ vm.packages ++ appPackages; security.tpm2 = { enable = true; @@ -137,6 +152,8 @@ let lib.mkIf configHost.ghaf.virtualization.microvm.idsvm.mitmproxy.enable [ ./idsvm/mitmproxy/mitmproxy-ca/mitmproxy-ca-cert.pem ]; + time.timeZone = configHost.time.timeZone; + microvm = { optimize.enable = false; mem = vm.ramMb; @@ -197,19 +214,9 @@ let { autostart = true; config = appvmConfiguration // { - imports = - appvmConfiguration.imports - ++ cfg.extraModules - ++ vm.extraModules - ++ [ { environment.systemPackages = vm.packages; } ]; + imports = appvmConfiguration.imports ++ cfg.extraModules ++ vm.extraModules ++ appExtraModules; }; }; - - # Host service dependencies - after = optional config.ghaf.services.audio.enable "pulseaudio.service"; - requires = after; - # Sleep appvms to give gui-vm time to start - serviceConfig.ExecStartPre = "/bin/sh -c 'sleep 8'"; in { options.ghaf.virtualization.microvm.appvm = { @@ -218,7 +225,7 @@ in description = '' List of AppVMs to be created ''; - type = lib.types.listOf ( + type = types.listOf ( types.submodule { options = { name = mkOption { @@ -227,6 +234,62 @@ in ''; type = types.str; }; + applications = mkOption { + description = '' + Applications to include in the AppVM + ''; + type = types.listOf ( + types.submodule ( + { config, lib, ... }: + { + options = rec { + name = mkOption { + type = types.str; + description = "The name of the application"; + }; + description = mkOption { + type = types.str; + description = "A brief description of the application"; + }; + packages = mkOption { + type = types.listOf types.package; + description = "A list of packages required for the application"; + default = [ ]; + }; + icon = mkOption { + type = types.str; + description = "Application icon"; + default = null; + }; + command = mkOption { + type = types.str; + description = "The command to run the application"; + default = null; + }; + extraModules = mkOption { + description = "Additional modules required for the application"; + type = types.listOf types.attrs; + default = [ ]; + }; + givcName = mkOption { + description = "GIVC name for the application"; + type = types.str; + }; + givcArgs = mkOption { + description = "A list of GIVC arguments for the application"; + type = types.listOf types.str; + default = [ ]; + }; + }; + config = { + # Create a default GIVC name for the application + givcName = lib.mkDefault (lib.strings.toLower (lib.replaceStrings [ " " ] [ "-" ] config.name)); + }; + } + ) + ); + default = [ ]; + }; packages = mkOption { description = '' Packages that are included into the AppVM @@ -356,6 +419,7 @@ in ) cfg.vms; in lib.mkIf cfg.enable { + # Define microvms for each AppVM configuration microvm.vms = let vms = lib.imap0 (vmIndex: vm: { "${vm.name}-vm" = makeVm { inherit vmIndex vm; }; }) cfg.vms; @@ -367,7 +431,11 @@ in let serviceDependencies = map (vm: { "microvm@${vm.name}-vm" = { - inherit after requires serviceConfig; + # Host service dependencies + after = optional config.ghaf.services.audio.enable "pulseaudio.service"; + requires = optional config.ghaf.services.audio.enable "pulseaudio.service"; + # Sleep appvms to give gui-vm time to start + serviceConfig.ExecStartPre = "/bin/sh -c 'sleep 8'"; }; "${vm.name}-swtpm" = makeSwtpmService { inherit vm; }; }) cfg.vms; diff --git a/modules/microvm/virtualization/microvm/guivm.nix b/modules/microvm/virtualization/microvm/guivm.nix index 7ddedd443..9dd1759d5 100644 --- a/modules/microvm/virtualization/microvm/guivm.nix +++ b/modules/microvm/virtualization/microvm/guivm.nix @@ -42,6 +42,27 @@ let ${lib.optionalString config.ghaf.givc.enableTls "--key /run/givc/ghaf-host-key.pem"} ${lib.optionalString (!config.ghaf.givc.enableTls) "--notls"} ''; + # A list of applications from all AppVMs + virtualApps = lib.lists.concatMap ( + vm: map (app: app // { vmName = "${vm.name}-vm"; }) vm.applications + ) config.ghaf.virtualization.microvm.appvm.vms; + + # Launchers for all virtualized applications that run in AppVMs + virtualLaunchers = map (app: rec { + inherit (app) name; + inherit (app) description; + #inherit (app) givcName; + vm = app.vmName; + path = "${pkgs.givc-cli}/bin/givc-cli ${cliArgs} start --vm ${vm} ${app.givcName}"; + inherit (app) icon; + }) virtualApps; + # Launchers for all desktop, non-virtualized applications that run in the GUIVM + guivmLaunchers = map (app: { + inherit (app) name; + inherit (app) description; + path = app.command; + inherit (app) icon; + }) cfg.applications; in { ghaf = { @@ -52,6 +73,9 @@ let graphics.enable = true; }; + # Create launchers for regular apps running in the GUIVM and virtualized ones if GIVC is enabled + graphics.launchers = guivmLaunchers ++ lib.optionals config.ghaf.givc.enable virtualLaunchers; + # To enable screen locking set to true graphics.labwc = { autolock.enable = lib.mkDefault config.ghaf.graphics.labwc.autolock.enable; @@ -303,6 +327,37 @@ in Context Identifier (CID) of the GUIVM VSOCK ''; }; + + applications = lib.mkOption { + description = '' + Applications to include in the GUIVM + ''; + type = lib.types.listOf ( + lib.types.submodule { + options = { + name = lib.mkOption { + type = lib.types.str; + description = "The name of the application"; + }; + description = lib.mkOption { + type = lib.types.str; + description = "A brief description of the application"; + }; + icon = lib.mkOption { + type = lib.types.str; + description = "Application icon"; + default = null; + }; + command = lib.mkOption { + type = lib.types.str; + description = "The command to run the application"; + default = null; + }; + }; + } + ); + default = [ ]; + }; }; config = lib.mkIf cfg.enable { diff --git a/modules/microvm/virtualization/microvm/modules.nix b/modules/microvm/virtualization/microvm/modules.nix index 96dbd91e7..63b7fa852 100644 --- a/modules/microvm/virtualization/microvm/modules.nix +++ b/modules/microvm/virtualization/microvm/modules.nix @@ -65,11 +65,6 @@ let # Fprint module fprint = optionalAttrs cfg.guivm.fprint { config.ghaf.services.fprint.enable = true; }; - # Desktop module - desktop = { - config.ghaf.services.desktop.enable = true; - }; - # XDG opener xdgOpener = { config.ghaf.services.xdgopener.enable = true; @@ -162,7 +157,6 @@ in kernelConfigs.guivm firmwareModule qemuModules.guivm - serviceModules.desktop serviceModules.fprint serviceModules.yubikey serviceModules.xdgOpener diff --git a/modules/reference/appvms/appflowy.nix b/modules/reference/appvms/appflowy.nix deleted file mode 100644 index 7a7604abf..000000000 --- a/modules/reference/appvms/appflowy.nix +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright 2024 TII (SSRC) and the Ghaf contributors -# SPDX-License-Identifier: Apache-2.0 -# -{ - lib, - pkgs, - config, - ... -}: -{ - name = "appflowy"; - packages = [ pkgs.appflowy ]; - macAddress = "02:00:00:03:08:01"; - ramMb = 768; - cores = 1; - extraModules = [ - { - hardware.graphics.enable = true; - time.timeZone = config.time.timeZone; - ghaf.givc.appvm = { - enable = true; - name = lib.mkForce "appflowy-vm"; - applications = [ - { - name = "appflowy"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/appflowy"; - } - ]; - }; - } - ]; - borderColor = "#4c3f7a"; -} diff --git a/modules/reference/appvms/business.nix b/modules/reference/appvms/business.nix index 14f6064d9..0121efa68 100644 --- a/modules/reference/appvms/business.nix +++ b/modules/reference/appvms/business.nix @@ -7,326 +7,152 @@ lib, ... }: -let - inherit (lib) mkIf optionalString; - #TODO: Move this to a common place - name = "business"; - proxyUserName = "proxy-user"; - proxyGroupName = "proxy-admin"; - tiiVpnAddr = "151.253.154.18"; - pacFileName = "ghaf.pac"; - pacServerAddr = "127.0.0.1:8000"; - pacFileUrl = "http://${pacServerAddr}/${pacFileName}"; - netvmEntry = builtins.filter (x: x.name == "net-vm") config.ghaf.networking.hosts.entries; - netvmAddress = lib.head (builtins.map (x: x.ip) netvmEntry); - # Remove rounded corners from the text editor window - gnomeTextEditor = pkgs.gnome-text-editor.overrideAttrs (oldAttrs: { - postPatch = - (oldAttrs.postPatch or "") - + '' - echo -e '\nwindow { border-radius: 0px; }' >> src/style.css - ''; - }); - - _ghafPacFileFetcher = - let - pacFileDownloadUrl = "https://raw.githubusercontent.com/tiiuae/ghaf-rt-config/refs/heads/main/network/proxy/ghaf.pac"; - proxyServerUrl = "http://${netvmAddress}:${toString config.ghaf.reference.services.proxy-server.bindPort}"; - logTag = "ghaf-pac-fetcher"; - in - pkgs.writeShellApplication { - name = "ghafPacFileFetcher"; - runtimeInputs = [ - pkgs.coreutils # Provides 'mv', 'rm', etc. - pkgs.curl # For downloading PAC files - pkgs.inetutils # Provides 'logger' - ]; - text = '' - # Variables - TEMP_PAC_PATH=$(mktemp) - LOCAL_PAC_PATH="/etc/proxy/${pacFileName}" - - # Logging function with timestamp - log() { - logger -t "${logTag}" "$1" - } - - log "Starting the pac file fetch process..." - - # Fetch the pac file using curl with a proxy - log "Fetching pac file from ${pacFileDownloadUrl} using proxy ${proxyServerUrl}..." - http_status=$(curl --proxy "${proxyServerUrl}" -s -o "$TEMP_PAC_PATH" -w "%{http_code}" "${pacFileDownloadUrl}") - - log "HTTP status code: $http_status" - - # Check if the fetch was successful - if [[ "$http_status" -ne 200 ]]; then - log "Error: Failed to download pac file from ${pacFileDownloadUrl}. HTTP status code: $http_status" - rm -f "$TEMP_PAC_PATH" # Clean up temporary file - exit 2 - fi - - # Verify the downloaded file is not empty - if [[ ! -s "$TEMP_PAC_PATH" ]]; then - log "Error: The downloaded pac file is empty." - rm -f "$TEMP_PAC_PATH" # Clean up temporary file - exit 3 - fi - - # Log the download success - log "Pac file downloaded successfully. Proceeding with update..." - - # Copy the content from the temporary pac file to the target file - log "Copying the content from temporary file to the target pac file at $LOCAL_PAC_PATH..." - - # Check if the copy was successful - if cat "$TEMP_PAC_PATH" > "$LOCAL_PAC_PATH"; then - log "Pac file successfully updated at $LOCAL_PAC_PATH." - else - log "Error: Failed to update the pac file at $LOCAL_PAC_PATH." - rm -f "$TEMP_PAC_PATH" # Clean up temporary file - exit 4 - fi - - # Clean up temporary file - rm -f "$TEMP_PAC_PATH" - - log "Pac file fetch and update process completed successfully." - exit 0 - ''; - }; - -in { - name = "${name}"; - packages = - [ - pkgs.google-chrome - pkgs.globalprotect-openconnect - pkgs.losslesscut-bin - pkgs.openconnect - gnomeTextEditor - pkgs.xarchiver - pkgs.busybox - ] - ++ lib.optionals config.ghaf.profiles.debug.enable [ pkgs.tcpdump ] - ++ lib.optionals config.ghaf.givc.enable [ pkgs.open-normal-extension ]; + name = "business"; + packages = lib.optionals config.ghaf.profiles.debug.enable [ pkgs.tcpdump ]; # TODO create a repository of mac addresses to avoid conflicts macAddress = "02:00:00:03:10:01"; ramMb = 6144; cores = 4; - extraModules = [ - ( - { pkgs, ... }: + borderColor = "#218838"; + ghafAudio.enable = true; + vtpm.enable = true; + applications = + let + inherit (config.microvm.vms."business-vm".config.config.ghaf.reference.services.pac) proxyPacUrl; + browserCommand = "google-chrome-stable --enable-features=UseOzonePlatform --ozone-platform=wayland ${config.ghaf.givc.idsExtraArgs} --load-extension=${pkgs.open-normal-extension} --proxy-pac-url=${proxyPacUrl}"; + in + [ { - imports = [ - # ../programs/chromium.nix - ../programs/google-chrome.nix - ../services/globalprotect-vpn/default.nix + name = "Trusted Browser"; + description = "Isolated Trusted Browsing"; + packages = [ pkgs.google-chrome ]; + icon = "thorium-browser"; + command = browserCommand; + givcArgs = [ + "url" ]; - time.timeZone = config.time.timeZone; - - microvm = { - qemu.extraArgs = lib.optionals ( - config.ghaf.hardware.usb.internal.enable - && (lib.hasAttr "cam0" config.ghaf.hardware.usb.internal.qemuExtraArgs) - ) config.ghaf.hardware.usb.internal.qemuExtraArgs.cam0; - devices = [ ]; - }; + extraModules = [ + { + imports = [ + #../programs/chromium.nix + ../programs/google-chrome.nix + ]; - ghaf = { - givc.appvm = { - enable = true; - name = lib.mkForce "business-vm"; - applications = [ - { - name = "google-chrome"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/google-chrome-stable --proxy-pac-url=${pacFileUrl} --enable-features=UseOzonePlatform --ozone-platform=wayland ${config.ghaf.givc.idsExtraArgs} --load-extension=${pkgs.open-normal-extension}"; - args = [ "url" ]; - } - { - name = "outlook"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/google-chrome-stable --proxy-pac-url=${pacFileUrl} --enable-features=UseOzonePlatform --ozone-platform=wayland --app=https://outlook.office.com/mail/ ${config.ghaf.givc.idsExtraArgs} --load-extension=${pkgs.open-normal-extension}"; - } - { - name = "office"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/google-chrome-stable --proxy-pac-url=${pacFileUrl} --enable-features=UseOzonePlatform --ozone-platform=wayland --app=https://microsoft365.com ${config.ghaf.givc.idsExtraArgs} --load-extension=${pkgs.open-normal-extension}"; - } - { - name = "teams"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/google-chrome-stable --proxy-pac-url=${pacFileUrl} --enable-features=UseOzonePlatform --ozone-platform=wayland --app=https://teams.microsoft.com ${config.ghaf.givc.idsExtraArgs} --load-extension=${pkgs.open-normal-extension}"; - } - { - name = "gpclient"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/gpclient -platform wayland"; - } - { - name = "gnome-text-editor"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/gnome-text-editor"; - } - { - name = "losslesscut"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/losslesscut --enable-features=UseOzonePlatform --ozone-platform=wayland"; - } - { - name = "xarchiver"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/xarchiver"; - } + ghaf.reference.programs.google-chrome.enable = true; + ghaf.reference.programs.google-chrome.openInNormalExtension = true; + ghaf.services.xdghandlers.enable = true; + } + ]; + } + { + name = "Microsoft Outlook"; + description = "Microsoft Email Client"; + icon = "ms-outlook"; + command = "${browserCommand} --app=https://outlook.office.com/mail/"; + } + { + name = "Microsoft 365"; + description = "Microsoft 365 Software Suite"; + icon = "microsoft-365"; + command = "${browserCommand} --app=https://microsoft365.com"; + } + { + name = "Teams"; + description = "Microsoft Teams Collaboration Application"; + icon = "teams-for-linux"; + command = "${browserCommand} --app=https://teams.microsoft.com"; + } + { + name = "VPN"; + description = "GlobalProtect VPN Client"; + packages = [ + pkgs.globalprotect-openconnect + pkgs.openconnect + ]; + icon = "yast-vpn"; + command = "gpclient -platform wayland"; + extraModules = [ + { + imports = [ + ../services/globalprotect-vpn/default.nix ]; - }; - reference = { - programs.google-chrome.enable = true; - services.globalprotect = { + ghaf.reference.services.globalprotect = { enable = true; csdWrapper = "${pkgs.openconnect}/libexec/openconnect/hipreport.sh"; }; - }; - - services.xdghandlers.enable = true; - }; - environment.etc."opt/chrome/native-messaging-hosts/fi.ssrc.open_normal.json" = - mkIf config.ghaf.givc.enable - { - source = "${pkgs.open-normal-extension}/fi.ssrc.open_normal.json"; - }; - - # environment.etc."chromium/native-messaging-hosts/fi.ssrc.open_normal.json" = - # mkIf config.ghaf.givc.enable - # { - # source = "${pkgs.open-normal-extension}/fi.ssrc.open_normal.json"; - # }; - environment.etc."open-normal-extension.cfg" = mkIf config.ghaf.givc.enable { - text = - let - cliArgs = builtins.replaceStrings [ "\n" ] [ " " ] '' - --name ${config.ghaf.givc.adminConfig.name} - --addr ${config.ghaf.givc.adminConfig.addr} - --port ${config.ghaf.givc.adminConfig.port} - ${optionalString config.ghaf.givc.enableTls "--cacert /run/givc/ca-cert.pem"} - ${optionalString config.ghaf.givc.enableTls "--cert /run/givc/business-vm-cert.pem"} - ${optionalString config.ghaf.givc.enableTls "--key /run/givc/business-vm-key.pem"} - ${optionalString (!config.ghaf.givc.enableTls) "--notls"} - ''; - in - '' - export GIVC_PATH="${pkgs.givc-cli}" - export GIVC_OPTS="${cliArgs}" - ''; - }; - - # Enable dconf and icon pack for gnome text editor - programs.dconf.enable = true; - environment.systemPackages = [ pkgs.adwaita-icon-theme ]; - # Define a new group for proxy management - users.groups.${proxyGroupName} = { }; # Create a group named proxy-admin - - # Define a new user with a specific username - users.users.${proxyUserName} = { - isSystemUser = true; - description = "Proxy User for managing allowlist and services"; - # extraGroups = [ "${proxyGroupName}" ]; # Adding to 'proxy-admin' for specific access - group = "${proxyGroupName}"; - }; - - environment.etc."proxy/${pacFileName}" = { - text = ''''; - user = "${proxyUserName}"; # Owner is proxy-user - group = "${proxyGroupName}"; # Group is proxy-admin - mode = "0664"; # Permissions: read/write for owner/group, no permissions for others - }; - - systemd.services.pacServer = { - description = "Http server to make PAC file accessible for web browsers"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - serviceConfig = { - ExecStart = "${pkgs.busybox}/bin/busybox httpd -f -p ${pacServerAddr} -h /etc/proxy"; - # Ensure ghafFetchUrl starts after the network is up - Type = "simple"; - # Restart policy on failure - Restart = "always"; # Restart the service if it fails - RestartSec = "15s"; # Wait 15 seconds before restarting - User = "${proxyUserName}"; - }; - }; - - systemd.services.ghafPacFileFetcher = { - description = "Fetch ghaf pac file periodically with retries if internet is available"; - - serviceConfig = { - ExecStart = "${_ghafPacFileFetcher}/bin/ghafPacFileFetcher"; - # Ensure ghafFetchUrl starts after the network is up - Type = "simple"; - # Restart policy on failure - Restart = "on-failure"; # Restart the service if it fails - RestartSec = "15s"; # Wait 15 seconds before restarting - User = "${proxyUserName}"; - }; - }; - - systemd.timers.ghafPacFileFetcher = { - description = "Run ghafPacFileFetcher periodically"; - wantedBy = [ "timers.target" ]; - timerConfig = { - User = "${proxyUserName}"; - Persistent = true; # Ensures the timer runs after a system reboot - OnCalendar = "daily"; # Set to your desired schedule - OnBootSec = "90s"; - }; - }; - - #Firewall Settings - networking = { - firewall = { - enable = true; - extraCommands = '' - - add_rule() { - local ip=$1 - iptables -I OUTPUT -p tcp -d $ip --dport 80 -j ACCEPT - iptables -I OUTPUT -p tcp -d $ip --dport 443 -j ACCEPT - iptables -I INPUT -p tcp -s $ip --sport 80 -j ACCEPT - iptables -I INPUT -p tcp -s $ip --sport 443 -j ACCEPT - } - # Default policy - iptables -P INPUT DROP - - iptables -A INPUT -i lo -j ACCEPT - iptables -A OUTPUT -o lo -j ACCEPT - - # Block any other unwanted traffic (optional) - iptables -N logreject - iptables -A logreject -j LOG - iptables -A logreject -j REJECT - - # allow everything for local VPN traffic - iptables -A INPUT -i tun0 -j ACCEPT - iptables -A FORWARD -i tun0 -j ACCEPT - iptables -A FORWARD -o tun0 -j ACCEPT - iptables -A OUTPUT -o tun0 -j ACCEPT + } + ]; + } + { + name = "Text Editor"; + description = "Simple Text Editor"; + packages = + let + # Remove rounded corners from the text editor window + gnomeTextEditor = pkgs.gnome-text-editor.overrideAttrs (oldAttrs: { + postPatch = + (oldAttrs.postPatch or "") + + '' + echo -e '\nwindow { border-radius: 0px; }' >> src/style.css + ''; + }); + in + [ + gnomeTextEditor + pkgs.adwaita-icon-theme + ]; + icon = "org.gnome.TextEditor"; + command = "gnome-text-editor"; + extraModules = [ + { + # Enable dconf for gnome text editor + programs.dconf.enable = true; + } + ]; + } + { + name = "Xarchiver"; + description = "File Compressor"; + packages = [ pkgs.xarchiver ]; + icon = "xarchiver"; + command = "xarchiver"; + } + { + name = "Video Editor"; + description = "Losslesscut Video Editor"; + packages = [ pkgs.losslesscut-bin ]; + icon = "${pkgs.losslesscut-bin}/share/icons/losslesscut.png"; + command = "losslesscut --enable-features=UseOzonePlatform --ozone-platform=wayland"; + } + ]; + extraModules = [ - # WARN: if all the traffic including VPN flowing through proxy is intended, - # remove "add_rule 151.253.154.18" rule and pass "--proxy-server=http://192.168.100.1:3128" to openconnect(VPN) app. - # also remove "151.253.154.18,tii.ae,.tii.ae,sapsf.com,.sapsf.com" addresses from noProxy option and add - # them to allow acl list in modules/reference/appvms/3proxy-config.nix file. - # Allow VPN access.tii.ae - add_rule ${tiiVpnAddr} + { + # Attach integrated camera to this vm + microvm = { + qemu.extraArgs = lib.optionals ( + config.ghaf.hardware.usb.internal.enable + && (lib.hasAttr "cam0" config.ghaf.hardware.usb.internal.qemuExtraArgs) + ) config.ghaf.hardware.usb.internal.qemuExtraArgs.cam0; + devices = [ ]; + }; + + imports = [ + ../services/pac/pac.nix + ../services/firewall/firewall.nix + ]; - # Block all other HTTP and HTTPS traffic - iptables -A OUTPUT -p tcp --dport 80 -j logreject - iptables -A OUTPUT -p tcp --dport 443 -j logreject - iptables -A OUTPUT -p udp --dport 80 -j logreject - iptables -A OUTPUT -p udp --dport 443 -j logreject + # Enable Proxy Auto-Configuration service for the browser + ghaf.reference.services.pac.enable = true; + ghaf.reference.services.pac.proxyAddress = + config.ghaf.reference.services.proxy-server.internalAddress; + ghaf.reference.services.pac.proxyPort = config.ghaf.reference.services.proxy-server.bindPort; - ''; - }; - }; - } - ) + # Enable firewall and allow access to TII VPN + ghaf.reference.services.firewall.enable = true; + } ]; - borderColor = "#218838"; - ghafAudio.enable = true; - vtpm.enable = true; } diff --git a/modules/reference/appvms/chromium.nix b/modules/reference/appvms/chromium.nix index 166ed6a7d..5888b88de 100644 --- a/modules/reference/appvms/chromium.nix +++ b/modules/reference/appvms/chromium.nix @@ -7,50 +7,47 @@ config, ... }: -let - name = "chromium"; -in { - name = "${name}"; - packages = [ - pkgs.chromium - ] ++ lib.optional config.ghaf.development.debug.tools.enable pkgs.alsa-utils; + name = "chromium"; + packages = lib.optional config.ghaf.development.debug.tools.enable pkgs.alsa-utils; # TODO create a repository of mac addresses to avoid conflicts macAddress = "02:00:00:03:05:01"; ramMb = 6144; cores = 4; + borderColor = "#B83232"; + ghafAudio.enable = true; + vtpm.enable = true; + applications = [ + { + # The SPKI fingerprint is calculated like this: + # $ openssl x509 -noout -in mitmproxy-ca-cert.pem -pubkey | openssl asn1parse -noout -inform pem -out public.key + # $ openssl dgst -sha256 -binary public.key | openssl enc -base64 + name = "Chromium"; + description = "Isolated General Browsing"; + packages = [ pkgs.chromium ]; + icon = "chromium"; + command = "chromium --enable-features=UseOzonePlatform --ozone-platform=wayland ${config.ghaf.givc.idsExtraArgs}"; + givcArgs = [ + "url" + "flag" + ]; + extraModules = [ + { + imports = [ ../programs/chromium.nix ]; + ghaf.reference.programs.chromium.enable = true; + ghaf.services.xdghandlers.enable = true; + } + ]; + } + ]; extraModules = [ { - imports = [ ../programs/chromium.nix ]; - - time.timeZone = config.time.timeZone; - # Disable camera for now, because, due to the bug, the camera is not accessable in BusinessVM # microvm.qemu.extraArgs = optionals ( # config.ghaf.hardware.usb.internal.enable # && (hasAttr "cam0" config.ghaf.hardware.usb.internal.qemuExtraArgs) # ) config.ghaf.hardware.usb.internal.qemuExtraArgs.cam0; microvm.devices = [ ]; - - ghaf.givc.appvm = { - enable = true; - name = lib.mkForce "chromium-vm"; - applications = [ - { - name = "chromium"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/chromium --enable-features=UseOzonePlatform --ozone-platform=wayland ${config.ghaf.givc.idsExtraArgs}"; - args = [ - "url" - "flag" - ]; - } - ]; - }; - ghaf.reference.programs.chromium.enable = true; - ghaf.services.xdghandlers.enable = true; } ]; - borderColor = "#B83232"; - ghafAudio.enable = true; - vtpm.enable = true; } diff --git a/modules/reference/appvms/comms.nix b/modules/reference/appvms/comms.nix index 54c6f8dd8..3a210ce3a 100644 --- a/modules/reference/appvms/comms.nix +++ b/modules/reference/appvms/comms.nix @@ -8,71 +8,65 @@ ... }: let - name = "comms"; inherit (lib) hasAttr optionals; - dendrite-pinecone = pkgs.callPackage ../../../packages/dendrite-pinecone { }; - isDendritePineconeEnabled = - if (hasAttr "services" config.ghaf.reference) then - config.ghaf.reference.services.dendrite - else - false; in { - name = "${name}"; - + name = "comms"; packages = [ pkgs.google-chrome - pkgs.element-desktop - pkgs.element-gps pkgs.gpsd - pkgs.tcpdump - ] ++ pkgs.lib.optionals isDendritePineconeEnabled [ dendrite-pinecone ]; + ] ++ lib.optionals config.ghaf.profiles.debug.enable [ pkgs.tcpdump ]; macAddress = "02:00:00:03:09:01"; ramMb = 4096; cores = 4; + borderColor = "#337aff"; + ghafAudio.enable = true; + applications = [ + { + name = "Element"; + description = "General Messaging Application"; + packages = [ pkgs.element-desktop ]; + icon = "element-desktop"; + command = "element-desktop --enable-features=UseOzonePlatform --ozone-platform=wayland"; + extraModules = [ + { + imports = [ + ../programs/element-desktop.nix + ]; + ghaf.reference.programs.element-desktop.enable = true; + } + ]; + } + { + name = "Slack"; + description = "Teams Collaboration & Messaging Application"; + icon = "slack"; + command = "google-chrome-stable --enable-features=UseOzonePlatform --ozone-platform=wayland --app=https://app.slack.com/client ${config.ghaf.givc.idsExtraArgs}"; + } + { + name = "Zoom"; + description = "Zoom Videoconferencing Application"; + icon = "Zoom"; + command = "google-chrome-stable --enable-features=UseOzonePlatform --ozone-platform=wayland --app=https://app.zoom.us/wc/home ${config.ghaf.givc.idsExtraArgs}"; + } + ]; extraModules = [ { imports = [ # ../programs/chromium.nix ../programs/google-chrome.nix - ]; - systemd = { - services = { - element-gps = { - description = "Element-gps is a GPS location provider for Element websocket interface."; - enable = true; - serviceConfig = { - Type = "simple"; - ExecStart = "${pkgs.element-gps}/bin/main.py"; - Restart = "on-failure"; - RestartSec = "2"; - }; - wantedBy = [ "multi-user.target" ]; - }; - - "dendrite-pinecone" = pkgs.lib.mkIf isDendritePineconeEnabled { - description = "Dendrite is a second-generation Matrix homeserver with Pinecone which is a next-generation P2P overlay network"; - enable = true; - serviceConfig = { - Type = "simple"; - ExecStart = "${dendrite-pinecone}/bin/dendrite-demo-pinecone"; - Restart = "on-failure"; - RestartSec = "2"; - }; - wantedBy = [ "multi-user.target" ]; - }; - }; - }; - - networking = pkgs.lib.mkIf isDendritePineconeEnabled { - firewall.allowedTCPPorts = [ dendrite-pinecone.TcpPortInt ]; - firewall.allowedUDPPorts = [ dendrite-pinecone.McastUdpPortInt ]; - }; + ghaf.reference.programs.google-chrome.enable = true; + ghaf.services.xdghandlers.enable = true; - time.timeZone = config.time.timeZone; + # Attach GPS receiver to this VM + microvm.qemu.extraArgs = optionals ( + config.ghaf.hardware.usb.external.enable + && (hasAttr "gps0" config.ghaf.hardware.usb.external.qemuExtraArgs) + ) config.ghaf.hardware.usb.external.qemuExtraArgs.gps0; + # GPSD collects data from GPS and makes it available on TCP port 2947 services.gpsd = { enable = true; devices = [ "/dev/ttyUSB0" ]; @@ -81,34 +75,6 @@ in listenany = true; extraArgs = [ "-n" ]; # Do not wait for a client to connect before polling }; - - microvm.qemu.extraArgs = optionals ( - config.ghaf.hardware.usb.external.enable - && (hasAttr "gps0" config.ghaf.hardware.usb.external.qemuExtraArgs) - ) config.ghaf.hardware.usb.external.qemuExtraArgs.gps0; - - ghaf.givc.appvm = { - enable = true; - name = lib.mkForce "${name}-vm"; - applications = [ - { - name = "element"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/element-desktop --enable-features=UseOzonePlatform --ozone-platform=wayland"; - } - { - name = "slack"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/google-chrome-stable --enable-features=UseOzonePlatform --ozone-platform=wayland --app=https://app.slack.com/client ${config.ghaf.givc.idsExtraArgs}"; - } - { - name = "zoom"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/google-chrome-stable --enable-features=UseOzonePlatform --ozone-platform=wayland --app=https://app.zoom.us/wc/home ${config.ghaf.givc.idsExtraArgs}"; - } - ]; - }; - ghaf.reference.programs.google-chrome.enable = true; - ghaf.services.xdghandlers.enable = true; } ]; - borderColor = "#337aff"; - ghafAudio.enable = true; } diff --git a/modules/reference/appvms/gala.nix b/modules/reference/appvms/gala.nix index 6ae25a08a..657753ffb 100644 --- a/modules/reference/appvms/gala.nix +++ b/modules/reference/appvms/gala.nix @@ -2,31 +2,22 @@ # SPDX-License-Identifier: Apache-2.0 # { - lib, pkgs, - config, ... }: { name = "gala"; - packages = [ pkgs.gala-app ]; macAddress = "02:00:00:03:06:01"; ramMb = 1536; cores = 2; - extraModules = [ + borderColor = "#027d7b"; + applications = [ { - time.timeZone = config.time.timeZone; - ghaf.givc.appvm = { - enable = true; - name = lib.mkForce "gala-vm"; - applications = [ - { - name = "gala"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/gala --enable-features=UseOzonePlatform --ozone-platform=wayland"; - } - ]; - }; + name = "GALA"; + description = "Secure Android-in-the-Cloud"; + packages = [ pkgs.gala-app ]; + icon = "distributor-logo-android"; + command = "gala --enable-features=UseOzonePlatform --ozone-platform=wayland"; } ]; - borderColor = "#027d7b"; } diff --git a/modules/reference/appvms/google-chrome.nix b/modules/reference/appvms/google-chrome.nix index 4d6abaf4b..0ee5b9d66 100644 --- a/modules/reference/appvms/google-chrome.nix +++ b/modules/reference/appvms/google-chrome.nix @@ -7,50 +7,47 @@ config, ... }: -let - name = "chrome"; -in { - name = "${name}"; - packages = [ - pkgs.google-chrome - ] ++ lib.optional config.ghaf.development.debug.tools.enable pkgs.alsa-utils; + name = "chrome"; + packages = lib.optional config.ghaf.development.debug.tools.enable pkgs.alsa-utils; # TODO create a repository of mac addresses to avoid conflicts macAddress = "02:00:00:03:11:01"; ramMb = 6144; cores = 4; + borderColor = "#630505"; + ghafAudio.enable = true; + vtpm.enable = true; + applications = [ + { + # The SPKI fingerprint is calculated like this: + # $ openssl x509 -noout -in mitmproxy-ca-cert.pem -pubkey | openssl asn1parse -noout -inform pem -out public.key + # $ openssl dgst -sha256 -binary public.key | openssl enc -base64 + name = "Google Chrome"; + description = "Isolated General Browsing"; + packages = [ pkgs.google-chrome ]; + icon = "google-chrome"; + command = "google-chrome-stable --enable-features=UseOzonePlatform --ozone-platform=wayland ${config.ghaf.givc.idsExtraArgs}"; + givcArgs = [ + "url" + "flag" + ]; + extraModules = [ + { + imports = [ ../programs/google-chrome.nix ]; + ghaf.reference.programs.google-chrome.enable = true; + ghaf.services.xdghandlers.enable = true; + } + ]; + } + ]; extraModules = [ { - imports = [ ../programs/google-chrome.nix ]; - - time.timeZone = config.time.timeZone; - # Disable camera for now, because, due to the bug, the camera is not accessable in BusinessVM # microvm.qemu.extraArgs = optionals ( # config.ghaf.hardware.usb.internal.enable # && (hasAttr "cam0" config.ghaf.hardware.usb.internal.qemuExtraArgs) # ) config.ghaf.hardware.usb.internal.qemuExtraArgs.cam0; microvm.devices = [ ]; - - ghaf.givc.appvm = { - enable = true; - name = lib.mkForce "chrome-vm"; - applications = [ - { - name = "google-chrome"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/google-chrome-stable --enable-features=UseOzonePlatform --ozone-platform=wayland ${config.ghaf.givc.idsExtraArgs}"; - args = [ - "url" - "flag" - ]; - } - ]; - }; - ghaf.reference.programs.google-chrome.enable = true; - ghaf.services.xdghandlers.enable = true; } ]; - borderColor = "#630505"; - ghafAudio.enable = true; - vtpm.enable = true; } diff --git a/modules/reference/appvms/zathura.nix b/modules/reference/appvms/zathura.nix index a62e7ecd0..cf6c58d97 100644 --- a/modules/reference/appvms/zathura.nix +++ b/modules/reference/appvms/zathura.nix @@ -4,40 +4,37 @@ { lib, pkgs, - config, ... }: { name = "zathura"; packages = [ - pkgs.zathura + # Image viewer pkgs.pqiv ]; macAddress = "02:00:00:03:07:01"; ramMb = 512; cores = 1; + borderColor = "#122263"; + applications = [ + { + name = "PDF Viewer"; + description = "Isolated PDF Viewer"; + packages = [ pkgs.zathura ]; + icon = "document-viewer"; + command = "zathura"; + extraModules = [ + { + imports = [ ../programs/zathura.nix ]; + ghaf.reference.programs.zathura.enable = true; + } + ]; + } + ]; extraModules = [ { - imports = [ ../programs/zathura.nix ]; - time.timeZone = config.time.timeZone; - ghaf = { - reference.programs.zathura.enable = true; - - givc.appvm = { - enable = true; - name = lib.mkForce "zathura-vm"; - applications = [ - { - name = "zathura"; - command = "${config.ghaf.givc.appPrefix}/run-waypipe ${config.ghaf.givc.appPrefix}/zathura"; - } - ]; - }; - - #this vm should be stateless so nothing stored between boots. - storagevm.enable = lib.mkForce false; - }; + # This vm should be stateless so nothing stored between boots + ghaf.storagevm.enable = lib.mkForce false; } ]; - borderColor = "#122263"; } diff --git a/modules/reference/desktop/applications.nix b/modules/reference/desktop/applications.nix new file mode 100644 index 000000000..9b8b4fc42 --- /dev/null +++ b/modules/reference/desktop/applications.nix @@ -0,0 +1,83 @@ +# Copyright 2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.ghaf.reference.desktop.applications; + inherit (config.ghaf.services.audio) pulseaudioTcpControlPort; +in +{ + options.ghaf.reference.desktop.applications = { + enable = lib.mkEnableOption "desktop applications"; + }; + config = lib.mkIf cfg.enable { + ghaf.virtualization.microvm.guivm.applications = + [ + { + name = "Calculator"; + description = "Solve Math Problems"; + icon = "${pkgs.gnome-calculator}/share/icons/hicolor/scalable/apps/org.gnome.Calculator.svg"; + command = "${pkgs.gnome-calculator}/bin/gnome-calculator"; + } + + { + name = "Sticky Notes"; + description = "Sticky Notes on your Desktop"; + icon = "${pkgs.sticky-notes}/share/icons/hicolor/scalable/apps/com.vixalien.sticky.svg"; + command = "${pkgs.sticky-notes}/bin/com.vixalien.sticky"; + } + + { + name = "File Manager"; + description = "Organize & Manage Files"; + icon = "system-file-manager"; + command = "${pkgs.pcmanfm}/bin/pcmanfm"; + } + + { + name = "Bluetooth Settings"; + description = "Manage Bluetooth Devices & Settings"; + icon = "bluetooth-48"; + command = "${pkgs.bt-launcher}/bin/bt-launcher"; + } + + { + name = "Audio Control"; + description = "System Audio Control"; + icon = "preferences-sound"; + command = "${pkgs.ghaf-audio-control}/bin/GhafAudioControlStandalone --pulseaudio_server=audio-vm:${toString pulseaudioTcpControlPort} --indicator_icon_name=preferences-sound"; + } + + { + name = "Falcon AI"; + description = "Your local large language model, developed by TII"; + icon = "${pkgs.ghaf-artwork}/icons/falcon-icon.svg"; + command = "${pkgs.alpaca}/bin/alpaca"; + } + + { + name = "Control panel"; + description = "Control panel"; + icon = "utilities-tweak-tool"; + command = "${pkgs.ctrl-panel}/bin/ctrl-panel"; + } + ] + ++ lib.optionals config.ghaf.reference.programs.windows-launcher.enable ( + let + winConfig = config.ghaf.reference.programs.windows-launcher; + in + [ + { + name = "Windows"; + description = "Virtualized Windows System"; + icon = "distributor-logo-windows"; + command = "${pkgs.virt-viewer}/bin/remote-viewer -f spice://${winConfig.spice-host}:${toString winConfig.spice-port}"; + } + ] + ); + }; +} diff --git a/modules/reference/desktop/default.nix b/modules/reference/desktop/default.nix new file mode 100644 index 000000000..06c42ad34 --- /dev/null +++ b/modules/reference/desktop/default.nix @@ -0,0 +1,7 @@ +# Copyright 2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + imports = [ + ./applications.nix + ]; +} diff --git a/modules/reference/profiles/laptop-x86.nix b/modules/reference/profiles/laptop-x86.nix index 93b7c6d85..0d97c423d 100644 --- a/modules/reference/profiles/laptop-x86.nix +++ b/modules/reference/profiles/laptop-x86.nix @@ -18,6 +18,7 @@ in ../../hardware/common ../../hardware/definition.nix ../../lanzaboote + ../desktop ]; options.ghaf.reference.profiles.laptop-x86 = { @@ -134,6 +135,8 @@ in port = 9999; }; }; + + reference.desktop.applications.enable = true; }; }; } diff --git a/modules/reference/programs/chromium.nix b/modules/reference/programs/chromium.nix index a3e3c7aab..ebb9e83b7 100644 --- a/modules/reference/programs/chromium.nix +++ b/modules/reference/programs/chromium.nix @@ -1,13 +1,18 @@ # Copyright 2024 TII (SSRC) and the Ghaf contributors # SPDX-License-Identifier: Apache-2.0 -{ config, lib, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.ghaf.reference.programs.chromium; in { options.ghaf.reference.programs.chromium = { enable = lib.mkEnableOption "Enable Chromium program settings"; - useZathuraVM = lib.mkEnableOption "Open PDFs in Zathura VM"; + openInNormalExtension = lib.mkEnableOption "browser extension to open links in the normal browser"; }; config = lib.mkIf cfg.enable { programs.chromium = { @@ -21,6 +26,32 @@ in # Don't use pdf.js, open externally. extraOpts."AlwaysOpenPdfExternally" = true; + + }; + + environment.etc = lib.mkIf (cfg.openInNormalExtension && config.ghaf.givc.enable) { + "chromium/native-messaging-hosts/fi.ssrc.open_normal.json" = { + source = "${pkgs.open-normal-extension}/fi.ssrc.open_normal.json"; + }; + + "open-normal-extension.cfg" = { + text = + let + cliArgs = builtins.replaceStrings [ "\n" ] [ " " ] '' + --name ${config.ghaf.givc.adminConfig.name} + --addr ${config.ghaf.givc.adminConfig.addr} + --port ${config.ghaf.givc.adminConfig.port} + ${lib.optionalString config.ghaf.givc.enableTls "--cacert /run/givc/ca-cert.pem"} + ${lib.optionalString config.ghaf.givc.enableTls "--cert /run/givc/business-vm-cert.pem"} + ${lib.optionalString config.ghaf.givc.enableTls "--key /run/givc/business-vm-key.pem"} + ${lib.optionalString (!config.ghaf.givc.enableTls) "--notls"} + ''; + in + '' + export GIVC_PATH="${pkgs.givc-cli}" + export GIVC_OPTS="${cliArgs}" + ''; + }; }; }; } diff --git a/modules/reference/programs/default.nix b/modules/reference/programs/default.nix index 0fbb37c51..aba3ce6e8 100644 --- a/modules/reference/programs/default.nix +++ b/modules/reference/programs/default.nix @@ -3,8 +3,9 @@ { imports = [ ./zathura.nix - # ./chromium.nix + ./chromium.nix ./google-chrome.nix ./windows-launcher.nix + ./element-desktop.nix ]; } diff --git a/modules/reference/programs/element-desktop.nix b/modules/reference/programs/element-desktop.nix new file mode 100644 index 000000000..8848e8797 --- /dev/null +++ b/modules/reference/programs/element-desktop.nix @@ -0,0 +1,60 @@ +# Copyright 2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.ghaf.reference.programs.element-desktop; + dendrite-pinecone = pkgs.callPackage ../../../packages/dendrite-pinecone { }; + isDendritePineconeEnabled = + if (lib.hasAttr "services" config.ghaf.reference) then + config.ghaf.reference.services.dendrite + else + false; + +in +{ + options.ghaf.reference.programs.element-desktop = { + enable = lib.mkEnableOption "element-desktop program settings"; + }; + config = lib.mkIf cfg.enable { + + systemd.services = { + + # The element-gps listens for WebSocket connections on localhost port 8000 from element-desktop + # When a new connection is received, it executes the gpspipe program to get GPS data from GPSD and forwards it over the WebSocket + element-gps = { + description = "Element-gps is a GPS location provider for Element websocket interface."; + enable = true; + serviceConfig = { + Type = "simple"; + ExecStart = "${pkgs.element-gps}/bin/main.py"; + Restart = "on-failure"; + RestartSec = "2"; + }; + wantedBy = [ "multi-user.target" ]; + }; + + "dendrite-pinecone" = pkgs.lib.mkIf isDendritePineconeEnabled { + description = "Dendrite is a second-generation Matrix homeserver with Pinecone which is a next-generation P2P overlay network"; + enable = true; + serviceConfig = { + Type = "simple"; + ExecStart = "${dendrite-pinecone}/bin/dendrite-demo-pinecone"; + Restart = "on-failure"; + RestartSec = "2"; + }; + wantedBy = [ "multi-user.target" ]; + }; + }; + + networking = pkgs.lib.mkIf isDendritePineconeEnabled { + firewall.allowedTCPPorts = [ dendrite-pinecone.TcpPortInt ]; + firewall.allowedUDPPorts = [ dendrite-pinecone.McastUdpPortInt ]; + }; + + }; +} diff --git a/modules/reference/programs/google-chrome.nix b/modules/reference/programs/google-chrome.nix index c54a36d4b..543f33a29 100644 --- a/modules/reference/programs/google-chrome.nix +++ b/modules/reference/programs/google-chrome.nix @@ -1,17 +1,22 @@ # Copyright 2024 TII (SSRC) and the Ghaf contributors # SPDX-License-Identifier: Apache-2.0 -{ config, lib, ... }: +{ + config, + lib, + pkgs, + ... +}: let cfg = config.ghaf.reference.programs.google-chrome; in { options.ghaf.reference.programs.google-chrome = { enable = lib.mkEnableOption "Enable Google chrome program settings"; - useZathuraVM = lib.mkEnableOption "Open PDFs in Zathura VM"; + openInNormalExtension = lib.mkEnableOption "browser extension to open links in the normal browser"; defaultPolicy = lib.mkOption { type = lib.types.attrs; description = '' - Google chrome policy options. A list of available policies + Google chrome policy options. A list of available policies can be found in the Chrome Enterprise documentation: Make sure the selected policy is supported on Linux and your browser version. @@ -68,20 +73,45 @@ in }; config = lib.mkIf cfg.enable { - environment.etc = { - "opt/chrome/policies/managed/default.json" = lib.mkIf (cfg.defaultPolicy != { }) { - text = builtins.toJSON cfg.defaultPolicy; - user = "${cfg.policyOwner}"; # Owner is proxy-user - group = "${cfg.policyOwnerGroup}"; # Group is proxy-admin - mode = "0664"; # Permissions: read/write for owner/group, no permissions for others - }; - "opt/chrome/policies/managed/extra.json" = { - text = builtins.toJSON cfg.extraOpts; - user = "${cfg.policyOwner}"; # Owner is proxy-user - group = "${cfg.policyOwnerGroup}"; # Group is proxy-admin - mode = "0664"; # Permissions: read/write for owner/group, no permissions for others - }; + environment.etc = lib.mkMerge [ + { + "opt/chrome/policies/managed/default.json" = { + text = builtins.toJSON cfg.defaultPolicy; + user = "${cfg.policyOwner}"; # Owner is proxy-user + group = "${cfg.policyOwnerGroup}"; # Group is proxy-admin + mode = "0664"; # Permissions: read/write for owner/group, no permissions for others + }; + "opt/chrome/policies/managed/extra.json" = { + text = builtins.toJSON cfg.extraOpts; + user = "${cfg.policyOwner}"; # Owner is proxy-user + group = "${cfg.policyOwnerGroup}"; # Group is proxy-admin + mode = "0664"; # Permissions: read/write for owner/group, no permissions for others + }; + } + (lib.mkIf (cfg.openInNormalExtension && config.ghaf.givc.enable) { + "opt/chrome/native-messaging-hosts/fi.ssrc.open_normal.json" = { + source = "${pkgs.open-normal-extension}/fi.ssrc.open_normal.json"; + }; - }; + "open-normal-extension.cfg" = { + text = + let + cliArgs = builtins.replaceStrings [ "\n" ] [ " " ] '' + --name ${config.ghaf.givc.adminConfig.name} + --addr ${config.ghaf.givc.adminConfig.addr} + --port ${config.ghaf.givc.adminConfig.port} + ${lib.optionalString config.ghaf.givc.enableTls "--cacert /run/givc/ca-cert.pem"} + ${lib.optionalString config.ghaf.givc.enableTls "--cert /run/givc/business-vm-cert.pem"} + ${lib.optionalString config.ghaf.givc.enableTls "--key /run/givc/business-vm-key.pem"} + ${lib.optionalString (!config.ghaf.givc.enableTls) "--notls"} + ''; + in + '' + export GIVC_PATH="${pkgs.givc-cli}" + export GIVC_OPTS="${cliArgs}" + ''; + }; + }) + ]; }; } diff --git a/modules/reference/services/firewall/firewall.nix b/modules/reference/services/firewall/firewall.nix new file mode 100644 index 000000000..6b9aaa4ae --- /dev/null +++ b/modules/reference/services/firewall/firewall.nix @@ -0,0 +1,71 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + lib, + config, + ... +}: +let + cfg = config.ghaf.reference.services.firewall; +in +{ + options.ghaf.reference.services.firewall = { + enable = lib.mkEnableOption "Ghaf reference firewall for virtual machines"; + + # WARN: if all the traffic including VPN flowing through proxy is intended, + # remove "151.253.154.18" rule and pass "--proxy-server=http://192.168.100.1:3128" to openconnect(VPN) app. + # also remove "151.253.154.18,tii.ae,.tii.ae,sapsf.com,.sapsf.com" addresses from noProxy option and add + # them to allow acl list in modules/reference/appvms/3proxy-config.nix file. + allowedIPs = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "151.253.154.18" ]; + description = "List of IP addresses allowed through the firewall"; + }; + }; + + config = lib.mkIf cfg.enable { + networking = { + firewall = { + enable = true; + extraCommands = + let + allowRules = lib.concatStringsSep "\n" ( + map (ip: '' + iptables -I OUTPUT -p tcp -d ${ip} --dport 80 -j ACCEPT + iptables -I OUTPUT -p tcp -d ${ip} --dport 443 -j ACCEPT + iptables -I INPUT -p tcp -s ${ip} --sport 80 -j ACCEPT + iptables -I INPUT -p tcp -s ${ip} --sport 443 -j ACCEPT + '') cfg.allowedIPs + ); + in + '' + # Default policy + iptables -P INPUT DROP + + iptables -A INPUT -i lo -j ACCEPT + iptables -A OUTPUT -o lo -j ACCEPT + + # Block any other unwanted traffic (optional) + iptables -N logreject + iptables -A logreject -j LOG + iptables -A logreject -j REJECT + + # allow everything for local VPN traffic + iptables -A INPUT -i tun0 -j ACCEPT + iptables -A FORWARD -i tun0 -j ACCEPT + iptables -A FORWARD -o tun0 -j ACCEPT + iptables -A OUTPUT -o tun0 -j ACCEPT + + ${allowRules} + + # Block all other HTTP and HTTPS traffic + iptables -A OUTPUT -p tcp --dport 80 -j logreject + iptables -A OUTPUT -p tcp --dport 443 -j logreject + iptables -A OUTPUT -p udp --dport 80 -j logreject + iptables -A OUTPUT -p udp --dport 443 -j logreject + + ''; + }; + }; + }; +} diff --git a/modules/reference/services/pac/pac.nix b/modules/reference/services/pac/pac.nix new file mode 100644 index 000000000..722d4de38 --- /dev/null +++ b/modules/reference/services/pac/pac.nix @@ -0,0 +1,154 @@ +# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors +# SPDX-License-Identifier: Apache-2.0 +{ + lib, + pkgs, + config, + ... +}: +let + cfg = config.ghaf.reference.services.pac; + proxyUserName = "proxy-user"; + proxyGroupName = "proxy-admin"; + pacFileName = "ghaf.pac"; + pacServerAddr = "127.0.0.1:8000"; + _ghafPacFileFetcher = + let + pacFileDownloadUrl = cfg.pacUrl; + proxyServerUrl = "http://${cfg.proxyAddress}:${toString cfg.proxyPort}"; + logTag = "ghaf-pac-fetcher"; + in + pkgs.writeShellApplication { + name = "ghafPacFileFetcher"; + runtimeInputs = [ + pkgs.coreutils # Provides 'mv', 'rm', etc. + pkgs.curl # For downloading PAC files + pkgs.inetutils # Provides 'logger' + ]; + text = '' + # Variables + TEMP_PAC_PATH=$(mktemp) + LOCAL_PAC_PATH="/etc/proxy/${pacFileName}" + # Logging function with timestamp + log() { + logger -t "${logTag}" "$1" + } + log "Starting the pac file fetch process..." + # Fetch the pac file using curl with a proxy + log "Fetching pac file from ${pacFileDownloadUrl} using proxy ${proxyServerUrl}..." + http_status=$(curl --proxy "${proxyServerUrl}" -s -o "$TEMP_PAC_PATH" -w "%{http_code}" "${pacFileDownloadUrl}") + log "HTTP status code: $http_status" + # Check if the fetch was successful + if [[ "$http_status" -ne 200 ]]; then + log "Error: Failed to download pac file from ${pacFileDownloadUrl}. HTTP status code: $http_status" + rm -f "$TEMP_PAC_PATH" # Clean up temporary file + exit 2 + fi + # Verify the downloaded file is not empty + if [[ ! -s "$TEMP_PAC_PATH" ]]; then + log "Error: The downloaded pac file is empty." + rm -f "$TEMP_PAC_PATH" # Clean up temporary file + exit 3 + fi + # Log the download success + log "Pac file downloaded successfully. Proceeding with update..." + # Copy the content from the temporary pac file to the target file + log "Copying the content from temporary file to the target pac file at $LOCAL_PAC_PATH..." + # Check if the copy was successful + if cat "$TEMP_PAC_PATH" > "$LOCAL_PAC_PATH"; then + log "Pac file successfully updated at $LOCAL_PAC_PATH." + else + log "Error: Failed to update the pac file at $LOCAL_PAC_PATH." + rm -f "$TEMP_PAC_PATH" # Clean up temporary file + exit 4 + fi + # Clean up temporary file + rm -f "$TEMP_PAC_PATH" + log "Pac file fetch and update process completed successfully." + exit 0 + ''; + }; +in +{ + options.ghaf.reference.services.pac = { + enable = lib.mkEnableOption "Proxy Auto-Configuration (PAC)"; + + proxyAddress = lib.mkOption { + type = lib.types.str; + description = "Proxy address"; + }; + + proxyPort = lib.mkOption { + type = lib.types.int; + description = "Proxy port"; + }; + + pacUrl = lib.mkOption { + type = lib.types.str; + description = "URL of the Proxy Auto-Configuration (PAC) file"; + default = "https://raw.githubusercontent.com/tiiuae/ghaf-rt-config/refs/heads/main/network/proxy/ghaf.pac"; + }; + + proxyPacUrl = lib.mkOption { + type = lib.types.str; + description = "Local PAC URL that can be passed to the browser"; + default = "http://${pacServerAddr}/${pacFileName}"; + readOnly = true; + }; + }; + + config = lib.mkIf cfg.enable { + # Define a new group for proxy management + users.groups.${proxyGroupName} = { }; # Create a group named proxy-admin + # Define a new user with a specific username + users.users.${proxyUserName} = { + isSystemUser = true; + description = "Proxy User for managing allowlist and services"; + # extraGroups = [ "${proxyGroupName}" ]; # Adding to 'proxy-admin' for specific access + group = "${proxyGroupName}"; + }; + environment.etc."proxy/${pacFileName}" = { + text = ''''; + user = "${proxyUserName}"; # Owner is proxy-user + group = "${proxyGroupName}"; # Group is proxy-admin + mode = "0664"; # Permissions: read/write for owner/group, no permissions for others + }; + systemd.services.pacServer = { + description = "Http server to make PAC file accessible for web browsers"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + ExecStart = "${pkgs.busybox}/bin/busybox httpd -f -p ${pacServerAddr} -h /etc/proxy"; + # Ensure ghafFetchUrl starts after the network is up + Type = "simple"; + # Restart policy on failure + Restart = "always"; # Restart the service if it fails + RestartSec = "15s"; # Wait 15 seconds before restarting + User = "${proxyUserName}"; + }; + }; + systemd.services.ghafPacFileFetcher = { + description = "Fetch ghaf pac file periodically with retries if internet is available"; + serviceConfig = { + ExecStart = "${_ghafPacFileFetcher}/bin/ghafPacFileFetcher"; + # Ensure ghafFetchUrl starts after the network is up + Type = "simple"; + # Restart policy on failure + Restart = "on-failure"; # Restart the service if it fails + RestartSec = "15s"; # Wait 15 seconds before restarting + User = "${proxyUserName}"; + }; + }; + systemd.timers.ghafPacFileFetcher = { + description = "Run ghafPacFileFetcher periodically"; + wantedBy = [ "timers.target" ]; + timerConfig = { + User = "${proxyUserName}"; + Persistent = true; # Ensures the timer runs after a system reboot + OnCalendar = "daily"; # Set to your desired schedule + OnBootSec = "90s"; + }; + }; + + }; +} diff --git a/modules/reference/services/proxy-server/3proxy-config.nix b/modules/reference/services/proxy-server/3proxy-config.nix index 502a9a6ba..ed768070c 100644 --- a/modules/reference/services/proxy-server/3proxy-config.nix +++ b/modules/reference/services/proxy-server/3proxy-config.nix @@ -65,7 +65,7 @@ let deny * * * * maxconn 200 - proxy -i${netvmAddr} -p${toString cfg.bindPort} + proxy -i${cfg.internalAddress} -p${toString cfg.bindPort} flush @@ -77,6 +77,11 @@ in { options.ghaf.reference.services.proxy-server = { enable = mkEnableOption "Enable proxy server module"; + internalAddress = lib.mkOption { + type = lib.types.str; + default = netvmAddr; + description = "Internal address for proxy server"; + }; bindPort = lib.mkOption { type = lib.types.int; default = 3128;