From 15eca3aae1df2b97d8147f00e1af241c12e95fe4 Mon Sep 17 00:00:00 2001 From: Amal Shaji Date: Fri, 3 Mar 2023 22:59:13 +0530 Subject: [PATCH] deploy: Add traefik configuration (#32) --- cmd/beaver_client/main.go | 2 +- deployments/.env | 6 +++++ deployments/docker-compose.yaml | 42 +++++++++++++++++++++++++++++++ internal/client/config.go | 10 ++++++++ internal/server/handlers/admin.go | 4 +++ internal/utils/string.go | 17 ++++++++++++- internal/utils/string_test.go | 17 +++++++++++++ 7 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 deployments/.env create mode 100644 deployments/docker-compose.yaml diff --git a/cmd/beaver_client/main.go b/cmd/beaver_client/main.go index 20cc4bd..13caa26 100644 --- a/cmd/beaver_client/main.go +++ b/cmd/beaver_client/main.go @@ -17,7 +17,7 @@ func startTunnels(tunnels []client.TunnelConfig) { for _, proxyTunnel := range tunnels { config, err := client.LoadConfiguration(configFile, proxyTunnel.Subdomain, proxyTunnel.Port, showWsReadErrors) if err != nil { - log.Fatalf("Unable to load configuration : %s", err) + log.Fatalf("Unable to load configuration: %s", err) } proxy := client.NewClient(&config) proxies = append(proxies, proxy) diff --git a/deployments/.env b/deployments/.env new file mode 100644 index 0000000..f49c863 --- /dev/null +++ b/deployments/.env @@ -0,0 +1,6 @@ +ACME_EMAIL= +TUNNEL_DOMAIN= +DNS_PROVIDER= + +# DNS specific +CLOUDFLARE_DNS_API_TOKEN= diff --git a/deployments/docker-compose.yaml b/deployments/docker-compose.yaml new file mode 100644 index 0000000..719b85c --- /dev/null +++ b/deployments/docker-compose.yaml @@ -0,0 +1,42 @@ +services: + beaver: + image: amalshaji/beaver:main + volumes: + - ./beaver_server.yaml:/app/config/beaver_server.yaml + - ./data:/app/data/ + expose: + - 8080 + restart: unless-stopped + labels: + - traefik.enable=true + - traefik.http.routers.beaver.tls=true + - traefik.http.routers.beaver.tls.certresolver=letsencrypt + - traefik.http.routers.beaver.rule=HostRegexp(`${TUNNEL_DOMAIN}`, `{subdomain:[a-z0-9-]+}.${TUNNEL_DOMAIN}`) + - traefik.http.routers.beaver.tls.domains[0].main=${TUNNEL_DOMAIN} + - traefik.http.routers.beaver.tls.domains[0].sans=*.${TUNNEL_DOMAIN} + traefik: + image: traefik:v2.9.8 + env_file: + - .env + ports: + - 80:80 + - 443:443 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./traefik-public-certificates:/certificates + labels: + - traefik.enable=true + command: + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --log.level=DEBUG + - --accesslog + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --certificatesresolvers.letsencrypt.acme.dnschallenge=true + - --certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL} + - --certificatesresolvers.letsencrypt.acme.storage=/certificates/acme.json + - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=${DNS_PROVIDER} + - --certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=0 diff --git a/internal/client/config.go b/internal/client/config.go index f666345..3810658 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -1,9 +1,11 @@ package client import ( + "fmt" "os" "strings" + "github.com/amalshaji/beaver/internal/utils" gonanoid "github.com/matoous/go-nanoid/v2" uuid "github.com/nu7hatch/gouuid" @@ -44,9 +46,11 @@ func (config *Config) setDefaults() { if config.Target == "" { config.Target = "wss://x.amal.sh" } + if config.PoolIdleSize == 0 { config.PoolIdleSize = 1 } + if config.PoolMaxSize == 0 { config.PoolMaxSize = 100 } @@ -78,7 +82,13 @@ func LoadConfiguration(configFile string, subdomain string, port int, showWsRead if err != nil { panic(err) } + } else { + err = utils.ValidateSubdomain(subdomain) + if err != nil { + return Config{}, fmt.Errorf("invalid subdomain: '%s'; %s", subdomain, err.Error()) + } } + config.subdomain = subdomain config.port = port config.showWsReadErrors = showWsReadErrors diff --git a/internal/server/handlers/admin.go b/internal/server/handlers/admin.go index 7d918af..495fb93 100644 --- a/internal/server/handlers/admin.go +++ b/internal/server/handlers/admin.go @@ -25,6 +25,10 @@ func register(c echo.Context) error { app := c.Get("app").(*app.App) subdomain := c.Request().Header.Get("X-TUNNEL-SUBDOMAIN") + if err := utils.ValidateSubdomain(subdomain); err != nil { + return utils.ProxyErrorf(c, "invalid subdomain: '%s'; %s", subdomain, utils.ErrInvalidSubdomain.Error()) + } + localServer := c.Request().Header.Get("X-LOCAL-SERVER") secretKey := c.Request().Header.Get("X-SECRET-KEY") diff --git a/internal/utils/string.go b/internal/utils/string.go index 033c39d..44b4b55 100644 --- a/internal/utils/string.go +++ b/internal/utils/string.go @@ -3,10 +3,14 @@ package utils import ( "errors" "net/mail" + "regexp" "strings" ) -var ErrPasswordNotLongEnough = errors.New("password must be atleast 6 chars long") +var ( + ErrPasswordNotLongEnough = errors.New("password must be atleast 6 chars long") + ErrInvalidSubdomain = errors.New("subdomain must contain only a-z, 0-9 and `-`(not leading or trailing)") +) // Remove any leading or trailing whitespace func SanitizeString(value string) string { @@ -24,3 +28,14 @@ func ValidatePassword(input string) error { } return nil } + +func ValidateSubdomain(subdomain string) error { + matched, err := regexp.Match(`^([a-z0-9]+([-][a-z0-9]+)*)$`, []byte(subdomain)) + if err != nil { + return err + } + if !matched { + return ErrInvalidSubdomain + } + return nil +} diff --git a/internal/utils/string_test.go b/internal/utils/string_test.go index 0a41eff..63a7474 100644 --- a/internal/utils/string_test.go +++ b/internal/utils/string_test.go @@ -52,3 +52,20 @@ func TestValidatePassword(t *testing.T) { assert.ErrorIs(t, ValidatePassword(tc.input), tc.want) } } + +func TestValidateSubdomain(t *testing.T) { + tests := []struct { + input string + want error + }{ + {input: "beaver-test-dev", want: nil}, + {input: "beaver.test", want: ErrInvalidSubdomain}, + {input: "-beaver", want: ErrInvalidSubdomain}, + {input: "beaver-", want: ErrInvalidSubdomain}, + {input: "beaver_test", want: ErrInvalidSubdomain}, + } + + for _, tc := range tests { + assert.ErrorIs(t, ValidateSubdomain(tc.input), tc.want) + } +}