From beba533a98179b7ecfbad3e8ed5dbff68cd98aa1 Mon Sep 17 00:00:00 2001 From: Han Qiao Date: Sat, 6 Jul 2024 01:05:42 +0800 Subject: [PATCH] feat: expose https port from kong (#2487) * feat: expose https port from kong * chore: add tls config block for future extension * fix: init hostname with docker client * fix: use modern tls certificate --- internal/db/start/start.go | 2 +- internal/functions/new/new.go | 7 +-- internal/functions/new/new_test.go | 8 +-- internal/functions/new/templates/index.ts | 2 +- internal/start/start.go | 42 ++++++++++----- internal/start/start_test.go | 6 +-- internal/status/kong.local.crt | 30 +++++++++++ internal/status/kong.local.key | 52 +++++++++++++++++++ internal/status/status.go | 51 +++++++++++++++--- internal/storage/client/api.go | 43 +++++++++------ internal/utils/config.go | 28 +++++++++- .../utils/templates/init_config.test.toml | 3 ++ internal/utils/templates/init_config.toml | 3 ++ 13 files changed, 223 insertions(+), 54 deletions(-) create mode 100644 internal/status/kong.local.crt create mode 100644 internal/status/kong.local.key diff --git a/internal/db/start/start.go b/internal/db/start/start.go index 879d3827e..1d5524266 100644 --- a/internal/db/start/start.go +++ b/internal/db/start/start.go @@ -250,7 +250,7 @@ func initSchema15(ctx context.Context, host string) error { return err } return utils.DockerRunOnceWithStream(ctx, utils.Config.Auth.Image, []string{ - fmt.Sprintf("API_EXTERNAL_URL=http://%s:%d", host, utils.Config.Api.Port), + "API_EXTERNAL_URL=" + utils.GetApiUrl(""), "GOTRUE_LOG_LEVEL=error", "GOTRUE_DB_DRIVER=postgres", fmt.Sprintf("GOTRUE_DB_DATABASE_URL=postgresql://supabase_auth_admin:%s@%s:5432/postgres", utils.Config.Db.Password, host), diff --git a/internal/functions/new/new.go b/internal/functions/new/new.go index f1602c927..c3bf173b6 100644 --- a/internal/functions/new/new.go +++ b/internal/functions/new/new.go @@ -20,8 +20,7 @@ var ( ) type indexConfig struct { - Port uint16 - Slug string + URL string Token string } @@ -46,13 +45,11 @@ func Run(ctx context.Context, slug string, fsys afero.Fs) error { } defer f.Close() // Templatize index.ts by config.toml if available - utils.Config.Api.Port = 54321 if err := utils.LoadConfigFS(fsys); err != nil { utils.CmdSuggestion = "" } config := indexConfig{ - Port: utils.Config.Api.Port, - Slug: slug, + URL: utils.GetApiUrl("/functions/v1/" + slug), Token: utils.Config.Auth.AnonKey, } if err := indexTemplate.Execute(f, config); err != nil { diff --git a/internal/functions/new/new_test.go b/internal/functions/new/new_test.go index 956a1fa32..d5e9c2fc8 100644 --- a/internal/functions/new/new_test.go +++ b/internal/functions/new/new_test.go @@ -19,11 +19,11 @@ func TestNewCommand(t *testing.T) { assert.NoError(t, Run(context.Background(), "test-func", fsys)) // Validate output funcPath := filepath.Join(utils.FunctionsDir, "test-func", "index.ts") - contains, err := afero.FileContainsBytes(fsys, funcPath, []byte( - `curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/test-func'`, - )) + content, err := afero.ReadFile(fsys, funcPath) assert.NoError(t, err) - assert.True(t, contains) + assert.Contains(t, string(content), + "curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/test-func'", + ) }) t.Run("throws error on malformed slug", func(t *testing.T) { diff --git a/internal/functions/new/templates/index.ts b/internal/functions/new/templates/index.ts index c6872cf03..264d9972e 100644 --- a/internal/functions/new/templates/index.ts +++ b/internal/functions/new/templates/index.ts @@ -24,7 +24,7 @@ Deno.serve(async (req) => { 1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start) 2. Make an HTTP request: - curl -i --location --request POST 'http://127.0.0.1:{{ .Port }}/functions/v1/{{ .Slug }}' \ + curl -i --location --request POST '{{ .URL }}' \ --header 'Authorization: Bearer {{ .Token }}' \ --header 'Content-Type: application/json' \ --data '{"name":"Functions"}' diff --git a/internal/start/start.go b/internal/start/start.go index 0bab56bef..4512ea227 100644 --- a/internal/start/start.go +++ b/internal/start/start.go @@ -358,6 +358,10 @@ EOF binds = append(binds, fmt.Sprintf("%s:%s:rw", hostPath, dockerPath)) } + dockerPort := uint16(8000) + if utils.Config.Api.Tls.Enabled { + dockerPort = 8443 + } if _, err := utils.DockerStart( ctx, container.Config{ @@ -373,17 +377,30 @@ EOF "KONG_NGINX_PROXY_PROXY_BUFFER_SIZE=160k", "KONG_NGINX_PROXY_PROXY_BUFFERS=64 160k", "KONG_NGINX_WORKER_PROCESSES=1", + // Use modern TLS certificate + "KONG_SSL_CERT=/home/kong/localhost.crt", + "KONG_SSL_CERT_KEY=/home/kong/localhost.key", }, - Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /home/kong/kong.yml && cat <<'EOF' > /home/kong/custom_nginx.template && ./docker-entrypoint.sh kong docker-start --nginx-conf /home/kong/custom_nginx.template + Entrypoint: []string{"sh", "-c", `cat <<'EOF' > /home/kong/kong.yml && \ +cat <<'EOF' > /home/kong/custom_nginx.template && \ +cat <<'EOF' > /home/kong/localhost.crt && \ +cat <<'EOF' > /home/kong/localhost.key && \ +./docker-entrypoint.sh kong docker-start --nginx-conf /home/kong/custom_nginx.template ` + kongConfigBuf.String() + ` EOF ` + nginxConfigEmbed + ` EOF +` + status.KongCert + ` +EOF +` + status.KongKey + ` +EOF `}, }, container.HostConfig{ - Binds: binds, - PortBindings: nat.PortMap{"8000/tcp": []nat.PortBinding{{HostPort: strconv.FormatUint(uint64(utils.Config.Api.Port), 10)}}}, + Binds: binds, + PortBindings: nat.PortMap{nat.Port(fmt.Sprintf("%d/tcp", dockerPort)): []nat.PortBinding{{ + HostPort: strconv.FormatUint(uint64(utils.Config.Api.Port), 10)}, + }}, RestartPolicy: container.RestartPolicy{Name: "always"}, }, network.NetworkingConfig{ @@ -408,7 +425,7 @@ EOF } env := []string{ - fmt.Sprintf("API_EXTERNAL_URL=http://%s:%d", utils.Config.Hostname, utils.Config.Api.Port), + "API_EXTERNAL_URL=" + utils.GetApiUrl(""), "GOTRUE_API_HOST=0.0.0.0", "GOTRUE_API_PORT=9999", @@ -425,7 +442,7 @@ EOF "GOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated", fmt.Sprintf("GOTRUE_JWT_EXP=%v", utils.Config.Auth.JwtExpiry), "GOTRUE_JWT_SECRET=" + utils.Config.Auth.JwtSecret, - fmt.Sprintf("GOTRUE_JWT_ISSUER=http://%s:%d/auth/v1", utils.Config.Hostname, utils.Config.Api.Port), + "GOTRUE_JWT_ISSUER=" + utils.GetApiUrl("/auth/v1"), fmt.Sprintf("GOTRUE_EXTERNAL_EMAIL_ENABLED=%v", utils.Config.Auth.Email.EnableSignup), fmt.Sprintf("GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED=%v", utils.Config.Auth.Email.DoubleConfirmChanges), @@ -441,13 +458,10 @@ EOF fmt.Sprintf("GOTRUE_SMTP_SENDER_NAME=%s", utils.Config.Auth.Email.Smtp.SenderName), fmt.Sprintf("GOTRUE_SMTP_MAX_FREQUENCY=%v", utils.Config.Auth.Email.MaxFrequency), - // TODO: To be reverted to `/auth/v1/verify` once - // https://github.com/supabase/supabase/issues/16100 - // is fixed on upstream GoTrue. - fmt.Sprintf("GOTRUE_MAILER_URLPATHS_INVITE=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port), - fmt.Sprintf("GOTRUE_MAILER_URLPATHS_CONFIRMATION=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port), - fmt.Sprintf("GOTRUE_MAILER_URLPATHS_RECOVERY=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port), - fmt.Sprintf("GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=http://%s:%d/auth/v1/verify", utils.Config.Hostname, utils.Config.Api.Port), + "GOTRUE_MAILER_URLPATHS_INVITE=" + utils.GetApiUrl("/auth/v1/verify"), + "GOTRUE_MAILER_URLPATHS_CONFIRMATION=" + utils.GetApiUrl("/auth/v1/verify"), + "GOTRUE_MAILER_URLPATHS_RECOVERY=" + utils.GetApiUrl("/auth/v1/verify"), + "GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=" + utils.GetApiUrl("/auth/v1/verify"), "GOTRUE_RATE_LIMIT_EMAIL_SENT=360000", fmt.Sprintf("GOTRUE_EXTERNAL_PHONE_ENABLED=%v", utils.Config.Auth.Sms.EnableSignup), @@ -591,7 +605,7 @@ EOF ) } else { env = append(env, - fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=http://%s:%d/auth/v1/callback", strings.ToUpper(name), utils.Config.Hostname, utils.Config.Api.Port), + fmt.Sprintf("GOTRUE_EXTERNAL_%s_REDIRECT_URI=%s", strings.ToUpper(name), utils.GetApiUrl("/auth/v1/callback")), ) } @@ -910,7 +924,7 @@ EOF "STUDIO_PG_META_URL=http://" + utils.PgmetaId + ":8080", "POSTGRES_PASSWORD=" + dbConfig.Password, "SUPABASE_URL=http://" + utils.KongId + ":8000", - fmt.Sprintf("SUPABASE_PUBLIC_URL=%s:%v/", utils.Config.Studio.ApiUrl, utils.Config.Api.Port), + "SUPABASE_PUBLIC_URL=" + utils.Config.Studio.ApiUrl, "AUTH_JWT_SECRET=" + utils.Config.Auth.JwtSecret, "SUPABASE_ANON_KEY=" + utils.Config.Auth.AnonKey, "SUPABASE_SERVICE_KEY=" + utils.Config.Auth.ServiceRoleKey, diff --git a/internal/start/start_test.go b/internal/start/start_test.go index 485f9a2c2..16d8a54e8 100644 --- a/internal/start/start_test.go +++ b/internal/start/start_test.go @@ -167,10 +167,10 @@ func TestDatabaseStart(t *testing.T) { }, }}) } - gock.New("127.0.0.1"). + gock.New(utils.GetApiUrl("")). Head("/rest-admin/v1/ready"). Reply(http.StatusOK) - gock.New("127.0.0.1"). + gock.New(utils.GetApiUrl("")). Head("/functions/v1/_internal/health"). Reply(http.StatusOK) // Seed tenant services @@ -183,7 +183,7 @@ func TestDatabaseStart(t *testing.T) { Health: &types.Health{Status: "healthy"}, }, }}) - gock.New("127.0.0.1"). + gock.New(utils.GetApiUrl("")). Get("/storage/v1/bucket"). Reply(http.StatusOK). JSON([]storage.BucketResponse{}) diff --git a/internal/status/kong.local.crt b/internal/status/kong.local.crt new file mode 100644 index 000000000..0a7b2dc53 --- /dev/null +++ b/internal/status/kong.local.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFJTCCAw2gAwIBAgIURUQAFIOhttOg0D12xZvx+pScX4cwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDcwNTE0MjI1NFoXDTM0MDcw +MzE0MjI1NFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAyL79RrCl78/XutDm7tVNyIJNZqbNSfX+3AsnP1dQc37Q +7QkxmNJ2EmfklTLyvY1A+CY/o+3S1blK+O8lm40yTHP+GC30InKS7lSnCUNqSrHy +DaGNShtphqJoao9vo69/anI6I2tKc8VQ2tebvMVzmjrsvGtWcU4ciyzbUterKQas +Iin8Aw5gDCLswVNZnl6wUvlcgbLmiTLPaJKnNsbYcBJxST2SFxY5q5YWzlugm9Q0 +3913XB64rVoKAyQ86Yabl4UXyVXknyJcbpfnsmn6ORxnZJebnuU0O9y8ys/E9hGw +5rkVTm7QjzsbczMYOgvpPbR1miyDQaZ1bGyj9SmJo1H5e12xO0RNi+awuMnpt3cB +8NeTsZctOgnEQB1Gidq2XcYV48e2OGJTNtVVVPu1UKCNeJur1AfLNPRz8fJq06A+ +Xy5xeRNGFsaeYRt/IO+/CZUOHcY/vR55i8PeeTj9cB1f9KjfmQVeOt25widxrhFK +RkpRgA1ofQ4O8/gPSOcnroQgSs2Whr9RXIF71uSLJyZU0IBxru0cznuEpXxkh7oK +Fb6t/n1SZB2WEQXHh5iG49fw7MT29CzNpJIP1ApT3LKasHZHf4yBn9ZwVAagCGSL +W7kMDhIUhOfVwpyfkXYHzQE2aaCdQ9IfZy9ybO/Y8sbUg6B5i5t8xODutv+fmd0C +AwEAAaNvMG0wHQYDVR0OBBYEFGtw+BogdJ6GJRCe+BHGQdA0f85XMB8GA1UdIwQY +MBaAFGtw+BogdJ6GJRCe+BHGQdA0f85XMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0R +BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQCO9ITlkXil ++dLTddpSq06UuRTYLx8vWGLnd8uEoUg4qlLKi/hKnqTEP9dePw+QeuIM+aLjvvXY +bP4SqBOUi192OohQs7ZPCpjXizmabDNl4/Z+qyiGvZK/N0gWPEaTn6tzeQ03hECK +vY8lRrAP7xLrEYmA6unjfaj3inNUJeqL26i3SIAq/koK4jPm1F/AzwslNH00MJld +xKormGzvNJtaZPr5xrtuLQAmeCotE6av+R5k9zmSYa25Nel50JTk31fNiuUFmECD +qop+Yqfzw9/UkObDWw+g1+N3uHlH9CB2RvfkB2fW9SFMsznjQlq+MyQCwKFs699Q +rNMeHbUEwovDEcPUaLc3d5gCk/dvH9aR8mVLEcs7lMyHSsiG1Co1QXg6UjpMCrIV +Tvk+MrSQVVxw0rKS4H7ZEhfC2PfEVe1az24HrrPWJDZKnRhv1Ohj4jsxkBSprXsu +3qxcMCr7yr4hjsu9BsKtmvRfHTrMOwY1WtZBcl7hjgAItfMs29Fd4Tkn0aZs5f+n +GSwG5BeJL3kfEKwOjw8j+EAqRBu/7mbcoUyErnuLK7FimU4jtL8VX03n6nTgLJzS +xjUTaplgerrazAvV31vMaiINoaGM0RDnGjSCgZT4Va33noqo3qVJJOnWpheFmaXq +bUMQtwrAWTt12NECJt1nWAT4VWhfDoBE7A== +-----END CERTIFICATE----- diff --git a/internal/status/kong.local.key b/internal/status/kong.local.key new file mode 100644 index 000000000..9b71aab61 --- /dev/null +++ b/internal/status/kong.local.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +MviBG5FCtAAJp+5btxy72dIKBYBVUJ8= +-----END PRIVATE KEY----- diff --git a/internal/status/status.go b/internal/status/status.go index d0e84c1b5..070176615 100644 --- a/internal/status/status.go +++ b/internal/status/status.go @@ -2,6 +2,9 @@ package status import ( "context" + "crypto/tls" + "crypto/x509" + _ "embed" "fmt" "io" "net/http" @@ -38,8 +41,8 @@ func (c *CustomName) toValues(exclude ...string) map[string]string { c.DbURL: fmt.Sprintf("postgresql://%s@%s:%d/postgres", url.UserPassword("postgres", utils.Config.Db.Password), utils.Config.Hostname, utils.Config.Db.Port), } if utils.Config.Api.Enabled && !utils.SliceContains(exclude, utils.RestId) && !utils.SliceContains(exclude, utils.ShortContainerImageName(utils.Config.Api.Image)) { - values[c.ApiURL] = fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Api.Port) - values[c.GraphqlURL] = fmt.Sprintf("http://%s:%d/graphql/v1", utils.Config.Hostname, utils.Config.Api.Port) + values[c.ApiURL] = utils.GetApiUrl("") + values[c.GraphqlURL] = utils.GetApiUrl("/graphql/v1") } if utils.Config.Studio.Enabled && !utils.SliceContains(exclude, utils.StudioId) && !utils.SliceContains(exclude, utils.ShortContainerImageName(utils.Config.Studio.Image)) { values[c.StudioURL] = fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Studio.Port) @@ -53,7 +56,7 @@ func (c *CustomName) toValues(exclude ...string) map[string]string { values[c.InbucketURL] = fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Inbucket.Port) } if utils.Config.Storage.Enabled && !utils.SliceContains(exclude, utils.StorageId) && !utils.SliceContains(exclude, utils.ShortContainerImageName(utils.Config.Storage.Image)) { - values[c.StorageS3URL] = fmt.Sprintf("http://%s:%d/storage/v1/s3", utils.Config.Hostname, utils.Config.Api.Port) + values[c.StorageS3URL] = utils.GetApiUrl("/storage/v1/s3") values[c.StorageS3AccessKeyId] = utils.Config.Storage.S3Credentials.AccessKeyId values[c.StorageS3SecretAccessKey] = utils.Config.Storage.S3Credentials.SecretAccessKey values[c.StorageS3Region] = utils.Config.Storage.S3Credentials.Region @@ -69,7 +72,6 @@ func Run(ctx context.Context, names CustomName, format string, fsys afero.Fs) er if err := assertContainerHealthy(ctx, utils.DbId); err != nil { return err } - stopped, err := checkServiceHealth(ctx) if err != nil { return err @@ -130,6 +132,41 @@ func IsServiceReady(ctx context.Context, container string) error { return assertContainerHealthy(ctx, container) } +var ( + //go:embed kong.local.crt + KongCert string + //go:embed kong.local.key + KongKey string +) + +// To regenerate local certificate pair: +// +// openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \ +// -nodes -keyout kong.local.key -out kong.local.crt -subj "/CN=localhost" \ +// -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" +func NewKongClient() *http.Client { + client := &http.Client{ + Timeout: 10 * time.Second, + } + if t, ok := http.DefaultTransport.(*http.Transport); ok { + pool, err := x509.SystemCertPool() + if err != nil { + fmt.Fprintln(utils.GetDebugLogger(), err) + pool = x509.NewCertPool() + } + // No need to replace TLS config if we fail to append cert + if pool.AppendCertsFromPEM([]byte(KongCert)) { + rt := t.Clone() + rt.TLSClientConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + RootCAs: pool, + } + client.Transport = rt + } + } + return client +} + var ( healthClient *fetcher.Fetcher healthOnce sync.Once @@ -137,13 +174,11 @@ var ( func checkHTTPHead(ctx context.Context, path string) error { healthOnce.Do(func() { - server := fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Api.Port) + server := utils.GetApiUrl("") header := func(req *http.Request) { req.Header.Add("apikey", utils.Config.Auth.AnonKey) } - client := &http.Client{ - Timeout: 10 * time.Second, - } + client := NewKongClient() healthClient = fetcher.NewFetcher( server, fetcher.WithHTTPClient(client), diff --git a/internal/storage/client/api.go b/internal/storage/client/api.go index 58b212439..e6459bca2 100644 --- a/internal/storage/client/api.go +++ b/internal/storage/client/api.go @@ -2,9 +2,9 @@ package client import ( "context" - "fmt" "github.com/spf13/viper" + "github.com/supabase/cli/internal/status" "github.com/supabase/cli/internal/utils" "github.com/supabase/cli/internal/utils/tenant" "github.com/supabase/cli/pkg/fetcher" @@ -12,23 +12,34 @@ import ( ) func NewStorageAPI(ctx context.Context, projectRef string) (storage.StorageAPI, error) { - server := fmt.Sprintf("http://%s:%d", utils.Config.Hostname, utils.Config.Api.Port) - token := utils.Config.Auth.ServiceRoleKey - if len(projectRef) > 0 { - server = "https://" + utils.GetSupabaseHost(projectRef) + client := storage.StorageAPI{} + if len(projectRef) == 0 { + client.Fetcher = newLocalClient() + } else if viper.IsSet("AUTH_SERVICE_ROLE_KEY") { // Special case for calling storage API without personal access token - if !viper.IsSet("AUTH_SERVICE_ROLE_KEY") { - apiKey, err := tenant.GetApiKeys(ctx, projectRef) - if err != nil { - return storage.StorageAPI{}, err - } - token = apiKey.ServiceRole - } + client.Fetcher = newRemoteClient(projectRef, utils.Config.Auth.ServiceRoleKey) + } else if apiKey, err := tenant.GetApiKeys(ctx, projectRef); err == nil { + client.Fetcher = newRemoteClient(projectRef, apiKey.ServiceRole) + } else { + return client, err } - api := storage.StorageAPI{Fetcher: fetcher.NewFetcher( - server, + return client, nil +} + +func newLocalClient() *fetcher.Fetcher { + client := status.NewKongClient() + return fetcher.NewFetcher( + utils.GetApiUrl(""), + fetcher.WithHTTPClient(client), + fetcher.WithBearerToken(utils.Config.Auth.ServiceRoleKey), + fetcher.WithUserAgent("SupabaseCLI/"+utils.Version), + ) +} + +func newRemoteClient(projectRef, token string) *fetcher.Fetcher { + return fetcher.NewFetcher( + "https://"+utils.GetSupabaseHost(projectRef), fetcher.WithBearerToken(token), fetcher.WithUserAgent("SupabaseCLI/"+utils.Version), - )} - return api, nil + ) } diff --git a/internal/utils/config.go b/internal/utils/config.go index 76a600e4a..9f98e7282 100644 --- a/internal/utils/config.go +++ b/internal/utils/config.go @@ -4,10 +4,12 @@ import ( "bytes" _ "embed" "fmt" + "net" "net/url" "os" "path/filepath" "regexp" + "strconv" "strings" "text/template" "time" @@ -187,6 +189,7 @@ const ( ) var Config = config{ + Hostname: GetHostname(), Api: api{ Image: PostgrestImage, }, @@ -316,6 +319,11 @@ type ( Schemas []string `toml:"schemas"` ExtraSearchPath []string `toml:"extra_search_path"` MaxRows uint `toml:"max_rows"` + Tls tlsKong `toml:"tls"` + } + + tlsKong struct { + Enabled bool `toml:"enabled"` } db struct { @@ -619,8 +627,6 @@ func LoadConfigFS(fsys afero.Fs) error { fmt.Fprintln(os.Stderr, Yellow("WARNING:"), "project_id field in config is invalid. Auto-fixing to", Aqua(sanitized)) Config.ProjectId = sanitized } - - Config.Hostname = GetHostname() UpdateDockerIds() // Validate api config if Config.Api.Port == 0 { @@ -707,6 +713,11 @@ func LoadConfigFS(fsys afero.Fs) error { if Config.Studio.Port == 0 { return errors.New("Missing required field in config: studio.port") } + if parsed, err := url.Parse(Config.Studio.ApiUrl); err != nil { + return errors.Errorf("Invalid config for studio.api_url: %w", err) + } else if parsed.Host == "" || parsed.Host == Config.Hostname { + Config.Studio.ApiUrl = GetApiUrl("") + } if version, err := afero.ReadFile(fsys, StudioVersionPath); err == nil && len(version) > 0 { Config.Studio.Image = replaceImageTag(StudioImage, string(version)) } @@ -1012,3 +1023,16 @@ func validateHookURI(uri, hookName string) error { } return nil } + +func GetApiUrl(path string) string { + host := net.JoinHostPort(Config.Hostname, + strconv.FormatUint(uint64(Config.Api.Port), 10), + ) + apiUrl := url.URL{Host: host, Path: path} + if Config.Api.Tls.Enabled { + apiUrl.Scheme = "https" + } else { + apiUrl.Scheme = "http" + } + return apiUrl.String() +} diff --git a/internal/utils/templates/init_config.test.toml b/internal/utils/templates/init_config.test.toml index a88118bbe..575c24d1b 100644 --- a/internal/utils/templates/init_config.test.toml +++ b/internal/utils/templates/init_config.test.toml @@ -15,6 +15,9 @@ extra_search_path = ["public", "extensions"] # for accidental or malicious requests. max_rows = 1000 +[api.tls] +enabled = true + [db] # Port to use for the local database URL. port = 54322 diff --git a/internal/utils/templates/init_config.toml b/internal/utils/templates/init_config.toml index eb68ef905..761637d6e 100644 --- a/internal/utils/templates/init_config.toml +++ b/internal/utils/templates/init_config.toml @@ -15,6 +15,9 @@ extra_search_path = ["public", "extensions"] # for accidental or malicious requests. max_rows = 1000 +[api.tls] +enabled = false + [db] # Port to use for the local database URL. port = 54322