diff --git a/docs/manual/src/user/quick_start.md b/docs/manual/src/user/quick_start.md index af76f3ed..daaa546e 100644 --- a/docs/manual/src/user/quick_start.md +++ b/docs/manual/src/user/quick_start.md @@ -20,7 +20,7 @@ In your inventory, you can organize items into two main sections: *machines* and At this point, you can customize your NixOS system. You have two options: -* Use the modules provided by Sécurix, such as `securix.firefox` for Firefox configuration. +* Use the modules provided by Sécurix, such as `securix.browser` for browser configuration. * Alternatively, you can use standard NixOS modules for system customization. ## 4. Deploy the USB Installer diff --git a/modules/tools/browsers/chromium.nix b/modules/tools/browsers/chromium.nix new file mode 100644 index 00000000..bacc691b --- /dev/null +++ b/modules/tools/browsers/chromium.nix @@ -0,0 +1,301 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: Elias Coppens +# +# SPDX-License-Identifier: MIT + +{ + config, + pkgs, + lib, + ... +}: +let + inherit (lib) + mkOption + mkEnableOption + mkIf + mapAttrsToList + concatStringsSep + optionalAttrs + ; + inherit (lib.types) + attrsOf + enum + submodule + nullOr + listOf + str + ; + inherit (import ./option-types.nix { inherit lib; }) lockFlagEnum bookmarkType proxyConfig; + + cfg = config.securix.chromium; + + # `extraOpts` is a plain attribute set: a nested `mkIf` is not discharged and + # would land verbatim in the policy JSON. + proxySettings = optionalAttrs (cfg.proxy != null) { + ProxySettings = { + ProxyMode = + if cfg.proxy.httpProxy != null then + "fixed_servers" + else if cfg.proxy.autoConfigURL != null then + "pac_script" + else + "auto_detect"; + ProxyBypassList = concatStringsSep "," cfg.proxy.noProxy; + } + // optionalAttrs (cfg.proxy.autoConfigURL != null) { + ProxyPacUrl = cfg.proxy.autoConfigURL; + # Do not fall back to a direct connection when the PAC script cannot be + # fetched. + # TODO: expose an option called `cfg.proxy.autoConfigFailSafe` + ProxyPacMandatory = true; + } + // optionalAttrs (cfg.proxy.httpProxy != null) { ProxyServer = cfg.proxy.httpProxy; }; + }; +in +{ + options.securix.chromium = { + enable = mkEnableOption "Chromium pre-configuration"; + enableEncryptedMediaExtensions = mkEnableOption '' + allow encrypted media extensions to be used. + + This is required for websites like Netflix or YouTube. + ''; + + lockFlags = mkOption { + type = listOf (enum lockFlagEnum); + + default = [ + "allow-default-overrides" + "allow-user-messaging-overrides" + ]; + + description = '' + The lock flags determine how locked down the Chromium configuration is. + + By default, we do not let the user install any extension, but we still + let them modify the Chromium defaults. + ''; + }; + + proxy = mkOption { + type = nullOr (submodule proxyConfig); + default = null; + description = '' + Proxy configuration for this instance of Chromium. + By default, it configures nothing. + ''; + }; + + homepage = mkOption { + type = nullOr str; + default = null; + description = '' + URL of the home page, or `null` to leave the browser default alone. + ''; + }; + + extensions = mkOption { + type = listOf str; + default = [ ]; + example = [ + "gcbommkclmclpchllfjekcdonpmejbdp" # https everywhere + "cjpalhdlnbpafiamejdnhcphjbkeiagm" # ublock origin + ]; + description = '' + List of extension IDs to install from the Chrome store. + ''; + }; + + bookmarks = mkOption { + type = attrsOf (attrsOf (submodule bookmarkType)); + default = { }; + example = '' + { + Productivity = { + Github = { + href = "https://github.com"; + icon = "github.png"; + }; + }; + + Entertainment = { + Youtube = { + href = "https://youtube.com"; + icon = "si-youtube"; + }; + }; + } + ''; + description = '' + Bookmarks to show to homepage and firefox bookmarks. + ''; + }; + }; + + config = mkIf cfg.enable { + # programs.chromium only writes policy files, it installs nothing. + environment.systemPackages = [ pkgs.chromium ]; + + programs.chromium = { + enable = true; + + homepageLocation = cfg.homepage; + + extraOpts = { + # Always show the bookmark bar. + BookmarkBarEnabled = true; + # Always let the user create more profiles. + BrowserAddPersonEnabled = true; + # Always let the user use the guest mode if they want. + BrowserGuestModeEnabled = true; + # Let the user edit bookmarks if needed. + EditBookmarksEnabled = true; + + # TODO: allow customization of the label. + EnterpriseCustomLabel = "Securix"; + EnterpriseCustomLabelForBrowser = "Securix"; + # EnterpriseLogoUrl = ""; + # EnterpriseLogoUrlForBrowser = ""; + # TODO: should we enable this security? + # EnterpriseRealTimeUrlCheckMode = false; + + # Try to upgrade connections to HTTPS as much as possible. + HttpsUpgradesEnabled = true; + + # Here are a bunch of power savings knobs. + # We try to optimize for a moderate power saving experience + # by default. + HighEfficiencyModeEnabled = true; + IntensiveWakeUpThrottlingEnabled = true; + MemorySaverModeSavings = 1; # 0 or 2 + BatterySaverModeAvailability = true; + + # Allow DNS interception to determine whether + # we have a proxy that knows how to deal with certain DNS. + # Allow to suggest "Try http://intranet" error messages. + IntranetRedirectBehavior = 3; + + # Home page != New tab page. + HomepageIsNewTabPage = false; + # Always restore previous tabs on startup. + RestoreOnStartup = 1; + # Show the home button. + ShowHomeButton = true; + + # In general, Autoplay is never a fun feature. + AutoplayAllowed = false; + + # Some websites may require it. + BlockThirdPartyCookies = true; + + # Isolate all origins into their own process/sandbox. + # IsolateOrigins takes a list of origins, SitePerProcess is the + # blanket switch. + SitePerProcess = true; + # Block any external extension to install. + BlockExternalExtensions = true; + # By default, we will block any extension install, mirroring what the + # Firefox side does. Unlocking it is opt-in through the lock flags. + ExtensionInstallBlocklist = lib.optionals (!lib.elem "allow-extension-installs" cfg.lockFlags) [ + "*" + ]; + # Block developer mode for extensions. + ExtensionDeveloperModeSettings = 1; + + # Allow system CA certificates. + CAPlatformIntegrationEnabled = true; + # Let the user only provision *user* certificates. + CACertificateManagementAllowed = 1; + + # Do not use Chromium native password manager. + PasswordManagerEnabled = false; + + # Forbid all generative AI from Google. + GenAiDefaultSettings = 2; # Do not allow GenAI features. + BuiltInAIAPIsEnabled = false; + + # Do not let the browser use Google to obtain accurate time information. + BrowserNetworkTimeQueriesEnabled = false; + + # Disable Google feedback surveys. + FeedbackSurveysEnabled = false; + # Disable Google Web Store icon. + HideWebStoreIcon = true; + # Disable any telemetry to Google. + MetricsReportingEnabled = false; + # Disable any advertising from Google. + PromotionsEnabled = false; + # Do not recommend media. + MediaRecommendationsEnabled = false; + # Do not report domain reliability to Google. + DomainReliabilityAllowed = false; + # Ensure that the Accept-Language and navigator.languages options + # are privacy-preserving. + ReduceAcceptLanguageEnabled = true; + + # These options controls nudges to the user + # to restart Chromium to benefit from updates. + # Sometimes, critical security updates. + # Or 1 + RelaunchNotification = 2; + # Every day. + RelaunchNotificationPeriod = 86400000; + + # RequireOnlineRevocationChecksForLocalAnchors = true; + + # Do not allow the browser connect to a Google account. + # Even if the user logs in to any Google service. + BrowserSignin = 0; + + # Disable the usage of built-in DNS client. + # Use the system DNS resolver. + BuiltInDnsClientEnabled = false; + + # TODO: allow DOH by default? + # More secure than usual DNS. + # DnsOverHttpsMode = "automatic"; + # DnsOverHttpsTemplates = ""; + + # Do not care if Chromium is by default. + DefaultBrowserSettingEnabled = false; + + # New Tab parameters. + # Provide some customizations options someday. + NTPCardsVisible = false; + NTPCustomBackgroundEnabled = false; + NTPFooterExtensionAttributionEnabled = true; + NTPFooterManagementNoticeEnabled = true; + + # Future architecture for the PDF viewer. + PdfViewerOutOfProcessIframeEnabled = true; + + # Enable PQC options by default. + PostQuantumKeyAgreementEnabled = true; + # Enable HTTP/3 QUIC by default. + QuicAllowed = true; + # Do not let WebAuthn store credentials on broken TLS certificates. + AllowWebAuthnWithBrokenTlsCerts = false; + + # Pre-installed bookmarks + ManagedBookmarks = + let + mkItems = mapAttrsToList ( + name: { href, ... }: { + inherit name; + url = href; + } + ); + mkChildren = folder: values: { + children = mkItems values; + name = folder; + }; + in + mapAttrsToList mkChildren cfg.bookmarks; + } + // proxySettings; + + extensions = if cfg.extensions == [ ] then null else cfg.extensions; + }; + }; +} diff --git a/modules/tools/browsers/default.nix b/modules/tools/browsers/default.nix new file mode 100644 index 00000000..e1b3a7a8 --- /dev/null +++ b/modules/tools/browsers/default.nix @@ -0,0 +1,161 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# +# SPDX-License-Identifier: MIT + +{ config, lib, ... }: +let + cfg = config.securix.browser; + inherit (lib) + mkIf + mkEnableOption + types + mkOption + ; + inherit (import ./option-types.nix { inherit lib; }) lockFlagEnum bookmarkType; + + homepage = if cfg.enableLocalHomepage then "http://127.0.0.1:8082" else cfg.homepage; +in +{ + options.securix.browser = { + enable = mkEnableOption "browser preconfiguration"; + enableLocalHomepage = mkEnableOption "the local dynamic homepage"; + enableEncryptedMediaExtensions = mkEnableOption '' + allow encrypted media extensions to be used. + + This is required for websites like Netflix or YouTube. + ''; + + lockFlags = mkOption { + type = types.listOf (types.enum lockFlagEnum); + description = '' + The lock flags determine how locked down the browser configuration is. + + By default, we do not let the user install any extension, but we still + let them modify the defaults. + ''; + }; + + homepage = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + URL to the home page. + The home page is usually locked by default. + ''; + }; + + browsers = mkOption { + type = types.listOf ( + types.enum [ + "firefox" + "chromium" + ] + ); + # Sécurix shipped Firefox only, keep it as the default. + default = [ "firefox" ]; + description = '' + Browsers to install and preconfigure. + ''; + }; + + extensions = mkOption { + type = types.submodule { + options = { + firefox = mkOption { + type = types.attrsOf types.str; + default = { }; + description = '' + Firefox extensions, keyed by their short ID in the Mozilla store, + valued by their UUID. + ''; + }; + + chromium = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Chromium extension IDs to install from the Chrome web store. + ''; + }; + }; + }; + default = { }; + description = '' + Per-browser set of extensions to install in each instance. + ''; + }; + + bookmarks = mkOption { + type = types.attrsOf (types.attrsOf (types.submodule bookmarkType)); + example = '' + { + Productivity = { + Github = { + href = "https://github.com"; + icon = "github.png"; + }; + }; + + Entertainment = { + Youtube = { + href = "https://youtube.com"; + icon = "si-youtube"; + }; + }; + } + ''; + description = '' + Folders of bookmarks with their icons and link target. + ''; + }; + }; + + imports = [ + ./firefox.nix + ./chromium.nix + ./homepage-dashboard.nix + ]; + + config = mkIf cfg.enable { + securix.browser = { + # NOTE: this is a backward compatibility default. + enableLocalHomepage = lib.mkDefault (lib.elem "firefox" cfg.browsers); + extensions = { + firefox = lib.mkDefault { + ublock-origin = "uBlock0@raymondhill.net"; + bitwarden-password-manager = "{446900e4-71c2-419f-a6a7-df9c091e268b}"; + }; + chromium = lib.mkDefault [ + "ddkjiahejlhfcafbddmgiahcphecmpfh" # uBlock Origin Lite + "nngceckbapebfimnlniiiahkandclblb" # Bitwarden Password Manager + ]; + }; + + lockFlags = lib.mkDefault [ + "allow-user-messaging-overrides" + "allow-default-overrides" + ]; + }; + + securix.homepage-dashboard = { + enable = cfg.enableLocalHomepage; + inherit (cfg) bookmarks; + }; + + securix.firefox = { + enable = lib.elem "firefox" cfg.browsers; + inherit (cfg) enableEncryptedMediaExtensions; + extensions = cfg.extensions.firefox; + inherit (cfg) bookmarks lockFlags; + inherit homepage; + }; + + securix.chromium = { + enable = lib.elem "chromium" cfg.browsers; + inherit (cfg) enableEncryptedMediaExtensions; + extensions = cfg.extensions.chromium; + inherit (cfg) bookmarks lockFlags; + inherit homepage; + }; + }; +} diff --git a/modules/tools/browsers/firefox.nix b/modules/tools/browsers/firefox.nix new file mode 100644 index 00000000..de7dc8a9 --- /dev/null +++ b/modules/tools/browsers/firefox.nix @@ -0,0 +1,222 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: Elias Coppens +# +# SPDX-License-Identifier: MIT + +{ config, lib, ... }: +let + inherit (lib) + mkOption + mkEnableOption + mkIf + mapAttrs' + ; + inherit (lib.types) + attrsOf + enum + listOf + submodule + str + nullOr + ; + inherit (import ./option-types.nix { inherit lib; }) lockFlagEnum bookmarkType proxyConfig; + + cfg = config.securix.firefox; +in +{ + options.securix.firefox = { + enable = mkEnableOption "Firefox pre-configuration"; + enableEncryptedMediaExtensions = mkEnableOption '' + allow encrypted media extensions to be used. + + This is required for websites like Netflix or YouTube. + ''; + + lockFlags = mkOption { + type = listOf (enum lockFlagEnum); + default = [ + "allow-default-overrides" + "allow-user-messaging-overrides" + ]; + + description = '' + The lock flags determine how locked down the Firefox configuration is. + + By default, we do not let the user install any extension, but we still + let them modify the Firefox defaults. + ''; + }; + + proxy = mkOption { + type = nullOr (submodule proxyConfig); + default = null; + description = '' + Proxy configuration for this instance of Firefox. + By default, it configures nothing. + ''; + }; + + homepage = mkOption { + type = nullOr str; + default = null; + description = '' + URL of the home page, or `null` to leave the browser default alone. + ''; + }; + + extensions = mkOption { + type = attrsOf str; + default = { }; + description = '' + Attribute set of extensions to install to the Firefox instance. + + The key should be the short ID of the extension in the Mozilla store. + The value should be the UUID. + ''; + example = { + bitwarden-password-manager = "{446900e4-71c2-419f-a6a7-df9c091e268b}"; + }; + }; + + bookmarks = mkOption { + type = attrsOf (attrsOf (submodule bookmarkType)); + default = { }; + example = '' + { + Productivity = { + Github = { + href = "https://github.com"; + icon = "github.png"; + }; + }; + + Entertainment = { + Youtube = { + href = "https://youtube.com"; + icon = "si-youtube"; + }; + }; + } + ''; + description = '' + Bookmarks to show to homepage and firefox bookmarks. + ''; + }; + }; + + config = mkIf cfg.enable { + programs.firefox = { + enable = true; + languagePacks = [ + "fr" + "en-US" + ]; + + policies = { + Homepage = mkIf (cfg.homepage != null) { + URL = cfg.homepage; + # By default, the user is not allowed to update the homepage. + # This can be bypassed if the lock flag contains an allow element. + Locked = !lib.elem "allow-homepage-overrides" cfg.lockFlags; + # homepage-locked will prevent the user from restoring session, that's bad UX! + StartPage = "homepage"; + }; + + Bookmarks = lib.flatten ( + map ( + folder: + map ({ name, value }: { + Title = name; + URL = value.href; + Folder = folder.name; + }) (lib.attrsToList folder.value) + ) (lib.attrsToList cfg.bookmarks) + ); + + DisplayBookmarksToolbar = "always"; + DisableProfileImport = true; + NoDefaultBookmarks = true; + NewTabPage = false; + + # Don't save password on Firefox to avoid data losses + PasswordManagerEnabled = false; + OfferToSaveLogins = false; + + # Unnecessary. + DontCheckDefaultBrowser = true; + # Firefox version is managed by Sécurix + AppAutoUpdate = false; + DisableAppUpdate = true; + + # By default, we disable DRMs APIs which makes little sense + # on an admin laptop. Office laptops might want to re-enable this. + EncryptedMediaExtensions = { + Enabled = lib.mkDefault cfg.enableEncryptedMediaExtensions; + }; + + Proxy = mkIf (cfg.proxy != null) { + Mode = + if cfg.proxy.httpProxy != null then + "manual" + else if cfg.proxy.autoConfigURL != null then + "autoConfig" + else + "autoDetect"; + + Locked = cfg.proxy.locked; + + HTTPProxy = mkIf (cfg.proxy.httpProxy != null) cfg.proxy.httpProxy; + UseHTTPProxyForAllProtocols = mkIf (cfg.proxy.httpProxy != null) true; + SOCKSVersion = 5; + + Passthrough = cfg.proxy.noProxy; + AutoConfigURL = mkIf (cfg.proxy.autoConfigURL != null) cfg.proxy.autoConfigURL; + + AutoLogin = lib.mkDefault true; + UseProxyForDNS = lib.mkDefault true; + }; + + ExtensionSettings = + let + extension = shortId: uuid: { + name = uuid; + value = { + install_url = "https://addons.mozilla.org/en-US/firefox/downloads/latest/${shortId}/latest.xpi"; + installation_mode = "normal_installed"; + }; + }; + in + { + # By default, we will block any extension installs. + # This is what makes the most sense on an admin laptop + # and any IT operated asset. + # In certain cases, the browser could be unlocked to simplify operations + # e.g. you use your own extension store. + "*".installation_mode = + if lib.elem "allow-extension-installs" cfg.lockFlags then "allowed" else "blocked"; + } + // mapAttrs' extension cfg.extensions; + + DisablePocket = true; + DisableFirefoxAccounts = true; + DisableTelemetry = true; + DisableFirefoxStudies = true; + + UserMessaging = { + ExtensionRecommendations = false; + UrlbarInterventions = false; + MoreFromMozilla = false; + FirefoxLabs = false; + # If people wants to get spammed by Firefox… They can. + # We may want to lock down user messaging for some hardening reason. + Locked = !lib.elem "allow-user-messaging-overrides" cfg.lockFlags; + }; + }; + + # By default, we would allow the user to override the preferences. + # In certain cases, we may want to lock further down this. + preferencesStatus = + if lib.elem "allow-default-overrides" cfg.lockFlags then "default" else "locked"; + }; + }; +} diff --git a/modules/tools/browsers/homepage-dashboard.nix b/modules/tools/browsers/homepage-dashboard.nix new file mode 100644 index 00000000..08203077 --- /dev/null +++ b/modules/tools/browsers/homepage-dashboard.nix @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: Elias Coppens +# SPDX-FileContributor: 2026 risk-alt +# +# SPDX-License-Identifier: MIT + +{ config, lib, ... }: +let + cfg = config.securix.homepage-dashboard; + inherit (lib) mkIf mkOption mkEnableOption; + inherit (lib.types) attrsOf submodule; + inherit (import ./option-types.nix { inherit lib; }) bookmarkType; +in +{ + options.securix.homepage-dashboard = { + enable = mkEnableOption "the local homepage dashboard"; + + bookmarks = mkOption { + type = attrsOf (attrsOf (submodule bookmarkType)); + default = { }; + description = '' + Folders of bookmarks to display on the local homepage. + ''; + }; + }; + + config = mkIf cfg.enable { + # This spawns the dashboard on 127.0.0.1:8082. + services.homepage-dashboard = { + enable = true; + + bookmarks = map ({ name, value }: { + ${name} = map ({ name, value }: { ${name} = [ value ]; }) (lib.attrsToList value); + }) (lib.attrsToList cfg.bookmarks); + }; + }; +} diff --git a/modules/tools/browsers/option-types.nix b/modules/tools/browsers/option-types.nix new file mode 100644 index 00000000..62c2271a --- /dev/null +++ b/modules/tools/browsers/option-types.nix @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: Elias Coppens +# +# SPDX-License-Identifier: MIT + +{ lib, ... }: +let + inherit (lib) mkOption; + inherit (lib.types) + str + bool + listOf + nullOr + ; +in +{ + bookmarkType = { + options = { + icon = mkOption { + type = str; + default = ""; + description = '' + Name of the icon of the bookmark. + ''; + }; + + href = mkOption { + type = str; + description = '' + URL of the website that the bookmark points to. + ''; + }; + + description = mkOption { + type = str; + default = ""; + description = '' + Description of the website that the bookmark points to. + ''; + }; + }; + }; + + # To support browser-specific lock flag, + # expand the browser-specific enum, not this one. + lockFlagEnum = [ + "allow-extension-installs" + "allow-default-overrides" + "allow-user-messaging-overrides" + "allow-homepage-overrides" + ]; + + proxyConfig = { + options = { + locked = mkOption { + type = bool; + default = false; + description = '' + Whether the proxy options can be changed or not by the user. + + By default, it is always possible as an admin user may need + to workaround a broken proxy configuration. + + Defense against proxy bypasses cannot rely on this option. + + This can be locked to avoid user errors, a firewall configuration + needs to be enabled to ensure security. + ''; + }; + + noProxy = mkOption { + type = listOf str; + default = [ ]; + description = '' + List of exempted URIs for the proxy. + ''; + }; + + autoConfigURL = mkOption { + type = nullOr str; + default = null; + description = '' + PAC URL to automatically configure the proxy. + + https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file + ''; + }; + + httpProxy = mkOption { + type = nullOr str; + default = null; + description = '' + URL to the HTTP proxy. + This proxy will be used for: SSL, FTP, SOCKS5 as well. + + SOCKS4 is not supported. + ''; + }; + }; + }; +} diff --git a/modules/tools/default.nix b/modules/tools/default.nix index 142d2ca8..804e9649 100644 --- a/modules/tools/default.nix +++ b/modules/tools/default.nix @@ -15,13 +15,16 @@ let cfg = config.securix.tools; in { - imports = [ ./firefox.nix ]; + imports = [ ./browsers ]; options.securix.tools = { enable = mkEnableOption "Install tools"; }; config = mkIf cfg.enable { + # Sécurix ships a preconfigured browser out of the box. + securix.browser.enable = lib.mkDefault true; + programs.mtr.enable = true; environment.systemPackages = with pkgs; [ @@ -97,8 +100,6 @@ in glibcInfo man-pages man-pages-posix - # Browser - firefox qrencode ]; }; diff --git a/modules/tools/firefox.nix b/modules/tools/firefox.nix deleted file mode 100644 index 9c907e65..00000000 --- a/modules/tools/firefox.nix +++ /dev/null @@ -1,168 +0,0 @@ -# SPDX-FileCopyrightText: 2025 Ryan Lahfa -# SPDX-FileContributor: Elias Coppens -# -# SPDX-License-Identifier: MIT - -{ - config, - pkgs, - lib, - ... -}: -let - inherit (lib) listToAttrs mkOption; - inherit (lib.types) attrsOf submodule str; - - cfg = config.securix.firefox; - - bookmarkType = submodule { - options = { - icon = mkOption { - type = str; - default = ""; - description = '' - Name of the icon of the bookmark. - ''; - }; - - href = mkOption { - type = str; - description = '' - URL of the website that the bookmark points to. - ''; - }; - - description = mkOption { - type = str; - default = ""; - description = '' - Description of the website that the bookmark points to. - ''; - }; - }; - }; -in -{ - options.securix.firefox.bookmarks = mkOption { - type = attrsOf (attrsOf bookmarkType); - default = { }; - example = '' - { - Productivity = { - Github = { - href = "https://github.com"; - icon = "github.png"; - }; - }; - - Entertainment = { - Youtube = { - href = "https://youtube.com"; - icon = "si-youtube"; - }; - }; - } - ''; - description = '' - Bookmarks to show to homepage and firefox bookmarks. - ''; - }; - - config = { - # This spawns the dashboard on 127.0.0.1:8082. - services.homepage-dashboard = { - enable = true; - - bookmarks = map ({ name, value }: { - ${name} = map ({ name, value }: { ${name} = [ value ]; }) (lib.attrsToList value); - }) (lib.attrsToList cfg.bookmarks); - }; - - programs.firefox = { - enable = true; - languagePacks = [ - "fr" - "en-US" - ]; - - nativeMessagingHosts.packages = [ pkgs.tridactyl-native ]; - - policies = { - Homepage = { - # Connect to the local dashboard. - URL = "http://127.0.0.1:8082"; - # The user cannot change the homepage. All changes should go via Sécurix. - Locked = true; - # homepage-locked will prevent the user from restoring session, that's bad UX! - StartPage = "homepage"; - }; - - Bookmarks = lib.flatten ( - map ( - folder: - map ({ name, value }: { - Title = name; - URL = value.href; - Folder = folder.name; - }) (lib.attrsToList folder.value) - ) (lib.attrsToList cfg.bookmarks) - ); - DisplayBookmarksToolbar = "always"; - DisableProfileImport = true; - NoDefaultBookmarks = true; - NewTabPage = false; - - # Don't save password on Firefox to avoid data losses - PasswordManagerEnabled = false; - OfferToSaveLogins = false; - - # Unnecessary. - DontCheckDefaultBrowser = true; - # Firefox version is managed by Sécurix - AppAutoUpdate = false; - DisableAppUpdate = true; - - # You are not supposed to watch Netflix on Sécurix. - EncryptedMediaExtensions = { - Enabled = lib.mkDefault false; - }; - - ExtensionSettings = - let - extension = shortId: uuid: { - name = uuid; - value = { - install_url = "https://addons.mozilla.org/en-US/firefox/downloads/latest/${shortId}/latest.xpi"; - installation_mode = "normal_installed"; - }; - }; - in - { - # Block all manual extension install. You NEED to propose your extension to the Sécurix repository. - "*".installation_mode = "blocked"; - } - // (listToAttrs [ - (extension "ublock-origin" "uBlock0@raymondhill.net") - (extension "bitwarden-password-manager" "{446900e4-71c2-419f-a6a7-df9c091e268b}") - ]); - - DisablePocket = true; - DisableFirefoxAccounts = true; - DisableTelemetry = true; - DisableFirefoxStudies = true; - - UserMessaging = { - ExtensionRecommendations = false; - UrlbarInterventions = false; - MoreFromMozilla = false; - FirefoxLabs = false; - # If people wants to get spammed by Firefox… They can. - Locked = false; - }; - }; - - # Let the user override the default. - preferencesStatus = "default"; - }; - }; -} diff --git a/tests/browsers.nix b/tests/browsers.nix new file mode 100644 index 00000000..a5113b55 --- /dev/null +++ b/tests/browsers.nix @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2026 risk-alt +# +# SPDX-License-Identifier: MIT + +{ pkgs, libSecurix }: +let + terminal = libSecurix.mkTerminal { + name = "browsers"; + userSpecificModule = { }; + vpnProfiles = { }; + modules = [ + { + securix = { + graphical-interface.variant = "sway"; + tools.enable = true; + self = { + mainDisk = "/dev/nvme0n1"; + machine = { + hardwareSKU = "x280"; + serialNumber = "000000"; + inventoryId = 0; + }; + }; + + browser = { + browsers = [ + "firefox" + "chromium" + ]; + + bookmarks.Productivity.Github = { + href = "https://github.com"; + icon = "si-github"; + }; + }; + }; + } + ]; + }; +in +pkgs.testers.nixosTest { + name = "browsers"; + nodes = { + securix-unbranded-0 = { + imports = terminal.modules; + }; + }; + testScript = '' + import json + + securix = securix_unbranded_0 + securix.wait_for_unit("default.target") + + firefox = json.loads(securix.succeed("cat /etc/firefox/policies/policies.json"))["policies"] + assert firefox["Homepage"]["URL"] == "http://127.0.0.1:8082", firefox["Homepage"] + assert { + "Title": "Github", + "URL": "https://github.com", + "Folder": "Productivity", + } in firefox["Bookmarks"], firefox["Bookmarks"] + assert firefox["ExtensionSettings"]["*"]["installation_mode"] == "blocked" + + chromium = json.loads( + securix.succeed("cat /etc/chromium/policies/managed/extra.json") + ) + assert chromium["SitePerProcess"] is True + assert chromium["PasswordManagerEnabled"] is False + assert chromium["ExtensionInstallBlocklist"] == ["*"] + + chromium_default = json.loads( + securix.succeed("cat /etc/chromium/policies/managed/default.json") + ) + assert chromium_default["HomepageLocation"] == "http://127.0.0.1:8082" + + securix.succeed("test -x /run/current-system/sw/bin/firefox") + securix.succeed("test -x /run/current-system/sw/bin/chromium") + + securix.wait_for_unit("homepage-dashboard.service") + securix.wait_for_open_port(8082) + ''; +} diff --git a/tests/default.nix b/tests/default.nix index 3f6f74b8..2e53e9aa 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -9,4 +9,5 @@ idempotent-autoinstall = import ./idempotent-autoinstall.nix { inherit pkgs libSecurix; }; portail = import ./portail.nix { inherit pkgs libSecurix; }; tools = import ./tools.nix { inherit pkgs libSecurix; }; + browsers = import ./browsers.nix { inherit pkgs libSecurix; }; }