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:
depends_on: vendor
image: woodpeckerci/plugin-docker-buildx:3.2.1
image: woodpeckerci/plugin-docker-buildx:4.0.0
settings:
dockerfile: Dockerfile
platforms: linux/amd64
@ -99,7 +99,7 @@ steps:
docker-next:
depends_on: vendor
image: woodpeckerci/plugin-docker-buildx:3.2.1
image: woodpeckerci/plugin-docker-buildx:4.0.0
settings:
registry: codeberg.org
dockerfile: Dockerfile
@ -114,9 +114,26 @@ steps:
- event: ['push']
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:
depends_on: vendor
image: woodpeckerci/plugin-docker-buildx:3.2.1
image: woodpeckerci/plugin-docker-buildx:4.0.0
settings:
registry: codeberg.org
dockerfile: Dockerfile

View file

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

View file

@ -16,13 +16,14 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg \
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 .
RUN mv -vf /build/pages-* /go/src/codeberg.org/codeberg/pages/pages
# Use a scratch image as the base image for the final container,
# which will contain only the built binary and the CA certificates
FROM scratch
# 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 \
/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,
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
- `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
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_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.
- `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_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
provider for the main domain wildcard.
See <https://go-acme.github.io/lego/dns/> for available values & additional environment variables.
- `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.
- `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_ROOT` (default: `https://codeberg.org`): root of the upstream Gitea instance.
@ -107,7 +105,7 @@ Previous maintainers:
### First steps
The code of this repository is split in several modules.
The code of this repository is split in several modules.
The [Architecture is explained](https://codeberg.org/Codeberg/pages-server/wiki/Architecture) in the wiki.
The `cmd` folder holds the data necessary for interacting with the service via the cli.
@ -120,7 +118,7 @@ Thank you very much.
Make sure you have [golang](https://go.dev) v1.21 or newer and [just](https://just.systems/man/en/) installed.
run `just dev`
run `just dev`
now these pages should work:
- <https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg>

View file

@ -22,29 +22,31 @@ var (
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{
Name: "gitea-root",
Usage: "specifies the root URL of the Gitea instance, without a trailing slash.",
EnvVars: []string{"GITEA_ROOT"},
Name: "forge-root",
Aliases: []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{
Name: "gitea-api-token",
Usage: "specifies an api token for the Gitea instance",
EnvVars: []string{"GITEA_API_TOKEN"},
Name: "forge-api-token",
Aliases: []string{"gitea-api-token"},
Usage: "specifies an api token for the Forgejo/Gitea instance",
EnvVars: []string{"FORGE_API_TOKEN", "GITEA_API_TOKEN"},
},
&cli.BoolFlag{
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"},
Value: false,
},
&cli.BoolFlag{
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"},
Value: false,
},

View file

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

View file

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

View file

@ -51,7 +51,7 @@ func MergeConfig(ctx *cli.Context, config *Config) {
}
mergeServerConfig(ctx, &config.Server)
mergeGiteaConfig(ctx, &config.Gitea)
mergeForgeConfig(ctx, &config.Forge)
mergeDatabaseConfig(ctx, &config.Database)
mergeACMEConfig(ctx, &config.ACME)
}
@ -89,12 +89,12 @@ func mergeServerConfig(ctx *cli.Context, config *ServerConfig) {
config.BlacklistedPaths = append(config.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
}
func mergeGiteaConfig(ctx *cli.Context, config *GiteaConfig) {
if ctx.IsSet("gitea-root") {
config.Root = ctx.String("gitea-root")
func mergeForgeConfig(ctx *cli.Context, config *ForgeConfig) {
if ctx.IsSet("forge-root") {
config.Root = ctx.String("forge-root")
}
if ctx.IsSet("gitea-api-token") {
config.Token = ctx.String("gitea-api-token")
if ctx.IsSet("forge-api-token") {
config.Token = ctx.String("forge-api-token")
}
if ctx.IsSet("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.Gitea.Root = "not-codeberg.org"
expectedConfig.Forge.Root = "not-codeberg.org"
expectedConfig.ACME.AcceptTerms = true
expectedConfig.Server.Host = "172.17.0.2"
expectedConfig.Server.BlacklistedPaths = append(expectedConfig.Server.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
@ -122,7 +122,7 @@ func TestValuesReadFromConfigFileShouldBeOverwrittenByArgs(t *testing.T) {
[]string{
"--config-file", "assets/test_config.toml",
"--log-level", "debug",
"--gitea-root", "not-codeberg.org",
"--forge-root", "not-codeberg.org",
"--acme-accept-terms",
"--host", "172.17.0.2",
},
@ -146,7 +146,7 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
AllowedCorsDomains: []string{"original"},
BlacklistedPaths: []string{"original"},
},
Gitea: GiteaConfig{
Forge: ForgeConfig{
Root: "original",
Token: "original",
LFSEnabled: false,
@ -186,7 +186,7 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
AllowedCorsDomains: []string{"changed"},
BlacklistedPaths: append([]string{"changed"}, ALWAYS_BLACKLISTED_PATHS...),
},
Gitea: GiteaConfig{
Forge: ForgeConfig{
Root: "changed",
Token: "changed",
LFSEnabled: true,
@ -227,9 +227,9 @@ func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T
"--port", "8443",
"--http-port", "443",
"--enable-http-server",
// Gitea
"--gitea-root", "changed",
"--gitea-api-token", "changed",
// Forge
"--forge-root", "changed",
"--forge-api-token", "changed",
"--enable-lfs-support",
"--enable-symlink-support",
"--default-mime-type", "changed",
@ -366,11 +366,11 @@ func TestMergeServerConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgE
}
}
func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
func TestMergeForgeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
runApp(
t,
func(ctx *cli.Context) error {
cfg := &GiteaConfig{
cfg := &ForgeConfig{
Root: "original",
Token: "original",
LFSEnabled: false,
@ -379,9 +379,9 @@ func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *test
ForbiddenMimeTypes: []string{"original"},
}
mergeGiteaConfig(ctx, cfg)
mergeForgeConfig(ctx, cfg)
expectedConfig := &GiteaConfig{
expectedConfig := &ForgeConfig{
Root: "changed",
Token: "changed",
LFSEnabled: true,
@ -395,8 +395,8 @@ func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *test
return nil
},
[]string{
"--gitea-root", "changed",
"--gitea-api-token", "changed",
"--forge-root", "changed",
"--forge-api-token", "changed",
"--enable-lfs-support",
"--enable-symlink-support",
"--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 {
args []string
callback func(*GiteaConfig)
callback func(*ForgeConfig)
}
testValuePairs := []testValuePair{
{args: []string{"--gitea-root", "changed"}, callback: func(gc *GiteaConfig) { gc.Root = "changed" }},
{args: []string{"--gitea-api-token", "changed"}, callback: func(gc *GiteaConfig) { gc.Token = "changed" }},
{args: []string{"--enable-lfs-support"}, callback: func(gc *GiteaConfig) { gc.LFSEnabled = true }},
{args: []string{"--enable-symlink-support"}, callback: func(gc *GiteaConfig) { gc.FollowSymlinks = true }},
{args: []string{"--default-mime-type", "changed"}, callback: func(gc *GiteaConfig) { gc.DefaultMimeType = "changed" }},
{args: []string{"--forbidden-mime-types", "changed"}, callback: func(gc *GiteaConfig) { gc.ForbiddenMimeTypes = []string{"changed"} }},
{args: []string{"--forge-root", "changed"}, callback: func(gc *ForgeConfig) { gc.Root = "changed" }},
{args: []string{"--forge-api-token", "changed"}, callback: func(gc *ForgeConfig) { gc.Token = "changed" }},
{args: []string{"--enable-lfs-support"}, callback: func(gc *ForgeConfig) { gc.LFSEnabled = true }},
{args: []string{"--enable-symlink-support"}, callback: func(gc *ForgeConfig) { gc.FollowSymlinks = true }},
{args: []string{"--default-mime-type", "changed"}, callback: func(gc *ForgeConfig) { gc.DefaultMimeType = "changed" }},
{args: []string{"--forbidden-mime-types", "changed"}, callback: func(gc *ForgeConfig) { gc.ForbiddenMimeTypes = []string{"changed"} }},
}
for _, pair := range testValuePairs {
runApp(
t,
func(ctx *cli.Context) error {
cfg := GiteaConfig{
cfg := ForgeConfig{
Root: "original",
Token: "original",
LFSEnabled: false,
@ -435,7 +435,7 @@ func TestMergeGiteaConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgEx
expectedConfig := cfg
pair.callback(&expectedConfig)
mergeGiteaConfig(ctx, &cfg)
mergeForgeConfig(ctx, &cfg)
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) {
runApp(
t,

View file

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

6
flake.lock generated
View file

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

View file

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

3
go.mod
View file

@ -10,6 +10,7 @@ require (
github.com/creasty/defaults v1.7.0
github.com/go-acme/lego/v4 v4.5.3
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/lib/pq v1.10.7
github.com/mattn/go-sqlite3 v1.14.16
@ -19,7 +20,7 @@ require (
github.com/rs/zerolog v1.27.0
github.com/stretchr/testify v1.8.4
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
)

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.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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
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-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
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/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/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/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=
@ -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-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-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w=
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
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-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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.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.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
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-20180826012351-8a410e7b638d/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-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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
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-20180823144017-11551d06cbcc/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-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.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
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-20190513163551-3ee3066db522/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/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/lego"
"github.com/hashicorp/golang-lru/v2/expirable"
"github.com/reugn/equalizer"
"github.com/rs/zerolog/log"
@ -31,11 +32,14 @@ func TLSConfig(mainDomainSuffix string,
giteaClient *gitea.Client,
acmeClient *AcmeClient,
firstDefaultBranch string,
keyCache, challengeCache, dnsLookupCache, canonicalDomainCache cache.ICache,
challengeCache, canonicalDomainCache cache.ICache,
certDB database.CertDB,
noDNS01 bool,
rawDomain string,
) *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{
// check DNS name & get certificate from Let's Encrypt
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
@ -86,7 +90,7 @@ func TLSConfig(mainDomainSuffix string,
}
} else {
var targetRepo, targetBranch string
targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
targetOwner, targetRepo, targetBranch = dnsutils.GetTargetFromDNS(domain, mainDomainSuffix, firstDefaultBranch)
if targetOwner == "" {
// DNS not set up, return main certificate to redirect to the docs
domain = mainDomainSuffix
@ -107,7 +111,7 @@ func TLSConfig(mainDomainSuffix string,
if tlsCertificate, ok := keyCache.Get(domain); ok {
// we can use an existing certificate object
return tlsCertificate.(*tls.Certificate), nil
return tlsCertificate, nil
}
var tlsCertificate *tls.Certificate
@ -132,9 +136,8 @@ func TLSConfig(mainDomainSuffix string,
}
}
if err := keyCache.Set(domain, tlsCertificate, 15*time.Minute); err != nil {
return nil, err
}
keyCache.Add(domain, tlsCertificate)
return tlsCertificate, nil
},
NextProtos: []string{
@ -186,11 +189,10 @@ func (c *AcmeClient) retrieveCertFromDB(sni, mainDomainSuffix string, useDnsProv
// TODO: document & put into own function
if !strings.EqualFold(sni, mainDomainSuffix) {
tlsCertificate.Leaf, err = x509.ParseCertificate(tlsCertificate.Certificate[0])
tlsCertificate.Leaf, err = leaf(&tlsCertificate)
if err != nil {
return nil, fmt.Errorf("error parsing leaf tlsCert: %w", err)
return nil, err
}
// renew certificates 7 days before they expire
if tlsCertificate.Leaf.NotAfter.Before(time.Now().Add(7 * 24 * time.Hour)) {
// 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)
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
renew.CSR = []byte(strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10))
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].
// After successfully parsing the cert c.Leaf gets set to the parsed cert.
func leaf(c *tls.Certificate) (*x509.Certificate, error) {
if 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"
"time"
"codeberg.org/codeberg/pages/server/cache"
"github.com/hashicorp/golang-lru/v2/expirable"
)
// lookupCacheTimeout specifies the timeout for the DNS lookup cache.
var lookupCacheTimeout = 15 * time.Minute
const (
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.
// 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
var cname string
var err error
if cachedName, ok := dnsLookupCache.Get(domain); ok {
cname = cachedName.(string)
if entry, ok := lookupCache.Get(domain); ok {
cname = entry
} else {
cname, err = net.LookupCNAME(domain)
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 == "" {
return

View file

@ -57,12 +57,13 @@ type Client struct {
defaultMimeType string
}
func NewClient(cfg config.GiteaConfig, respCache cache.ICache) (*Client, error) {
rootURL, err := url.Parse(cfg.Root)
func NewClient(cfg config.ForgeConfig, respCache cache.ICache) (*Client, error) {
// url.Parse returns valid on almost anything...
rootURL, err := url.ParseRequestURI(cfg.Root)
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}

View file

@ -23,7 +23,7 @@ const (
func Handler(
cfg config.ServerConfig,
giteaClient *gitea.Client,
dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache,
canonicalDomainCache, redirectsCache cache.ICache,
) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
log.Debug().Msg("\n----------------------------------------------------------")
@ -108,7 +108,7 @@ func Handler(
trimmedHost,
pathElements,
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,
pathElements []string,
firstDefaultBranch string,
dnsLookupCache, canonicalDomainCache, redirectsCache cache.ICache,
canonicalDomainCache, redirectsCache cache.ICache,
) {
// Serve pages from custom domains
targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, firstDefaultBranch, dnsLookupCache)
targetOwner, targetRepo, targetBranch := dns.GetTargetFromDNS(trimmedHost, mainDomainSuffix, firstDefaultBranch)
if targetOwner == "" {
html.ReturnErrorPage(ctx,
"could not obtain repo owner from custom domain",
@ -53,7 +53,7 @@ func handleCustomDomain(log zerolog.Logger, ctx *context.Context, giteaClient *g
return
} else if canonicalDomain != trimmedHost {
// 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 != "" {
ctx.Redirect("https://"+canonicalDomain+"/"+targetOpt.TargetPath, http.StatusTemporaryRedirect)
return

View file

@ -13,7 +13,7 @@ import (
)
func TestHandlerPerformance(t *testing.T) {
cfg := config.GiteaConfig{
cfg := config.ForgeConfig{
Root: "https://codeberg.org",
Token: "",
LFSEnabled: false,
@ -29,7 +29,7 @@ func TestHandlerPerformance(t *testing.T) {
AllowedCorsDomains: []string{"raw.codeberg.org", "fonts.codeberg.org", "design.codeberg.org"},
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) {
t.Run(uri, func(t *testing.T) {

View file

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