Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prod deploy #2615

Merged
merged 5 commits into from
Aug 14, 2024
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
22 changes: 21 additions & 1 deletion internal/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ func run(p utils.Program, ctx context.Context, fsys afero.Fs, excludedContainers
excluded[name] = true
}

jwks, err := utils.Config.Auth.ResolveJWKS(ctx)
if err != nil {
return err
}

// Start Postgres.
w := utils.StatusWriter{Program: p}
if dbConfig.Host == utils.DbId {
Expand Down Expand Up @@ -497,6 +502,11 @@ EOF
fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_ROTATION_ENABLED=%v", utils.Config.Auth.EnableRefreshTokenRotation),
fmt.Sprintf("GOTRUE_SECURITY_REFRESH_TOKEN_REUSE_INTERVAL=%v", utils.Config.Auth.RefreshTokenReuseInterval),
fmt.Sprintf("GOTRUE_SECURITY_MANUAL_LINKING_ENABLED=%v", utils.Config.Auth.EnableManualLinking),
fmt.Sprintf("GOTRUE_MFA_PHONE_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.Phone.EnrollEnabled),
fmt.Sprintf("GOTRUE_MFA_PHONE_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.Phone.VerifyEnabled),
fmt.Sprintf("GOTRUE_MFA_TOTP_ENROLL_ENABLED=%v", utils.Config.Auth.MFA.TOTP.EnrollEnabled),
fmt.Sprintf("GOTRUE_MFA_TOTP_VERIFY_ENABLED=%v", utils.Config.Auth.MFA.TOTP.VerifyEnabled),
fmt.Sprintf("GOTRUE_MFA_MAX_ENROLLED_FACTORS=%v", utils.Config.Auth.MFA.MaxEnrolledFactors),
}

if utils.Config.Auth.Sessions.Timebox > 0 {
Expand Down Expand Up @@ -611,6 +621,14 @@ EOF
"GOTRUE_HOOK_SEND_EMAIL_SECRETS="+utils.Config.Auth.Hook.SendEmail.Secrets,
)
}
if utils.Config.Auth.MFA.Phone.EnrollEnabled || utils.Config.Auth.MFA.Phone.VerifyEnabled {
env = append(
env,
"GOTRUE_MFA_PHONE_TEMPLATE="+utils.Config.Auth.MFA.Phone.Template,
fmt.Sprintf("GOTRUE_MFA_PHONE_OTP_LENGTH=%v", utils.Config.Auth.MFA.Phone.OtpLength),
fmt.Sprintf("GOTRUE_MFA_PHONE_MAX_FREQUENCY=%v", utils.Config.Auth.MFA.Phone.MaxFrequency),
)
}

for name, config := range utils.Config.Auth.External {
env = append(
Expand Down Expand Up @@ -724,6 +742,7 @@ EOF
"DB_AFTER_CONNECT_QUERY=SET search_path TO _realtime",
"DB_ENC_KEY=" + utils.Config.Realtime.EncryptionKey,
"API_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
fmt.Sprintf("API_JWT_JWKS=%s", jwks),
"METRICS_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
"APP_NAME=realtime",
"SECRET_KEY_BASE=" + utils.Config.Realtime.SecretKeyBase,
Expand Down Expand Up @@ -774,7 +793,7 @@ EOF
"PGRST_DB_EXTRA_SEARCH_PATH=" + strings.Join(utils.Config.Api.ExtraSearchPath, ","),
fmt.Sprintf("PGRST_DB_MAX_ROWS=%d", utils.Config.Api.MaxRows),
"PGRST_DB_ANON_ROLE=anon",
"PGRST_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
fmt.Sprintf("PGRST_JWT_SECRET=%s", jwks),
"PGRST_ADMIN_SERVER_PORT=3001",
},
// PostgREST does not expose a shell for health check
Expand Down Expand Up @@ -807,6 +826,7 @@ EOF
"ANON_KEY=" + utils.Config.Auth.AnonKey,
"SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey,
"AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret,
fmt.Sprintf("AUTH_JWT_JWKS=%s", jwks),
fmt.Sprintf("DATABASE_URL=postgresql://supabase_storage_admin:%s@%s:%d/%s", dbConfig.Password, dbConfig.Host, dbConfig.Port, dbConfig.Database),
fmt.Sprintf("FILE_SIZE_LIMIT=%v", utils.Config.Storage.FileSizeLimit),
"STORAGE_BACKEND=file",
Expand Down
237 changes: 237 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package config

import (
"bytes"
"context"
_ "embed"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/fs"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
Expand All @@ -23,6 +27,8 @@ import (
"github.com/joho/godotenv"
"github.com/spf13/viper"
"golang.org/x/mod/semver"

"github.com/supabase/cli/pkg/fetcher"
)

// Type for turning human-friendly bytes string ("5MB", "32kB") into an int64 during toml decoding.
Expand Down Expand Up @@ -237,6 +243,7 @@ type (
EnableManualLinking bool `toml:"enable_manual_linking"`

Hook hook `toml:"hook"`
MFA mfa `toml:"mfa"`
Sessions sessions `toml:"sessions"`

EnableSignup bool `toml:"enable_signup"`
Expand All @@ -249,6 +256,34 @@ type (
JwtSecret string `toml:"-" mapstructure:"jwt_secret"`
AnonKey string `toml:"-" mapstructure:"anon_key"`
ServiceRoleKey string `toml:"-" mapstructure:"service_role_key"`

ThirdParty thirdParty `toml:"third_party"`
}

thirdParty struct {
Firebase tpaFirebase `toml:"firebase"`
Auth0 tpaAuth0 `toml:"auth0"`
Cognito tpaCognito `toml:"aws_cognito"`
}

tpaFirebase struct {
Enabled bool `toml:"enabled"`

ProjectID string `toml:"project_id"`
}

tpaAuth0 struct {
Enabled bool `toml:"enabled"`

Tenant string `toml:"tenant"`
TenantRegion string `toml:"tenant_region"`
}

tpaCognito struct {
Enabled bool `toml:"enabled"`

UserPoolID string `toml:"user_pool_id"`
UserPoolRegion string `toml:"user_pool_region"`
}

email struct {
Expand Down Expand Up @@ -294,6 +329,23 @@ type (
SendSMS hookConfig `toml:"send_sms"`
SendEmail hookConfig `toml:"send_email"`
}
factorTypeConfiguration struct {
EnrollEnabled bool `toml:"enroll_enabled"`
VerifyEnabled bool `toml:"verify_enabled"`
}

phoneFactorTypeConfiguration struct {
factorTypeConfiguration
OtpLength uint `toml:"otp_length"`
Template string `toml:"template"`
MaxFrequency time.Duration `toml:"max_frequency"`
}

mfa struct {
TOTP factorTypeConfiguration `toml:"totp"`
Phone phoneFactorTypeConfiguration `toml:"phone"`
MaxEnrolledFactors uint `toml:"max_enrolled_factors"`
}

hookConfig struct {
Enabled bool `toml:"enabled"`
Expand Down Expand Up @@ -837,6 +889,10 @@ func (c *config) Validate() error {
c.Auth.External[ext] = provider
}
}
// Validate Third-Party Auth config
if err := c.Auth.ThirdParty.validate(); err != nil {
return err
}
// Validate functions config
if c.EdgeRuntime.Enabled {
allowed := []RequestPolicy{PolicyPerWorker, PolicyOneshot}
Expand Down Expand Up @@ -978,3 +1034,184 @@ func ValidateBucketName(name string) error {
}
return nil
}

func (f *tpaFirebase) issuerURL() string {
return fmt.Sprintf("https://securetoken.google.com/%s", f.ProjectID)
}

func (f *tpaFirebase) validate() error {
if f.ProjectID == "" {
return errors.New("Invalid config: auth.third_party.firebase is enabled but without a project_id.")
}

return nil
}

func (a *tpaAuth0) issuerURL() string {
if a.TenantRegion != "" {
return fmt.Sprintf("https://%s.%s.auth0.com", a.Tenant, a.TenantRegion)
}

return fmt.Sprintf("https://%s.auth0.com", a.Tenant)
}

func (a *tpaAuth0) validate() error {
if a.Tenant == "" {
return errors.New("Invalid config: auth.third_party.auth0 is enabled but without a tenant.")
}

return nil
}

func (c *tpaCognito) issuerURL() string {
return fmt.Sprintf("https://cognito-idp.%s.amazonaws.com/%s", c.UserPoolRegion, c.UserPoolID)
}

func (c *tpaCognito) validate() error {
if c.UserPoolID == "" {
return errors.New("Invalid config: auth.third_party.cognito is enabled but without a user_pool_id.")
}

if c.UserPoolRegion == "" {
return errors.New("Invalid config: auth.third_party.cognito is enabled but without a user_pool_region.")
}

return nil
}

func (tpa *thirdParty) validate() error {
enabled := 0

if tpa.Firebase.Enabled {
enabled += 1

if err := tpa.Firebase.validate(); err != nil {
return err
}
}

if tpa.Auth0.Enabled {
enabled += 1

if err := tpa.Auth0.validate(); err != nil {
return err
}
}

if tpa.Cognito.Enabled {
enabled += 1

if err := tpa.Cognito.validate(); err != nil {
return err
}
}

if enabled > 1 {
return errors.New("Invalid config: Only one third_party provider allowed to be enabled at a time.")
}

return nil
}

func (tpa *thirdParty) IssuerURL() string {
if tpa.Firebase.Enabled {
return tpa.Firebase.issuerURL()
}

if tpa.Auth0.Enabled {
return tpa.Auth0.issuerURL()
}

if tpa.Cognito.Enabled {
return tpa.Cognito.issuerURL()
}

return ""
}

// ResolveJWKS creates the JWKS from the JWT secret and Third-Party Auth
// configs by resolving the JWKS via the OIDC discovery URL.
// It always returns a JWKS string, except when there's an error fetching.
func (a *auth) ResolveJWKS(ctx context.Context) (string, error) {
var jwks struct {
Keys []json.RawMessage `json:"keys"`
}

issuerURL := a.ThirdParty.IssuerURL()
if issuerURL != "" {
discoveryURL := issuerURL + "/.well-known/openid-configuration"

t := &http.Client{Timeout: 10 * time.Second}
client := fetcher.NewFetcher(
issuerURL,
fetcher.WithHTTPClient(t),
fetcher.WithExpectedStatus(http.StatusOK),
)

resp, err := client.Send(ctx, http.MethodGet, "", nil)
if err != nil {
return "", err
}

type oidcConfiguration struct {
JWKSURI string `json:"jwks_uri"`
}

oidcConfig, err := fetcher.ParseJSON[oidcConfiguration](resp.Body)
if err != nil {
return "", err
}

if oidcConfig.JWKSURI == "" {
return "", fmt.Errorf("auth.third_party: OIDC configuration at URL %q does not expose a jwks_uri property", discoveryURL)
}

client = fetcher.NewFetcher(
oidcConfig.JWKSURI,
fetcher.WithHTTPClient(t),
fetcher.WithExpectedStatus(http.StatusOK),
)

resp, err = client.Send(ctx, http.MethodGet, "", nil)
if err != nil {
return "", err
}

type remoteJWKS struct {
Keys []json.RawMessage `json:"keys"`
}

rJWKS, err := fetcher.ParseJSON[remoteJWKS](resp.Body)
if err != nil {
return "", err
}

if len(rJWKS.Keys) == 0 {
return "", fmt.Errorf("auth.third_party: JWKS at URL %q as discovered from %q does not contain any JWK keys", oidcConfig.JWKSURI, discoveryURL)
}

jwks.Keys = rJWKS.Keys
}

var secretJWK struct {
KeyType string `json:"kty"`
KeyBase64URL string `json:"k"`
}

secretJWK.KeyType = "oct"
secretJWK.KeyBase64URL = base64.RawURLEncoding.EncodeToString([]byte(a.JwtSecret))

secretJWKEncoded, err := json.Marshal(&secretJWK)
if err != nil {
return "", errors.Errorf("failed to marshal secret jwk: %w", err)
}

jwks.Keys = append(jwks.Keys, json.RawMessage(secretJWKEncoded))

jwksEncoded, err := json.Marshal(jwks)
if err != nil {
return "", errors.Errorf("failed to marshal jwks keys: %w", err)
}

return string(jwksEncoded), nil
}
2 changes: 1 addition & 1 deletion pkg/config/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const (
edgeRuntimeImage = "supabase/edge-runtime:v1.56.0"
vectorImage = "timberio/vector:0.28.1-alpine"
supavisorImage = "supabase/supavisor:1.1.56"
gotrueImage = "supabase/gotrue:v2.157.1"
gotrueImage = "supabase/gotrue:v2.158.1"
realtimeImage = "supabase/realtime:v2.30.23"
storageImage = "supabase/storage-api:v1.0.6"
logflareImage = "supabase/logflare:1.4.0"
Expand Down
Loading
Loading