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
17 changes: 13 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ linters:
- reference
- tmp
- bin
- node_modules
rules:
- path: '(.+)_test\.go'
linters:
- gocognit
- funlen
- goconst
# Command-line programs print their output.
- path: '^(cmd|internal/cli|scripts)/'
linters:
- forbidigo
text: 'fmt\.Print'
settings:
gosec:
# Exclude rules that produce false positives on manually-verified secure code:
Expand Down Expand Up @@ -96,13 +102,15 @@ linters:
line-length: 120
tab-width: 4
forbidigo:
# Patterns match the called identifier (e.g. `time.Sleep`), not source text, so they're anchored and have no
# parentheses.
forbid:
- pattern: 'panic\('
- pattern: '^panic$'
msg: "panic is forbidden; return errors instead"
- pattern: 'fmt\.Print(f|ln)?\('
- pattern: '^fmt\.Print(f|ln)?$'
msg: "use structured logging instead of fmt.Print*"
- pattern: 'time\.Sleep\('
msg: "avoid time.Sleep; use context with timeout"
- pattern: '^time\.Sleep$'
msg: "avoid time.Sleep; wait on a timer or ticker with a context"
misspell:
locale: US

Expand All @@ -116,6 +124,7 @@ formatters:
- reference
- tmp
- bin
- node_modules
settings:
gofmt:
simplify: true
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ the Rack Gateway API are exposed to the public internet.**
5. **Audit Logs**: Monitor for anomalies and denied requests
6. **Session Management**: All sessions stored in database and revocable

To report a vulnerability, see [SECURITY.md](./SECURITY.md). Please don't open a public issue.

## Testing

```bash
Expand Down
35 changes: 35 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Security Policy

## Supported Versions

Only the latest release of Rack Gateway gets security fixes. Upgrade to it before reporting, and check whether the
problem is still there.

## Reporting a Vulnerability

Please don't report security vulnerabilities in public GitHub issues, pull requests or discussions.

Report them privately, either way:

- **GitHub:** [Report a vulnerability](https://github.com/DocSpring/rack-gateway/security/advisories/new) on the
repository's Security tab
- **Email:** [security@docspring.com](mailto:security@docspring.com)

Include:

- The version (or commit) you tested
- What an attacker can do, and what they need first (an account, a role, network access)
- Steps or a proof of concept that reproduce it
- Any configuration it depends on

We'll acknowledge your report, keep you updated while we fix it, and agree a disclosure date with you. We credit
reporters in the release notes and the advisory unless you'd rather we didn't.

## Scope

In scope: the gateway server, the `rack-gateway` CLI, the web UI and the release artifacts (Docker image, CLI
archives) published from this repository.

Out of scope: Convox itself (report those to [Convox](https://github.com/convox/convox)), and problems that need a
misconfiguration the documentation warns against, such as exposing the gateway without TLS or running a test build
(`DEV_MODE`, `E2E_TEST_MODE`) in production.
3 changes: 0 additions & 3 deletions cmd/mock-convox/handlers_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ func appLogs(w http.ResponseWriter, r *http.Request) {
}()

_ = c.WriteMessage(websocket.TextMessage, []byte("Promoting release...\n"))
time.Sleep(100 * time.Millisecond)
_ = c.WriteMessage(websocket.TextMessage, []byte("Release promoted successfully.\n"))

ping := time.NewTicker(30 * time.Second)
Expand Down Expand Up @@ -57,9 +56,7 @@ func buildLogs(w http.ResponseWriter, r *http.Request) {
}()

_ = c.WriteMessage(websocket.TextMessage, []byte("Building app...\n"))
time.Sleep(100 * time.Millisecond)
_ = c.WriteMessage(websocket.TextMessage, []byte("Step 1/1: mock build step\n"))
time.Sleep(100 * time.Millisecond)
_ = c.WriteMessage(websocket.TextMessage, []byte("Build complete\n"))
_ = c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
}
4 changes: 2 additions & 2 deletions docs/src/content/docs/concepts/rbac-principles.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ Wildcards appear only in the admin role; see [Permissions](/security/rbac/permis

Roles are assigned through:

1. **Seed lists**: users in `ADMIN_USERS`, `DEPLOYER_USERS`, `OPERATIONS_USERS` and `VIEWER_USERS` are created
with that role at startup
1. **Seed lists**: users in `ADMIN_USERS`, `DEPLOYER_USERS`, `OPERATIONS_USERS` and `VIEWER_USERS` that don't exist
yet are created with that role at startup (audit-logged; existing users are left alone)
2. **Web UI**: Admins add users and assign their roles
3. **API tokens**: Tokens get individual permissions (role shortcuts fill them in), and can only use the ones their
owner's current role allows
Expand Down
6 changes: 4 additions & 2 deletions docs/src/content/docs/configuration/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ but the upgrade sweep encrypts any it finds (see **Existing data is upgraded at
| `DEPLOYER_USERS` | Comma-separated deployer emails |
| `OPERATIONS_USERS` | Comma-separated ops emails |

Every start creates the listed users that don't exist yet, and resets each listed user's roles to that list's role,
so a role you change in the web UI for a seeded user reverts at the next start. Seeding writes no audit entries.
Every start creates the listed users that don't exist yet, with the role of each list that names them. Existing
users are never changed, so a role you change in the web UI stays changed; to take a seeded user's access away, lock
or delete them in the web UI and remove them from the list. Each user it creates is recorded in the audit log as
`user.create` by `system`, in the same transaction, and the gateway doesn't start if that record can't be written.

## Convox Rack

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ Each gateway has its own users: signing in never creates one. Either:
1. **Add them on each gateway** - an admin adds users and roles on its **Users** page
2. **Seed them from configuration** - set the same `ADMIN_USERS`, `DEPLOYER_USERS`, `OPERATIONS_USERS` and
`VIEWER_USERS` on every gateway (for example from one Terraform variable). Each start creates missing users
and resets listed users' roles to the list's role
with the list's role, and leaves existing users alone

## Disaster Recovery Runbook

Expand Down
1 change: 1 addition & 0 deletions docs/src/content/docs/development/api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ CLI login uses the RFC 8252 loopback flow (see [OAuth Flow](/security/authentica
- `POST /auth/cli/cancel` - cancels the login from the bound browser; returns the loopback URL that tells the CLI
- `POST /auth/cli/complete` - redeems the login code with the CLI's code verifier for a session token
- `GET /auth/cli/initiator` - the device name and IP that started the login, shown on the approval page
- `POST /auth/cli/logout` - revokes the CLI session sent as the bearer token (cookies are ignored)
- `GET /auth/web/login` (also supports `HEAD`)
- `GET /auth/web/callback`
- `GET /auth/web/logout`
Expand Down
4 changes: 3 additions & 1 deletion docs/src/content/docs/development/contributing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,9 @@ Why is this change needed?
Do NOT open public issues for security vulnerabilities.
</Aside>

Email security concerns to: security@docspring.com
Report them privately: [report a vulnerability](https://github.com/DocSpring/rack-gateway/security/advisories/new)
on GitHub, or email [security@docspring.com](mailto:security@docspring.com). See the
[security policy](https://github.com/DocSpring/rack-gateway/blob/main/SECURITY.md).

### Secure Coding Practices

Expand Down
6 changes: 5 additions & 1 deletion docs/src/content/docs/integrations/email.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,18 @@ Sent when a user account is locked.

#### MFA Auto-Lock

Sent when an account is locked automatically after 5 failed MFA attempts in 5 minutes.
Sent when an account is locked automatically after 5 failed MFA attempts in 5 minutes. The lock is recorded in
the audit log as `user.lock` by `system`.

| Field | Content |
|-------|---------|
| Subject | Account Locked - Multiple Failed Login Attempts |
| To | Locked user email |
| Content | Reason, contact instructions |

Every admin who isn't locked also gets **User locked: \{email\}**, asking them to check with the user before
unlocking the account.

### API Tokens

When an API token is created, its owner gets **New API token created** (the token's name and who created it),
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/integrations/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Every audit log entry can be routed to Slack with an action pattern; the default
| Category | Examples | Email |
|----------|----------|-------|
| **Authentication** | New CLI login, failed login | The user |
| **MFA** | Failed verification, automatic lock after 5 failures | The user |
| **MFA** | Failed verification, automatic lock after 5 failures | The user; admins when an account is locked |
| **User Management** | User added, locked, unlocked | The user; admins when a user is added |
| **API Tokens** | Token created | The owner and the other admins |
| **Rack Parameters** | Parameters changed | Admins |
Expand Down
3 changes: 3 additions & 0 deletions docs/src/content/docs/operations/database-maintenance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ export DEV_MODE=true # development (testbypass) builds only
rack-gateway-api reset-db
```

It drops every table and re-runs the migrations. The gateway creates the
[seed users](/configuration/environment-variables/#seed-users) again when it next starts.

<Aside type="danger">
This command **drops all tables and data**. It requires explicit environment variable guards to prevent accidental execution.
</Aside>
Expand Down
4 changes: 3 additions & 1 deletion docs/src/content/docs/operations/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ to be redirected there with a single-use login code.

### Account Locked After Failed MFA

Five failed MFA attempts in five minutes lock the account. An admin unlocks it on the Users page.
Five failed MFA attempts in five minutes lock the account. The admins are emailed, and the lock is in the audit log
as `user.lock` by `system`. An admin unlocks it on the Users page once they've confirmed the attempts were the
user's.

### Lost MFA Device

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ API tokens provide authentication for automated systems like CI/CD pipelines, sc
| **Lifetime** | Until deleted or the optional `expires_at` | Web: idle timeout; CLI: 90 days from last use |
| **MFA** | Not applicable | Supported |
| **CSRF** | Not required | Required |
| **Revocation** | Manual delete | Web logout, or revoking it from the user's sessions |
| **Revocation** | Manual delete | Logout (web or `rack-gateway logout`), or revoking it from the user's sessions |

## Token Anatomy

Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/security/authentication/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ See [MFA Overview](/user-guide/mfa/) for setup details.
|---------------|-----------------|------------------------|
| **Creation** | OAuth flow | Created in the web UI or with `rack-gateway api-token create` (deployers for themselves, admins for anyone) |
| **Lifetime** | Web: idle timeout; CLI: 90 days from last use | Until deleted or its optional expiry |
| **Revocation** | Web logout, or revoking it from the user's sessions | Delete token |
| **Revocation** | Logout (web or `rack-gateway logout`), or revoking it from the user's sessions | Delete token |
| **MFA** | Required | N/A (limited by permissions instead) |
| **CSRF** | Required (web) | Not required |
| **Use Case** | Interactive access | CI/CD, scripts |
Expand Down
6 changes: 3 additions & 3 deletions docs/src/content/docs/security/authentication/sessions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ Sessions can be revoked through multiple mechanisms:

### User-Initiated Logout

**Logout** in the web UI's sidebar revokes the web session. `rack-gateway logout` only removes the rack and its
token from the CLI's config; it doesn't revoke the CLI session on the gateway, so revoke that from your sessions
(below).
**Logout** in the web UI's sidebar revokes the web session. `rack-gateway logout` revokes the CLI session
(`POST /api/v1/auth/cli/logout` with the session token), then removes the rack and its token from the CLI's config.
Both are audited before the session is revoked, and refused if the audit log can't record them.

### Revoking Sessions

Expand Down
9 changes: 6 additions & 3 deletions docs/src/content/docs/security/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,11 @@ See [Production Checklist](/deployment/production-checklist/) for complete deplo
If you discover a security vulnerability in Rack Gateway:

1. **Do not** open a public GitHub issue
2. Email security concerns to the maintainers
3. Include detailed reproduction steps
2. Report it privately: [report a vulnerability](https://github.com/DocSpring/rack-gateway/security/advisories/new)
on GitHub, or email [security@docspring.com](mailto:security@docspring.com)
3. Include the version, what an attacker can do, and steps that reproduce it
4. Allow time for a fix before public disclosure

We appreciate responsible disclosure and will acknowledge security researchers in release notes.
Only the latest release gets security fixes. See the repository's
[security policy](https://github.com/DocSpring/rack-gateway/blob/main/SECURITY.md) for details. We credit reporters
in the release notes.
7 changes: 4 additions & 3 deletions docs/src/content/docs/user-guide/cli/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,10 @@ When your session has expired or been revoked, commands fail with an authenticat
rack-gateway logout
```

This removes the rack, with its URL and session token, from your local config. It doesn't revoke the session on
the gateway: to end it there, revoke it under your account's sessions in the web UI. To use the rack again, run
`rack-gateway login <rack> <gateway-url>`.
This ends the session on the gateway (a copy of the token stops working), then removes the rack, with its URL and
session token, from your local config. If the gateway can't be reached, nothing is removed and the command fails;
`rack-gateway logout --local` removes the rack anyway, and you can revoke the session later under your account's
sessions in the web UI. To use the rack again, run `rack-gateway login <rack> <gateway-url>`.

## Using API Tokens

Expand Down
8 changes: 6 additions & 2 deletions docs/src/content/docs/user-guide/cli/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,17 @@ rack-gateway login # sign in again to the current rack

### logout

Remove the rack, with its URL and session token, from your local config. This doesn't revoke the session on the
gateway: revoke it under your sessions in the web UI.
End the rack's session on the gateway, then remove the rack, with its URL and session token, from your local
config. If the gateway can't be reached or doesn't end the session, the rack stays configured and the command fails.

```bash
rack-gateway logout [flags]
```

**Flags:**
- `--local` - Only remove the rack from this machine. The session stays valid on the gateway until it expires or
you revoke it under your sessions in the web UI.

Use the global `--rack` flag to log out of another rack.

**Example:**
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/user-guide/cli/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ You can override this with the `--config` flag or `GATEWAY_CLI_CONFIG_DIR` envir
| Command | Description |
|---------|-------------|
| `login [rack] [url]` | Login to a gateway via OAuth (no arguments: sign in again to the current rack) |
| `logout` | Logout from the current rack |
| `logout` | End the session on the gateway and remove the rack |
| `test-auth` | Test authentication (optionally with MFA) |

### Rack Management
Expand Down
5 changes: 3 additions & 2 deletions docs/src/content/docs/user-guide/cli/multi-rack.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,14 @@ Your rack configurations are stored in `~/.config/rack-gateway/config.json`:

## Removing a Rack

`logout` removes a rack, with its URL and session token, from your config:
`logout` ends your session on that rack's gateway, then removes the rack, with its URL and session token, from your
config:

```bash
rack-gateway logout -r staging
```

It doesn't revoke the session on that gateway; revoke it under your sessions in the web UI.
If the gateway is gone for good, `rack-gateway logout -r staging --local` removes the rack without contacting it.

## Updating a Gateway URL

Expand Down
3 changes: 2 additions & 1 deletion internal/cli/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ Opens browser for OAuth flow, stores session token in config.
rack-gateway logout
```

Removes current rack from config.
Ends the session on the gateway (`POST /api/v1/auth/cli/logout`), then removes the rack from config.
`--local` skips the gateway call.

### Convox Commands

Expand Down
Loading