feat(portal): add service drawers and employee portal preview
This commit is contained in:
39 files changed
+1446
-130
No files matched your search
@@ -0,0 +1,72 @@
|
||||
# Inactive SOPS/age preparation, 2026-10-30
|
||||
|
||||
The owner selected SOPS, age and sops-nix. This directory is a template, not a
|
||||
host configuration. The development flake remains unchanged. No real recipient,
|
||||
key, encrypted secret or server user is supplied. No module is deployed.
|
||||
|
||||
Official references inspected for this pass:
|
||||
- [SOPS documentation](https://getsops.io/docs/)
|
||||
- [sops-nix installation and secret permissions](https://github.com/Mic92/sops-nix)
|
||||
|
||||
SOPS encrypts configuration at rest. sops-nix can supply decrypted values at
|
||||
runtime under `/run/secrets` with explicit owners and modes. This does not make
|
||||
browser ciphertext or publicly served documents private.
|
||||
|
||||
## Owner setup before activation
|
||||
|
||||
1. Supply the actual NixOS topology, host flake, deploy user, persisted key
|
||||
location, backup owner, HTTPS termination and resource-service identity.
|
||||
2. Pin a reviewed `github:Mic92/sops-nix` input in that host flake; import
|
||||
`inputs.sops-nix.nixosModules.sops` and the reviewed template. Do not add the
|
||||
input to the Astro development flake just to evaluate a deployment sketch.
|
||||
3. The owner generates independent developer/recovery and VPS age identities in
|
||||
private terminals. Record only public recipients in the reviewed SOPS
|
||||
creation rule. Use distinct recipients, never a placeholder recipient. Keep
|
||||
the VPS private identity on persistent root-controlled storage (0600), outside
|
||||
`/nix/store`, Git, the public static root and CI artifacts.
|
||||
4. Encrypt the approved credential material using SOPS with both recipients.
|
||||
Review recipient access, least privilege and an offline recovery copy. SOPS
|
||||
encrypted files may enter a deployment repository only after owner approval.
|
||||
5. Set `aia.portalSecrets` options explicitly, then evaluate the complete host
|
||||
configuration and test decryption on authorized nonproduction infrastructure.
|
||||
Runtime consumers use the generated `.path`, not Nix evaluation of plaintext.
|
||||
6. Decide which service reloads after rotation. Rotate application credentials
|
||||
as well as SOPS recipients when revoking access: rewrapping alone does not
|
||||
invalidate an already copied password. Test recovery and key restoration.
|
||||
|
||||
## Separate staff and management resource authorization
|
||||
|
||||
Keep original documents outside Astro `public`, `dist`, release archives and
|
||||
CDN roots. Proposed delivery namespaces are `/staff-resources/` and
|
||||
`/management-resources/`; neither is implemented or considered working here.
|
||||
Use nginx `auth_request` to an approved authorization service or an equivalent
|
||||
server-enforced policy with individually revocable identities and staff and
|
||||
management roles. Staff credentials must not authorize management resources;
|
||||
management access to staff resources must be an explicit policy. Backend file
|
||||
locations must be internal, inaccessible through static aliases or alternate
|
||||
paths. Never trust a browser-provided role header.
|
||||
|
||||
A separate shared Basic Auth credential file per namespace can be considered
|
||||
only as an explicitly approved weaker interim model, not identity management or
|
||||
automatic role inheritance. The template prepares separate secret inputs without
|
||||
selecting that access model or installing an identity provider.
|
||||
|
||||
Use HTTPS, `Cache-Control: private, no-store`, no shared/CDN caching, and
|
||||
`Referrer-Policy: no-referrer` for protected delivery. Avoid credentials and
|
||||
sensitive resource paths in access/error logs; review monitoring and backups.
|
||||
Authorization failures must fail closed, including at proxy/cache boundaries.
|
||||
Test direct requests, alternate encodings, role crossover and rollback.
|
||||
|
||||
## Distinct boundaries and release checks
|
||||
|
||||
- SOPS: deployment secrets at rest and controlled runtime access.
|
||||
- Portal HTML: shared-code browser decryption, offline-guessable access friction.
|
||||
- Documents: require server authorization even if URLs are encrypted in the UI.
|
||||
|
||||
The compatibility preview cannot grant production portal access. Before cutover,
|
||||
classify every document, implement and verify its delivery destination, rewrite
|
||||
and re-encrypt links, test two-view separation, rotate shared codes privately,
|
||||
check the public artifact for private files, and obtain owner/security approval.
|
||||
Back up encrypted configuration and documents separately from keys; rehearse
|
||||
restore and rollback. Revoking a static release cannot retract ciphertext or
|
||||
files already downloaded by visitors. Site cutover is still unauthorized.
|
||||
@@ -0,0 +1,26 @@
|
||||
# INACTIVE TEMPLATE. Not imported by the development flake or any deployed host.
|
||||
# Requires an owner-reviewed sops-nix NixOS module import in the host flake.
|
||||
{ config, lib, ... }:
|
||||
let cfg = config.aia.portalSecrets;
|
||||
in {
|
||||
options.aia.portalSecrets = {
|
||||
enable = lib.mkEnableOption "owner-reviewed portal deployment secrets";
|
||||
encryptedFile = lib.mkOption { type = lib.types.path; description = "SOPS-encrypted YAML only; never a plaintext file"; };
|
||||
ageKeyFile = lib.mkOption { type = lib.types.str; description = "Private persisted host age key path outside the Nix store"; };
|
||||
readerUser = lib.mkOption { type = lib.types.str; description = "Approved resource authorization service user"; };
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [{ assertion = lib.hasPrefix "/" cfg.ageKeyFile && !(lib.hasPrefix "/nix/store/" cfg.ageKeyFile);
|
||||
message = "The host age identity must be an absolute runtime path outside the Nix store."; }];
|
||||
sops.defaultSopsFile = cfg.encryptedFile;
|
||||
sops.age.keyFile = cfg.ageKeyFile;
|
||||
sops.age.generateKey = false;
|
||||
sops.age.sshKeyPaths = [];
|
||||
sops.gnupg.sshKeyPaths = [];
|
||||
sops.secrets."portal/staff-credentials" = { owner = cfg.readerUser; mode = "0400"; };
|
||||
sops.secrets."portal/management-credentials" = { owner = cfg.readerUser; mode = "0400"; };
|
||||
# Consumers reference config.sops.secrets.<name>.path at runtime.
|
||||
# Do not use builtins.readFile on a decrypted secret, environment.etc.text,
|
||||
# PUBLIC_* values, or Astro/Vite imports to transport secrets.
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user