A simple static page service for Forgejo/Gitea.
Find a file
2025-04-22 07:57:11 +02:00
.forgejo chore: update build workflow to include ci/metadata.yaml in push and release triggers 2025-04-21 09:23:12 +02:00
ci chore: update app version to 0.5.1; refactor git repository update logic to handle non-fast-forward errors and improve error handling 2025-04-22 07:57:11 +02:00
src chore: update app version to 0.5.1; refactor git repository update logic to handle non-fast-forward errors and improve error handling 2025-04-22 07:57:11 +02:00
.dockerignore refactor: only support webhook mode and add instructions for serving the static content with nginx via app-template 2025-04-18 19:41:12 +02:00
.gitignore chore: add .gitignore and update Dockerfile for improved build process; enhance README with repository URL handling and authentication instructions 2025-04-20 16:13:13 +02:00
Dockerfile chore: add .gitignore and update Dockerfile for improved build process; enhance README with repository URL handling and authentication instructions 2025-04-20 16:13:13 +02:00
LICENSE.txt init 2025-01-06 12:05:10 +08:00
README.md feat: owasp top 10 review 2025-04-21 09:42:10 +02:00
renovate.json5 refactor: only support webhook mode and add instructions for serving the static content with nginx via app-template 2025-04-18 19:41:12 +02:00

forgejo-pages: a no-fuzz static page webhook server for Forgejo

A lightweight webhook service that pulls static pages from Forgejo/Gitea repositories based on webhook events.

Overview

forgejo-pages acts as a simple webhook receiver that:

  1. Listens for push events from Forgejo/Gitea
  2. Pulls repository content when changes occur
  3. Stores files in a directory for serving by a web server like nginx

Repository Setup

Everything in branch static-pages (configurable with --branch flag) will be stored and available for serving.

Repository URL Handling

The application intelligently handles different repository URL formats:

  1. If the webhook payload contains an SSH URL (ssh_url), it will use that directly
  2. If only an HTTP URL (clone_url) is available, it will convert it to an SSH URL
  3. As a last resort, it constructs an SSH URL using the server hostname and repository path
  4. If SSH cloning fails, it will fall back to HTTP cloning (useful for public repositories)

This flexibility ensures seamless operation with various Forgejo/Gitea configurations.

Authentication

The application uses a Forgejo access token for HTTP-based authentication.

Setting Up Access Token

To generate a token in Forgejo:

  1. Go to your user settings (click your profile picture in the top right and select "Settings")
  2. Navigate to "Applications" in the sidebar
  3. Under "Generate New Token":
    • Enter a token name (e.g., "forgejo-pages")
    • Select the "repo" scope to grant read access to repositories
    • Click "Generate Token"
  4. Copy the generated token and provide it to forgejo-pages using the PAGES_TOKEN environment variable

This token allows read-only access to repositories. The token is embedded in the clone URL when Git commands are executed.

Container Security

The container is built on a minimal scratch base with:

  • Only essential components
  • No unnecessary libraries or tools
  • Proper permissions for secure operation

Configuration

Configuration is managed through environment variables:

Environment Variable Description Default
PAGES_BIND Address and port to bind the webhook server :8080
PAGES_SERVER URL of the Forgejo/Gitea server -
PAGES_TOKEN Forgejo access token for repository authentication -
PAGES_BRANCH Branch containing static content static-pages
PAGES_DIR Directory to store repository data -
RUN_AT_START Run the webhook at startup true
DEFAULT_REPO Default repository to use when run_at_start is true (format: user/repo) -
TRIGGER_ONLY Use webhook as trigger only and ignore payload false

Security Features

The application implements several security features:

  1. Input Validation: Repository and user names are validated against a safe pattern to prevent path traversal and command injection.
  2. Security-Focused Logging: Full IP addresses are logged for security monitoring and auditing purposes.
  3. Trigger-Only Mode: When TRIGGER_ONLY=true, the webhook ignores the payload content and uses the DEFAULT_REPO configuration, mitigating risks from untrusted webhook sources.
  4. Early Response: The webhook handler returns a response immediately after validation, reducing the risk of denial-of-service attacks.
  5. Secure Token Management: Tokens are stored and transmitted securely, using Kubernetes secrets for deployment.

Using Trigger-Only Mode

For enhanced security, you can enable trigger-only mode:

env:
  TRIGGER_ONLY: "true"
  DEFAULT_REPO: "user/repo"

In this mode, the webhook endpoint acts only as a trigger, ignoring the payload content and using the configured DEFAULT_REPO instead. This mitigates risks from untrusted webhook sources, as the repository details come from your trusted configuration instead of the payload.

Logging

The application logs important events to the standard output:

  • Webhook events (received, processed)
  • Repository events (pulling, cloning, fetching)
  • Git operations (clone, fetch, checkout)
  • Error conditions with detailed output

This makes it easy to monitor the application's operations and troubleshoot issues.

Kubernetes Deployment

Deploy forgejo-pages with a shared volume for a web server:

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: &app blog
spec:
  interval: 1h
  timeout: 30s
  chartRef:
    kind: OCIRepository
    name: app-template
    namespace: flux-system
  install:
    remediation:
      retries: 3
  upgrade:
    cleanupOnFail: true
    remediation:
      strategy: rollback
      retries: 3
  values:
    controllers:
      # Webhook container - pulls content from git
      blog:
        annotations:
          reloader.stakater.com/auto: "true"
        containers:
          app:
            image:
              repository: code.252.no/pub/forgejo-pages
              tag: 0.1.1
            args:
              - listen
            env:
              PAGES_SERVER: https://code.252.no
              DEFAULT_REPO: /tommy/website
              PAGES_BRANCH: static-pages
              PAGES_DIR: /data
              PAGES_BIND: :8080
              RUN_AT_START: true
            envFrom:
            - secretRef:
                name: blog-secret
            resources:
              requests:
                cpu: 15m
                memory: 200M
              limits:
                cpu: 500m
                memory: 512M
          web:
            image:
              repository: nginx
              tag: alpine
    defaultPodOptions:
      imagePullSecrets:
      - name: forgejo-code-252-no-imagepull
    persistence:
      data:
        type: emptyDir
        globalMounts:
        - path: /data
        - path: /usr/share/nginx/html
    service:
      # Webhook service
      webhook:
        controller: blog
        ports:
          http:
            port: 8081

      # Web service for nginx
      web:
        controller: blog
        ports:
          http:
            port: 8080

    persistence:
      data:
        type: emptyDir
        globalMounts:
        - path: /data

Creating the Token Secret

Create a Kubernetes secret for your Forgejo token:

# Create Kubernetes secret
kubectl create secret generic static-pages-token \
  --from-literal=token=YOUR_FORGEJO_TOKEN \
  --namespace=YOUR_NAMESPACE

Then reference it in your deployment:

env:
  - name: PAGES_TOKEN
    valueFrom:
      secretKeyRef:
        name: static-pages-token
        key: token

External Secret Configuration

If using external-secrets operator, you can create the token secret from an external vault:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: static-pages-token
spec:
  secretStoreRef:
    kind: ClusterSecretStore
    name: onepassword
  target:
    name: static-pages-token
    creationPolicy: Owner
  dataFrom:
  - extract:
      key: static-pages-token

HTTPRoute Configuration

To expose your static pages with Gateway API:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: static-pages
spec:
  parentRefs:
  - name: gateway
    namespace: gateway-system
  hostnames:
  - "www.252.no"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: static-pages-server
      port: 8080

Webhook Setup

In your Forgejo/Gitea repository settings:

  1. Add a webhook pointing to http://static-pages-webhook.web.svc.cluster.local:8081/webhook
  2. Select HTTP-method POST
  3. Select only the "Push" event for the static-pages branch (or the branch you've configured)
  4. Set the content type to application/json

Testing the Webhook

The webhook endpoint only accepts POST requests. Test the webhook locally with:

curl -X POST http://localhost:8080/webhook \
  -H "Content-Type: application/json" \
  -H "X-GitHub-Event: push" \
  -d '{
    "ref": "refs/heads/static-pages",
    "repository": {
      "clone_url": "https://code.252.no/tommy/website.git",
      "html_url": "https://code.252.no/tommy/website",
      "full_name": "tommy/website"
    }
  }'

The webhook supports multiple event header variations:

  • X-GitHub-Event: push
  • X-Forgejo-Event: push
  • X-Gitea-Event: push
  • X-Gogs-Event: push

Any of these headers with the value "push" will trigger the webhook processing.

License

MPLv2.

Current source available at https://code.252.no/pub/forgejo-pages

The repo is based on https://github.com/Ronmi/forgejo-pages