AI overhaul
This commit is contained in:
@@ -4,35 +4,212 @@ Role Name
|
||||
A brief description of the role goes here.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
# ansible-role-www
|
||||
|
||||
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
|
||||
Deploy the eom.dev website and membership services to **Kubernetes**, not the
|
||||
development Docker Compose stack.
|
||||
|
||||
Role Variables
|
||||
--------------
|
||||
## What is deployed
|
||||
|
||||
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
|
||||
- Existing namespace, `data` ReadWriteMany archive PVC, Apache, HTTP/Prometheus
|
||||
ClusterIP services, TLS ingress and optional Tor OnionService.
|
||||
- Website source from `www_repo_url` at `www_repo_version`. Static content is
|
||||
copied without deleting the persistent archive.
|
||||
- Public membership ConfigMaps, the five existing live Stripe tiers
|
||||
($4/$8/$16/$32/$64 USD monthly), optional SHKeeper prepaid crypto and Secret
|
||||
references. LDAP/Dex and both payment providers use the same application.
|
||||
- Persistent PostgreSQL 17 StatefulSet with a separate ReadWriteOnce claim, or
|
||||
an external PostgreSQL server using `verify-full`.
|
||||
- A release/configuration-specific migration Job, completed **before** new web
|
||||
and worker Deployments are applied. Failed migrations stop the role.
|
||||
- Non-root, read-only membership web pods and one reconciliation worker, resource
|
||||
limits, probes, optional CA mounts and no service-account token automounts.
|
||||
NetworkPolicy allows membership web ingress only from Apache and database
|
||||
ingress only from membership clients. Worker ingress is denied.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
The role does **not** provision LDAP accounts/groups/ACLs, Dex clients, Stripe
|
||||
products/webhooks, SHKeeper wallets, image builds or database backups. Review the
|
||||
application's deployment instructions before launch.
|
||||
|
||||
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
|
||||
## Requirements
|
||||
|
||||
Example Playbook
|
||||
----------------
|
||||
- Ansible Core 2.15+ on a Linux/WSL controller, the collections in
|
||||
[requirements.yml](requirements.yml), Kubernetes Python client and authorized
|
||||
kubeconfig. No application pod needs Kubernetes API permissions.
|
||||
- Kubernetes with StatefulSets, Jobs, Ingress/NetworkPolicy, a CNI that enforces
|
||||
NetworkPolicy, default persistent StorageClass, and archive RWX provisioner.
|
||||
- Ingress-nginx, cert-manager (`ca-issuer` by default), and the Tor controller
|
||||
with OnionService CRD when `www_onion_enabled` is true.
|
||||
- A **built and pushed** image from the www repository's membership Dockerfile,
|
||||
including Stripe and SHKeeper migrations. Set `www_membership_image` to a
|
||||
versioned tag, ideally an immutable digest. No unpublished image is assumed
|
||||
and nothing is built inside the cluster. The image contract is `/app`,
|
||||
Python/manage.py, UID/GID 10001, port 8000 and the Gunicorn default command.
|
||||
|
||||
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
|
||||
## Required configuration
|
||||
|
||||
- hosts: servers
|
||||
roles:
|
||||
- { role: username.rolename, x: 42 }
|
||||
Defaults deliberately refuse incomplete membership deployments. Configure:
|
||||
|
||||
License
|
||||
-------
|
||||
| Variable | Purpose / default |
|
||||
| --- | --- |
|
||||
| `www_namespace` | Namespace, default `www` |
|
||||
| `www_hostname` | Canonical HTTPS hostname, default **www.eom.dev** |
|
||||
| `www_membership_enabled` | Deploy membership/proxy, default true |
|
||||
| `www_membership_image` | Required versioned application image/digest |
|
||||
| `www_membership_secret_name` | Existing Secret, default `membership` |
|
||||
| `www_membership_secret_data` | Optional Vault/lookup mapping to manage Secret; default empty |
|
||||
| `www_membership_environment_overrides` | Partial public overrides merged with defaults |
|
||||
| `www_membership_tiers` | Tier mapping with label, price_id, price_label, usd_amount, group_dn |
|
||||
| `www_membership_replicas` | Web replicas, default 2 |
|
||||
| `www_membership_worker_interval` | Seconds after full sweep, default 60 |
|
||||
| `www_membership_ca_secret_name` | Optional CA Secret mounted at `/etc/membership/ca` |
|
||||
| `www_membership_stripe_enabled` | Require Stripe settings/secrets, default true |
|
||||
| `www_membership_shkeeper_enabled` | Require SHKeeper settings/key, default false |
|
||||
| `www_membership_postgres_managed` | Provision local PostgreSQL, default true |
|
||||
| `www_membership_postgres_storage_size` | Default 10Gi |
|
||||
| `www_membership_postgres_storage_class` | Empty uses cluster default |
|
||||
| `www_repo_url` / `www_repo_version` | Public source URL and ref/commit; default existing URL / HEAD |
|
||||
| `www_rollout_revision` | Optional marker to force source/config rollout |
|
||||
| `www_image_pull_secrets` | List of `{name: registry-secret}` objects |
|
||||
| `www_onion_enabled` | Retain static Tor mirror, default true |
|
||||
|
||||
BSD
|
||||
Public overrides must include nonempty `LDAP_BIND_DN`, `EMAIL_HOST`, verified
|
||||
`LDAP_PASSWORD_SCHEME: '{ARGON2}'`, and the correct Dex issuer/client/connector.
|
||||
Stripe needs `STRIPE_PORTAL_CONFIGURATION`. SMTP STARTTLS, production PostgreSQL
|
||||
and HTTPS are required; production SQLite is rejected. A complete override of
|
||||
`www_membership_environment` is possible but must preserve mandatory defaults.
|
||||
|
||||
Author Information
|
||||
------------------
|
||||
Payment flags validate operator intent; **actual application availability is
|
||||
controlled by its environment, secrets and tiers**. Disabling Stripe validation
|
||||
alone does not remove its button. Set an empty tier mapping to disable all paid
|
||||
tiers. SHKeeper needs both its flag and URL/currencies plus API key in the Secret.
|
||||
It prepays one calendar month per invoice, without automatic renewal.
|
||||
|
||||
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
|
||||
Defaults reuse product `prod_Sc9N5J0vNAdLCn` and the same five live Price IDs as
|
||||
the application. Provision dedicated `www-level-0` through `www-level-4`
|
||||
groupOfUniqueNames groups, sentinel and ACLs separately. Override group DNs if
|
||||
needed; sandbox testing requires separate test-mode prices.
|
||||
|
||||
### Secrets
|
||||
|
||||
Create an Opaque Secret using External Secrets, Sealed Secrets or your secret
|
||||
manager, or provide `www_membership_secret_data` from **Ansible Vault**/an external
|
||||
lookup. Role-managed values use base64 `data` for repeatable reconciliation.
|
||||
Secret create/read/validation tasks use `no_log: true`, and diffs are disabled.
|
||||
Base64 is not encryption: enable etcd encryption at rest and appropriate RBAC.
|
||||
Never put credentials in public overrides or commit real values to git.
|
||||
|
||||
Only these keys are permitted:
|
||||
|
||||
| Secret key | Required |
|
||||
| --- | --- |
|
||||
| `MEMBERSHIP_SECRET_KEY` | Always; at least 32 random characters |
|
||||
| `POSTGRES_PASSWORD` | Always |
|
||||
| `DEX_CLIENT_SECRET` | Always |
|
||||
| `LDAP_BIND_PASSWORD` | Always |
|
||||
| `EMAIL_HOST_PASSWORD` | When SMTP authentication requires it |
|
||||
| `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET` | With Stripe enabled |
|
||||
| `SHKEEPER_API_KEY` | With SHKeeper enabled |
|
||||
|
||||
Do not use root/admin LDAP credentials. Restrict ACLs to the website account OU
|
||||
and dedicated group attributes. Use SHKeeper's invoice API key, never wallet
|
||||
private keys or UI/payout credentials.
|
||||
|
||||
Private trust roots belong in a separate CA Secret. Set paths such as
|
||||
`LDAP_CA_CERT_FILE`, `SHKEEPER_CA_CERT_FILE`, `PGSSLROOTCERT` or
|
||||
`REQUESTS_CA_BUNDLE` to `/etc/membership/ca/<secret-key>`. The read-only mount is
|
||||
shared by web, worker and migrations. Verify certificate expiry, strong keys,
|
||||
SHA-2 signatures and trust independently; do not disable TLS verification.
|
||||
|
||||
### Example inventory and hostname
|
||||
|
||||
[tests/example-vars.yml](tests/example-vars.yml) contains public configuration
|
||||
only. Supply credentials separately via Vault or a pre-existing Secret. Run
|
||||
through your existing deployment playbook after pushing matching website and
|
||||
membership releases and installing the collection requirements.
|
||||
|
||||
**Hostname matters:** the old role serves **www.eom.dev**, whereas the standalone
|
||||
app defaults to eom.dev. This role sets `MEMBERSHIP_PUBLIC_URL` to
|
||||
`https://{{ www_hostname }}`. Register these exact callbacks, including slashes:
|
||||
|
||||
- Dex: `https://<www_hostname>/members/oidc/callback/`.
|
||||
- Stripe: `https://<www_hostname>/members/stripe/webhook/`.
|
||||
- SHKeeper: `https://<www_hostname>/members/shkeeper/callback/`.
|
||||
|
||||
To serve the apex instead, set `www_hostname: eom.dev` and align DNS,
|
||||
certificates, callbacks and redirects. Membership requires one canonical HTTPS
|
||||
origin for sessions, CSRF, email verification and OIDC.
|
||||
|
||||
## Upgrades and operations
|
||||
|
||||
- Pin source ref and images to the same reviewed release. Change
|
||||
`www_rollout_revision` when deliberately tracking a moving branch. Git URL/ref
|
||||
are passed as environment variables, not shell-interpolated. Never put deploy
|
||||
tokens in the repository URL.
|
||||
- Apache config/source ref changes replace its pod. Membership image, public
|
||||
settings, tiers and Secret/CA **resource versions** trigger a new migration Job
|
||||
and workload rollout without publishing secret content hashes. The worker
|
||||
uses Recreate to avoid parallel release workers.
|
||||
- Completed migration Jobs are retained for idempotent reruns. Prune superseded
|
||||
Jobs manually after an upgrade; retain the current Job. Review/remove failed
|
||||
Jobs or choose a new rollout revision to retry. Keep schema changes compatible
|
||||
with old pods during rollout; coordinate downtime for incompatible changes.
|
||||
- The archive PVC is not wiped. Repository files (including dotfiles) overlay
|
||||
it; stale deleted static assets are not automatically pruned. Review archive
|
||||
path collisions deliberately. Database storage remains separate.
|
||||
- PostgreSQL initializes credentials only on an empty directory. Updating the
|
||||
Secret **does not rotate an existing DB password**: coordinate SQL rotation,
|
||||
application rollout and DB restart manually. Do not change the database
|
||||
name/user or major PostgreSQL version on an existing claim without a migration
|
||||
plan. Expand existing claims through your approved storage procedure rather
|
||||
than editing immutable StatefulSet claim templates.
|
||||
- Local PostgreSQL is network-isolated but its default connection is not TLS.
|
||||
Use an encrypted private CNI/service mesh or an external database with
|
||||
`POSTGRES_SSLMODE=verify-full` and a trusted CA when encrypted DB transport is
|
||||
required. Use encrypted disks/backups, restrict access and test restores. The
|
||||
single managed instance is not HA and has no automatic backup schedule.
|
||||
- For external PostgreSQL set `www_membership_postgres_managed: false` and
|
||||
override POSTGRES_HOST/PORT/DB/USER and SSLMODE. Existing managed resources and
|
||||
claims are deliberately **not deleted** when switching.
|
||||
- Monitor worker errors and stale `last_ldap_sync`, `last_billing_sync` and
|
||||
`last_shkeeper_sync`. Process health is not proof of successful reconciliation.
|
||||
Downstream apps must refresh cached Dex group/session claims promptly.
|
||||
|
||||
## Network and privacy
|
||||
|
||||
No direct membership/database ingress, NodePort or LoadBalancer is created.
|
||||
Apache alone may assert the trusted headers. Do not create permissive policies
|
||||
or give untrusted pods its label. NetworkPolicy enforcement is required. Egress
|
||||
is left to cluster policy: allow DNS, PostgreSQL, LDAP TLS, Dex HTTPS, SMTP
|
||||
STARTTLS, Stripe HTTPS and optional SHKeeper HTTPS to configured endpoints.
|
||||
Private destinations are supported; the role cannot infer safe IP allowlists.
|
||||
|
||||
Ingress enforces TLS and disables access logging of email bearer tokens/OIDC
|
||||
codes. Apache excludes member access logs; the app image disables Gunicorn
|
||||
access logs. Redact URIs/bodies at upstream load balancers, WAFs, APM and error
|
||||
tracing too. The default IP throttle sees the ingress peer; configure trusted
|
||||
mod_remoteip ranges separately for per-client limits. Never trust arbitrary
|
||||
X-Forwarded-For headers.
|
||||
|
||||
Tor remains a **static mirror**; membership is denied for noncanonical Host
|
||||
headers. Disabling membership only disables its Apache route: existing workers,
|
||||
database and history are not deleted. Keep workers alive to revoke expiries
|
||||
until a reviewed shutdown. Turning off Tor likewise does not delete an existing
|
||||
OnionService automatically.
|
||||
|
||||
## Validation
|
||||
|
||||
[tests/Dockerfile](tests/Dockerfile) runs real Ansible syntax checks and template
|
||||
rendering without a cluster, then
|
||||
[tests/validate_manifests.py](tests/validate_manifests.py) asserts Secret/config
|
||||
separation, tiers, migrations, private services, network isolation, non-root
|
||||
constraints, probes and archive preservation. Fixtures cover managed PostgreSQL,
|
||||
external TLS DB/private CA/image pull Secrets, SHKeeper, and disabled membership/
|
||||
Tor. Before production, validate in a disposable namespace, including storage
|
||||
permissions, NetworkPolicy, migrations, LDAP/Dex and sandbox payment flows.
|
||||
|
||||
The editor's container scanner currently flags one high-severity finding in the
|
||||
validation runner's upstream Python base image, even after updating/pulling it.
|
||||
This runner is test-only and is not deployed by the role. Review that finding
|
||||
with your authenticated image scanner and refresh approved base-image digests
|
||||
before using it in CI; no scanner authentication was configured by this change.
|
||||
|
||||
Reference in New Issue
Block a user