# GitLab Event Forwarder

LLMS index: [llms.txt](/llms.txt) | Full content: [llms-full.txt](/llms-full.txt)

---

An AWS Lambda function that receives GitLab webhook events and forwards them to
an SNS topic with structured metadata for filtering. Validates webhook
signatures and extracts minimal metadata (source, event type, project/group
paths) as SNS message attributes, enabling downstream subscribers to filter
events precisely.

The original GitLab JSON payload is forwarded unchanged as the SNS message
body.

## Features

- **Webhook Signature Validation**: Verifies authenticity using HMAC-SHA256
  signing tokens (Standard Webhooks), with legacy `X-Gitlab-Token` fallback
- **Structured Metadata**: Extracts event type, project/group paths as SNS
  message attributes
- **SNS Subscription Filtering**: Enables precise event routing to subscribers
- **Custom Domain**: Optional custom domain with automatic TLS certificate
  management via ACM and Route53

## Architecture

Single Lambda function handles the webhook processing:

1. **Handler** - API Gateway endpoint (`/webhook`) that validates GitLab
   webhook signatures, extracts metadata, and publishes to SNS

## Prerequisites

- AWS CLI configured with appropriate credentials (IAM permissions for Lambda,
  API Gateway, SNS, CloudFormation, CloudWatch Logs, and optionally Route53/ACM
  for custom domain)
- Podman or Docker (for containerized SAM build/deploy)
- Python 3.11 or later (for development)

## Deployment

Build and deploy using containerized AWS SAM CLI:

```bash
cd gitlab-event-forwarder
make build     # Build Lambda package
make deploy    # First deployment (interactive/guided)
make redeploy  # Subsequent deployments (non-interactive)
```

**Deployment outputs:** `ApiEndpoint` - API Gateway endpoint URL (`<api-endpoint>`)

### Custom Domain

Optional custom domain with automatic TLS certificate management (ACM +
Route53). Requires a Route53 hosted zone. Deploy with `CustomDomainName` and
`HostedZoneId` parameters - CloudFormation handles certificate creation, DNS
validation, and configuration. Certificate validation takes 5-30 minutes; allow
up to 1 hour for DNS propagation.

### Parameters

| Parameter             | Description                  | Default      |
| --------------------- | ---------------------------- | ------------ |
| `ResourcePrefix`      | Prefix for resources         | `myapp-prod` |
| `SnsTopicArn`         | SNS topic ARN                | (required)   |
| `GitLabWebhookTokens` | GitLab webhook tokens (JSON) | (required)   |
| `GitLabSigningTokens` | Signing tokens (JSON array)  | `[]`         |
| `SentryDsn`           | Optional Sentry DSN          | ``           |
| `CustomDomainName`    | Custom domain name           | ``           |
| `HostedZoneId`        | Route53 hosted zone          | ``           |

**Resource naming:** Lambda and API resources follow
`{ResourcePrefix}-{type}-{name}` pattern (e.g., `myapp-prod-lambda-handler`,
`myapp-prod-api`).

**Prerequisites:** Requires an existing SNS topic. Deploy
[hummingbird-events-topic][het-docs] first to create the topic, then use its
ARN for the `SnsTopicArn` parameter.

[het-docs]: https://gitlab.com/redhat/hummingbird/tools/-/blob/main/documentation/hummingbird-events-topic.md

## Usage

Configure GitLab projects or groups to send webhooks to `<api-endpoint>`:

```yaml
webhooks:
  <api-endpoint>:
    token: <secret-token>
    signing_token: <whsec-signing-token>
    push_events: true
    merge_requests_events: true
    pipeline_events: true
    job_events: true
```

`signing_token` (recommended) uses HMAC-SHA256 verification with replay
protection (±5 min tolerance) per the Standard Webhooks spec. `token` uses
a static `X-Gitlab-Token` header. When signing tokens are configured on
the forwarder, they take priority and `webhook-signature` is required on
all requests.

**SNS Subscription Filter Examples:**

Push events from a specific project:

```json
{
  "source": ["gitlab"],
  "event_type": ["push"],
  "project_path": ["redhat/hummingbird/containers"]
}
```

All merge request events:

```json
{
  "source": ["gitlab"],
  "event_type": ["merge_request"]
}
```

Member events from a group:

```json
{
  "source": ["gitlab"],
  "event_type": ["member"],
  "group_path": ["redhat/hummingbird"]
}
```

## Development

See the main [README][readme] for development workflows.

```bash
make setup     # Install dependencies
make check     # Lint code (ruff)
make fmt       # Format code
make test      # Run unit tests
make coverage  # Run tests with coverage
```

## Configuration

Lambda function receives configuration via environment variables (automatically
set by CloudFormation):

| Variable                | Description                        |
| ----------------------- | ---------------------------------- |
| `SNS_TOPIC_ARN`         | SNS topic ARN                      |
| `GITLAB_WEBHOOK_TOKENS` | GitLab webhook tokens (JSON array) |
| `GITLAB_SIGNING_TOKENS` | Signing tokens (JSON array)        |
| `SENTRY_DSN`            | Optional Sentry DSN                |
| `AWS_REGION`            | AWS region (auto-set)              |

## Event Metadata

The Lambda function extracts minimal metadata from GitLab webhook events and
adds them as SNS message attributes:

| Attribute      | Description        | Example                                      |
| -------------- | ------------------ | -------------------------------------------- |
| `source`       | Always `"gitlab"`  | `gitlab`                                     |
| `event_type`   | From `object_kind` | `push`, `merge_request`, `pipeline`, `build` |
| `project_path` | Full project path  | `redhat/hummingbird/containers`              |
| `group_path`   | Full group path    | `redhat/hummingbird`                         |

## Security & Limitations

**Security:**

- HMAC-SHA256 signing token verification via `webhook-signature` header
  (Standard Webhooks spec) with ±5 min replay protection
- When signing tokens are configured, static `X-Gitlab-Token` is not accepted
- Legacy `X-Gitlab-Token` validation when no signing tokens are configured
- Supports multiple active tokens for zero-downtime rotation
- Invalid/missing tokens return HTTP 401
- SNS topic follows least privilege principle
- CloudWatch logs capture all webhook deliveries (7-day retention)
- Sentry integration for error tracking

**Limitations:**

- Lambda timeout: 30 seconds
- Lambda memory: 256 MB
- Webhook payload size: Up to 6 MB (API Gateway limit)
- SNS limits the compressed and base64-encoded message plus attributes to 256 KiB

Pipeline Hooks use `object_kind: pipeline`; Job Hooks use `object_kind: build`.
The forwarder compresses the original JSON for SNS transport. Consumers that
decode `gzip+base64` receive the unchanged webhook payload.

## License

This project is licensed under the GNU General Public License v3.0 or later -
see the [LICENSE][license] file for details.

[readme]: https://gitlab.com/redhat/hummingbird/tools/-/blob/main/README.md
[license]: https://gitlab.com/redhat/hummingbird/tools/-/blob/main/LICENSE
