From 0ff358552c24cec1488c41690eee30d8d150c802 Mon Sep 17 00:00:00 2001 From: Dani Date: Fri, 22 Aug 2025 12:40:09 -0400 Subject: [PATCH] Finished updating the readme --- README.md | 236 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 218 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index ec4fd28..8c6bbf9 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,224 @@ -# GreenCoast — Privacy-First, Shardable Social (Dockerized) +# GreenCoast -**Goal:** A BlueSky-like experience with **shards**, **zero-trust**, **no data collection**, **E2EE**, and easy self-hosting — from x86_64 down to **Raspberry Pi Zero**. -License: **The Unlicense** (public-domain equivalent). - -This repo contains a minimal, working **shard**: an append-only object API with zero-data-collection defaults. It’s structured to evolve into full federation, E2EE, and client apps, while keeping Pi Zero as a supported host. +A privacy-first, shardable social backend + minimalist client. **Zero PII**, **zero passwords**, optional **E2EE per post**, and **public-key accounts**. Includes **DPoP-style proof-of-possession**, **Discord SSO with PKCE**, and a tiny static client. --- -## Quick Start (Laptop / Dev) +## Features -**Requirements:** Docker + Compose v2 +- **Zero-trust by design**: server stores no emails or passwords. +- **Accounts = public keys** (Ed25519 or P-256). No usernames required. +- **Proof-of-possession (PoP)** on every authenticated API call. +- **Short-lived tokens** (HMAC “gc2”) bound to device keys. +- **Shardable storage** (mTLS or signed shard requests). +- **No fingerprinting**: no IP/UA logs; coarse timestamps optional. +- **Static client** with strong CSP; optional E2EE per post. +- **Discord SSO (PKCE)** as an *optional* convenience. +- **Filesystem storage** supports both **flat** and **nested** object layouts. + +--- + +## Architecture (brief) + +- **Shard**: stateless API + local FS object store + in-memory index. +- **Client**: static files (HTML/JS/CSS) served by the shard or any static host. +- **Identity**: device key (P-256/Ed25519) or passkey; server mints short-lived **gc2** tokens bound to the device key (`cnf` claim). +- **Privacy**: objects can be plaintext (public) or client-encrypted (private). + +--- + +## Security posture + +- **Zero-trust**: no passwords/emails; optional SSO is *linking*, not source-of-truth. +- **DPoP-style PoP** on requests: + - Client sends: + - `Authorization: Bearer gc2.…` + - `X-GC-Key: p256:` (or `ed25519:…`) + - `X-GC-TS: ` + - `X-GC-Proof: sig( METHOD "\n" URL "\n" TS "\n" SHA256(body) )` + - Server verifies `gc2` signature, key binding (`cnf`), timestamp window, and replay cache. +- **Replay protection**: 10-minute proof cache. +- **No fingerprinting/logging**: no IPs, no UAs. +- **Strict CSP** for client: blocks XSS/token theft. +- **Limits**: request body limits (default 10 MiB), simple per-account rate limiting. +- **Shard↔shard**: mTLS or per-shard signatures with timestamp + replay cache. + +--- + +## Requirements + +- Go 1.21+ +- Docker (optional) +- A signing key for tokens: `GC_SIGNING_SECRET_HEX` (32+ bytes hex) +- (Optional) Discord OAuth app (Client ID/Secret + redirect URI) +- (Optional) Cloudflare Tunnel or other TLS reverse proxy + +--- + +## Environment variables + + GC_HTTP_ADDR=:9080 + GC_HTTPS_ADDR= # optional + GC_TLS_CERT= # optional + GC_TLS_KEY= # optional + + GC_STATIC_ADDR=:9082 + GC_STATIC_DIR=/opt/greencoast/client + + GC_DATA_DIR=/var/lib/greencoast + GC_ZERO_TRUST=true + GC_COARSE_TS=false + + GC_SIGNING_SECRET_HEX=<64+ hex chars> # required for gc2 tokens + GC_REQUIRE_POP=true # default true; set false for first-run + + # Dev convenience (testing only; disable for production) + GC_DEV_ALLOW_UNAUTH=false + GC_DEV_BEARER= + + # Discord SSO (optional) + GC_DISCORD_CLIENT_ID= + GC_DISCORD_CLIENT_SECRET= + GC_DISCORD_REDIRECT_URI=https://greencoast.example.com/auth-callback.html + +--- + +## Quickstart (Docker) + +Minimal compose for local testing (PoP disabled + dev unauth allowed for first run): + + services: + shard-test: + build: . + environment: + - GC_HTTP_ADDR=:9080 + - GC_STATIC_ADDR=:9082 + - GC_STATIC_DIR=/opt/greencoast/client + - GC_DATA_DIR=/var/lib/greencoast + - GC_ZERO_TRUST=true + - GC_SIGNING_SECRET_HEX=7f6e1a0f2b4d7e3a... # replace with your secret + - GC_REQUIRE_POP=false # easier first-run + - GC_DEV_ALLOW_UNAUTH=true + volumes: + - ./testdata:/var/lib/greencoast + - ./client:/opt/greencoast/client:ro + ports: + - "9080:9080" + - "9082:9082" + +Open `http://localhost:9082` → set the Shard URL (`http://localhost:9080`) → publish a test post. + +When ready, **turn PoP on** by removing `GC_REQUIRE_POP=false` and disabling `GC_DEV_ALLOW_UNAUTH`. + +--- + +## Cloudflare Tunnel example + + ingress: + - hostname: greencoast.example.com + service: http://shard-test:9082 + - hostname: api-gc.greencoast.example.com + service: http://shard-test:9080 + - service: http_status:404 + +Use “Full (strict)” TLS and ensure your cert covers both hosts. + +--- + +## Client usage + +- **Shard URL**: set it in the top “Connect” section (or use `?api=` query or ``). +- **Device key sign-in (no OAuth)**: + 1) Client generates/stores a P-256 device key in the browser. + 2) Client calls `/v1/auth/key/challenge` then `/v1/auth/key/verify` to obtain a **gc2** token bound to that key. +- **Discord SSO (optional)**: + - Requires `GC_DISCORD_CLIENT_*` env vars and a valid `GC_DISCORD_REDIRECT_URI`. + - Uses PKCE (`S256`) and binds the minted **gc2** token to the device key presented at `/start`. + +--- + +## API (overview) + +- `GET /healthz` – liveness +- `PUT /v1/object` – upload blob (headers: optional `X-GC-Private: 1`, `X-GC-TZ`) +- `GET /v1/object/{hash}` – download blob +- `DELETE /v1/object/{hash}` – delete blob +- `GET /v1/index` – list indexed entries (latest first) +- `GET /v1/index/stream` – SSE updates +- `POST /v1/admin/reindex` – rebuild index from disk +- **Auth** + - `POST /v1/auth/key/challenge` → `{nonce, exp}` + - `POST /v1/auth/key/verify` `{nonce, alg, pub, sig}` → `{bearer, sub, exp}` + - `POST /v1/auth/discord/start` (requires `X-GC-3P-Assent: 1` and `X-GC-Key`) + - `GET /v1/auth/discord/callback` → redirects with `#bearer=…` +- **GDPR** + - `GET /v1/gdpr/policy` – current data-handling posture + +> When `GC_REQUIRE_POP=true`, all authenticated endpoints require PoP headers. + +### PoP header format (pseudocode) + + Authorization: Bearer gc2.. + X-GC-Key: p256: # or ed25519: + X-GC-TS: + X-GC-Proof: base64( + Sign_device_key( + UPPER(METHOD) + "\n" + URL + "\n" + X-GC-TS + "\n" + SHA256(body) + ) + ) + +--- + +## Storage layout & migration + +- **Writes** are flat: `objects/` +- **Reads** (and reindex) also support: + - `objects//blob|data|content` + - `objects//` + - `objects//` (two-level prefix) +- To **restore** data into a fresh container: + 1) Mount your objects at `/var/lib/greencoast/objects` + 2) Call `POST /v1/admin/reindex` (with auth+PoP or enable dev unauth briefly) + +--- + +## Reindex examples + +Unauth (dev only): + + curl -X POST https://api-gc.yourdomain/v1/admin/reindex + +With bearer + PoP (placeholders): + + curl -X POST https://api-gc.yourdomain/v1/admin/reindex ^ + -H "Authorization: Bearer " ^ + -H "X-GC-Key: p256:" ^ + -H "X-GC-TS: " ^ + -H "X-GC-Proof: " + +--- + +## Hardening checklist (prod) + +- Set `GC_REQUIRE_POP=true`, remove dev bypass. +- Keep access token TTL ≤ 8h; rotate signing key periodically. +- Static client served with strong CSP (already enabled). +- Containers run non-root, read-only FS, `no-new-privileges`, `cap_drop: ["ALL"]`. +- Edge WAF/rate limits; 10 MiB default request cap (tunable). +- Commit `go.sum`; run `go mod verify` in CI. + +--- + +## GDPR + +- Server stores **no PII** (no emails, no IP/UA logs). +- Timestamps are UTC (or coarse UTC if enabled). +- `/v1/gdpr/policy` exposes current posture. +- Roadmap: `/v1/gdpr/export` and `/v1/gdpr/delete` to enumerate/remove blobs signed by a given key. + +--- + +## License + +This project is licensed under **The Unlicense**. See `LICENSE` for details. -```bash -git clone greencoast -cd greencoast -cp .env.example .env -docker compose -f docker-compose.dev.yml up --build -# Health: -curl -s http://localhost:8080/healthz -# Put an object (dev mode allows unauthenticated PUT/GET): -curl -s -X PUT --data-binary @README.md http://localhost:8080/v1/object -# -> {"ok":true,"hash":"",...} -curl -s http://localhost:8080/v1/object/ | head