Skip to content

Latest commit

 

History

History
455 lines (313 loc) · 16.2 KB

File metadata and controls

455 lines (313 loc) · 16.2 KB

Contributing to fogwall

Prerequisites

The easiest way to get the right toolchain versions is mise:

mise install   # installs Java 25 (Temurin) and Node 26 as defined in mise.toml

If you prefer to manage tools yourself, you need:

  • Java 25+
  • Node 26+
  • Docker or Podman (for e2e tests and Docker Compose workflows)

Gradle itself is included via the wrapper — no separate installation needed.

Project structure

Multi-module Gradle project; dependencies flow upward (coreserverdashboard):

Module Purpose
fogwall-core Shared library: filter chain, JGit hooks, push store, provider model, approval abstraction
fogwall-server Standalone proxy-only server — no dashboard, no Spring
fogwall-dashboard Dashboard + REST API — Spring MVC, approval UI, depends on fogwall-server

See docs/ARCHITECTURE.md for how the modules fit together at runtime.

Build

./gradlew spotlessApply      # fix formatting (palantir-java-format) — run before build
./gradlew build              # compile + unit tests

Formatting is enforced in CI. Always run spotlessApply before pushing.

When working on Java-only changes in the dashboard module, pass -PskipFrontend to skip the Node/npm frontend build steps (requires Node to be available otherwise):

./gradlew :fogwall-dashboard:compileJava -PskipFrontend
./gradlew :fogwall-dashboard:build -PskipFrontend

Running the server locally

Proxy only (no dashboard)

./gradlew :fogwall-server:run

Listens on http://localhost:8080. Logs go to fogwall-server/logs/application.log. Stop with:

./gradlew :fogwall-server:stop

Dashboard + REST API

./gradlew :fogwall-dashboard:run

Opens the approval dashboard at http://localhost:8080/. Stop with:

./gradlew :fogwall-dashboard:stop

The dashboard module always uses UI-mode approval (pushes block until manually approved). The standalone server defaults to auto-approve.

Local config override

Place overrides in fogwall-server/src/main/resources/fogwall-local.yml. The local file takes priority over fogwall.yml. At minimum, add an allow rule for your test repo and a permission entry for your proxy user:

rules:
  allow:
    - enabled: true
      order: 110
      operations: BOTH
      provider: github
      match:
        target: SLUG
        value: /your-org/your-repo
        type: LITERAL

permissions:
  - username: your-proxy-user
    provider: github
    match:
      target: SLUG
      value: /your-org/your-repo
      type: LITERAL
    operations: PUSH

See docs/CONFIGURATION.md for the full reference.

Tests

Unit tests

./gradlew test

Unit tests live under each module's src/test/. They run without containers.

E2E tests (JUnit, requires Docker/Podman)

./gradlew e2eTest

These start a containerised Gitea instance and a live Jetty proxy in-process. They are tagged @Tag("e2e") and live in fogwall-server/src/test/java/com/rbc/fogwall/e2e/.

Manual integration test scripts (test/)

The test/ directory contains bash scripts for exercising both proxy modes against a running server. They are the fastest way to verify a feature end-to-end without writing Java.

Test scripts share a common library (test/common.sh) with setup, cleanup, and assertion helpers. Individual test cases are organized into logical groupings by test outcome (pass/fail) and proxy mode (push/proxy).

Environment variables

All scripts share these variables:

Variable Default Description
GIT_USERNAME me HTTP Basic-auth username (arbitrary for the proxy)
GIT_PASSWORD (read from PAT file, see below) Personal access token for the upstream SCM
GIT_REPO github.com/coopernetes/test-repo.git Target repo for GitHub pass/fail scripts
GITHUB_REPO github.com/coopernetes/test-repo.git Target repo for GitHub identity scripts
GITLAB_REPO gitlab.com/coopernetes/test-repo-gitlab.git Target repo for GitLab identity scripts
CODEBERG_REPO codeberg.org/coopernetes/test-repo-codeberg.git Target repo for Codeberg identity scripts
FOGWALL_API_KEY change-me-in-production API key used by approval scripts

Scripts read the upstream PAT from a file if GIT_PASSWORD is not set:

Script group PAT file
GitHub scripts ~/.github-pat
GitLab scripts ~/.gitlab-pat
Codeberg scripts ~/.codeberg-pat

Test entry points

Run tests by logical grouping. Each entry point orchestrates multiple related test cases:

Store-and-forward (push):

  • bash test/push-pass-all.sh — golden-path pushes and tag pushes (should succeed)
  • bash test/push-fail-all.sh — validation failures (should be rejected)

Transparent proxy:

  • bash test/proxy-pass-all.sh — golden-path and tag pushes (require manual approval)
  • bash test/proxy-fail-all.sh — validation failures (should be rejected)

Identity verification:

  • bash test/push-identity-all.sh — SCM identity resolution across providers
  • bash test/proxy-identity-all.sh — SCM identity resolution via proxy

Individual test scripts

If running a single test case by name:

Store-and-forward (push):

Script Category What it tests
push-pass.sh Pass Golden-path push — should succeed and forward upstream
push-pass-tag.sh Pass Lightweight and annotated tags — should succeed
push-pass-secrets.sh Pass File patterns that look like secrets but pass gitleaks
push-fail-author.sh Fail Invalid author email domains (noreply, disallowed)
push-fail-message.sh Fail Commit message validation (WIP, fixup, DO NOT MERGE)
push-fail-diff.sh Fail Diff content scanning (internal URLs, patterns)
push-fail-secrets.sh Fail Gitleaks detecting secrets in diff (AWS, GitHub, PEM)

Transparent proxy:

Script Category What it tests
proxy-pass.sh Pass Golden-path push — blocks for approval, then auto-approves
proxy-pass-tag.sh Pass Lightweight and annotated tags through proxy
proxy-fail-author.sh Fail Invalid author email domains (noreply, disallowed)
proxy-fail-message.sh Fail Commit message validation (WIP, fixup, DO NOT MERGE)
proxy-fail-diff.sh Fail Diff content scanning (internal URLs, patterns)
proxy-fail-secrets.sh Fail Gitleaks detecting secrets in diff (AWS, GitHub, PEM)

Running tests against your own repo

The scripts default to repos owned by the project maintainer. To run them against your own repos you need:

  1. A test repo you can push to on GitHub (and optionally GitLab/Codeberg for identity tests).

  2. PAT files for each provider you want to test:

    echo "ghp_yourtoken" > ~/.github-pat
    echo "glpat-yourtoken" > ~/.gitlab-pat   # optional
    echo "yourtoken" > ~/.codeberg-pat       # optional
    chmod 600 ~/.github-pat ~/.gitlab-pat ~/.codeberg-pat
  3. Allow rules and permissions in fogwall-local.yml — add your repo slug to the rules.allow slugs list and add PUSH/REVIEW permission entries for your proxy user. See docs/CONFIGURATION.md for the full reference.

  4. Run with your repo — single scripts accept an inline override; orchestrators need an export:

    # Single script — inline is fine:
    GIT_REPO=github.com/your-org/your-repo.git bash test/push-pass.sh
    
    # Orchestrators (call subscripts via bash) — must export:
    export GIT_REPO=github.com/your-org/your-repo.git
    bash test/push-pass-all.sh
    bash test/proxy-pass-all.sh
    
    # Provider-specific identity tests use separate variables:
    export GITHUB_REPO=github.com/your-org/your-repo.git
    export GITLAB_REPO=gitlab.com/your-org/your-repo.git
    bash test/push-identity-all.sh

Running tests manually

Make sure the server is running first (see above), then:

# Run all passing push tests:
bash test/push-pass-all.sh

# Run all failure push tests:
bash test/push-fail-all.sh

# Run a single test case:
bash test/push-fail-secrets.sh

Full suite runners

Two scripts spin up a complete Docker Compose environment (fogwall + Gitea + database), run all test groups, then tear down:

bash test/run-postgres.sh             # PostgreSQL backend
bash test/run-mongo.sh                # MongoDB backend

# Leave the environment running after the suite (useful for debugging):
bash test/run-postgres.sh --no-teardown

These build the Docker image from source, so no pre-existing server is needed.

Docker Compose (local Gitea)

The Compose setup runs fogwall against a local Gitea instance. Overlay files are independent mixins — one for the auth provider, one for the database backend. They can be combined freely.

Overlay files

Auth overlays — each mounts a different fogwall-local.yml config into the container:

File Auth provider Default database
(none) Static (password hashes in config) H2 in-memory
docker/docker-compose.ldap.yml OpenLDAP H2 in-memory
docker/docker-compose.oidc.yml OIDC (mock-oauth2-server) H2 in-memory

Database overlays — each sets FOGWALL_DATABASE_* environment variables; no config file swap needed:

File Backend Profile flag UI
(none) H2 in-memory
docker/docker-compose.postgres.yml PostgreSQL --profile postgres Adminer at :8082
docker/docker-compose.mysql.yml MySQL --profile mysql Adminer at :8082
docker/docker-compose.mariadb.yml MariaDB --profile mariadb Adminer at :8082
docker/docker-compose.mongo.yml MongoDB --profile mongo Mongo Express at :8081

Any auth overlay can be combined with any database overlay (or none, to keep H2). Use the compose.sh wrapper rather than bare docker compose — it assembles the right -f/--profile flags and auto-detects docker vs podman:

bash compose.sh [--auth ldap|oidc] [--db postgres|mysql|mariadb|mongo] -- up -d

First-time setup

After starting any stack, run this once to create the Gitea admin user and test repository:

bash docker/gitea-setup.sh

Common stacks

Static auth + H2 (simplest — no external dependencies):

bash compose.sh -- up -d

LDAP + H2:

bash compose.sh --auth ldap -- up -d

LDAP + PostgreSQL (recommended for verifying IdP email locking and auto-provisioning):

bash compose.sh --auth ldap --db postgres -- up -d

OIDC + PostgreSQL:

bash compose.sh --auth oidc --db postgres -- up -d

LDAP + MongoDB:

bash compose.sh --auth ldap --db mongo -- up -d

Auth provider details

Static auth

Log in at http://localhost:8080 with admin / admin (defined in docker/fogwall-local.yml).

LDAP auth

Test accounts are defined in docker/ldap-bootstrap.ldif:

Username Password LDAP email
testuser testpass123 testuser@example.com
admin admin admin@example.com

On first login the account is auto-provisioned and the LDAP mail attribute is stored as a locked email (not editable from the profile UI). Inspect the user_emails table in Adminer or Mongo Express to see the locked=true row.

To add more users, edit docker/ldap-bootstrap.ldif and recreate the container:

bash compose.sh --auth ldap -- rm -sf openldap
bash compose.sh --auth ldap -- up -d openldap

OIDC auth

Uses navikt/mock-oauth2-server, which accepts any username with no password required.

One-time /etc/hosts entry — required so the OIDC issuer URL is the same from your browser and from fogwall inside Docker:

127.0.0.1  mock-oauth2

Open http://localhost:8080 and log in with any username.

Proxy URLs

After docker/gitea-setup.sh, the test repository is reachable at:

http://localhost:8080/push/gitea:3000/test-owner/test-repo.git
http://localhost:8080/proxy/gitea:3000/test-owner/test-repo.git

Clone example:

git clone http://fogwalladmin:Admin1234!@localhost:8080/push/gitea:3000/test-owner/test-repo.git

Teardown

bash compose.sh [same --auth/--db flags as start] -- down -v

Code style

Java

Formatting is enforced by Spotless using palantir-java-format:

./gradlew spotlessApply

Frontend (React/TypeScript)

Formatting uses Prettier, lint checks use ESLint. Both are Gradle tasks that use the same Node binary as the build:

./gradlew :fogwall-dashboard:npmFormat   # auto-format src/ with Prettier
./gradlew :fogwall-dashboard:npmLint     # ESLint check (fails on errors)

Pre-commit hook

Install once after cloning:

./gradlew installGitHooks

This sets core.hooksPath to .githooks/. The hook runs on every git commit:

  1. spotlessApply — auto-formats Java and re-stages changed files
  2. npmFormat — auto-formats frontend source with Prettier and re-stages changed files
  3. npmLint — ESLint check; fails the commit if there are errors (no auto-fix)

Releases

Releases follow a two-phase process to ensure every published image is identical to what was already scanned and running as :edge.

Phase 1 — version bump. Create a release/<version> branch, update version in build.gradle, open a PR, and enable auto-merge. The PR must pass all CI, CodeQL, CVE, and container scan checks before it can merge. Use the /release Claude command to automate this.

Phase 2 — tag. Once the version bump lands on main, push an annotated tag (v<version>). The tag ruleset enforces the same checks must have passed on that commit. The publish workflow then promotes the already-built :edge image directly to the release tags (:v1.0.0, :latest, etc.) — no rebuild occurs. Use the /release-tag Claude command for this step.

This means every release image is byte-for-byte identical to the :edge image that was scanned when the version bump merged.

Documenting new config surface

When a PR introduces a new config section (not just a key on an existing one) in docs/CONFIGURATION.md, tag it with the release it's shipping in, e.g. _Available since v1.3.0._, right under the heading. This isn't backfilled onto existing sections — only applied going forward from a section's introduction.