diff --git a/cmd/certs.go b/cmd/certs.go index 4adf076..96244b7 100644 --- a/cmd/certs.go +++ b/cmd/certs.go @@ -98,9 +98,6 @@ func listCerts(ctx *cli.Context) error { fmt.Printf("Domain\tValidTill\n\n") for _, cert := range items { - if cert.Domain[0] == '.' { - cert.Domain = "*" + cert.Domain - } fmt.Printf("%s\t%s\n", cert.Domain, time.Unix(cert.ValidTill, 0).Format(time.RFC3339)) diff --git a/cmd/flags.go b/cmd/flags.go index 40bb053..da3febc 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -43,6 +43,18 @@ var ( EnvVars: []string{"GITEA_API_TOKEN"}, Value: "", }, + &cli.BoolFlag{ + Name: "enable-lfs-support", + Usage: "enable lfs support, require gitea >= v1.17.0 as backend", + EnvVars: []string{"ENABLE_LFS_SUPPORT"}, + Value: true, + }, + &cli.BoolFlag{ + Name: "enable-symlink-support", + Usage: "follow symlinks if enabled, require gitea >= v1.18.0 as backend", + EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"}, + Value: true, + }, // ########################### // ### Page Server Domains ### @@ -73,7 +85,9 @@ var ( Value: "https://docs.codeberg.org/codeberg-pages/raw-content/", }, - // Server + // ######################### + // ### Page Server Setup ### + // ######################### &cli.StringFlag{ Name: "host", Usage: "specifies host of listening address", @@ -91,19 +105,6 @@ var ( // TODO: desc EnvVars: []string{"ENABLE_HTTP_SERVER"}, }, - // Server Options - &cli.BoolFlag{ - Name: "enable-lfs-support", - Usage: "enable lfs support, require gitea v1.17.0 as backend", - EnvVars: []string{"ENABLE_LFS_SUPPORT"}, - Value: true, - }, - &cli.BoolFlag{ - Name: "enable-symlink-support", - Usage: "follow symlinks if enabled, require gitea v1.18.0 as backend", - EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"}, - Value: true, - }, &cli.StringFlag{ Name: "log-level", Value: "warn", @@ -111,7 +112,9 @@ var ( EnvVars: []string{"LOG_LEVEL"}, }, - // ACME + // ############################ + // ### ACME Client Settings ### + // ############################ &cli.StringFlag{ Name: "acme-api-endpoint", EnvVars: []string{"ACME_API"}, diff --git a/integration/get_test.go b/integration/get_test.go index 6dd57bc..55e6d12 100644 --- a/integration/get_test.go +++ b/integration/get_test.go @@ -20,7 +20,9 @@ func TestGetRedirect(t *testing.T) { log.Println("=== TestGetRedirect ===") // test custom domain redirect resp, err := getTestHTTPSClient().Get("https://calciumdibromid.localhost.mock.directory:4430") - assert.NoError(t, err) + if !assert.NoError(t, err) { + t.FailNow() + } if !assert.EqualValues(t, http.StatusTemporaryRedirect, resp.StatusCode) { t.FailNow() } diff --git a/server/database/interface.go b/server/database/interface.go index 56537a4..a0edc91 100644 --- a/server/database/interface.go +++ b/server/database/interface.go @@ -54,10 +54,14 @@ func toCert(name string, c *certificate.Resource) (*Cert, error) { } validTill := tlsCertificates[0].NotAfter.Unix() - // TODO: do we need this or can we just go with domain name for wildcard cert - // default *.mock cert is prefixed with '.' - if name != c.Domain && name[1:] != c.Domain && name[0] != '.' { - return nil, fmt.Errorf("domain key and cert domain not equal") + // handle wildcard certs + if name[:1] == "." { + name = "*" + name + } + if name != c.Domain { + err := fmt.Errorf("domain key '%s' and cert domain '%s' not equal", name, c.Domain) + log.Error().Err(err).Msg("toCert conversion did discover mismatch") + // TODO: fail hard: return nil, err } return &Cert{ diff --git a/server/database/xorm.go b/server/database/xorm.go index 68ff18c..a14f887 100644 --- a/server/database/xorm.go +++ b/server/database/xorm.go @@ -3,7 +3,6 @@ package database import ( "errors" "fmt" - "strings" "github.com/rs/zerolog/log" @@ -52,18 +51,38 @@ func (x xDB) Close() error { func (x xDB) Put(domain string, cert *certificate.Resource) error { log.Trace().Str("domain", cert.Domain).Msg("inserting cert to db") + + domain = integrationTestReplacements(domain) c, err := toCert(domain, cert) if err != nil { return err } - _, err = x.engine.Insert(c) - return err + sess := x.engine.NewSession() + if err := sess.Begin(); err != nil { + return err + } + defer sess.Close() + + if exist, _ := sess.ID(c.Domain).Exist(); exist { + if _, err := sess.ID(c.Domain).Update(c); err != nil { + return err + } + } else { + if _, err = sess.Insert(c); err != nil { + return err + } + } + + return sess.Commit() } func (x xDB) Get(domain string) (*certificate.Resource, error) { - // TODO: do we need this or can we just go with domain name for wildcard cert - domain = strings.TrimPrefix(domain, ".") + // handle wildcard certs + if domain[:1] == "." { + domain = "*" + domain + } + domain = integrationTestReplacements(domain) cert := new(Cert) log.Trace().Str("domain", domain).Msg("get cert from db") @@ -76,6 +95,12 @@ func (x xDB) Get(domain string) (*certificate.Resource, error) { } func (x xDB) Delete(domain string) error { + // handle wildcard certs + if domain[:1] == "." { + domain = "*" + domain + } + domain = integrationTestReplacements(domain) + log.Trace().Str("domain", domain).Msg("delete cert from db") _, err := x.engine.ID(domain).Delete(new(Cert)) return err @@ -119,3 +144,13 @@ func supportedDriver(driver string) bool { return false } } + +// integrationTestReplacements is needed because integration tests use a single domain cert, +// while production use a wildcard cert +// TODO: find a better way to handle this +func integrationTestReplacements(domainKey string) string { + if domainKey == "*.localhost.mock.directory" { + return "localhost.mock.directory" + } + return domainKey +} diff --git a/server/database/xorm_test.go b/server/database/xorm_test.go new file mode 100644 index 0000000..9c032ee --- /dev/null +++ b/server/database/xorm_test.go @@ -0,0 +1,92 @@ +package database + +import ( + "errors" + "testing" + + "github.com/go-acme/lego/v4/certificate" + "github.com/stretchr/testify/assert" + "xorm.io/xorm" +) + +func newTestDB(t *testing.T) *xDB { + e, err := xorm.NewEngine("sqlite3", ":memory:") + assert.NoError(t, err) + assert.NoError(t, e.Sync2(new(Cert))) + return &xDB{engine: e} +} + +func TestSanitizeWildcardCerts(t *testing.T) { + certDB := newTestDB(t) + + _, err := certDB.Get(".not.found") + assert.True(t, errors.Is(err, ErrNotFound)) + + // TODO: cert key and domain mismatch are don not fail hard jet + // https://codeberg.org/Codeberg/pages-server/src/commit/d8595cee882e53d7f44f1ddc4ef8a1f7b8f31d8d/server/database/interface.go#L64 + // + // assert.Error(t, certDB.Put(".wildcard.de", &certificate.Resource{ + // Domain: "*.localhost.mock.directory", + // Certificate: localhost_mock_directory_certificate, + // })) + + // insert new wildcard cert + assert.NoError(t, certDB.Put(".wildcard.de", &certificate.Resource{ + Domain: "*.wildcard.de", + Certificate: localhost_mock_directory_certificate, + })) + + // update existing cert + assert.Error(t, certDB.Put(".wildcard.de", &certificate.Resource{ + Domain: "*.wildcard.de", + Certificate: localhost_mock_directory_certificate, + })) + + c1, err := certDB.Get(".wildcard.de") + assert.NoError(t, err) + c2, err := certDB.Get("*.wildcard.de") + assert.NoError(t, err) + assert.EqualValues(t, c1, c2) +} + +var localhost_mock_directory_certificate = []byte(`-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgIIJyBaXHmLk6gwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE +AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA0OWE0ZmIwHhcNMjMwMjEwMDEwOTA2 +WhcNMjgwMjEwMDEwOTA2WjAjMSEwHwYDVQQDExhsb2NhbGhvc3QubW9jay5kaXJl +Y3RvcnkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIU/CjzS7t62Gj +neEMqvP7sn99ULT7AEUzEfWL05fWG2z714qcUg1hXkZLgdVDgmsCpplyddip7+2t +ZH/9rLPLMqJphzvOL4CF6jDLbeifETtKyjnt9vUZFnnNWcP3tu8lo8iYSl08qsUI +Pp/hiEriAQzCDjTbR5m9xUPNPYqxzcS4ALzmmCX9Qfc4CuuhMkdv2G4TT7rylWrA +SCSRPnGjeA7pCByfNrO/uXbxmzl3sMO3k5sqgMkx1QIHEN412V8+vtx88mt2sM6k +xjzGZWWKXlRq+oufIKX9KPplhsCjMH6E3VNAzgOPYDqXagtUcGmLWghURltO8Mt2 +zwM6OgjjAgMBAAGjgaUwgaIwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG +AQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSMQvlJ1755 +sarf8i1KNqj7s5o/aDAfBgNVHSMEGDAWgBTcZcxJMhWdP7MecHCCpNkFURC/YzAj +BgNVHREEHDAaghhsb2NhbGhvc3QubW9jay5kaXJlY3RvcnkwDQYJKoZIhvcNAQEL +BQADggEBACcd7TT28OWwzQN2PcH0aG38JX5Wp2iOS/unDCfWjNAztXHW7nBDMxza +VtyebkJfccexpuVuOsjOX+bww0vtEYIvKX3/GbkhogksBrNkE0sJZtMnZWMR33wa +YxAy/kJBTmLi02r8fX9ZhwjldStHKBav4USuP7DXZjrgX7LFQhR4LIDrPaYqQRZ8 +ltC3mM9LDQ9rQyIFP5cSBMO3RUAm4I8JyLoOdb/9G2uxjHr7r6eG1g8DmLYSKBsQ +mWGQDOYgR3cGltDe2yMxM++yHY+b1uhxGOWMrDA1+1k7yI19LL8Ifi2FMovDfu/X +JxYk1NNNtdctwaYJFenmGQvDaIq1KgE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDUDCCAjigAwIBAgIIKBJ7IIA6W1swDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE +AxMVUGViYmxlIFJvb3QgQ0EgNTdmZjE2MCAXDTIzMDIwOTA1MzMxMloYDzIwNTMw +MjA5MDUzMzEyWjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRlIENBIDQ5 +YTRmYjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOvlqRx8SXQFWo2 +gFCiXxls53eENcyr8+meFyjgnS853eEvplaPxoa2MREKd+ZYxM8EMMfj2XGvR3UI +aqR5QyLQ9ihuRqvQo4fG91usBHgH+vDbGPdMX8gDmm9HgnmtOVhSKJU+M2jfE1SW +UuWB9xOa3LMreTXbTNfZEMoXf+GcWZMbx5WPgEga3DvfmV+RsfNvB55eD7YAyZgF +ZnQ3Dskmnxxlkz0EGgd7rqhFHHNB9jARlL22gITADwoWZidlr3ciM9DISymRKQ0c +mRN15fQjNWdtuREgJlpXecbYQMGhdTOmFrqdHkveD1o63rGSC4z+s/APV6xIbcRp +aNpO7L8CAwEAAaOBgzCBgDAOBgNVHQ8BAf8EBAMCAoQwHQYDVR0lBBYwFAYIKwYB +BQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNxlzEky +FZ0/sx5wcIKk2QVREL9jMB8GA1UdIwQYMBaAFOqfkm9rebIz4z0SDIKW5edLg5JM +MA0GCSqGSIb3DQEBCwUAA4IBAQBRG9AHEnyj2fKzVDDbQaKHjAF5jh0gwyHoIeRK +FkP9mQNSWxhvPWI0tK/E49LopzmVuzSbDd5kZsaii73rAs6f6Rf9W5veo3AFSEad +stM+Zv0f2vWB38nuvkoCRLXMX+QUeuL65rKxdEpyArBju4L3/PqAZRgMLcrH+ak8 +nvw5RdAq+Km/ZWyJgGikK6cfMmh91YALCDFnoWUWrCjkBaBFKrG59ONV9f0IQX07 +aNfFXFCF5l466xw9dHjw5iaFib10cpY3iq4kyPYIMs6uaewkCtxWKKjiozM4g4w3 +HqwyUyZ52WUJOJ/6G9DJLDtN3fgGR+IAp8BhYd5CqOscnt3h +-----END CERTIFICATE-----`) diff --git a/server/upstream/domains.go b/server/upstream/domains.go index bb4b57a..eb30394 100644 --- a/server/upstream/domains.go +++ b/server/upstream/domains.go @@ -49,7 +49,7 @@ func (o *Options) CheckCanonicalDomain(giteaClient *gitea.Client, actualDomain, } } - // Add [owner].[pages-domain] as valid domnain. + // Add [owner].[pages-domain] as valid domain. domains = append(domains, o.TargetOwner+mainDomainSuffix) if domains[len(domains)-1] == actualDomain { valid = true