Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
working-directory: control-plane
run: |
make fmt-check
GOWORK=off make lint
GOWORK=off go vet ./...
GOWORK=off go test ./...
GOWORK=off go test -tags=integration -count=1 ./internal/integration
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
merge-multiple: true
- uses: goreleaser/goreleaser-action@v7.2.3
with:
version: v2.18.0
version: v2.18.2
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ version = "0.0.0" # the release tag is the version: release.yml stamps it before
edition = "2024"
license = "AGPL-3.0-only"
repository = "https://github.com/cocoonstack/gateway"
rust-version = "1.98"
rust-version = "1.98.1"
publish = false

[profile.release]
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Pinned: successive image builds must embed the same toolchain, not whatever
# `rust:1` floats to on the day of the build.
FROM rust:1.98.0@sha256:620dbcd124499c59e2406d3741574b5c5838cf9eb9656f0c3a03948f79b02959 AS builder
FROM rust:1.98.1@sha256:a8a5f0a1e5fe7dfe1d352591e4a1c7dd2c08fd70475cae872cf3458ba0df0546 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release -p gw-server --locked
Expand Down
90 changes: 90 additions & 0 deletions control-plane/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
version: "2"

run:
timeout: 5m
modules-download-mode: readonly

linters:
default: none
enable:
- asciicheck
- bodyclose
- dogsled
- errcheck
- gochecknoinits
- goconst
- gocritic
- gocyclo
- goprintffuncname
- gosec
- govet
- ineffassign
- misspell
- modernize
- nakedret
- nestif
- nolintlint
- revive
- staticcheck
- unconvert
- unparam
- unused
settings:
gocritic:
disabled-checks:
- captLocal
govet:
enable-all: true
disable:
- fieldalignment
revive:
enable-default-rules: true
rules:
- name: exported
disabled: true
nolintlint:
allow-unused: false
misspell:
locale: US
goconst:
min-len: 3
min-occurrences: 3
gocyclo:
min-complexity: 30
nakedret:
max-func-lines: 30
nestif:
min-complexity: 5
exclusions:
generated: strict
paths:
- vendor
- mocks
- dist
rules:
- path: _test\.go
linters:
- errcheck
- gosec
- unparam
- goconst
- gocyclo
- nestif
# Table-driven subtests re-declare err per case; the outer one is fixture setup.
- path: _test\.go
linters:
- govet
text: "shadow"
# Fixture strings in the dev-mode mock client; naming them is churn.
- path: internal/gateway/mock/
linters:
- goconst

formatters:
enable:
- gofumpt
- goimports
settings:
goimports:
local-prefixes:
- github.com/cocoonstack/
20 changes: 19 additions & 1 deletion control-plane/Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
.PHONY: test test-integration web web-test build fmt fmt-check gofumpt goimports
.PHONY: test test-integration web web-test build fmt fmt-check gofumpt goimports lint golangci-lint

RUST_CARGO ?= cargo

LOCALBIN ?= $(shell pwd)/bin
GOLANGCILINT_VERSION ?= v2.13.2
GOLANGCILINT_ROOT := $(LOCALBIN)/golangci-lint-$(GOLANGCILINT_VERSION)
GOLANGCILINT := $(GOLANGCILINT_ROOT)/golangci-lint
GOFUMPT_VERSION ?= v0.11.0
GOFUMPT_ROOT := $(LOCALBIN)/gofumpt-$(GOFUMPT_VERSION)
GOFMT := $(GOFUMPT_ROOT)/gofumpt
Expand All @@ -11,6 +14,13 @@ GOIMPORTS_ROOT := $(LOCALBIN)/goimports-$(GOIMPORTS_VERSION)
GOIMPORTS := $(GOIMPORTS_ROOT)/goimports
GOIMPORTS_LOCAL_PREFIXES := github.com/cocoonstack/

## Target OSes for lint
GOOSES ?= linux darwin

golangci-lint: $(GOLANGCILINT)
$(GOLANGCILINT):
GOBIN=$(GOLANGCILINT_ROOT) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCILINT_VERSION)

gofumpt: $(GOFMT)
$(GOFMT):
GOBIN=$(GOFUMPT_ROOT) go install mvdan.cc/gofumpt@$(GOFUMPT_VERSION)
Expand All @@ -27,6 +37,14 @@ fmt-check: gofumpt goimports
@test -z "$$($(GOFMT) -l .)" || { echo "Files need formatting (gofumpt):"; $(GOFMT) -l .; exit 1; }
@test -z "$$($(GOIMPORTS) -l .)" || { echo "Files need formatting (goimports):"; $(GOIMPORTS) -l .; exit 1; }

lint: golangci-lint ## Run golangci-lint on every target OS, integration harness included
@for goos in $(GOOSES); do \
echo "==> golangci-lint GOOS=$$goos"; \
GOOS=$$goos $(GOLANGCILINT) run ./... || exit 1; \
echo "==> golangci-lint GOOS=$$goos -tags integration ./internal/integration"; \
GOOS=$$goos $(GOLANGCILINT) run --build-tags integration ./internal/integration || exit 1; \
done

test:
GOWORK=off go test ./...

Expand Down
19 changes: 13 additions & 6 deletions control-plane/cmd/control-plane/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package main is the gateway control-plane entry point.
package main

import (
Expand Down Expand Up @@ -39,13 +40,14 @@ func main() {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := log.SetupLog(ctx, &types.ServerLogConfig{Level: cfg.LogLevel}, ""); err != nil {
if err = log.SetupLog(ctx, &types.ServerLogConfig{Level: cfg.LogLevel, UseJSON: !stderrIsTerminal()}, ""); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
defer stop()
if err := run(ctx, cfg); err != nil {
err = run(ctx, cfg)
stop()
if err != nil {
log.WithFunc("main").Error(ctx, err, "control plane stopped")
os.Exit(1)
}
Expand All @@ -66,7 +68,7 @@ func run(ctx context.Context, cfg config.Config) (err error) {
if err != nil {
return err
}
if err := seedUsers(ctx, users, cfg); err != nil {
if err = seedUsers(ctx, users, cfg); err != nil {
return err
}

Expand All @@ -91,8 +93,8 @@ func run(ctx context.Context, cfg config.Config) (err error) {
defer close(done)
shutdownCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 10*time.Second)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
log.WithFunc("main.run").Error(shutdownCtx, err, "drain HTTP server")
if shutdownErr := server.Shutdown(shutdownCtx); shutdownErr != nil {
log.WithFunc("main.run").Error(shutdownCtx, shutdownErr, "drain HTTP server")
}
})
log.WithFunc("main.run").Infof(ctx, "control plane listening on http://%s", cfg.ListenAddr)
Expand Down Expand Up @@ -171,3 +173,8 @@ func ensureUser(ctx context.Context, store user.Store, seed userSeed) error {
}
return nil
}

func stderrIsTerminal() bool {
fi, err := os.Stderr.Stat()
return err == nil && fi.Mode()&os.ModeCharDevice != 0
}
10 changes: 5 additions & 5 deletions control-plane/go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module github.com/cocoonstack/gateway/control-plane

go 1.27.0
go 1.27.1

require (
github.com/jackc/pgx/v5 v5.10.0
github.com/projecteru2/core v0.1.3
github.com/projecteru2/core v0.1.5
github.com/redis/go-redis/v9 v9.22.0
golang.org/x/crypto v0.55.0
golang.org/x/crypto v0.56.0
golang.org/x/sync v0.22.0
)

Expand All @@ -16,7 +16,7 @@ require (
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect
github.com/cockroachdb/redact v1.1.8 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/getsentry/sentry-go v0.48.0 // indirect
github.com/getsentry/sentry-go v0.49.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
Expand All @@ -31,7 +31,7 @@ require (
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260918162117-cecb64721679 // indirect
google.golang.org/grpc v1.83.2 // indirect
google.golang.org/protobuf v1.36.12 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
Expand Down
16 changes: 8 additions & 8 deletions control-plane/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/getsentry/sentry-go v0.48.0 h1:FRZNr7Uk1C86ev1bSJmYlUkL9oyivQA6YOcdYfaaMmY=
github.com/getsentry/sentry-go v0.48.0/go.mod h1:E5UkA5wp1qR2+MDydNYlVeUiNN2xEdjYMidkgf0Qoss=
github.com/getsentry/sentry-go v0.49.0 h1:Ehejknu1l023Ub7QoRBVLAI7g3Jnhqku4oWx4B4Sh5s=
github.com/getsentry/sentry-go v0.49.0/go.mod h1:nuMJAoCfe1u0Bts2ocyNI+TW8HT84vRMqwA5Qq/SKUI=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
Expand Down Expand Up @@ -48,8 +48,8 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/projecteru2/core v0.1.3 h1:0odzNdtDKRvbMir3xKpqCPJaThHdzfzvou/vGFWquZY=
github.com/projecteru2/core v0.1.3/go.mod h1:4WGV1OlU8TY0hra+N8Ua2s8hbX1AeJmcjnj3PcztnI4=
github.com/projecteru2/core v0.1.5 h1:cS3xfqa6I1bMXtGdbu2A2UzhjiHaP2mogjQWFTkSEF0=
github.com/projecteru2/core v0.1.5/go.mod h1:MDMJmmlJ3ZVvFQl5BnRcKV1/AVCHyiWoI4BI+sCnuYM=
github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0=
github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
Expand Down Expand Up @@ -79,8 +79,8 @@ go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
Expand Down Expand Up @@ -111,8 +111,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260918162117-cecb64721679 h1:KmqdJU4vrNcxy/6qdg3JduZtalEXrJLspVltnR1cE+8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260918162117-cecb64721679/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc=
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
Expand Down
2 changes: 1 addition & 1 deletion control-plane/internal/auth/password.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func VerifyPassword(encoded, password string) bool {
if err != nil || len(want) == 0 || len(want) > 64 {
return false
}
got := argon2.IDKey([]byte(password), salt, timeCost, memory, threads, uint32(len(want)))
got := argon2.IDKey([]byte(password), salt, timeCost, memory, threads, uint32(len(want))) //nolint:gosec // len(want) is at most 64
return subtle.ConstantTimeCompare(got, want) == 1
}

Expand Down
6 changes: 3 additions & 3 deletions control-plane/internal/httpapi/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func (s *Server) createKey(w http.ResponseWriter, r *http.Request) {
return
}
s.auditLog(r, "key_create", key.AK)
writeJSON(w, http.StatusCreated, map[string]string{"status": "created", "ak": key.AK})
writeJSON(w, http.StatusCreated, map[string]string{statusField: "created", "ak": key.AK})
}

func (s *Server) patchKey(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -220,7 +220,7 @@ func (s *Server) publishConfig(w http.ResponseWriter, r *http.Request) {
return
}
s.auditLog(r, "config_publish", strconv.FormatInt(version, 10))
writeJSON(w, http.StatusOK, map[string]any{"status": "published", "version": version})
writeJSON(w, http.StatusOK, map[string]any{statusField: "published", "version": version})
}

func (s *Server) configVersions(w http.ResponseWriter, r *http.Request) {
Expand All @@ -244,7 +244,7 @@ func (s *Server) rollbackConfig(w http.ResponseWriter, r *http.Request) {
return
}
s.auditLog(r, "config_rollback", strconv.FormatInt(version, 10))
writeJSON(w, http.StatusOK, map[string]any{"status": "rolled_back", "version": version})
writeJSON(w, http.StatusOK, map[string]any{statusField: "rolled_back", "version": version})
}

func (s *Server) audit(w http.ResponseWriter, r *http.Request) {
Expand Down
6 changes: 3 additions & 3 deletions control-plane/internal/httpapi/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var dummyHash = sync.OnceValue(func() string {
})

func (s *Server) health(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ok", "service": "gateway-control-plane"})
writeJSON(w, http.StatusOK, map[string]string{statusField: "ok", "service": "gateway-control-plane"})
}

func (s *Server) login(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -71,7 +71,7 @@ func (s *Server) login(w http.ResponseWriter, r *http.Request) {
loginError(r.Context(), w, err)
return
}
http.SetCookie(w, &http.Cookie{
http.SetCookie(w, &http.Cookie{ //nolint:gosec // Secure follows CP_COOKIE_SECURE
Name: sessionCookie, Value: session.ID, Path: "/", HttpOnly: true,
Secure: s.cookieSecure, SameSite: http.SameSiteLaxMode,
MaxAge: int(s.sessionTTL.Seconds()), Expires: time.Unix(session.ExpiresAt, 0),
Expand All @@ -85,7 +85,7 @@ func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
mapError(r.Context(), w, err)
return
}
http.SetCookie(w, &http.Cookie{
http.SetCookie(w, &http.Cookie{ //nolint:gosec // Secure follows CP_COOKIE_SECURE
Name: sessionCookie, Path: "/", HttpOnly: true, Secure: s.cookieSecure,
SameSite: http.SameSiteLaxMode, MaxAge: -1, Expires: time.Unix(1, 0),
})
Expand Down
1 change: 1 addition & 0 deletions control-plane/internal/httpapi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
sessionCookie = "cp_session"
maxJSONBody = 1 << 20
maxConfigBody = 4 << 20
statusField = "status"
)

type principal struct {
Expand Down
4 changes: 2 additions & 2 deletions control-plane/internal/user/postgres/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@ func (s *Store) migrate(ctx context.Context) error {
return fmt.Errorf("begin migrations: %w", err)
}
defer func() { _ = tx.Rollback(ctx) }()
if _, err := tx.Exec(ctx, "SELECT pg_advisory_xact_lock($1)", migrationLockKey); err != nil {
if _, err = tx.Exec(ctx, "SELECT pg_advisory_xact_lock($1)", migrationLockKey); err != nil {
return fmt.Errorf("lock migrations: %w", err)
}
if _, err := tx.Exec(ctx, "CREATE TABLE IF NOT EXISTS schema_migrations (name TEXT PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT now())"); err != nil {
if _, err = tx.Exec(ctx, "CREATE TABLE IF NOT EXISTS schema_migrations (name TEXT PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT now())"); err != nil {
return fmt.Errorf("create schema migrations: %w", err)
}
entries, err := migrationFiles.ReadDir("migrations")
Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
channel = "1.98.0"
channel = "1.98.1"
components = ["rustfmt", "clippy"]
Loading