Skip to content

Commit

Permalink
Merge pull request #28 from hasghari/multiple-tls-domains
Browse files Browse the repository at this point in the history
Support multiple TLS domains
  • Loading branch information
kevinmcconnell authored Jul 9, 2024
2 parents ec8c5f6 + 55518cd commit 59300d2
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 26 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Unreleased

* Accept comma-separated `TLS_DOMAIN` to support multiple domains (#28)
* Set in the outbound request `X-Forwarded-For` (to the client IP address), `X-Forwarded-Host` (to the host name requested by the client), and `X-Forwarded-Proto` (to "http" or "https" depending on whether the inbound request was made on a TLS-enabled connection) (#29)

## v0.1.4 / 2024-04-26
Expand Down
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,25 +72,25 @@ In most cases, Thruster should work out of the box with no additional
configuration. But if you need to customize its behavior, there are a few
environment variables that you can set.

| Variable Name | Description | Default Value |
|-----------------------|---------------------------------------------------------------------------------|---------------|
| `TLS_DOMAIN` | The domain name to use for TLS provisioning. If not set, TLS will be disabled. | None |
| `TARGET_PORT` | The port that your Puma server should run on. Thruster will set `PORT` to this value when starting your server. | 3000 |
| `CACHE_SIZE` | The size of the HTTP cache in bytes. | 64MB |
| `MAX_CACHE_ITEM_SIZE` | The maximum size of a single item in the HTTP cache in bytes. | 1MB |
| `X_SENDFILE_ENABLED` | Whether to enable X-Sendfile support. Set to `0` or `false` to disable. | Enabled |
| `MAX_REQUEST_BODY` | The maximum size of a request body in bytes. Requests larger than this size will be refused; `0` means no maximum size is enforced. | `0` |
| `STORAGE_PATH` | The path to store Thruster's internal state. Provisioned TLS certificates will be stored here, so that they will not need to be requested every time your application is started. | `./storage/thruster` |
| Variable Name | Description | Default Value |
|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
| `TLS_DOMAIN` | Comma-separated list of domain names to use for TLS provisioning. If not set, TLS will be disabled. | None |
| `TARGET_PORT` | The port that your Puma server should run on. Thruster will set `PORT` to this value when starting your server. | 3000 |
| `CACHE_SIZE` | The size of the HTTP cache in bytes. | 64MB |
| `MAX_CACHE_ITEM_SIZE` | The maximum size of a single item in the HTTP cache in bytes. | 1MB |
| `X_SENDFILE_ENABLED` | Whether to enable X-Sendfile support. Set to `0` or `false` to disable. | Enabled |
| `MAX_REQUEST_BODY` | The maximum size of a request body in bytes. Requests larger than this size will be refused; `0` means no maximum size is enforced. | `0` |
| `STORAGE_PATH` | The path to store Thruster's internal state. Provisioned TLS certificates will be stored here, so that they will not need to be requested every time your application is started. | `./storage/thruster` |
| `BAD_GATEWAY_PAGE` | Path to an HTML file to serve when the backend server returns a 502 Bad Gateway error. If there is no file at the specific path, Thruster will serve an empty 502 response instead. Because Thruster boots very quickly, a custom page can be a useful way to show that your application is starting up. | `./public/502.html` |
| `HTTP_PORT` | The port to listen on for HTTP traffic. | 80 |
| `HTTPS_PORT` | The port to listen on for HTTPS traffic. | 443 |
| `HTTP_IDLE_TIMEOUT` | The maximum time in seconds that a client can be idle before the connection is closed. | 60 |
| `HTTP_READ_TIMEOUT` | The maximum time in seconds that a client can take to send the request headers and body. | 30 |
| `HTTP_WRITE_TIMEOUT` | The maximum time in seconds during which the client must read the response. | 30 |
| `ACME_DIRECTORY` | The URL of the ACME directory to use for TLS certificate provisioning. | `https://acme-v02.api.letsencrypt.org/directory` (Let's Encrypt production) |
| `EAB_KID` | The EAB key identifier to use when provisioning TLS certificates, if required. | None |
| `EAB_HMAC_KEY` | The Base64-encoded EAB HMAC key to use when provisioning TLS certificates, if required. | None |
| `DEBUG` | Set to `1` or `true` to enable debug logging. | Disabled |
| `HTTP_PORT` | The port to listen on for HTTP traffic. | 80 |
| `HTTPS_PORT` | The port to listen on for HTTPS traffic. | 443 |
| `HTTP_IDLE_TIMEOUT` | The maximum time in seconds that a client can be idle before the connection is closed. | 60 |
| `HTTP_READ_TIMEOUT` | The maximum time in seconds that a client can take to send the request headers and body. | 30 |
| `HTTP_WRITE_TIMEOUT` | The maximum time in seconds during which the client must read the response. | 30 |
| `ACME_DIRECTORY` | The URL of the ACME directory to use for TLS certificate provisioning. | `https://acme-v02.api.letsencrypt.org/directory` (Let's Encrypt production) |
| `EAB_KID` | The EAB key identifier to use when provisioning TLS certificates, if required. | None |
| `EAB_HMAC_KEY` | The Base64-encoded EAB HMAC key to use when provisioning TLS certificates, if required. | None |
| `DEBUG` | Set to `1` or `true` to enable debug logging. | Disabled |

To prevent naming clashes with your application's own environment variables,
Thruster's environment variables can optionally be prefixed with `THRUSTER_`.
Expand Down
20 changes: 18 additions & 2 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log/slog"
"os"
"strconv"
"strings"
"time"

"golang.org/x/crypto/acme"
Expand Down Expand Up @@ -45,7 +46,7 @@ type Config struct {
XSendfileEnabled bool
MaxRequestBody int

TLSDomain string
TLSDomains []string
ACMEDirectoryURL string
EAB_KID string
EAB_HMACKey string
Expand Down Expand Up @@ -81,7 +82,7 @@ func NewConfig() (*Config, error) {
XSendfileEnabled: getEnvBool("X_SENDFILE_ENABLED", true),
MaxRequestBody: getEnvInt("MAX_REQUEST_BODY", defaultMaxRequestBody),

TLSDomain: getEnvString("TLS_DOMAIN", ""),
TLSDomains: getEnvStrings("TLS_DOMAIN", []string{}),
ACMEDirectoryURL: getEnvString("ACME_DIRECTORY", defaultACMEDirectoryURL),
EAB_KID: getEnvString("EAB_KID", ""),
EAB_HMACKey: getEnvString("EAB_HMAC_KEY", ""),
Expand Down Expand Up @@ -121,6 +122,21 @@ func getEnvString(key, defaultValue string) string {
return defaultValue
}

func getEnvStrings(key string, defaultValue []string) []string {
value, ok := findEnv(key)
if ok {
items := strings.Split(value, ",")

for i, item := range items {
items[i] = strings.TrimSpace(item)
}

return items
}

return defaultValue
}

func getEnvInt(key string, defaultValue int) int {
value, ok := findEnv(key)
if !ok {
Expand Down
16 changes: 13 additions & 3 deletions internal/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,27 @@ func TestConfig_tls(t *testing.T) {
c, err := NewConfig()
require.NoError(t, err)

assert.Equal(t, "", c.TLSDomain)
assert.Equal(t, []string{}, c.TLSDomains)
})

t.Run("with TLS_DOMAIN", func(t *testing.T) {
t.Run("with single TLS_DOMAIN", func(t *testing.T) {
usingProgramArgs(t, "thruster", "echo", "hello")
usingEnvVar(t, "TLS_DOMAIN", "example.com")

c, err := NewConfig()
require.NoError(t, err)

assert.Equal(t, "example.com", c.TLSDomain)
assert.Equal(t, []string{"example.com"}, c.TLSDomains)
})

t.Run("with multiple TLS_DOMAIN", func(t *testing.T) {
usingProgramArgs(t, "thruster", "echo", "hello")
usingEnvVar(t, "TLS_DOMAIN", "example.com, example.io")

c, err := NewConfig()
require.NoError(t, err)

assert.Equal(t, []string{"example.com", "example.io"}, c.TLSDomains)
})
}

Expand Down
6 changes: 3 additions & 3 deletions internal/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (s *Server) Start() {
httpAddress := fmt.Sprintf(":%d", s.config.HttpPort)
httpsAddress := fmt.Sprintf(":%d", s.config.HttpsPort)

if s.config.TLSDomain != "" {
if len(s.config.TLSDomains) > 0 {
manager := s.certManager()

s.httpServer = s.defaultHttpServer(httpAddress)
Expand All @@ -44,7 +44,7 @@ func (s *Server) Start() {
go s.httpServer.ListenAndServe()
go s.httpsServer.ListenAndServeTLS("", "")

slog.Info("Server started", "http", httpAddress, "https", httpsAddress, "tls_domain", s.config.TLSDomain)
slog.Info("Server started", "http", httpAddress, "https", httpsAddress, "tls_domain", s.config.TLSDomains)
} else {
s.httpsServer = nil
s.httpServer = s.defaultHttpServer(httpAddress)
Expand Down Expand Up @@ -79,7 +79,7 @@ func (s *Server) certManager() *autocert.Manager {
Cache: autocert.DirCache(s.config.StoragePath),
Client: client,
ExternalAccountBinding: binding,
HostPolicy: autocert.HostWhitelist(s.config.TLSDomain),
HostPolicy: autocert.HostWhitelist(s.config.TLSDomains...),
Prompt: autocert.AcceptTOS,
}
}
Expand Down

0 comments on commit 59300d2

Please sign in to comment.