Compare commits
3 Commits
5181aed0b8
...
b2761a3a89
Author | SHA1 | Date | |
---|---|---|---|
b2761a3a89 | |||
ebdd56013d | |||
3012878c94 |
7
.woodpecker.yml
Normal file
7
.woodpecker.yml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
steps:
|
||||||
|
build: golang/1.21.5-alpine
|
||||||
|
commands:
|
||||||
|
- go get
|
||||||
|
- go build ./cmd/rio
|
||||||
|
- go fmt $(go list ./... | grep -v /vendor/)
|
||||||
|
- go vet $(go list ./... | grep -v /vendor/)
|
@ -97,7 +97,7 @@ func runServer(ctx *cli.Context) error {
|
|||||||
|
|
||||||
// Setup the Gitea stuff
|
// Setup the Gitea stuff
|
||||||
httpClient := http.Client{Timeout: 10 * time.Second}
|
httpClient := http.Client{Timeout: 10 * time.Second}
|
||||||
client, err := gitea.NewClient(
|
giteaClient, err := gitea.NewClient(
|
||||||
giteaUrl,
|
giteaUrl,
|
||||||
gitea.SetHTTPClient(&httpClient),
|
gitea.SetHTTPClient(&httpClient),
|
||||||
gitea.SetToken(""),
|
gitea.SetToken(""),
|
||||||
@ -108,7 +108,8 @@ func runServer(ctx *cli.Context) error {
|
|||||||
addr := ctx.String("listen-host") + ":" + ctx.String("listen-port")
|
addr := ctx.String("listen-host") + ":" + ctx.String("listen-port")
|
||||||
listener, err := net.Listen("tcp", addr)
|
listener, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Errorf("Failed to create listener: %v", err)
|
errMsg := fmt.Errorf("Failed to create listener: %v", err)
|
||||||
|
fmt.Println(errMsg.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,11 +163,12 @@ func runServer(ctx *cli.Context) error {
|
|||||||
certsFile,
|
certsFile,
|
||||||
&cache,
|
&cache,
|
||||||
acmeClient,
|
acmeClient,
|
||||||
|
giteaClient,
|
||||||
)
|
)
|
||||||
listener = tls.NewListener(listener, tlsConfig)
|
listener = tls.NewListener(listener, tlsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := http.Serve(listener, Handler(domain, giteaUrl, client)); err != nil {
|
if err := http.Serve(listener, Handler(domain, giteaUrl, giteaClient)); err != nil {
|
||||||
fmt.Printf("Listening failed")
|
fmt.Printf("Listening failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
pathCache = cache.New(1*time.Hour, 1*time.Hour)
|
pathCache = cache.New(1*time.Hour, 1*time.Hour)
|
||||||
|
|
||||||
|
// Caching the existence of an user
|
||||||
|
userCache = cache.New(24*time.Hour, 12*time.Hour)
|
||||||
)
|
)
|
||||||
|
|
||||||
type PageCacheEntry struct {
|
type PageCacheEntry struct {
|
||||||
@ -132,3 +135,19 @@ func RepoFromPath(username, host, cname, path string, giteaClient *gitea.Client)
|
|||||||
)
|
)
|
||||||
return repo, path, err
|
return repo, path, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if the username exists as an organisation or an user on the Gitea
|
||||||
|
// instance, so that an attacker can't just request certificates for random
|
||||||
|
// usernames.
|
||||||
|
func CanRequestCertificate(username string, giteaClient *gitea.Client) bool {
|
||||||
|
if _, found := userCache.Get(username); found {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
user, _, err := giteaClient.GetUserInfo(username)
|
||||||
|
if user != nil && err == nil {
|
||||||
|
userCache.Set(username, true, cache.DefaultExpiration)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
39
internal/repo/repo_test.go
Normal file
39
internal/repo/repo_test.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
giteaClient, _ = gitea.NewClient(
|
||||||
|
"https://git.polynom.me",
|
||||||
|
gitea.SetHTTPClient(&http.Client{Timeout: 10 * time.Second}),
|
||||||
|
gitea.SetToken(""),
|
||||||
|
gitea.SetUserAgent("rio/testing"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCanRequestCertificatePositiveUser(t *testing.T) {
|
||||||
|
res := CanRequestCertificate("papatutuwawa", giteaClient)
|
||||||
|
if !res {
|
||||||
|
t.Fatalf("User papatutuwawa should be servable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCanRequestCertificatePositiveOrganisation(t *testing.T) {
|
||||||
|
res := CanRequestCertificate("moxxy", giteaClient)
|
||||||
|
if !res {
|
||||||
|
t.Fatalf("Organisation moxxy should be servable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCanRequestCertificateNegative(t *testing.T) {
|
||||||
|
res := CanRequestCertificate("user-who-does-not-exist", giteaClient)
|
||||||
|
if res {
|
||||||
|
t.Fatalf("User user-who-does-not-exist should not be servable")
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,9 @@ import (
|
|||||||
|
|
||||||
"git.polynom.me/rio/internal/certificates"
|
"git.polynom.me/rio/internal/certificates"
|
||||||
"git.polynom.me/rio/internal/dns"
|
"git.polynom.me/rio/internal/dns"
|
||||||
|
"git.polynom.me/rio/internal/repo"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -41,16 +43,16 @@ func unlockDomain(domain string) {
|
|||||||
delete(workingDomains, domain)
|
delete(workingDomains, domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.CertificatesCache, acmeClient *lego.Client) *tls.Config {
|
func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.CertificatesCache, acmeClient *lego.Client, giteaClient *gitea.Client) *tls.Config {
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
InsecureSkipVerify: true,
|
|
||||||
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
// Validate that we should even care about this domain
|
// Validate that we should even care about this domain
|
||||||
|
cname := ""
|
||||||
if !strings.HasSuffix(info.ServerName, pagesDomain) {
|
if !strings.HasSuffix(info.ServerName, pagesDomain) {
|
||||||
// Note: We do not check err here because err != nil
|
// Note: We do not check err here because err != nil
|
||||||
// always implies that cname == "", which does not have
|
// always implies that cname == "", which does not have
|
||||||
// pagesDomain as a suffix.
|
// pagesDomain as a suffix.
|
||||||
cname, _ := dns.LookupCNAME(info.ServerName)
|
cname, _ = dns.LookupCNAME(info.ServerName)
|
||||||
if !strings.HasSuffix(cname, pagesDomain) {
|
if !strings.HasSuffix(cname, pagesDomain) {
|
||||||
log.Warnf("Got ServerName for Domain %s that we're not responsible for", info.ServerName)
|
log.Warnf("Got ServerName for Domain %s that we're not responsible for", info.ServerName)
|
||||||
return cache.FallbackCertificate.TlsCertificate, nil
|
return cache.FallbackCertificate.TlsCertificate, nil
|
||||||
@ -64,11 +66,31 @@ func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.Certificat
|
|||||||
domain = "*." + pagesDomain
|
domain = "*." + pagesDomain
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
// Figure out a username for later username checks
|
||||||
|
username := ""
|
||||||
|
if cname == "" {
|
||||||
|
// domain ends on pagesDomain
|
||||||
|
username = strings.Split(domain, ".")[0]
|
||||||
|
} else {
|
||||||
|
// cname ends on pagesDomain
|
||||||
|
username = strings.Split(cname, ".")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the correct certificate
|
||||||
cert, found := cache.Certificates[info.ServerName]
|
cert, found := cache.Certificates[info.ServerName]
|
||||||
if found {
|
if found {
|
||||||
if cert.IsValid() {
|
if cert.IsValid() {
|
||||||
return cert.TlsCertificate, nil
|
return cert.TlsCertificate, nil
|
||||||
} else {
|
} else {
|
||||||
|
if !repo.CanRequestCertificate(username, giteaClient) {
|
||||||
|
log.Warnf(
|
||||||
|
"Cannot renew certificate for %s because CanRequestCertificate(%s) returned false",
|
||||||
|
domain,
|
||||||
|
username,
|
||||||
|
)
|
||||||
|
return cert.TlsCertificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
// If we're already working on the domain,
|
// If we're already working on the domain,
|
||||||
// return the old certificate
|
// return the old certificate
|
||||||
if lockIfUnlockedDomain(domain) {
|
if lockIfUnlockedDomain(domain) {
|
||||||
@ -76,7 +98,7 @@ func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.Certificat
|
|||||||
}
|
}
|
||||||
defer unlockDomain(domain)
|
defer unlockDomain(domain)
|
||||||
|
|
||||||
// Renew
|
// Renew the certificate
|
||||||
log.Infof("Certificate for %s expired, renewing", domain)
|
log.Infof("Certificate for %s expired, renewing", domain)
|
||||||
newCert, err := certificates.RenewCertificate(&cert, acmeClient)
|
newCert, err := certificates.RenewCertificate(&cert, acmeClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -89,6 +111,15 @@ func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.Certificat
|
|||||||
return newCert.TlsCertificate, nil
|
return newCert.TlsCertificate, nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if !repo.CanRequestCertificate(username, giteaClient) {
|
||||||
|
log.Warnf(
|
||||||
|
"Cannot request certificate for %s because CanRequestCertificate(%s) returned false",
|
||||||
|
domain,
|
||||||
|
username,
|
||||||
|
)
|
||||||
|
return cache.FallbackCertificate.TlsCertificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Don't request if we're already requesting.
|
// Don't request if we're already requesting.
|
||||||
if lockIfUnlockedDomain(domain) {
|
if lockIfUnlockedDomain(domain) {
|
||||||
return cache.FallbackCertificate.TlsCertificate, nil
|
return cache.FallbackCertificate.TlsCertificate, nil
|
||||||
@ -115,9 +146,6 @@ func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.Certificat
|
|||||||
cache.AddCert(cert, cachePath)
|
cache.AddCert(cert, cachePath)
|
||||||
return cert.TlsCertificate, nil
|
return cert.TlsCertificate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("TLS ServerName: %s", info.ServerName)
|
|
||||||
return cache.FallbackCertificate.TlsCertificate, nil
|
|
||||||
},
|
},
|
||||||
NextProtos: []string{
|
NextProtos: []string{
|
||||||
"http/0.9",
|
"http/0.9",
|
||||||
|
Loading…
Reference in New Issue
Block a user