diff --git a/modules/tools/browsers/chromium.nix b/modules/tools/browsers/chromium.nix new file mode 100644 index 00000000..aed353a0 --- /dev/null +++ b/modules/tools/browsers/chromium.nix @@ -0,0 +1,276 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: Elias Coppens +# +# SPDX-License-Identifier: MIT + +{ + config, + pkgs, + lib, + ... +}: +let + inherit (lib) + mkOption + mkEnableOption + mkIf + mapAttrsToList + concatStringsSep + ; + inherit (lib.types) + attrsOf + enum + submodule + nullOr + listOf + str + ; + inherit (import ./option-types.nix { inherit lib; }) lockFlagEnum bookmarkType proxyConfig; + + cfg = config.securix.chromium; +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 = 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. + ''; + }; + + 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 = { + enable = true; + + initialPrefs = { + # 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.u + IntranetRedirectBehavior = 3; + + # Home page != New tab page. + HomepageIsNewTabPage = false; + HomepageLocation = ""; + # 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; + }; + + extraOpts = { + # Isolate all origins into their own process/sandbox. + IsolateOrigins = true; + # Block any external extension to install. + BlockExternalExtensions = true; + # 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. + GenAiSettings = 2; + BuiltInAIAPIIsEnabled = 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; + + # HTTP proxy + 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; + # TODO: expose an option called `cfg.proxy.autoConfigFailSafe` + ProxyPacMandatory = false; + ProxyPacUrl = mkIf (cfg.proxy.autoConfigUrl != null) cfg.proxy.autoConfigUrl; + ProxyServer = mkIf (cfg.proxy.httpProxy != null) cfg.proxy.httpProxy; + }; + + # 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; + }; + + inherit (cfg) extensions; + }; + }; +} diff --git a/modules/tools/browsers/default.nix b/modules/tools/browsers/default.nix new file mode 100644 index 00000000..d68f32f0 --- /dev/null +++ b/modules/tools/browsers/default.nix @@ -0,0 +1,134 @@ +# 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 tobe used. + + This is required for websites like Netflix or YouTube. + ''; + + lockFlags = mkOption { + type = types.listOf 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" + ] + ); + # This is the default recommended one. + default = [ "chromium" ]; + }; + + extensions = mkOption { + type = types.attrsOf (types.either types.attrs types.list); + description = '' + Per-browser attribute set of list 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"; }; + chromium = lib.mkDefault [ + "ddkjiahejlhfcafbddmgiahcphecmpfh" # uBlock Origin Lite + ]; + }; + + 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 or [ ]; + inherit (cfg) bookmarks lockFlags; + inherit homepage; + }; + + securix.chromium = { + enable = lib.elem "chromium" cfg.browsers; + inherit (cfg) enableEncryptedMediaExtensions; + extensions = cfg.extensions.chromium or { }; + 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..e91f908f --- /dev/null +++ b/modules/tools/browsers/firefox.nix @@ -0,0 +1,218 @@ +# 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 + 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 = 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. + ''; + }; + + 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 = { + 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; + # // (listToAttrs [ + # (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. + # 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..9264b088 --- /dev/null +++ b/modules/tools/browsers/homepage-dashboard.nix @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2025 Ryan Lahfa +# SPDX-FileContributor: Elias Coppens +# +# SPDX-License-Identifier: MIT + +{ config, lib, ... }: +let + cfg = config.securix.homepage-dashboard; + inherit (lib) mkIf mkOption mkEnableOption; +in +{ + 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 ab13e6a1..8386dbee 100644 --- a/modules/tools/default.nix +++ b/modules/tools/default.nix @@ -4,7 +4,7 @@ { pkgs, ... }: { - imports = [ ./firefox.nix ]; + imports = [ ./browsers ]; programs.mtr.enable = true; diff --git a/modules/tools/firefox.nix b/modules/tools/firefox.nix deleted file mode 100644 index a106f7d6..00000000 --- a/modules/tools/firefox.nix +++ /dev/null @@ -1,179 +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"; - }; - }; -}