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
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ JWT_KEY_DIR=~/.observal/keys
JWT_SIGNING_ALGORITHM=ES256


# -----------------------------------------------------------------------------
# Google OAuth
# Imported once into dynamic settings on startup when set in the container env.
# GOOGLE_OAUTH_CLIENT_ID=
# GOOGLE_OAUTH_CLIENT_SECRET=
# GOOGLE_OAUTH_ALLOWED_DOMAINS=acme.com,acme.io


# -----------------------------------------------------------------------------
# Container credentials (docker-compose only — not read by the app)
# Must match DATABASE_URL / CLICKHOUSE_URL above.
Expand Down
41 changes: 40 additions & 1 deletion docs/self-hosting/authentication.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- SPDX-FileCopyrightText: 2026 Apoorv Garg <apoorvgarg.21@gmail.com> -->
<!-- SPDX-FileCopyrightText: 2026 Apoorv Garg <apoorvgarg.work@gmail.com> -->
<!-- SPDX-License-Identifier: AGPL-3.0-only -->

# Authentication and SSO
Expand Down Expand Up @@ -126,6 +126,45 @@ The first user who logs in via OAuth is **not** automatically an admin. Bootstra

Observal requests standard `openid profile email` scope. The IdP's `email` claim is the canonical user identifier.

## Google OAuth (first-class provider)

Google sign-in runs as its own provider, separate from the generic OIDC slot above. Both can be enabled at the same time, so an org can offer Okta *and* Google on the login screen.

Set these in the SSO settings page, or set them as container env vars for one-time import at startup. The **Sign in with Google** button appears after the API restarts:

```
GOOGLE_OAUTH_CLIENT_ID=1234567890-abc...apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-...
```

The Google OIDC discovery URL is hardcoded server-side, so you don't need to set it.

### Creating the Google OAuth client

1. Open the [Google Cloud Console](https://console.cloud.google.com/apis/credentials) in the project you want to use.
2. Click **Create Credentials → OAuth client ID**.
3. **Application type:** Web application.
4. **Authorized JavaScript origins:** `{FRONTEND_URL}` (e.g. `https://observal.your-company.internal`).
5. **Authorized redirect URI:** `{FRONTEND_URL}/api/v1/auth/oauth/google/callback`.
6. Copy the generated **Client ID** and **Client secret** into SSO settings or your container env.
7. Restart the API container so the Authlib client is rebuilt.

### Restricting to specific email domains

Set `GOOGLE_OAUTH_ALLOWED_DOMAINS` to a comma-separated list of domains. Anyone outside the list is rejected with a 403, even if they have a valid Google account.

```
GOOGLE_OAUTH_ALLOWED_DOMAINS=acme.com,acme.io
```

Leave it unset to allow any Google account (including personal `@gmail.com` addresses) to provision themselves as `role=user`.

### Notes

- Observal additionally requires Google's `email_verified` claim to be `true`. Unverified accounts (rare on Google but possible) are rejected with a 400.
- The first Google user is **not** automatically an admin (matches the generic OIDC behavior). Bootstrap a local admin first, then use that account to promote the Google user.
- The auth provider and Google subject ID are recorded on the user row (`auth_provider="google"`, `sso_subject_id=<google-sub>`) for audit purposes.

## Role-based access control (RBAC)

Four built-in roles enforced on every endpoint:
Expand Down
15 changes: 15 additions & 0 deletions infra/terraform/aws-ec2/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<!-- SPDX-FileCopyrightText: 2026 Vishnu Muthiah <vishnu.muthiah04@gmail.com> -->
<!-- SPDX-FileCopyrightText: 2026 Apoorv Garg <apoorvgarg.work@gmail.com> -->
<!-- SPDX-License-Identifier: AGPL-3.0-only -->

# Observal on AWS EC2 — Single Instance
Expand Down Expand Up @@ -85,6 +86,20 @@ route53_zone_id = "Z1234567890ABC"

The deploy script automatically obtains a Let's Encrypt TLS certificate and configures HTTPS.

### Enabling Google OAuth (and other env overrides)

Any value in `.env.example` can be overridden via the `env_overrides` map. To turn on Google sign-in:

```hcl
env_overrides = {
GOOGLE_OAUTH_CLIENT_ID = "1234567890-abc...apps.googleusercontent.com"
GOOGLE_OAUTH_CLIENT_SECRET = "GOCSPX-..."
GOOGLE_OAUTH_ALLOWED_DOMAINS = "acme.com,acme.io" # optional
}
```

After `terraform apply` re-runs the user-data and the API container restarts, the login page shows a "Sign in with Google" button. The same mechanism configures the generic OIDC slot (`OAUTH_CLIENT_ID`, `OAUTH_CLIENT_SECRET`, `OAUTH_SERVER_METADATA_URL`). See [Authentication and SSO](../../../docs/self-hosting/authentication.md) for the full setup walkthrough.

### Instance sizes

| Type | vCPU | RAM | Recommended for |
Expand Down
10 changes: 7 additions & 3 deletions infra/terraform/aws-ec2/terraform.tfvars.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: 2026 Vishnu Muthiah <vishnu.muthiah04@gmail.com>
# SPDX-FileCopyrightText: 2026 Apoorv Garg <apoorvgarg.work@gmail.com>
# SPDX-License-Identifier: AGPL-3.0-only

# Deployment name (used for resource naming — lowercase, no spaces)
Expand All @@ -22,9 +23,12 @@ observal_ref = "main"
# Environment variable overrides
# Empty values ("") use the default from .env.example. Only fill in what you need to change.
env_overrides = {
OBSERVAL_LICENSE_KEY = ""
OAUTH_CLIENT_ID = ""
OAUTH_CLIENT_SECRET = ""
OBSERVAL_LICENSE_KEY = ""
OAUTH_CLIENT_ID = ""
OAUTH_CLIENT_SECRET = ""
GOOGLE_OAUTH_CLIENT_ID = ""
GOOGLE_OAUTH_CLIENT_SECRET = ""
GOOGLE_OAUTH_ALLOWED_DOMAINS = ""
JWT_KEY_DIR = ""
JWT_SIGNING_ALGORITHM = ""
LOG_LEVEL = ""
Expand Down
26 changes: 26 additions & 0 deletions infra/terraform/gcp/cloud-run.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: 2026 Lokesh Selvam <lokeshselvam7025@gmail.com>
# SPDX-FileCopyrightText: 2026 Apoorv Garg <apoorvgarg.work@gmail.com>
# SPDX-License-Identifier: AGPL-3.0-only

resource "google_service_account" "cloud_run" {
Expand Down Expand Up @@ -117,6 +118,31 @@ resource "google_cloud_run_v2_service" "api" {
}
}

env {
name = "GOOGLE_OAUTH_CLIENT_ID"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.app["GOOGLE_OAUTH_CLIENT_ID"].secret_id
version = "latest"
}
}
}

env {
name = "GOOGLE_OAUTH_CLIENT_SECRET"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.app["GOOGLE_OAUTH_CLIENT_SECRET"].secret_id
version = "latest"
}
}
}

env {
name = "GOOGLE_OAUTH_ALLOWED_DOMAINS"
value = var.google_oauth_allowed_domains
}

startup_probe {
http_get {
path = "/readyz"
Expand Down
13 changes: 8 additions & 5 deletions infra/terraform/gcp/secrets.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: 2026 Lokesh Selvam <lokeshselvam7025@gmail.com>
# SPDX-FileCopyrightText: 2026 Apoorv Garg <apoorvgarg.work@gmail.com>
# SPDX-License-Identifier: AGPL-3.0-only

resource "random_password" "secret_key" {
Expand All @@ -13,11 +14,13 @@ resource "random_password" "clickhouse" {

locals {
secrets = {
DATABASE_URL = local.database_url
REDIS_URL = local.redis_url
SECRET_KEY = random_password.secret_key.result
CLICKHOUSE_URL = local.clickhouse_url
OBSERVAL_LICENSE_KEY = var.observal_license_key
DATABASE_URL = local.database_url
REDIS_URL = local.redis_url
SECRET_KEY = random_password.secret_key.result
CLICKHOUSE_URL = local.clickhouse_url
OBSERVAL_LICENSE_KEY = var.observal_license_key
GOOGLE_OAUTH_CLIENT_ID = var.google_oauth_client_id
GOOGLE_OAUTH_CLIENT_SECRET = var.google_oauth_client_secret
}
}

Expand Down
6 changes: 6 additions & 0 deletions infra/terraform/gcp/terraform.tfvars.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: 2026 Lokesh Selvam <lokeshselvam7025@gmail.com>
# SPDX-FileCopyrightText: 2026 Apoorv Garg <apoorvgarg.work@gmail.com>
# SPDX-License-Identifier: AGPL-3.0-only

project_id = "my-gcp-project"
Expand Down Expand Up @@ -33,3 +34,8 @@ data_retention_days = 90

# License (leave empty for community edition)
# observal_license_key = ""

# Google OAuth (leave empty to disable Google sign-in)
# google_oauth_client_id = ""
# google_oauth_client_secret = ""
# google_oauth_allowed_domains = "acme.com,acme.io"
21 changes: 21 additions & 0 deletions infra/terraform/gcp/variables.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: 2026 Lokesh Selvam <lokeshselvam7025@gmail.com>
# SPDX-FileCopyrightText: 2026 Apoorv Garg <apoorvgarg.work@gmail.com>
# SPDX-License-Identifier: AGPL-3.0-only

variable "project_id" {
Expand Down Expand Up @@ -259,6 +260,26 @@ variable "edition" {
}
}

variable "google_oauth_client_id" {
description = "Google OAuth 2.0 client ID from the GCP console. Leave empty to disable Google sign-in."
type = string
default = ""
sensitive = true
}

variable "google_oauth_client_secret" {
description = "Google OAuth 2.0 client secret. Required if google_oauth_client_id is set."
type = string
default = ""
sensitive = true
}

variable "google_oauth_allowed_domains" {
description = "Comma-separated email-domain allowlist for Google sign-in (e.g. 'acme.com,acme.io'). Leave empty to allow any Google account."
type = string
default = ""
}

# ── Backups ───────────────────────────────────────────────────────────────

variable "backup_retention_days" {
Expand Down
Loading
Loading