Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ jobs:
- name: Run migrations
run: for file in schema/*.sql; do psql "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable" -f $file; done

- name: Run unit tests and generate the coverage report
run: RUN_DB_TESTS=1 make test-race
- name: Run unit tests
run: make test-with-db

lint:
name: Lint
Expand All @@ -61,7 +61,7 @@ jobs:
run: go install honnef.co/go/tools/cmd/[email protected]

- name: Install golangci-lint
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.8
run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.2

- name: Install NilAway
run: go install go.uber.org/nilaway/cmd/[email protected]
Expand Down
18 changes: 16 additions & 2 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
version: "2"
run:
tests: false
linters:
Expand All @@ -6,7 +7,6 @@ linters:
- cyclop
- forbidigo
- funlen
- gci
- gochecknoglobals
- gochecknoinits
- gocritic
Expand Down Expand Up @@ -43,7 +43,6 @@ linters:
#
# Disabled because deprecated:
#
- tenv

linters-settings:
#
Expand Down Expand Up @@ -91,3 +90,18 @@ linters-settings:
- 'MockBeaconClient'
- 'RelayAPI'
- 'Webserver'
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
settings:
gofumpt:
extra-rules: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
38 changes: 38 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Repository Guidelines

## Project Structure & Modules
- `cmd/httpserver/`: CLI entrypoint and service startup.
- `httpserver/`: HTTP server, handlers, routing, and tests.
- `application/`, `domain/`, `ports/`, `adapters/`: Clean/hexagonal layers (core logic, interfaces, DB/secrets adapters).
- `schema/`: SQL migrations; `testdata/`: fixtures; `metrics/`: Prometheus metrics; `docker/`: Dockerfiles and compose; `scripts/`: CI/e2e helpers.

## Build, Test, and Dev Commands
- `make build`: Build the server to `build/builder-hub`.
- `go run cmd/httpserver/main.go`: Run locally (see env flags below).
- `make test`: Run unit tests. Use `RUN_DB_TESTS=1 make test` to include DB/e2e tests.
- `make cover` / `make cover-html`: Coverage summary / HTML report.
- `make fmt`: Format and tidy imports/modules.
- `make lint`: `go vet`, `staticcheck`, `golangci-lint`, and format checks.
- `make docker-httpserver`: Build the Docker image.
- Helpful: `docs/devenv-setup.md`, `scripts/ci/integration-test.sh`.

## Coding Style & Conventions
- Go formatting is enforced: run `make fmt` (gofmt, gofumpt, gci, go mod tidy).
- Package names: lower-case; exported symbols: PascalCase; locals: camelCase.
- Keep files focused; group handlers in `httpserver`, business logic in `application/domain`.
- Imports: standard → third-party → local (enforced by gci).
- Indentation: Use tab, not spaces

## Testing Guidelines
- Framework: Go `testing`; tests live in `*_test.go`. Use `testdata/` fixtures.
- Quick run: `make test`; with DB/e2e: `RUN_DB_TESTS=1 make test`.
- Coverage: `make cover`. Prefer table-driven tests and clear Arrange/Act/Assert sections.

## Commit & Pull Request Guidelines
- Commits: concise, imperative mood (e.g., "Add API spec tests"); optional scope tags (e.g., `[CI]`); reference issues/PRs when relevant.
- PRs: include description, linked issues, testing steps (curl examples for API changes), and screenshots/log snippets when useful.
- Required before merge: `make lt` (lint + tests) green, updated docs in `docs/` when API/behavior changes.

## Configuration & Security
- Common env flags (via CLI/env): `LISTEN_ADDR`, `ADMIN_ADDR`, `INTERNAL_ADDR`, `METRICS_ADDR`, `POSTGRES_DSN`, `LOG_JSON`, `LOG_DEBUG`, `PPROF`, `MOCK_SECRETS`, `AWS_BUILDER_CONFIGS_SECRET_NAME`/`_PREFIX`.
- Default DSN targets local Postgres; do not commit secrets. Use `MOCK_SECRETS=1` for local dev.
41 changes: 32 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

VERSION := $(shell git describe --tags --always --dirty="-dev")

# Admin API auth for curl examples (set ADMIN_AUTH="admin:secret" or similar. can be empty when server is run with --disable-admin-auth)
ADMIN_AUTH ?=
CURL ?= curl -v
CURL_AUTH := $(if $(ADMIN_AUTH),-u $(ADMIN_AUTH),)

# A few colors
RED:=\033[0;31m
BLUE:=\033[0;34m
Expand Down Expand Up @@ -33,6 +38,24 @@ build: ## Build the HTTP server

##@ Test & Development

.PHONY: dev-postgres-up
dev-postgres-up: ## Start the PostgreSQL database for development
docker run -d --name postgres-test -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres postgres
for file in schema/*.sql; do psql "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable" -f $file; done

.PHONY: dev-postgres-down
dev-postgres-down: ## Stop the PostgreSQL database for development
docker rm -f postgres-test

.PHONY: dev-docker-compose-up
dev-docker-compose-up: ## Start Docker compose
docker compose -f docker/docker-compose.yaml build
docker compose -f docker/docker-compose.yaml up -d

.PHONY: dev-docker-compose-down
dev-docker-compose-down: ## Stop Docker compose
docker compose -f docker/docker-compose.yaml down

.PHONY: lt
lt: lint test ## Run linters and tests (always do this!)

Expand All @@ -45,12 +68,12 @@ fmt: ## Format the code (use this often)

.PHONY: test
test: ## Run tests
go test ./...

.PHONY: test-race
test-race: ## Run tests with race detector
go test -race ./...

.PHONY: test-with-db
test-with-db: ## Run tests including live database tests
RUN_DB_TESTS=1 go test -race ./...

.PHONY: lint
lint: ## Run linters
gofmt -d -s .
Expand Down Expand Up @@ -92,16 +115,16 @@ db-dump: ## Dump the database contents to file 'database.dump'
.PHONY: dev-db-setup
dev-db-setup: ## Create the basic database entries for testing and development
@printf "$(BLUE)Create the allow-all measurements $(NC)\n"
curl -v --request POST --url http://localhost:8081/api/admin/v1/measurements --data '{"measurement_id": "test1","attestation_type": "test","measurements": {}}'
$(CURL) $(CURL_AUTH) --request POST --url http://localhost:8081/api/admin/v1/measurements --data '{"measurement_id": "test1","attestation_type": "test","measurements": {}}'

@printf "$(BLUE)Enable the measurements $(NC)\n"
curl -v --request POST --url http://localhost:8081/api/admin/v1/measurements/activation/test1 --data '{"enabled": true}'
$(CURL) $(CURL_AUTH) --request POST --url http://localhost:8081/api/admin/v1/measurements/activation/test1 --data '{"enabled": true}'

@printf "$(BLUE)Create the builder $(NC)\n"
curl -v --request POST --url http://localhost:8081/api/admin/v1/builders --data '{"name": "test_builder","ip_address": "1.2.3.4", "network": "production"}'
$(CURL) $(CURL_AUTH) --request POST --url http://localhost:8081/api/admin/v1/builders --data '{"name": "test_builder","ip_address": "1.2.3.4", "network": "production"}'

@printf "$(BLUE)Create the builder configuration $(NC)\n"
curl -v --request POST --url http://localhost:8081/api/admin/v1/builders/configuration/test_builder --data '{"dns_name": "foobar-v1.a.b.c","rbuilder": {"extra_data": "FooBar"}}'
$(CURL) $(CURL_AUTH) --request POST --url http://localhost:8081/api/admin/v1/builders/configuration/test_builder --data '{"dns_name": "foobar-v1.a.b.c","rbuilder": {"extra_data": "FooBar"}}'

@printf "$(BLUE)Enable the builder $(NC)\n"
curl -v --request POST --url http://localhost:8081/api/admin/v1/builders/activation/test_builder --data '{"enabled": true}'
$(CURL) $(CURL_AUTH) --request POST --url http://localhost:8081/api/admin/v1/builders/activation/test_builder --data '{"enabled": true}'
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,35 @@ BuilderHub has these responsibilities:
## Getting started


### Admin authentication

The Admin API (port 8081) requires HTTP Basic Auth. Configure via env vars or flags:

- `ADMIN_BASIC_USER` (default: `admin`)
- `ADMIN_BASIC_PASSWORD_BCRYPT` (bcrypt hash of the password; required)

Generate a bcrypt hash (example using htpasswd):

```bash
htpasswd -nbBC 10 "" 'secret' | cut -d: -f2
```

Run with:

```bash
export ADMIN_BASIC_USER=admin
export ADMIN_BASIC_PASSWORD_BCRYPT='$2y$12$...'
go run cmd/httpserver/main.go
```

Use Basic Auth when calling admin endpoints, e.g.:

```bash
curl -u admin:secret http://localhost:8081/api/admin/v1/measurements
```

Local development only: you can disable Admin API auth with `--disable-admin-auth` or `DISABLE_ADMIN_AUTH=1`. This is unsafe; never use in production.

### Manual setup

**Start the database and the server:**
Expand Down Expand Up @@ -287,4 +316,4 @@ Payload: JSON with secrets, both flattened/unflattened
{
...
}
```
```
26 changes: 13 additions & 13 deletions adapters/database/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ func NewDatabaseService(dsn string) (*Service, error) {
return nil, err
}

db.DB.SetMaxOpenConns(50)
db.DB.SetMaxIdleConns(10)
db.DB.SetConnMaxIdleTime(0)
db.SetMaxOpenConns(50)
db.SetMaxIdleConns(10)
db.SetConnMaxIdleTime(0)

dbService := &Service{DB: db} //nolint:exhaustruct
return dbService, err
Expand Down Expand Up @@ -117,9 +117,9 @@ func (s *Service) RegisterCredentialsForBuilder(ctx context.Context, builderName
}

_, err = tx.Exec(`
INSERT INTO service_credential_registrations
INSERT INTO service_credential_registrations
(builder_name, service, tls_cert, ecdsa_pubkey, is_active, measurement_id)
VALUES ($1, $2, $3, $4, true,
VALUES ($1, $2, $3, $4, true,
(SELECT id FROM measurements_whitelist WHERE name = $5 AND attestation_type = $6)
)
`, builderName, service, nullableTLSCert, ecdsaPubKey, measurementName, attestationType)
Expand Down Expand Up @@ -150,26 +150,26 @@ func (s *Service) GetActiveConfigForBuilder(ctx context.Context, builderName str

func (s *Service) GetActiveBuildersWithServiceCredentials(ctx context.Context, network string) ([]domain.BuilderWithServices, error) {
rows, err := s.DB.QueryContext(ctx, `
SELECT
SELECT
b.name,
b.ip_address,
b.dns_name,
scr.service,
scr.tls_cert,
scr.ecdsa_pubkey
FROM
FROM
builders b
LEFT JOIN
LEFT JOIN
service_credential_registrations scr ON b.name = scr.builder_name
WHERE
WHERE
b.is_active = true AND (scr.is_active = true OR scr.is_active IS NULL) AND b.network = $1
ORDER BY
ORDER BY
b.name, scr.service
`, network)
if err != nil {
return nil, err
}
defer rows.Close()
defer rows.Close() //nolint:errcheck

buildersMap := make(map[string]*BuilderWithCredentials)

Expand Down Expand Up @@ -227,9 +227,9 @@ func (s *Service) GetActiveBuildersWithServiceCredentials(ctx context.Context, n
func (s *Service) LogEvent(ctx context.Context, eventName, builderName, name string) error {
// Insert new event log entry with a subquery to fetch the measurement_id
_, err := s.DB.ExecContext(ctx, `
INSERT INTO event_log
INSERT INTO event_log
(event_name, builder_name, measurement_id)
VALUES ($1, $2,
VALUES ($1, $2,
(SELECT id FROM measurements_whitelist WHERE name = $3)
)
`, eventName, builderName, name)
Expand Down
29 changes: 28 additions & 1 deletion cmd/httpserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,23 @@ var flags = []cli.Flag{
Usage: "enable pprof debug endpoint",
EnvVars: []string{"PPROF"},
},
&cli.StringFlag{
Name: "admin-basic-user",
Value: "admin",
Usage: "username for admin Basic Auth",
EnvVars: []string{"ADMIN_BASIC_USER"},
},
&cli.StringFlag{
Name: "admin-basic-password-bcrypt",
Value: "",
Usage: "bcrypt hash of admin password (required to enable admin API, generate with `htpasswd -nbBC 12 admin 'secret' | cut -d: -f2`)",
EnvVars: []string{"ADMIN_BASIC_PASSWORD_BCRYPT"},
},
&cli.BoolFlag{
Name: "disable-admin-auth",
Usage: "disable admin Basic Auth (local development only)",
EnvVars: []string{"DISABLE_ADMIN_AUTH"},
},
&cli.Int64Flag{
Name: "drain-seconds",
Value: 15,
Expand Down Expand Up @@ -124,6 +141,9 @@ func runCli(cCtx *cli.Context) error {
enablePprof := cCtx.Bool("pprof")
drainDuration := time.Duration(cCtx.Int64("drain-seconds")) * time.Second
mockSecretsStorage := cCtx.Bool("mock-secrets")
adminBasicUser := cCtx.String("admin-basic-user")
adminPasswordBcrypt := cCtx.String("admin-basic-password-bcrypt")
disableAdminAuth := cCtx.Bool("disable-admin-auth")

logTags := map[string]string{
"version": common.Version,
Expand All @@ -142,13 +162,16 @@ func runCli(cCtx *cli.Context) error {
})

log.With("version", common.Version).Info("starting builder-hub")
if disableAdminAuth {
log.Warn("ADMIN AUTH DISABLED! DO NOT USE IN PRODUCTION", "flag", "--disable-admin-auth")
}

db, err := database.NewDatabaseService(cCtx.String("postgres-dsn"))
if err != nil {
log.Error("failed to create database", "err", err)
return err
}
defer db.Close()
defer db.Close() //nolint:errcheck

var sm ports.AdminSecretService

Expand All @@ -175,6 +198,10 @@ func runCli(cCtx *cli.Context) error {
Log: log,
EnablePprof: enablePprof,

AdminBasicUser: adminBasicUser,
AdminPasswordBcrypt: adminPasswordBcrypt,
AdminAuthDisabled: disableAdminAuth,

DrainDuration: drainDuration,
GracefulShutdownDuration: 30 * time.Second,
ReadTimeout: 60 * time.Second,
Expand Down
1 change: 1 addition & 0 deletions docker/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ services:
ADMIN_ADDR: "0.0.0.0:8081"
INTERNAL_ADDR: "0.0.0.0:8082"
METRICS_ADDR: "0.0.0.0:8090"
DISABLE_ADMIN_AUTH: "1" # local dev only; do not use in production

proxy:
image: flashbots/builder-hub-mock-proxy
Expand Down
Loading
Loading