diff --git a/clients/quadraticserver/lasuite-docs.nix b/clients/quadraticserver/lasuite-docs/default.nix similarity index 96% rename from clients/quadraticserver/lasuite-docs.nix rename to clients/quadraticserver/lasuite-docs/default.nix index dff2d75..df11cfc 100644 --- a/clients/quadraticserver/lasuite-docs.nix +++ b/clients/quadraticserver/lasuite-docs/default.nix @@ -5,7 +5,7 @@ ... }: let - s3Domain = "http://127.0.0.1${config.services.minio.listenAddress}"; + s3Domain = "http://127.0.0.1${config.services.minio-vendored.listenAddress}"; cfg = config.services.lasuite-docs; in { @@ -76,7 +76,7 @@ in environmentFile = config.age.secrets."lasuiteSecrets.age".path; }; - minio = { + minio-vendored = { enable = true; rootCredentialsFile = config.age.secrets."minioCredentials.age".path; }; @@ -135,5 +135,6 @@ in nixpkgs.config.permittedInsecurePackages = [ "minio-2025-10-15T17-29-55Z" + "minio-client-2025-08-13T08-35-41Z" ]; } diff --git a/clients/quadraticserver/lasuite-docs/minio.nix b/clients/quadraticserver/lasuite-docs/minio.nix new file mode 100644 index 0000000..8bed6cd --- /dev/null +++ b/clients/quadraticserver/lasuite-docs/minio.nix @@ -0,0 +1,317 @@ +# MinIO package and module, vendored from nixpkgs 6510408d147d8c5e6d76043e899e9251756f6a75 +# (nixos/modules/services/web-servers/minio.nix and pkgs/by-name/mi/minio/package.nix), since +# both were removed upstream because MinIO was abandoned. Only here until lasuite-docs is +# migrated to another S3 server. +# +# Changes from upstream: +# - The option is `services.minio-vendored`, since nixpkgs still reserves `services.minio` to +# show a removal message. The unit, user and paths are unchanged, so existing data keeps working. +# - The package is defined here, as `pkgs.minio` now throws, and without `passthru.tests`, as +# `nixosTests.minio` was removed too. +{ + config, + lib, + pkgs, + ... +}: + +with lib; + +let + cfg = config.services.minio-vendored; + + minio = pkgs.callPackage ( + { + lib, + buildGoModule, + fetchFromGitHub, + }: + let + # The web client verifies, that the server version is a valid datetime string: + # https://github.com/minio/minio/blob/3a0e7347cad25c60b2e51ff3194588b34d9e424c/browser/app/js/web.js#L51-L53 + # + # Example: + # versionToTimestamp "2021-04-22T15-44-28Z" + # => "2021-04-22T15:44:28Z" + versionToTimestamp = + version: + let + splitTS = builtins.elemAt (builtins.split "(.*)(T.*)" version) 1; + in + builtins.concatStringsSep "" [ + (builtins.elemAt splitTS 0) + (builtins.replaceStrings [ "-" ] [ ":" ] (builtins.elemAt splitTS 1)) + ]; + + # CopyrightYear will be printed to the CLI UI. + # Example: + # versionToYear "2021-04-22T15-44-28Z" + # => "2021" + versionToYear = version: builtins.elemAt (lib.splitString "-" version) 0; + in + buildGoModule (finalAttrs: { + pname = "minio"; + version = "2025-10-15T17-29-55Z"; + + src = fetchFromGitHub { + owner = "minio"; + repo = "minio"; + rev = "RELEASE.${finalAttrs.version}"; + hash = "sha256-HbjmCJYkWyRRHKriLP6QohaXYLk3QEVfi32Krq3ujjo="; + }; + + vendorHash = "sha256-BFnTJE9QFWmPsx90hDTG8MusdnwaBPYJxM5bCFk3hew="; + + doCheck = false; + + subPackages = [ "." ]; + + env.CGO_ENABLED = 0; + + tags = [ "kqueue" ]; + + ldflags = + let + t = "github.com/minio/minio/cmd"; + in + [ + "-s" + "-w" + "-X ${t}.Version=${versionToTimestamp finalAttrs.version}" + "-X ${t}.CopyrightYear=${versionToYear finalAttrs.version}" + "-X ${t}.ReleaseTag=RELEASE.${finalAttrs.version}" + "-X ${t}.CommitID=${finalAttrs.src.rev}" + ]; + + meta = { + homepage = "https://www.minio.io/"; + description = "S3-compatible object storage server"; + changelog = "https://github.com/minio/minio/releases/tag/RELEASE.${finalAttrs.version}"; + maintainers = with lib.maintainers; [ + bachp + ryan4yin + ]; + license = lib.licenses.agpl3Plus; + mainProgram = "minio"; + knownVulnerabilities = [ + "CVE-2026-40344: MinIO has an Unauthenticated Object Write via Missing Signature Verification in Unsigned-Trailer Uploads" + "CVE-2026-41145: Unauthenticated Object Write via Query-String Credential Signature Bypass in Unsigned-Trailer Uploads" + "CVE-2026-33322: JWT Algorithm Confusion in OIDC Authentication" + "CVE-2026-33419: LDAP login brute-force via user enumeration and missing rate limit" + "CVE-2026-34204: SSE Metadata Injection via Replication Headers" + "CVE-2026-39414: DoS via Unbounded Memory Allocation in S3 Select CSV Parsing" + "minio has been abandoned by upstream and security issues won't be fixed. Users should migrate to alternatives such as Garage, SeaweedFS, or Ceph. S3-compatible clients such as rclone can be used to move data." + ]; + }; + }) + ) { }; + + legacyCredentials = + cfg: + pkgs.writeText "minio-legacy-credentials" '' + MINIO_ROOT_USER=${cfg.accessKey} + MINIO_ROOT_PASSWORD=${cfg.secretKey} + ''; +in +{ + options.services.minio-vendored = { + enable = mkEnableOption "Minio Object Storage"; + + listenAddress = mkOption { + default = ":9000"; + type = types.str; + description = "IP address and port of the server."; + }; + + consoleAddress = mkOption { + default = ":9001"; + type = types.str; + description = "IP address and port of the web UI (console)."; + }; + + dataDir = mkOption { + default = [ "/var/lib/minio/data" ]; + type = types.listOf (types.either types.path types.str); + description = "The list of data directories or nodes for storing the objects. Use one path for regular operation and the minimum of 4 endpoints for Erasure Code mode."; + }; + + configDir = mkOption { + default = "/var/lib/minio/config"; + type = types.path; + description = "The config directory, for the access keys and other settings."; + }; + + certificatesDir = mkOption { + default = "/var/lib/minio/certs"; + type = types.path; + description = "The directory where TLS certificates are stored."; + }; + + accessKey = mkOption { + default = ""; + type = types.str; + description = '' + Access key of 5 to 20 characters in length that clients use to access the server. + This overrides the access key that is generated by minio on first startup and stored inside the + `configDir` directory. + ''; + }; + + secretKey = mkOption { + default = ""; + type = types.str; + description = '' + Specify the Secret key of 8 to 40 characters in length that clients use to access the server. + This overrides the secret key that is generated by minio on first startup and stored inside the + `configDir` directory. + ''; + }; + + rootCredentialsFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing the MINIO_ROOT_USER, default is "minioadmin", and + MINIO_ROOT_PASSWORD (length >= 8), default is "minioadmin"; in the format of + an EnvironmentFile=, as described by {manpage}`systemd.exec(5)`. + ''; + example = "/etc/nixos/minio-root-credentials"; + }; + + region = mkOption { + default = "us-east-1"; + type = types.str; + description = '' + The physical location of the server. By default it is set to us-east-1, which is same as AWS S3's and Minio's default region. + ''; + }; + + browser = mkOption { + default = true; + type = types.bool; + description = "Enable or disable access to web UI."; + }; + + package = mkOption { + type = types.package; + default = minio; + defaultText = literalMD "MinIO 2025-10-15T17-29-55Z, vendored above"; + description = "The MinIO package to use."; + }; + }; + + config = mkIf cfg.enable { + warnings = + optional ((cfg.accessKey != "") || (cfg.secretKey != "")) + "services.minio-vendored.`accessKey` and services.minio-vendored.`secretKey` are deprecated, please use services.minio-vendored.`rootCredentialsFile` instead."; + + systemd = lib.mkMerge [ + { + tmpfiles.rules = [ + "d '${cfg.configDir}' - minio minio - -" + ] + ++ (map (x: "d '" + x + "' - minio minio - - ") (builtins.filter lib.types.path.check cfg.dataDir)); + + services.minio = { + description = "Minio Object Storage"; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} --certs-dir=${cfg.certificatesDir} ${toString cfg.dataDir}"; + Type = "simple"; + User = "minio"; + Group = "minio"; + LimitNOFILE = 65536; + EnvironmentFile = + if (cfg.rootCredentialsFile != null) then + cfg.rootCredentialsFile + else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then + (legacyCredentials cfg) + else + null; + + # hardening + DevicePolicy = "closed"; + CapabilityBoundingSet = ""; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + "AF_UNIX" + ]; + DeviceAllow = ""; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + MemoryDenyWriteExecute = true; + LockPersonality = true; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + ProtectProc = "invisible"; + ProtectHostname = true; + UMask = "0077"; + # minio opens /proc/mounts on startup + ProcSubset = "all"; + }; + environment = { + MINIO_REGION = "${cfg.region}"; + MINIO_BROWSER = "${if cfg.browser then "on" else "off"}"; + }; + }; + } + + (lib.mkIf (cfg.rootCredentialsFile != null) { + # The service will fail if the credentials file is missing + services.minio.unitConfig.ConditionPathExists = cfg.rootCredentialsFile; + + # The service will not restart if the credentials file has + # been changed. This can cause stale root credentials. + paths.minio-root-credentials = { + wantedBy = [ "multi-user.target" ]; + + pathConfig = { + PathChanged = [ cfg.rootCredentialsFile ]; + Unit = "minio-restart.service"; + }; + }; + + services.minio-restart = { + description = "Restart MinIO"; + + script = '' + systemctl restart minio.service + ''; + + serviceConfig = { + Type = "oneshot"; + Restart = "on-failure"; + RestartSec = 5; + }; + }; + }) + ]; + + users.users.minio = { + group = "minio"; + uid = config.ids.uids.minio; + }; + + users.groups.minio.gid = config.ids.uids.minio; + }; +} diff --git a/flake.lock b/flake.lock index d048959..0e209be 100644 --- a/flake.lock +++ b/flake.lock @@ -23,11 +23,11 @@ ] }, "locked": { - "lastModified": 1790734419, - "narHash": "sha256-n7wew722m1OTBT9DOIMxNX/NF4zfsSUABAO5eW3wikE=", + "lastModified": 1792507013, + "narHash": "sha256-8tKjSQajD3mUND1GaAuaWTcuyUpgcGsh5V+l4ibzC7U=", "owner": "ryantm", "repo": "agenix", - "rev": "3daa710894355fa2fad8243380af373a2b4046ef", + "rev": "5e5b88a0c8e56f291a108c2fe9934054d9745b2e", "type": "github" }, "original": { @@ -163,11 +163,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1792171958, - "narHash": "sha256-ZpJe7MK24KnEfpbC4IsKzl1MLusbcDB3PB1SaFd0GSI=", + "lastModified": 1792473472, + "narHash": "sha256-kGZbWSNyp7DWhSo1DmF83LVxp/Gp/4qHXuylJgE7UO4=", "ref": "refs/heads/main", - "rev": "b0a5caf57606b34baa94afc5f348d0484f5c0f3b", - "revCount": 7962, + "rev": "2f93711a0969bb858fe5faa032c683096c82c333", + "revCount": 7964, "type": "git", "url": "https://forgejo.ellis.link/continuwuation/continuwuity" }, @@ -241,11 +241,11 @@ ] }, "locked": { - "lastModified": 1789770686, - "narHash": "sha256-uZkBR7yHdIKUFB5SZdfgh1qkGfI3XmYmI/lTiquxbck=", + "lastModified": 1792505528, + "narHash": "sha256-BsLGrgqKJg2o9IsjU4Xvzo8RZV9Fn1GajEasifBwqsI=", "owner": "nix-community", "repo": "disko", - "rev": "725ea35e410ad83be4931d1bff7e090eacaf3563", + "rev": "7fdf779611daf0e250889cb27e4f1f602147a8f2", "type": "github" }, "original": { @@ -306,11 +306,11 @@ "rust-overlay": [] }, "locked": { - "lastModified": 1792507568, - "narHash": "sha256-RGXq8SkxpxU0TaMk3oXbJ5SwkRDY90tvN2zIiofAVMQ=", + "lastModified": 1792509123, + "narHash": "sha256-beuuyEmQTfgLd7smvvSeVhsLF5wBgPnUeCeJpd2QD+M=", "owner": "nferhat", "repo": "fht-compositor", - "rev": "458dc48d96aa6d8aa0b834a8052f33c09700ffe8", + "rev": "a9b6a8cc2714288b3f5b2752ea7871eee21e927b", "type": "github" }, "original": { @@ -322,11 +322,11 @@ "firefox-gnome-theme": { "flake": false, "locked": { - "lastModified": 1791905368, - "narHash": "sha256-K8fIXFKovw6zOwWE96wKRyADLB+vIkf+rVGPUqdSd1g=", + "lastModified": 1792416163, + "narHash": "sha256-0M5/QfbfHwI7gP+q6IGdidxl5caGXMaEalNj2E2Rxyg=", "owner": "rafaelmardojai", "repo": "firefox-gnome-theme", - "rev": "6841588955c23c8fc0f3e067fe1a1009113f7fd2", + "rev": "fdc1af075073f93dc63e43b28a499b2251bcfcc2", "type": "github" }, "original": { @@ -724,11 +724,11 @@ ] }, "locked": { - "lastModified": 1792098797, - "narHash": "sha256-u4DIIZgznEGzByxXrxWCXK19VcMtTBmi/M9gZ7Jk9bM=", + "lastModified": 1792495086, + "narHash": "sha256-TyoYH983Nsc++1scw0SoOEKshPSJWJDxK7tTmuZ974M=", "owner": "nix-community", "repo": "home-manager", - "rev": "640725978b99076e18e069f6e7d2959124584f8f", + "rev": "ec80131710bffcdf3b8498cf41f3a0579833b023", "type": "github" }, "original": { @@ -800,11 +800,11 @@ ] }, "locked": { - "lastModified": 1792268367, - "narHash": "sha256-CsB/G1ut2CossRJ2dWg1G/RI1DA6F2F/qUcYT3ovk6k=", + "lastModified": 1792459113, + "narHash": "sha256-boQ5vhKJyoQJsozBrFVTJGiKXxcApJnc0tPdwT5iBRo=", "ref": "refs/heads/main", - "rev": "a6fe80fea294588fee8bfeb319bed70d2160bb8b", - "revCount": 1052, + "rev": "a2906746b9273f04833a1bdea36d8ffc87b2c2ec", + "revCount": 1066, "submodules": true, "type": "git", "url": "https://git.federated.nexus/Nexus/nexus" @@ -1043,11 +1043,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1791956154, - "narHash": "sha256-jBYGPi4udQ9uo4KQp9m2J0546nJVQitG15gvZSaVVAs=", + "lastModified": 1792210252, + "narHash": "sha256-Mh0PUaIv4nFY+8wAGonaEh8iBzwsmdNlwjVHmFg1Biw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "55bfb4d7437bd4e09ab7cba58a43bd68eb310044", + "rev": "09063bd1f2b36fd9fd793c36dfade7bd40a93dfc", "type": "github" }, "original": { @@ -1213,11 +1213,11 @@ ] }, "locked": { - "lastModified": 1792276809, - "narHash": "sha256-ocLR3Nn5LaZflG1wZeslUwDt7Roh5kg9ZEQ9FTV13e4=", + "lastModified": 1792505713, + "narHash": "sha256-gYLowB8hLLje3rKZhLhB0VZhUqgOOgDG3HYYaWa+fUU=", "ref": "refs/heads/main", - "rev": "0cb286a792f23f2506d5c973c074386f79d9a9f7", - "revCount": 6380, + "rev": "ef92a87d1ead6859518b532c5e4cd02e91046d99", + "revCount": 6528, "type": "git", "url": "https://git.sable.moe/SableClient/sable-next" }, diff --git a/modules/common/ssl-leakage.nix b/modules/common/ssl-leakage.nix new file mode 100644 index 0000000..8b9eb74 --- /dev/null +++ b/modules/common/ssl-leakage.nix @@ -0,0 +1,11 @@ +{ + security.pki.caCertificateBlacklist = [ + # CVE-2026-111943 + "Comodo AAA Services root" + # https://hg-edge.mozilla.org/projects/nss/rev/26d38a0c3fb81e7fb2acedbaf4c42dd21dce0517 + "emSign ECC Root CA - G3" + # https://hg-edge.mozilla.org/projects/nss/rev/8159223c48ceeb1273a1915bca95a517294ab0e0 + "emSign Root CA - C1" + "emSign ECC Root CA - C3" + ]; +} diff --git a/modules/server/caddy.nix b/modules/server/caddy.nix index ee6ae8e..961f50a 100644 --- a/modules/server/caddy.nix +++ b/modules/server/caddy.nix @@ -53,7 +53,7 @@ plugins = [ "github.com/ggicci/caddy-jwt@v1.3.0" ]; - hash = "sha256-NagDw+U1aV+NgW+gx5Sr996ZUEHZefuFIfDPu2bsD0Q="; + hash = "sha256-59TJxJMOuVzS5H9jmo3+esDFP1HLI1ov+BGC5ozgDuA="; }; globalConfig = ''