Merge branch 'main' into readme-sort-env-variables

modified:   .woodpecker/build.yml
	modified:   .woodpecker/lint.yml
	modified:   Dockerfile
	modified:   README.md
	modified:   cli/flags.go
	modified:   config/assets/test_config.toml
	modified:   config/config.go
	modified:   config/setup.go
	modified:   config/setup_test.go
	modified:   example_config.toml
	modified:   flake.lock
	modified:   flake.nix
	modified:   go.mod
	modified:   go.sum
	modified:   server/certificates/certificates.go
	modified:   server/dns/dns.go
	modified:   server/gitea/client.go
	modified:   server/handler/handler.go
	modified:   server/handler/handler_custom_domain.go
	modified:   server/handler/handler_test.go
	modified:   server/startup.go
This commit is contained in:
adagio 2024-05-26 19:59:28 -04:00
commit fd7e805aa3
No known key found for this signature in database
GPG key ID: 0AE37F9649850573
21 changed files with 173 additions and 107 deletions

View file

@ -29,7 +29,7 @@ steps:
docker-dryrun: docker-dryrun:
depends_on: vendor depends_on: vendor
image: woodpeckerci/plugin-docker-buildx:3.2.1 image: woodpeckerci/plugin-docker-buildx:4.0.0
settings: settings:
dockerfile: Dockerfile dockerfile: Dockerfile
platforms: linux/amd64 platforms: linux/amd64
@ -99,7 +99,7 @@ steps:
docker-next: docker-next:
depends_on: vendor depends_on: vendor
image: woodpeckerci/plugin-docker-buildx:3.2.1 image: woodpeckerci/plugin-docker-buildx:4.0.0
settings: settings:
registry: codeberg.org registry: codeberg.org
dockerfile: Dockerfile dockerfile: Dockerfile
@ -114,9 +114,26 @@ steps:
- event: ['push'] - event: ['push']
branch: ${CI_REPO_DEFAULT_BRANCH} branch: ${CI_REPO_DEFAULT_BRANCH}
'Publish PR image':
image: woodpeckerci/plugin-docker-buildx:3.2.1
depends_on: test
settings:
registry: codeberg.org
dockerfile: Dockerfile
platforms: linux/amd64
repo: codeberg.org/codeberg/pages-server
tags: next
username:
from_secret: bot_user
password:
from_secret: bot_token
when:
evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_image"'
event: pull_request
docker-tag: docker-tag:
depends_on: vendor depends_on: vendor
image: woodpeckerci/plugin-docker-buildx:3.2.1 image: woodpeckerci/plugin-docker-buildx:4.0.0
settings: settings:
registry: codeberg.org registry: codeberg.org
dockerfile: Dockerfile dockerfile: Dockerfile

View file

@ -8,7 +8,7 @@ when:
steps: steps:
lint: lint:
depends_on: [] depends_on: []
image: golangci/golangci-lint:v1.58.1 image: golangci/golangci-lint:v1.58.2
commands: commands:
- go version - go version
- go install mvdan.cc/gofumpt@latest - go install mvdan.cc/gofumpt@latest
@ -28,7 +28,7 @@ steps:
branch: renovate/* branch: renovate/*
yamllint: yamllint:
image: pipelinecomponents/yamllint:0.31.1 image: pipelinecomponents/yamllint:0.31.2
depends_on: [] depends_on: []
commands: commands:
- yamllint . - yamllint .

View file

@ -16,13 +16,14 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \ --mount=type=cache,target=/go/pkg \
GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=1 \ GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=1 \
xgo -x -v --targets=${TARGETOS}/${TARGETARCH} -tags='sqlite sqlite_unlock_notify netgo' -ldflags='-s -w -extldflags "-static" -linkmode external' -out pages . xgo -x -v --targets=${TARGETOS}/${TARGETARCH} -tags='sqlite sqlite_unlock_notify netgo' -ldflags='-s -w -extldflags "-static" -linkmode external' -out pages .
RUN mv -vf /build/pages-* /go/src/codeberg.org/codeberg/pages/pages
# Use a scratch image as the base image for the final container, # Use a scratch image as the base image for the final container,
# which will contain only the built binary and the CA certificates # which will contain only the built binary and the CA certificates
FROM scratch FROM scratch
# Copy the built binary and the CA certificates from the build container to the final container # Copy the built binary and the CA certificates from the build container to the final container
COPY --from=build /go/src/codeberg.org/codeberg/pages/ /pages COPY --from=build /go/src/codeberg.org/codeberg/pages/pages /pages
COPY --from=build \ COPY --from=build \
/etc/ssl/certs/ca-certificates.crt \ /etc/ssl/certs/ca-certificates.crt \
/etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

View file

@ -64,19 +64,17 @@ but forward the requests on the IP level to the Pages Server.
You can check out a proof of concept in the `examples/haproxy-sni` folder, You can check out a proof of concept in the `examples/haproxy-sni` folder,
and especially have a look at [this section of the haproxy.cfg](https://codeberg.org/Codeberg/pages-server/src/branch/main/examples/haproxy-sni/haproxy.cfg#L38). and especially have a look at [this section of the haproxy.cfg](https://codeberg.org/Codeberg/pages-server/src/branch/main/examples/haproxy-sni/haproxy.cfg#L38).
If you want to test a change, you can open a PR and ask for the label `build_pr_image` to be added.
This will trigger a build of the PR which will build a docker image to be used for testing.
### Environment Variables ### Environment Variables
- `ACME_ACCEPT_TERMS` (default: use self-signed certificate): Set this to "true" to accept the Terms of Service of your ACME provider. - `ACME_ACCEPT_TERMS` (default: use self-signed certificate): Set this to "true" to accept the Terms of Service of your ACME provider.
- `ACME_API` (default: <https://acme-v02.api.letsencrypt.org/directory>): set - `ACME_API` (default: <https://acme-v02.api.letsencrypt.org/directory>): set this to <https://acme.mock.director> to use invalid certificates without any verification (great for debugging). ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
this to <https://acme.mock.director> to use invalid certificates without any
verification (great for debugging).
ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
- `ACME_EAB_KID` & `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL. - `ACME_EAB_KID` & `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL.
- `ACME_EMAIL` (default: `noreply@example.email`): Set the email sent to the ACME API server to receive, for example, renewal reminders. - `ACME_EMAIL` (default: `noreply@example.email`): Set the email sent to the ACME API server to receive, for example, renewal reminders.
- `ACME_USE_RATE_LIMITS` (default: true): Set this to false to disable rate limits, e.g. with ZeroSSL. - `ACME_USE_RATE_LIMITS` (default: true): Set this to false to disable rate limits, e.g. with ZeroSSL.
- `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS - `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS provider for the main domain wildcard. See <https://go-acme.github.io/lego/dns/> for available values & additional environment variables.
provider for the main domain wildcard.
See <https://go-acme.github.io/lego/dns/> for available values & additional environment variables.
- `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80. - `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80.
- `GITEA_API_TOKEN` (default: empty): API token for the Gitea instance to access non-public (e.g. limited) repos. - `GITEA_API_TOKEN` (default: empty): API token for the Gitea instance to access non-public (e.g. limited) repos.
- `GITEA_ROOT` (default: `https://codeberg.org`): root of the upstream Gitea instance. - `GITEA_ROOT` (default: `https://codeberg.org`): root of the upstream Gitea instance.

View file

@ -22,29 +22,31 @@ var (
ServerFlags = append(CertStorageFlags, []cli.Flag{ ServerFlags = append(CertStorageFlags, []cli.Flag{
// ############# // #############
// ### Gitea ### // ### Forge ###
// ############# // #############
// GiteaRoot specifies the root URL of the Gitea instance, without a trailing slash. // ForgeRoot specifies the root URL of the Forge instance, without a trailing slash.
&cli.StringFlag{ &cli.StringFlag{
Name: "gitea-root", Name: "forge-root",
Usage: "specifies the root URL of the Gitea instance, without a trailing slash.", Aliases: []string{"gitea-root"},
EnvVars: []string{"GITEA_ROOT"}, Usage: "specifies the root URL of the Forgejo/Gitea instance, without a trailing slash.",
EnvVars: []string{"FORGE_ROOT", "GITEA_ROOT"},
}, },
// GiteaApiToken specifies an api token for the Gitea instance // ForgeApiToken specifies an api token for the Forge instance
&cli.StringFlag{ &cli.StringFlag{
Name: "gitea-api-token", Name: "forge-api-token",
Usage: "specifies an api token for the Gitea instance", Aliases: []string{"gitea-api-token"},
EnvVars: []string{"GITEA_API_TOKEN"}, Usage: "specifies an api token for the Forgejo/Gitea instance",
EnvVars: []string{"FORGE_API_TOKEN", "GITEA_API_TOKEN"},
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "enable-lfs-support", Name: "enable-lfs-support",
Usage: "enable lfs support, require gitea >= v1.17.0 as backend", Usage: "enable lfs support, gitea must be version v1.17.0 or higher",
EnvVars: []string{"ENABLE_LFS_SUPPORT"}, EnvVars: []string{"ENABLE_LFS_SUPPORT"},
Value: false, Value: false,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "enable-symlink-support", Name: "enable-symlink-support",
Usage: "follow symlinks if enabled, require gitea >= v1.18.0 as backend", Usage: "follow symlinks if enabled, gitea must be version v1.18.0 or higher",
EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"}, EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"},
Value: false, Value: false,
}, },

View file

@ -10,8 +10,8 @@ rawDomain = 'raw.codeberg.page'
allowedCorsDomains = ['fonts.codeberg.org', 'design.codeberg.org'] allowedCorsDomains = ['fonts.codeberg.org', 'design.codeberg.org']
blacklistedPaths = ['do/not/use'] blacklistedPaths = ['do/not/use']
[gitea] [forge]
root = 'codeberg.org' root = 'https://codeberg.org'
token = 'XXXXXXXX' token = 'XXXXXXXX'
lfsEnabled = true lfsEnabled = true
followSymlinks = true followSymlinks = true

View file

@ -3,7 +3,7 @@ package config
type Config struct { type Config struct {
LogLevel string `default:"warn"` LogLevel string `default:"warn"`
Server ServerConfig Server ServerConfig
Gitea GiteaConfig Forge ForgeConfig
Database DatabaseConfig Database DatabaseConfig
ACME ACMEConfig ACME ACMEConfig
} }
@ -20,7 +20,7 @@ type ServerConfig struct {
BlacklistedPaths []string BlacklistedPaths []string
} }
type GiteaConfig struct { type ForgeConfig struct {
Root string Root string
Token string Token string
LFSEnabled bool `default:"false"` LFSEnabled bool `default:"false"`

View file

@ -51,7 +51,7 @@ func MergeConfig(ctx *cli.Context, config *Config) {
} }
mergeServerConfig(ctx, &config.Server) mergeServerConfig(ctx, &config.Server)
mergeGiteaConfig(ctx, &config.Gitea) mergeForgeConfig(ctx, &config.Forge)
mergeDatabaseConfig(ctx, &config.Database) mergeDatabaseConfig(ctx, &config.Database)
mergeACMEConfig(ctx, &config.ACME) mergeACMEConfig(ctx, &config.ACME)
} }
@ -89,12 +89,12 @@ func mergeServerConfig(ctx *cli.Context, config *ServerConfig) {
config.BlacklistedPaths = append(config.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...) config.BlacklistedPaths = append(config.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
} }
func mergeGiteaConfig(ctx *cli.Context, config *GiteaConfig) { func mergeForgeConfig(ctx *cli.Context, config *ForgeConfig) {
if ctx.IsSet("gitea-root") { if ctx.IsSet("forge-root") {
config.Root = ctx.String("gitea-root") config.Root = ctx.String("forge-root")
} }
if ctx.IsSet("gitea-api-token") { if ctx.IsSet("forge-api-token") {
config.Token = ctx.String("gitea-api-token") config.Token = ctx.String("forge-api-token")
} }
if ctx.IsSet("enable-lfs-support") { if ctx.IsSet("enable-lfs-support") {
config.LFSEnabled = ctx.Bool("enable-lfs-support") config.LFSEnabled = ctx.Bool("enable-lfs-support")

View file

@ -110,7 +110,7 @@ func TestValuesReadFromConfigFileShouldBeOverwrittenByArgs(t *testing.T) {
} }
expectedConfig.LogLevel = "debug" expectedConfig.LogLevel = "debug"
expectedConfig.Gitea.Root = "not-codeberg.org" expectedConfig.Forge.Root = "not-codeberg.org"
expectedConfig.ACME.AcceptTerms = true expectedConfig.ACME.AcceptTerms = true
expectedConfig.Server.Host = "172.17.0.2" expectedConfig.Server.Host = "172.17.0.2"
expectedConfig.Server.BlacklistedPaths = append(expectedConfig.Server.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...) expectedConfig.Server.BlacklistedPaths = append(expectedConfig.Server.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
@ -122,7 +122,7 @@ func TestValuesReadFromConfigFileShouldBeOverwrittenByArgs(t *testing.T) {
[]string{ []string{
"--config-file", "assets/test_config.toml", "--config-file", "assets/test_config.toml",
"--log-level", "debug", "--log-level", "debug",
"--gitea-root", "not-codeberg.org", "--forge-root", "not-codeberg.org",
"--acme-accept-terms", "--acme-accept-terms",
"--host", "172.17.0.2", "--host", "172.17.0.2",
}, },
@ -146,7 +146,7 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
AllowedCorsDomains: []string{"original"}, AllowedCorsDomains: []string{"original"},
BlacklistedPaths: []string{"original"}, BlacklistedPaths: []string{"original"},
}, },
Gitea: GiteaConfig{ Forge: ForgeConfig{
Root: "original", Root: "original",
Token: "original", Token: "original",
LFSEnabled: false, LFSEnabled: false,
@ -186,7 +186,7 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
AllowedCorsDomains: []string{"changed"}, AllowedCorsDomains: []string{"changed"},
BlacklistedPaths: append([]string{"changed"}, ALWAYS_BLACKLISTED_PATHS...), BlacklistedPaths: append([]string{"changed"}, ALWAYS_BLACKLISTED_PATHS...),
}, },
Gitea: GiteaConfig{ Forge: ForgeConfig{
Root: "changed", Root: "changed",
Token: "changed", Token: "changed",
LFSEnabled: true, LFSEnabled: true,
@ -227,9 +227,9 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
"--port", "8443", "--port", "8443",
"--http-port", "443", "--http-port", "443",
"--enable-http-server", "--enable-http-server",
// Gitea // Forge
"--gitea-root", "changed", "--forge-root", "changed",
"--gitea-api-token", "changed", "--forge-api-token", "changed",
"--enable-lfs-support", "--enable-lfs-support",
"--enable-symlink-support", "--enable-symlink-support",
"--default-mime-type", "changed", "--default-mime-type", "changed",
@ -366,11 +366,11 @@ func TestMergeServerConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgE
} }
} }
func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) { func TestMergeForgeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
runApp( runApp(
t, t,
func(ctx *cli.Context) error { func(ctx *cli.Context) error {
cfg := &GiteaConfig{ cfg := &ForgeConfig{
Root: "original", Root: "original",
Token: "original", Token: "original",
LFSEnabled: false, LFSEnabled: false,
@ -379,9 +379,9 @@ func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *test
ForbiddenMimeTypes: []string{"original"}, ForbiddenMimeTypes: []string{"original"},
} }
mergeGiteaConfig(ctx, cfg) mergeForgeConfig(ctx, cfg)
expectedConfig := &GiteaConfig{ expectedConfig := &ForgeConfig{
Root: "changed", Root: "changed",
Token: "changed", Token: "changed",
LFSEnabled: true, LFSEnabled: true,
@ -395,8 +395,8 @@ func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *test
return nil return nil
}, },
[]string{ []string{
"--gitea-root", "changed", "--forge-root", "changed",
"--gitea-api-token", "changed", "--forge-api-token", "changed",
"--enable-lfs-support", "--enable-lfs-support",
"--enable-symlink-support", "--enable-symlink-support",
"--default-mime-type", "changed", "--default-mime-type", "changed",
@ -405,25 +405,25 @@ func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *test
) )
} }
func TestMergeGiteaConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) { func TestMergeForgeConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
type testValuePair struct { type testValuePair struct {
args []string args []string
callback func(*GiteaConfig) callback func(*ForgeConfig)
} }
testValuePairs := []testValuePair{ testValuePairs := []testValuePair{
{args: []string{"--gitea-root", "changed"}, callback: func(gc *GiteaConfig) { gc.Root = "changed" }}, {args: []string{"--forge-root", "changed"}, callback: func(gc *ForgeConfig) { gc.Root = "changed" }},
{args: []string{"--gitea-api-token", "changed"}, callback: func(gc *GiteaConfig) { gc.Token = "changed" }}, {args: []string{"--forge-api-token", "changed"}, callback: func(gc *ForgeConfig) { gc.Token = "changed" }},
{args: []string{"--enable-lfs-support"}, callback: func(gc *GiteaConfig) { gc.LFSEnabled = true }}, {args: []string{"--enable-lfs-support"}, callback: func(gc *ForgeConfig) { gc.LFSEnabled = true }},
{args: []string{"--enable-symlink-support"}, callback: func(gc *GiteaConfig) { gc.FollowSymlinks = true }}, {args: []string{"--enable-symlink-support"}, callback: func(gc *ForgeConfig) { gc.FollowSymlinks = true }},
{args: []string{"--default-mime-type", "changed"}, callback: func(gc *GiteaConfig) { gc.DefaultMimeType = "changed" }}, {args: []string{"--default-mime-type", "changed"}, callback: func(gc *ForgeConfig) { gc.DefaultMimeType = "changed" }},
{args: []string{"--forbidden-mime-types", "changed"}, callback: func(gc *GiteaConfig) { gc.ForbiddenMimeTypes = []string{"changed"} }}, {args: []string{"--forbidden-mime-types", "changed"}, callback: func(gc *ForgeConfig) { gc.ForbiddenMimeTypes = []string{"changed"} }},
} }
for _, pair := range testValuePairs { for _, pair := range testValuePairs {
runApp( runApp(
t, t,
func(ctx *cli.Context) error { func(ctx *cli.Context) error {
cfg := GiteaConfig{ cfg := ForgeConfig{
Root: "original", Root: "original",
Token: "original", Token: "original",
LFSEnabled: false, LFSEnabled: false,
@ -435,7 +435,7 @@ func TestMergeGiteaConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgEx
expectedConfig := cfg expectedConfig := cfg
pair.callback(&expectedConfig) pair.callback(&expectedConfig)
mergeGiteaConfig(ctx, &cfg) mergeForgeConfig(ctx, &cfg)
expectedConfig.ForbiddenMimeTypes = fixArrayFromCtx(ctx, "forbidden-mime-types", expectedConfig.ForbiddenMimeTypes) expectedConfig.ForbiddenMimeTypes = fixArrayFromCtx(ctx, "forbidden-mime-types", expectedConfig.ForbiddenMimeTypes)
@ -448,6 +448,33 @@ func TestMergeGiteaConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgEx
} }
} }
func TestMergeForgeConfigShouldReplaceValuesGivenGiteaOptionsExist(t *testing.T) {
runApp(
t,
func(ctx *cli.Context) error {
cfg := &ForgeConfig{
Root: "original",
Token: "original",
}
mergeForgeConfig(ctx, cfg)
expectedConfig := &ForgeConfig{
Root: "changed",
Token: "changed",
}
assert.Equal(t, expectedConfig, cfg)
return nil
},
[]string{
"--gitea-root", "changed",
"--gitea-api-token", "changed",
},
)
}
func TestMergeDatabaseConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) { func TestMergeDatabaseConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
runApp( runApp(
t, t,

View file

@ -11,7 +11,7 @@ pagesBranches = ["pages"]
allowedCorsDomains = [] allowedCorsDomains = []
blacklistedPaths = [] blacklistedPaths = []
[gitea] [forge]
root = 'https://codeberg.org' root = 'https://codeberg.org'
token = 'ASDF1234' token = 'ASDF1234'
lfsEnabled = true lfsEnabled = true

6
flake.lock generated
View file

@ -19,11 +19,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1714030708, "lastModified": 1716715802,
"narHash": "sha256-JOGPOxa8N6ySzB7SQBsh0OVz+UXZriyahgvfNHMIY0Y=", "narHash": "sha256-usk0vE7VlxPX8jOavrtpOqphdfqEQpf9lgedlY/r66c=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b0d52b31f7f4d80f8bf38f0253652125579c35ff", "rev": "e2dd4e18cc1c7314e24154331bae07df76eb582f",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -16,6 +16,7 @@
gcc gcc
go go
gofumpt gofumpt
golangci-lint
gopls gopls
gotools gotools
go-tools go-tools

3
go.mod
View file

@ -10,6 +10,7 @@ require (
github.com/creasty/defaults v1.7.0 github.com/creasty/defaults v1.7.0
github.com/go-acme/lego/v4 v4.5.3 github.com/go-acme/lego/v4 v4.5.3
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/joho/godotenv v1.4.0 github.com/joho/godotenv v1.4.0
github.com/lib/pq v1.10.7 github.com/lib/pq v1.10.7
github.com/mattn/go-sqlite3 v1.14.16 github.com/mattn/go-sqlite3 v1.14.16
@ -19,7 +20,7 @@ require (
github.com/rs/zerolog v1.27.0 github.com/rs/zerolog v1.27.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
xorm.io/xorm v1.3.2 xorm.io/xorm v1.3.2
) )

19
go.sum
View file

@ -254,8 +254,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@ -323,6 +323,8 @@ github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
@ -784,8 +786,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -807,8 +809,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -864,8 +867,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -999,8 +1003,9 @@ golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -14,6 +14,7 @@ import (
"github.com/go-acme/lego/v4/certificate" "github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/lego"
"github.com/hashicorp/golang-lru/v2/expirable"
"github.com/reugn/equalizer" "github.com/reugn/equalizer"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -31,11 +32,14 @@ func TLSConfig(mainDomainSuffix string,
giteaClient *gitea.Client, giteaClient *gitea.Client,
acmeClient *AcmeClient, acmeClient *AcmeClient,
firstDefaultBranch string, firstDefaultBranch string,
keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.ICache, challengeCache, canonicalDomainCache cache.ICache,
certDB database.CertDB, certDB database.CertDB,
noDNS01 bool, noDNS01 bool,
rawDomain string, rawDomain string,
) *tls.Config { ) *tls.Config {
// every cert is at most 24h in the cache and 7 days before expiry the cert is renewed
keyCache := expirable.NewLRU[string, *tls.Certificate](32, nil, 24*time.Hour)
return &tls.Config{ return &tls.Config{
// check DNS name & get certificate from Let's Encrypt // check DNS name & get certificate from Let's Encrypt
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
@ -86,7 +90,7 @@ func TLSConfig(mainDomainSuffix string,
} }
} else { } else {
var targetRepo, targetBranch string var targetRepo, targetBranch string
targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch, dnsLookupCache) targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch)
if targetOwner == "" { if targetOwner == "" {
// DNS not set up, return main certificate to redirect to the docs // DNS not set up, return main certificate to redirect to the docs
domain = mainDomainSuffix domain = mainDomainSuffix
@ -107,7 +111,7 @@ func TLSConfig(mainDomainSuffix string,
if tlsCertificate, ok := keyCache.Get(domain); ok { if tlsCertificate, ok := keyCache.Get(domain); ok {
// we can use an existing certificate object // we can use an existing certificate object
return tlsCertificate.(*tls.Certificate), nil return tlsCertificate, nil
} }
var tlsCertificate *tls.Certificate var tlsCertificate *tls.Certificate
@ -132,9 +136,8 @@ func TLSConfig(mainDomainSuffix string,
} }
} }
if err := keyCache.Set(domain, tlsCertificate, 15*time.Minute); err != nil { keyCache.Add(domain, tlsCertificate)
return nil, err
}
return tlsCertificate, nil return tlsCertificate, nil
}, },
NextProtos: []string{ NextProtos: []string{
@ -186,11 +189,10 @@ func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProv
// TODO: document & put into own function // TODO: document & put into own function
if !strings.EqualFold(sni, mainDomainSuffix) { if !strings.EqualFold(sni, mainDomainSuffix) {
tlsCertificate.Leaf, err = x509.ParseCertificate(tlsCertificate.Certificate[0]) tlsCertificate.Leaf, err = leaf(&tlsCertificate)
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing leaf tlsCert: %w", err) return nil, err
} }
// renew certificates 7 days before they expire // renew certificates 7 days before they expire
if tlsCertificate.Leaf.NotAfter.Before(time.Now().Add(7 * 24 * time.Hour)) { if tlsCertificate.Leaf.NotAfter.Before(time.Now().Add(7 * 24 * time.Hour)) {
// TODO: use ValidTill of custom cert struct // TODO: use ValidTill of custom cert struct
@ -291,6 +293,7 @@ func (c *AcmeClient) obtainCert(acmeClient *lego.Client, domains []string, renew
} }
leaf, err := leaf(&tlsCertificate) leaf, err := leaf(&tlsCertificate)
if err == nil && leaf.NotAfter.After(time.Now()) { if err == nil && leaf.NotAfter.After(time.Now()) {
tlsCertificate.Leaf = leaf
// avoid sending a mock cert instead of a still valid cert, instead abuse CSR field to store time to try again at // avoid sending a mock cert instead of a still valid cert, instead abuse CSR field to store time to try again at
renew.CSR = []byte(strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10)) renew.CSR = []byte(strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10))
if err := keyDatabase.Put(name, renew); err != nil { if err := keyDatabase.Put(name, renew); err != nil {
@ -388,11 +391,20 @@ func MaintainCertDB(ctx context.Context, interval time.Duration, acmeClient *Acm
} }
} }
// leaf returns the parsed leaf certificate, either from c.leaf or by parsing // leaf returns the parsed leaf certificate, either from c.Leaf or by parsing
// the corresponding c.Certificate[0]. // the corresponding c.Certificate[0].
// After successfully parsing the cert c.Leaf gets set to the parsed cert.
func leaf(c *tls.Certificate) (*x509.Certificate, error) { func leaf(c *tls.Certificate) (*x509.Certificate, error) {
if c.Leaf != nil { if c.Leaf != nil {
return c.Leaf, nil return c.Leaf, nil
} }
return x509.ParseCertificate(c.Certificate[0])
leaf, err := x509.ParseCertificate(c.Certificate[0])
if err != nil {
return nil, fmt.Errorf("tlsCert - failed to parse leaf: %w", err)
}
c.Leaf = leaf
return leaf, err
} }

View file

@ -5,22 +5,26 @@ import (
"strings" "strings"
"time" "time"
"codeberg.org/codeberg/pages/server/cache" "github.com/hashicorp/golang-lru/v2/expirable"
) )
// lookupCacheTimeout specifies the timeout for the DNS lookup cache. const (
var lookupCacheTimeout = 15 * time.Minute lookupCacheValidity = 30 * time.Second
defaultPagesRepo = "pages"
)
var defaultPagesRepo = "pages" // TODO(#316): refactor to not use global variables
var lookupCache *expirable.LRU[string, string] = expirable.NewLRU[string, string](4096, nil, lookupCacheValidity)
// GetTargetFromDNS searches for CNAME or TXT entries on the request domain ending with MainDomainSuffix. // GetTargetFromDNS searches for CNAME or TXT entries on the request domain ending with MainDomainSuffix.
// If everything is fine, it returns the target data. // If everything is fine, it returns the target data.
func GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch string, dnsLookupCache cache.ICache) (targetOwner, targetRepo, targetBranch string) { func GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch string) (targetOwner, targetRepo, targetBranch string) {
// Get CNAME or TXT // Get CNAME or TXT
var cname string var cname string
var err error var err error
if cachedName, ok := dnsLookupCache.Get(domain); ok {
cname = cachedName.(string) if entry, ok := lookupCache.Get(domain); ok {
cname = entry
} else { } else {
cname, err = net.LookupCNAME(domain) cname, err = net.LookupCNAME(domain)
cname = strings.TrimSuffix(cname, ".") cname = strings.TrimSuffix(cname, ".")
@ -38,7 +42,7 @@ func GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch string, dnsLo
} }
} }
} }
_ = dnsLookupCache.Set(domain, cname, lookupCacheTimeout) _ = lookupCache.Add(domain, cname)
} }
if cname == "" { if cname == "" {
return return

View file

@ -57,12 +57,13 @@ type Client struct {
defaultMimeType string defaultMimeType string
} }
func NewClient(cfg config.GiteaConfig, respCache cache.ICache) (*Client, error) { func NewClient(cfg config.ForgeConfig, respCache cache.ICache) (*Client, error) {
rootURL, err := url.Parse(cfg.Root) // url.Parse returns valid on almost anything...
rootURL, err := url.ParseRequestURI(cfg.Root)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("invalid forgejo/gitea root url: %w", err)
} }
giteaRoot := strings.Trim(rootURL.String(), "/") giteaRoot := strings.TrimSuffix(rootURL.String(), "/")
stdClient := http.Client{Timeout: 10 * time.Second} stdClient := http.Client{Timeout: 10 * time.Second}

View file

@ -23,7 +23,7 @@ const (
func Handler( func Handler(
cfg config.ServerConfig, cfg config.ServerConfig,
giteaClient *gitea.Client, giteaClient *gitea.Client,
dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache, canonicalDomainCache, redirectsCache cache.ICache,
) http.HandlerFunc { ) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) {
log.Debug().Msg("\n----------------------------------------------------------") log.Debug().Msg("\n----------------------------------------------------------")
@ -108,7 +108,7 @@ func Handler(
trimmedHost, trimmedHost,
pathElements, pathElements,
cfg.PagesBranches[0], cfg.PagesBranches[0],
dnsLookupCache, canonicalDomainCache, redirectsCache) canonicalDomainCache, redirectsCache)
} }
} }
} }

View file

@ -19,10 +19,10 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
trimmedHost string, trimmedHost string,
pathElements []string, pathElements []string,
firstDefaultBranch string, firstDefaultBranch string,
dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache, canonicalDomainCache, redirectsCache cache.ICache,
) { ) {
// Serve pages from custom domains // Serve pages from custom domains
targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, firstDefaultBranch, dnsLookupCache) targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, firstDefaultBranch)
if targetOwner == "" { if targetOwner == "" {
html.ReturnErrorPage(ctx, html.ReturnErrorPage(ctx,
"could not obtain repo owner from custom domain", "could not obtain repo owner from custom domain",
@ -53,7 +53,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
return return
} else if canonicalDomain != trimmedHost { } else if canonicalDomain != trimmedHost {
// only redirect if the target is also a codeberg page! // only redirect if the target is also a codeberg page!
targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix, firstDefaultBranch, dnsLookupCache) targetOwner, _, _ = dns.GetTargetFromDNS(strings.SplitN(canonicalDomain, "/", 2)[0], mainDomainSuffix, firstDefaultBranch)
if targetOwner != "" { if targetOwner != "" {
ctx.Redirect("https://"+canonicalDomain+"/"+targetOpt.TargetPath, http.StatusTemporaryRedirect) ctx.Redirect("https://"+canonicalDomain+"/"+targetOpt.TargetPath, http.StatusTemporaryRedirect)
return return

View file

@ -13,7 +13,7 @@ import (
) )
func TestHandlerPerformance(t *testing.T) { func TestHandlerPerformance(t *testing.T) {
cfg := config.GiteaConfig{ cfg := config.ForgeConfig{
Root: "https://codeberg.org", Root: "https://codeberg.org",
Token: "", Token: "",
LFSEnabled: false, LFSEnabled: false,
@ -29,7 +29,7 @@ func TestHandlerPerformance(t *testing.T) {
AllowedCorsDomains: []string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"}, AllowedCorsDomains: []string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"},
PagesBranches: []string{"pages"}, PagesBranches: []string{"pages"},
} }
testHandler := Handler(serverCfg, giteaClient, cache.NewInMemoryCache(), cache.NewInMemoryCache(), cache.NewInMemoryCache()) testHandler := Handler(serverCfg, giteaClient, cache.NewInMemoryCache(), cache.NewInMemoryCache())
testCase := func(uri string, status int) { testCase := func(uri string, status int) {
t.Run(uri, func(t *testing.T) { t.Run(uri, func(t *testing.T) {

View file

@ -66,18 +66,15 @@ func Serve(ctx *cli.Context) error {
} }
defer closeFn() defer closeFn()
keyCache := cache.NewInMemoryCache()
challengeCache := cache.NewInMemoryCache() challengeCache := cache.NewInMemoryCache()
// canonicalDomainCache stores canonical domains // canonicalDomainCache stores canonical domains
canonicalDomainCache := cache.NewInMemoryCache() canonicalDomainCache := cache.NewInMemoryCache()
// dnsLookupCache stores DNS lookups for custom domains
dnsLookupCache := cache.NewInMemoryCache()
// redirectsCache stores redirects in _redirects files // redirectsCache stores redirects in _redirects files
redirectsCache := cache.NewInMemoryCache() redirectsCache := cache.NewInMemoryCache()
// clientResponseCache stores responses from the Gitea server // clientResponseCache stores responses from the Gitea server
clientResponseCache := cache.NewInMemoryCache() clientResponseCache := cache.NewInMemoryCache()
giteaClient, err := gitea.NewClient(cfg.Gitea, clientResponseCache) giteaClient, err := gitea.NewClient(cfg.Forge, clientResponseCache)
if err != nil { if err != nil {
return fmt.Errorf("could not create new gitea client: %v", err) return fmt.Errorf("could not create new gitea client: %v", err)
} }
@ -104,7 +101,7 @@ func Serve(ctx *cli.Context) error {
giteaClient, giteaClient,
acmeClient, acmeClient,
cfg.Server.PagesBranches[0], cfg.Server.PagesBranches[0],
keyCache, challengeCache, dnsLookupCache, canonicalDomainCache, challengeCache, canonicalDomainCache,
certDB, certDB,
cfg.ACME.NoDNS01, cfg.ACME.NoDNS01,
cfg.Server.RawDomain, cfg.Server.RawDomain,
@ -134,7 +131,7 @@ func Serve(ctx *cli.Context) error {
} }
// Create ssl handler based on settings // Create ssl handler based on settings
sslHandler := handler.Handler(cfg.Server, giteaClient, dnsLookupCache, canonicalDomainCache, redirectsCache) sslHandler := handler.Handler(cfg.Server, giteaClient, canonicalDomainCache, redirectsCache)
// Start the ssl listener // Start the ssl listener
log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr()) log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr())