Compare commits
6 Commits
1ad9ef6d93
...
731a3ce2cb
Author | SHA1 | Date | |
---|---|---|---|
731a3ce2cb | |||
e390ca6047 | |||
12aef17cc0 | |||
c4ef20f513 | |||
d0a24a60ed | |||
3af3f6bb7e |
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Artificats
|
||||||
|
rio
|
||||||
|
|
||||||
|
# Testing stuff
|
||||||
|
*.json
|
343
acme.go
343
acme.go
@ -1,343 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
AcmeChallengePathPrefix = "/.well-known/acme-challenge/"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Well-known -> Challenge solution
|
|
||||||
runningChallenges = make(map[string]string)
|
|
||||||
Certificates = CertificatesCache{
|
|
||||||
Certificates: make(map[string]CertificateWrapper),
|
|
||||||
}
|
|
||||||
|
|
||||||
// To access requestingDomains, first acquire the lock.
|
|
||||||
requestingLock = sync.Mutex{}
|
|
||||||
|
|
||||||
// Domain -> _. Check if domain is a key here to see if we're already requeting
|
|
||||||
// a certificate for it.
|
|
||||||
requestingDomains = make(map[string]bool)
|
|
||||||
)
|
|
||||||
|
|
||||||
func lockIfUnlockedDomain(domain string) bool {
|
|
||||||
requestingLock.Lock()
|
|
||||||
defer requestingLock.Unlock()
|
|
||||||
|
|
||||||
_, found := requestingDomains[domain]
|
|
||||||
if !found {
|
|
||||||
requestingDomains[domain] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func unlockDomain(domain string) {
|
|
||||||
requestingLock.Lock()
|
|
||||||
defer requestingLock.Unlock()
|
|
||||||
|
|
||||||
delete(requestingDomains, domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateWrapper struct {
|
|
||||||
TlsCertificate *tls.Certificate `json:"-"`
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
NotAfter time.Time `json:"not_after"`
|
|
||||||
PrivateKeyEncoded string `json:"private_key"`
|
|
||||||
Certificate []byte `json:"certificate"`
|
|
||||||
IssuerCertificate []byte `json:"issuer_certificate"`
|
|
||||||
CertificateUrl string `json:"certificate_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CertificateWrapper) GetPrivateKey() *rsa.PrivateKey {
|
|
||||||
data, _ := base64.StdEncoding.DecodeString(c.PrivateKeyEncoded)
|
|
||||||
pk, _ := certcrypto.ParsePEMPrivateKey(data)
|
|
||||||
|
|
||||||
return pk.(*rsa.PrivateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificatesCache struct {
|
|
||||||
FallbackCertificate *CertificateWrapper
|
|
||||||
Certificates map[string]CertificateWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificatesStore struct {
|
|
||||||
FallbackCertificate CertificateWrapper `json:"fallback"`
|
|
||||||
Certificates []CertificateWrapper `json:"certificates"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CertificatesCache) toStoreData() string {
|
|
||||||
certs := make([]CertificateWrapper, 0)
|
|
||||||
for _, cert := range c.Certificates {
|
|
||||||
certs = append(certs, cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := json.Marshal(CertificatesStore{
|
|
||||||
FallbackCertificate: *c.FallbackCertificate,
|
|
||||||
Certificates: certs,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to Marshal cache: %v", err)
|
|
||||||
}
|
|
||||||
return string(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CertificateWrapper) initTlsCertificate() {
|
|
||||||
pk, _ := base64.StdEncoding.DecodeString(c.PrivateKeyEncoded)
|
|
||||||
tlsCert, _ := tls.X509KeyPair(
|
|
||||||
c.Certificate,
|
|
||||||
pk,
|
|
||||||
)
|
|
||||||
c.TlsCertificate = &tlsCert
|
|
||||||
}
|
|
||||||
|
|
||||||
func CertificateFromStoreData(rawJson string) CertificatesCache {
|
|
||||||
var store CertificatesStore
|
|
||||||
_ = json.Unmarshal([]byte(rawJson), &store)
|
|
||||||
|
|
||||||
store.FallbackCertificate.initTlsCertificate()
|
|
||||||
cache := CertificatesCache{
|
|
||||||
FallbackCertificate: &store.FallbackCertificate,
|
|
||||||
}
|
|
||||||
|
|
||||||
certs := make(map[string]CertificateWrapper)
|
|
||||||
for _, cert := range store.Certificates {
|
|
||||||
cert.initTlsCertificate()
|
|
||||||
certs[cert.Domain] = cert
|
|
||||||
}
|
|
||||||
cache.Certificates = certs
|
|
||||||
|
|
||||||
return cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadCertificateStoreFromFile(path string) error {
|
|
||||||
content, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
Certificates = CertificateFromStoreData(string(content))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FlushCertificateStoreToFile(path string) {
|
|
||||||
data := Certificates.toStoreData()
|
|
||||||
ioutil.WriteFile(path, []byte(data), 0600)
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitialiseFallbackCert(pagesDomain string) error {
|
|
||||||
cert, err := fallbackCert(pagesDomain)
|
|
||||||
Certificates.FallbackCertificate = cert
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func fallbackCert(pagesDomain string) (*CertificateWrapper, error) {
|
|
||||||
key, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
notAfter := time.Now().Add(time.Hour * 24 * 7)
|
|
||||||
cert := x509.Certificate{
|
|
||||||
SerialNumber: big.NewInt(1),
|
|
||||||
Subject: pkix.Name{
|
|
||||||
CommonName: pagesDomain,
|
|
||||||
Organization: []string{"Pages Server"},
|
|
||||||
},
|
|
||||||
NotAfter: notAfter,
|
|
||||||
NotBefore: time.Now(),
|
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
}
|
|
||||||
certBytes, err := x509.CreateCertificate(
|
|
||||||
rand.Reader,
|
|
||||||
&cert,
|
|
||||||
&cert,
|
|
||||||
&key.(*rsa.PrivateKey).PublicKey,
|
|
||||||
key,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
err = pem.Encode(out, &pem.Block{
|
|
||||||
Bytes: certBytes,
|
|
||||||
Type: "CERTIFICATE",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
outBytes := out.Bytes()
|
|
||||||
res := &certificate.Resource{
|
|
||||||
PrivateKey: certcrypto.PEMEncode(key),
|
|
||||||
Certificate: outBytes,
|
|
||||||
IssuerCertificate: outBytes,
|
|
||||||
Domain: pagesDomain,
|
|
||||||
}
|
|
||||||
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &CertificateWrapper{
|
|
||||||
TlsCertificate: &tlsCertificate,
|
|
||||||
Domain: pagesDomain,
|
|
||||||
NotAfter: notAfter,
|
|
||||||
PrivateKeyEncoded: base64.StdEncoding.EncodeToString(certcrypto.PEMEncode(key)),
|
|
||||||
Certificate: outBytes,
|
|
||||||
IssuerCertificate: outBytes,
|
|
||||||
CertificateUrl: "localhost",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isCertStillValid(cert CertificateWrapper) bool {
|
|
||||||
return time.Now().Compare(cert.NotAfter) <= -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTlsConfig(pagesDomain, path string, acmeClient *lego.Client) *tls.Config {
|
|
||||||
return &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
// Validate that we should even care about this domain
|
|
||||||
if !strings.HasSuffix(info.ServerName, pagesDomain) {
|
|
||||||
// Note: We do not check err here because err != nil
|
|
||||||
// always implies that cname == "", which does not have
|
|
||||||
// pagesDomain as a suffix.
|
|
||||||
cname, _ := lookupCNAME(info.ServerName)
|
|
||||||
if !strings.HasSuffix(cname, pagesDomain) {
|
|
||||||
log.Warnf("Got ServerName for Domain %s that we're not responsible for", info.ServerName)
|
|
||||||
return Certificates.FallbackCertificate.TlsCertificate, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we want to access <user>.<pages domain>, then we can just
|
|
||||||
// use a wildcard certificate.
|
|
||||||
domain := info.ServerName
|
|
||||||
/*if strings.HasSuffix(info.ServerName, pagesDomain) {
|
|
||||||
domain = "*." + pagesDomain
|
|
||||||
}*/
|
|
||||||
|
|
||||||
cert, found := Certificates.Certificates[info.ServerName]
|
|
||||||
if found {
|
|
||||||
if isCertStillValid(cert) {
|
|
||||||
return cert.TlsCertificate, nil
|
|
||||||
} else {
|
|
||||||
// If we're already working on the domain,
|
|
||||||
// return the old certificate
|
|
||||||
if lockIfUnlockedDomain(domain) {
|
|
||||||
return cert.TlsCertificate, nil
|
|
||||||
}
|
|
||||||
defer unlockDomain(domain)
|
|
||||||
|
|
||||||
// TODO: Renew
|
|
||||||
log.Debugf("Certificate for %s expired, renewing", domain)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Don't request if we're already requesting.
|
|
||||||
if lockIfUnlockedDomain(domain) {
|
|
||||||
return Certificates.FallbackCertificate.TlsCertificate, nil
|
|
||||||
}
|
|
||||||
defer unlockDomain(domain)
|
|
||||||
|
|
||||||
// Request new certificate
|
|
||||||
log.Debugf("Obtaining new certificate for %s...", domain)
|
|
||||||
err := ObtainNewCertificate(
|
|
||||||
[]string{domain},
|
|
||||||
path,
|
|
||||||
acmeClient,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf(
|
|
||||||
"Failed to get certificate for %s: %v",
|
|
||||||
domain,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
return Certificates.FallbackCertificate.TlsCertificate, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, _ = Certificates.Certificates[domain]
|
|
||||||
return cert.TlsCertificate, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("TLS ServerName: %s", info.ServerName)
|
|
||||||
return Certificates.FallbackCertificate.TlsCertificate, nil
|
|
||||||
},
|
|
||||||
NextProtos: []string{
|
|
||||||
"http/0.9",
|
|
||||||
"http/1.0",
|
|
||||||
"http/1.1",
|
|
||||||
"h2",
|
|
||||||
"h2c",
|
|
||||||
},
|
|
||||||
MinVersion: tls.VersionTLS12,
|
|
||||||
CipherSuites: []uint16{
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
|
||||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addChallenge(id, token string) {
|
|
||||||
runningChallenges[id] = token
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeChallenge(id string) {
|
|
||||||
delete(runningChallenges, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getChallenge(id string) string {
|
|
||||||
if value, found := runningChallenges[id]; found {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLetsEncryptChallenge(w http.ResponseWriter, req *http.Request) bool {
|
|
||||||
if !strings.HasPrefix(req.URL.Path, AcmeChallengePathPrefix) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Handling ACME challenge path")
|
|
||||||
id := strings.TrimPrefix(req.URL.Path, AcmeChallengePathPrefix)
|
|
||||||
challenge := getChallenge(id)
|
|
||||||
if id == "" {
|
|
||||||
w.WriteHeader(404)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(200)
|
|
||||||
w.Write([]byte(challenge))
|
|
||||||
|
|
||||||
removeChallenge(id)
|
|
||||||
return true
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
// "github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ObtainNewCertificate(domains []string, path string, acmeClient *lego.Client) error {
|
|
||||||
req := certificate.ObtainRequest{
|
|
||||||
Domains: domains,
|
|
||||||
Bundle: true,
|
|
||||||
}
|
|
||||||
cert, err := acmeClient.Certificate.Obtain(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsCert, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper := CertificateWrapper{
|
|
||||||
TlsCertificate: &tlsCert,
|
|
||||||
Domain: cert.Domain,
|
|
||||||
//NotAfter: tlsCert.Leaf.NotAfter,
|
|
||||||
NotAfter: time.Now().Add(time.Hour * 24 * 60),
|
|
||||||
PrivateKeyEncoded: base64.StdEncoding.EncodeToString(cert.PrivateKey),
|
|
||||||
Certificate: cert.Certificate,
|
|
||||||
IssuerCertificate: cert.IssuerCertificate,
|
|
||||||
CertificateUrl: cert.CertURL,
|
|
||||||
}
|
|
||||||
Certificates.Certificates[cert.Domain] = wrapper
|
|
||||||
FlushCertificateStoreToFile(path)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -10,16 +10,19 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.polynom.me/rio/internal/acme"
|
||||||
|
"git.polynom.me/rio/internal/certificates"
|
||||||
|
"git.polynom.me/rio/internal/dns"
|
||||||
|
"git.polynom.me/rio/internal/pages"
|
||||||
|
"git.polynom.me/rio/internal/repo"
|
||||||
|
"git.polynom.me/rio/internal/server"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
PagesBranch = "pages"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handleSubdomain(domain string, cname string, path, giteaUrl string, giteaClient *gitea.Client, w http.ResponseWriter) {
|
func handleSubdomain(domain string, cname string, path, giteaUrl string, giteaClient *gitea.Client, w http.ResponseWriter) {
|
||||||
hostParts := strings.Split(domain, ".")
|
hostParts := strings.Split(domain, ".")
|
||||||
username := hostParts[0]
|
username := hostParts[0]
|
||||||
@ -29,7 +32,7 @@ func handleSubdomain(domain string, cname string, path, giteaUrl string, giteaCl
|
|||||||
path = path[1:]
|
path = path[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, path, err := RepoFromPath(
|
repo, path, err := repo.RepoFromPath(
|
||||||
username,
|
username,
|
||||||
domain,
|
domain,
|
||||||
cname,
|
cname,
|
||||||
@ -42,7 +45,7 @@ func handleSubdomain(domain string, cname string, path, giteaUrl string, giteaCl
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
serveFile(username, repo.Name, path, giteaUrl, w)
|
pages.ServeFile(username, repo.Name, path, giteaUrl, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Handler(pagesDomain, giteaUrl string, giteaClient *gitea.Client) http.HandlerFunc {
|
func Handler(pagesDomain, giteaUrl string, giteaClient *gitea.Client) http.HandlerFunc {
|
||||||
@ -50,16 +53,12 @@ func Handler(pagesDomain, giteaUrl string, giteaClient *gitea.Client) http.Handl
|
|||||||
w.Header().Set("Server", "rio")
|
w.Header().Set("Server", "rio")
|
||||||
|
|
||||||
if strings.HasSuffix(req.Host, pagesDomain) {
|
if strings.HasSuffix(req.Host, pagesDomain) {
|
||||||
if handleLetsEncryptChallenge(w, req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Domain can be directly handled")
|
log.Debug("Domain can be directly handled")
|
||||||
handleSubdomain(req.Host, "", req.URL.Path, giteaUrl, giteaClient, w)
|
handleSubdomain(req.Host, "", req.URL.Path, giteaUrl, giteaClient, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cname, err := lookupCNAME(req.Host)
|
cname, err := dns.LookupCNAME(req.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("CNAME request failed, we don't handle %s", req.Host)
|
log.Warningf("CNAME request failed, we don't handle %s", req.Host)
|
||||||
w.WriteHeader(400)
|
w.WriteHeader(400)
|
||||||
@ -68,12 +67,7 @@ func Handler(pagesDomain, giteaUrl string, giteaClient *gitea.Client) http.Handl
|
|||||||
log.Debugf("Got CNAME %s", cname)
|
log.Debugf("Got CNAME %s", cname)
|
||||||
|
|
||||||
if strings.HasSuffix(cname, pagesDomain) {
|
if strings.HasSuffix(cname, pagesDomain) {
|
||||||
log.Debugf("%s is alias of %s", req.Host, cname)
|
log.Debugf("%s is alias of %s and can be handled after a CNAME query", req.Host, cname)
|
||||||
if handleLetsEncryptChallenge(w, req) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Domain can be handled after a CNAME query")
|
|
||||||
handleSubdomain(cname, cname, req.URL.Path, giteaUrl, giteaClient, w)
|
handleSubdomain(cname, cname, req.URL.Path, giteaUrl, giteaClient, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -84,7 +78,6 @@ func Handler(pagesDomain, giteaUrl string, giteaClient *gitea.Client) http.Handl
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runServer(ctx *cli.Context) error {
|
func runServer(ctx *cli.Context) error {
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
giteaUrl := ctx.String("gitea-url")
|
giteaUrl := ctx.String("gitea-url")
|
||||||
domain := ctx.String("pages-domain")
|
domain := ctx.String("pages-domain")
|
||||||
certsFile := ctx.String("certs-file")
|
certsFile := ctx.String("certs-file")
|
||||||
@ -95,6 +88,13 @@ func runServer(ctx *cli.Context) error {
|
|||||||
acmePort := ctx.String("acme-port")
|
acmePort := ctx.String("acme-port")
|
||||||
acmeDisable := ctx.Bool("acme-disable")
|
acmeDisable := ctx.Bool("acme-disable")
|
||||||
|
|
||||||
|
// Init Logging
|
||||||
|
if ctx.Bool("debug") {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
} else {
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
// 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(
|
client, err := gitea.NewClient(
|
||||||
@ -117,26 +117,28 @@ func runServer(ctx *cli.Context) error {
|
|||||||
return errors.New("The options acme-file, acme-email, and certs-file are required")
|
return errors.New("The options acme-file, acme-email, and certs-file are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := LoadCertificateStoreFromFile(certsFile)
|
cache, err := certificates.CertificateCacheFromFile(certsFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Generating cert")
|
log.Debugf("Generating cert")
|
||||||
err := InitialiseFallbackCert(domain)
|
fallback, err := certificates.MakeFallbackCertificate(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to generate fallback certificate: %v", err)
|
log.Fatalf("Failed to generate fallback certificate: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
FlushCertificateStoreToFile(certsFile)
|
cache.FallbackCertificate = fallback
|
||||||
|
cache.Certificates = make(map[string]certificates.CertificateWrapper)
|
||||||
|
cache.FlushToDisk(certsFile)
|
||||||
log.Debug("Certificate wrote to disk")
|
log.Debug("Certificate wrote to disk")
|
||||||
} else {
|
} else {
|
||||||
log.Debug("Certificate store read from disk")
|
log.Debug("Certificate store read from disk")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an ACME client, if we failed to load one
|
// Create an ACME client, if we failed to load one
|
||||||
acmeClient, err := ClientFromFile(acmeFile, acmeServer)
|
acmeClient, err := acme.ClientFromFile(acmeFile, acmeServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Failed to load ACME client data from disk. Generating new account")
|
log.Warn("Failed to load ACME client data from disk. Generating new account")
|
||||||
acmeClient, err = GenerateNewAccount(acmeEmail, acmeFile, acmeServer)
|
acmeClient, err = acme.GenerateNewAccount(acmeEmail, acmeFile, acmeServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to generate new ACME client: %v", err)
|
log.Fatalf("Failed to generate new ACME client: %v", err)
|
||||||
return err
|
return err
|
||||||
@ -155,9 +157,10 @@ func runServer(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := makeTlsConfig(
|
tlsConfig := server.MakeTlsConfig(
|
||||||
domain,
|
domain,
|
||||||
certsFile,
|
certsFile,
|
||||||
|
&cache,
|
||||||
acmeClient,
|
acmeClient,
|
||||||
)
|
)
|
||||||
listener = tls.NewListener(listener, tlsConfig)
|
listener = tls.NewListener(listener, tlsConfig)
|
||||||
@ -240,6 +243,11 @@ func main() {
|
|||||||
Usage: "Whether to disable automatic ACME certificates",
|
Usage: "Whether to disable automatic ACME certificates",
|
||||||
EnvVars: []string{"ACME_DISABLE"},
|
EnvVars: []string{"ACME_DISABLE"},
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "debug",
|
||||||
|
Usage: "Whether to enable debug logging",
|
||||||
|
EnvVars: []string{"DEBUG_ENABLE"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
14
go.mod
14
go.mod
@ -1,9 +1,9 @@
|
|||||||
module paptutuwawa/rio
|
module git.polynom.me/rio
|
||||||
|
|
||||||
go 1.20
|
go 1.21.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/sdk/gitea v0.17.0
|
code.gitea.io/sdk/gitea v0.17.1
|
||||||
github.com/go-acme/lego/v4 v4.14.2
|
github.com/go-acme/lego/v4 v4.14.2
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
@ -16,14 +16,14 @@ require (
|
|||||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||||
github.com/hashicorp/go-version v1.5.0 // indirect
|
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||||
github.com/miekg/dns v1.1.55 // indirect
|
github.com/miekg/dns v1.1.55 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
golang.org/x/crypto v0.10.0 // indirect
|
golang.org/x/crypto v0.17.0 // indirect
|
||||||
golang.org/x/mod v0.11.0 // indirect
|
golang.org/x/mod v0.11.0 // indirect
|
||||||
golang.org/x/net v0.11.0 // indirect
|
golang.org/x/net v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.9.0 // indirect
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
golang.org/x/text v0.10.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.10.0 // indirect
|
golang.org/x/tools v0.10.0 // indirect
|
||||||
)
|
)
|
||||||
|
63
go.sum
63
go.sum
@ -1,10 +1,11 @@
|
|||||||
code.gitea.io/sdk/gitea v0.17.0 h1:8JPBss4+Jf7AE1YcfyiGrngTXE8dFSG3si/bypsTH34=
|
code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
|
||||||
code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
|
code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
||||||
@ -14,13 +15,15 @@ github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
|||||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||||
|
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E=
|
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||||
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
@ -29,44 +32,70 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||||
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||||
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
136
internal/certificates/certificate.go
Normal file
136
internal/certificates/certificate.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package certificates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RenewCertificate(old *CertificateWrapper, acmeClient *lego.Client) (CertificateWrapper, error) {
|
||||||
|
pk, _ := base64.StdEncoding.DecodeString(old.PrivateKeyEncoded)
|
||||||
|
res := certificate.Resource{
|
||||||
|
PrivateKey: pk,
|
||||||
|
Certificate: old.Certificate,
|
||||||
|
CSR: old.CSR,
|
||||||
|
}
|
||||||
|
|
||||||
|
new, err := acmeClient.Certificate.Renew(res, true, false, "")
|
||||||
|
if err != nil {
|
||||||
|
return CertificateWrapper{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the new certificate into a wrapper struct
|
||||||
|
tlsCert, err := tls.X509KeyPair(new.Certificate, new.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return CertificateWrapper{}, err
|
||||||
|
}
|
||||||
|
wrapper := CertificateWrapper{
|
||||||
|
TlsCertificate: &tlsCert,
|
||||||
|
Domain: old.Domain,
|
||||||
|
NotAfter: time.Now().Add(time.Hour * 24 * 60),
|
||||||
|
PrivateKeyEncoded: base64.StdEncoding.EncodeToString(new.PrivateKey),
|
||||||
|
Certificate: new.Certificate,
|
||||||
|
CSR: new.CSR,
|
||||||
|
}
|
||||||
|
return wrapper, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ObtainNewCertificate(domains []string, acmeClient *lego.Client) (CertificateWrapper, error) {
|
||||||
|
req := certificate.ObtainRequest{
|
||||||
|
Domains: domains,
|
||||||
|
Bundle: true,
|
||||||
|
}
|
||||||
|
cert, err := acmeClient.Certificate.Obtain(req)
|
||||||
|
if err != nil {
|
||||||
|
return CertificateWrapper{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsCert, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return CertificateWrapper{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper := CertificateWrapper{
|
||||||
|
TlsCertificate: &tlsCert,
|
||||||
|
Domain: cert.Domain,
|
||||||
|
//NotAfter: tlsCert.Leaf.NotAfter,
|
||||||
|
NotAfter: time.Now().Add(time.Hour * 24 * 60),
|
||||||
|
PrivateKeyEncoded: base64.StdEncoding.EncodeToString(cert.PrivateKey),
|
||||||
|
Certificate: cert.Certificate,
|
||||||
|
CSR: cert.CSR,
|
||||||
|
}
|
||||||
|
return wrapper, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a fallback certificate for the domain.
|
||||||
|
func MakeFallbackCertificate(pagesDomain string) (*CertificateWrapper, error) {
|
||||||
|
key, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
notAfter := time.Now().Add(time.Hour * 24 * 7)
|
||||||
|
cert := x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: pagesDomain,
|
||||||
|
Organization: []string{"Pages Server"},
|
||||||
|
},
|
||||||
|
NotAfter: notAfter,
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
certBytes, err := x509.CreateCertificate(
|
||||||
|
rand.Reader,
|
||||||
|
&cert,
|
||||||
|
&cert,
|
||||||
|
&key.(*rsa.PrivateKey).PublicKey,
|
||||||
|
key,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &bytes.Buffer{}
|
||||||
|
err = pem.Encode(out, &pem.Block{
|
||||||
|
Bytes: certBytes,
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outBytes := out.Bytes()
|
||||||
|
res := &certificate.Resource{
|
||||||
|
PrivateKey: certcrypto.PEMEncode(key),
|
||||||
|
Certificate: outBytes,
|
||||||
|
IssuerCertificate: outBytes,
|
||||||
|
Domain: pagesDomain,
|
||||||
|
}
|
||||||
|
tlsCertificate, err := tls.X509KeyPair(res.Certificate, res.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &CertificateWrapper{
|
||||||
|
TlsCertificate: &tlsCertificate,
|
||||||
|
Domain: pagesDomain,
|
||||||
|
NotAfter: notAfter,
|
||||||
|
PrivateKeyEncoded: base64.StdEncoding.EncodeToString(certcrypto.PEMEncode(key)),
|
||||||
|
Certificate: outBytes,
|
||||||
|
CSR: []byte{},
|
||||||
|
}, nil
|
||||||
|
}
|
113
internal/certificates/store.go
Normal file
113
internal/certificates/store.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package certificates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A convenience wrapper around a TLS certificate
|
||||||
|
type CertificateWrapper struct {
|
||||||
|
TlsCertificate *tls.Certificate `json:"-"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
NotAfter time.Time `json:"not_after"`
|
||||||
|
PrivateKeyEncoded string `json:"private_key"`
|
||||||
|
Certificate []byte `json:"certificate"`
|
||||||
|
CSR []byte `json:"csr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// A structure to store all the certificates we know of in.
|
||||||
|
type CertificatesCache struct {
|
||||||
|
// The certificate to use as a fallback if all else fails.
|
||||||
|
FallbackCertificate *CertificateWrapper
|
||||||
|
|
||||||
|
// Mapping of domain name to certificate.
|
||||||
|
Certificates map[string]CertificateWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal type to let encoding JSON handle the bulk of the work.
|
||||||
|
type certificatesStore struct {
|
||||||
|
FallbackCertificate CertificateWrapper `json:"fallback"`
|
||||||
|
Certificates []CertificateWrapper `json:"certificates"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decodes the private key of the certificate wrapper.
|
||||||
|
func (c *CertificateWrapper) GetPrivateKey() *rsa.PrivateKey {
|
||||||
|
data, _ := base64.StdEncoding.DecodeString(c.PrivateKeyEncoded)
|
||||||
|
pk, _ := certcrypto.ParsePEMPrivateKey(data)
|
||||||
|
|
||||||
|
return pk.(*rsa.PrivateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the certificate's TlsCertificate field.
|
||||||
|
func (c *CertificateWrapper) initTlsCertificate() {
|
||||||
|
pk, _ := base64.StdEncoding.DecodeString(c.PrivateKeyEncoded)
|
||||||
|
tlsCert, _ := tls.X509KeyPair(
|
||||||
|
c.Certificate,
|
||||||
|
pk,
|
||||||
|
)
|
||||||
|
c.TlsCertificate = &tlsCert
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the certificate is still valid now.
|
||||||
|
func (c *CertificateWrapper) IsValid() bool {
|
||||||
|
return time.Now().Compare(c.NotAfter) <= -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serializes the certificate cache to a JSON string for writing to a file.
|
||||||
|
func (c *CertificatesCache) toStoreData() []byte {
|
||||||
|
certs := make([]CertificateWrapper, 0)
|
||||||
|
for _, cert := range c.Certificates {
|
||||||
|
certs = append(certs, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := json.Marshal(certificatesStore{
|
||||||
|
FallbackCertificate: *c.FallbackCertificate,
|
||||||
|
Certificates: certs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to Marshal cache: %v", err)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Saves the cache to disk.
|
||||||
|
func (c *CertificatesCache) FlushToDisk(path string) {
|
||||||
|
ioutil.WriteFile(path, c.toStoreData(), 0600)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CertificatesCache) AddCert(cert CertificateWrapper, path string) {
|
||||||
|
c.Certificates[cert.Domain] = cert
|
||||||
|
c.FlushToDisk(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the certificate cache from the file.
|
||||||
|
func CertificateCacheFromFile(path string) (CertificatesCache, error) {
|
||||||
|
content, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return CertificatesCache{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var store certificatesStore
|
||||||
|
_ = json.Unmarshal(content, &store)
|
||||||
|
|
||||||
|
store.FallbackCertificate.initTlsCertificate()
|
||||||
|
cache := CertificatesCache{
|
||||||
|
FallbackCertificate: &store.FallbackCertificate,
|
||||||
|
}
|
||||||
|
|
||||||
|
certs := make(map[string]CertificateWrapper)
|
||||||
|
for _, cert := range store.Certificates {
|
||||||
|
cert.initTlsCertificate()
|
||||||
|
certs[cert.Domain] = cert
|
||||||
|
}
|
||||||
|
cache.Certificates = certs
|
||||||
|
|
||||||
|
return cache, nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -10,15 +10,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// TXT record name that lookupRepoTXT will try to lookup.
|
||||||
|
TxtRepoRecord = "_rio-pages."
|
||||||
|
|
||||||
|
// The key that the TXT record will have to start with, e.g.
|
||||||
|
// "repo=some-random-repo".
|
||||||
TxtRepoKey = "repo="
|
TxtRepoKey = "repo="
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cnameCache = cache.New(1*time.Hour, 1*time.Hour)
|
// Cache for CNAME resolution results.
|
||||||
|
cnameCache = cache.New(1*time.Hour, 1*time.Hour)
|
||||||
|
|
||||||
|
// Cache for TXT resolution results.
|
||||||
txtRepoCache = cache.New(1*time.Hour, 1*time.Hour)
|
txtRepoCache = cache.New(1*time.Hour, 1*time.Hour)
|
||||||
)
|
)
|
||||||
|
|
||||||
func lookupRepoTXT(domain string) (string, error) {
|
// Query the domain for the a repository redirect.
|
||||||
|
// Returns the new repository name or "", if we could not
|
||||||
|
// resolve a repository redirect.
|
||||||
|
func LookupRepoTXT(domain string) (string, error) {
|
||||||
repoLookup, found := txtRepoCache.Get(domain)
|
repoLookup, found := txtRepoCache.Get(domain)
|
||||||
if found {
|
if found {
|
||||||
return repoLookup.(string), nil
|
return repoLookup.(string), nil
|
||||||
@ -43,7 +54,9 @@ func lookupRepoTXT(domain string) (string, error) {
|
|||||||
return repo, nil
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupCNAME(domain string) (string, error) {
|
// Query the domain for a CNAME record. Returns the resolved
|
||||||
|
// CNAME or "", if no CNAME could be queried.
|
||||||
|
func LookupCNAME(domain string) (string, error) {
|
||||||
cname, found := cnameCache.Get(domain)
|
cname, found := cnameCache.Get(domain)
|
||||||
if found {
|
if found {
|
||||||
if cname == "" {
|
if cname == "" {
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package pages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -12,6 +12,11 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The branch name on which files must reside.
|
||||||
|
PagesBranch = "pages"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
pageCache = cache.New(6*time.Hour, 1*time.Hour)
|
pageCache = cache.New(6*time.Hour, 1*time.Hour)
|
||||||
)
|
)
|
||||||
@ -26,7 +31,7 @@ func makePageContentCacheEntry(username, path string) string {
|
|||||||
return username + ":" + path
|
return username + ":" + path
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveFile(username, reponame, path, giteaUrl string, w http.ResponseWriter) {
|
func ServeFile(username, reponame, path, giteaUrl string, w http.ResponseWriter) {
|
||||||
// Provide a default
|
// Provide a default
|
||||||
if path == "" {
|
if path == "" {
|
||||||
path = "/index.html"
|
path = "/index.html"
|
||||||
@ -90,6 +95,12 @@ func serveFile(username, reponame, path, giteaUrl string, w http.ResponseWriter)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Correctly propagate 404s.
|
||||||
|
if resp.StatusCode == 404 {
|
||||||
|
w.WriteHeader(404)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
content, err = io.ReadAll(resp.Body)
|
content, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to get file %s/%s/%s (%s)", username, reponame, path, err)
|
log.Errorf("Failed to get file %s/%s/%s (%s)", username, reponame, path, err)
|
||||||
@ -113,7 +124,7 @@ func serveFile(username, reponame, path, giteaUrl string, w http.ResponseWriter)
|
|||||||
)
|
)
|
||||||
|
|
||||||
log.Debugf("Page %s requested from Gitea and cached in memory at %v", path, now)
|
log.Debugf("Page %s requested from Gitea and cached in memory at %v", path, now)
|
||||||
w.WriteHeader(200)
|
|
||||||
w.Header().Set("Content-Type", mimeType)
|
w.Header().Set("Content-Type", mimeType)
|
||||||
|
w.WriteHeader(200)
|
||||||
w.Write(content)
|
w.Write(content)
|
||||||
}
|
}
|
@ -1,10 +1,13 @@
|
|||||||
package main
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.polynom.me/rio/internal/dns"
|
||||||
|
"git.polynom.me/rio/internal/pages"
|
||||||
|
|
||||||
"code.gitea.io/sdk/gitea"
|
"code.gitea.io/sdk/gitea"
|
||||||
"github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -39,7 +42,7 @@ func lookupRepositoryAndCache(username, reponame, host, domain, path, cname stri
|
|||||||
file, _, err := giteaClient.GetFile(
|
file, _, err := giteaClient.GetFile(
|
||||||
username,
|
username,
|
||||||
repo.Name,
|
repo.Name,
|
||||||
PagesBranch,
|
pages.PagesBranch,
|
||||||
"CNAME",
|
"CNAME",
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
@ -104,7 +107,7 @@ func RepoFromPath(username, host, cname, path string, giteaClient *gitea.Client)
|
|||||||
if cname != "" {
|
if cname != "" {
|
||||||
lookupDomain = cname
|
lookupDomain = cname
|
||||||
}
|
}
|
||||||
repoLookup, err := lookupRepoTXT(lookupDomain)
|
repoLookup, err := dns.LookupRepoTXT(lookupDomain)
|
||||||
if err != nil && repoLookup != "" {
|
if err != nil && repoLookup != "" {
|
||||||
log.Infof(
|
log.Infof(
|
||||||
"TXT lookup for %s resulted in choosing repository %s",
|
"TXT lookup for %s resulted in choosing repository %s",
|
139
internal/server/tls.go
Normal file
139
internal/server/tls.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.polynom.me/rio/internal/certificates"
|
||||||
|
"git.polynom.me/rio/internal/dns"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// To access requestingDomains, first acquire the lock.
|
||||||
|
domainsLock = sync.Mutex{}
|
||||||
|
|
||||||
|
// Domain -> _. Check if domain is a key here to see if we're already requesting
|
||||||
|
// or renewing a certificate for that domain.
|
||||||
|
workingDomains = make(map[string]bool)
|
||||||
|
)
|
||||||
|
|
||||||
|
func lockIfUnlockedDomain(domain string) bool {
|
||||||
|
domainsLock.Lock()
|
||||||
|
defer domainsLock.Unlock()
|
||||||
|
|
||||||
|
_, found := workingDomains[domain]
|
||||||
|
if !found {
|
||||||
|
workingDomains[domain] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockDomain(domain string) {
|
||||||
|
domainsLock.Lock()
|
||||||
|
defer domainsLock.Unlock()
|
||||||
|
|
||||||
|
delete(workingDomains, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeTlsConfig(pagesDomain, cachePath string, cache *certificates.CertificatesCache, acmeClient *lego.Client) *tls.Config {
|
||||||
|
return &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
// Validate that we should even care about this domain
|
||||||
|
if !strings.HasSuffix(info.ServerName, pagesDomain) {
|
||||||
|
// Note: We do not check err here because err != nil
|
||||||
|
// always implies that cname == "", which does not have
|
||||||
|
// pagesDomain as a suffix.
|
||||||
|
cname, _ := dns.LookupCNAME(info.ServerName)
|
||||||
|
if !strings.HasSuffix(cname, pagesDomain) {
|
||||||
|
log.Warnf("Got ServerName for Domain %s that we're not responsible for", info.ServerName)
|
||||||
|
return cache.FallbackCertificate.TlsCertificate, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we want to access <user>.<pages domain>, then we can just
|
||||||
|
// use a wildcard certificate.
|
||||||
|
domain := info.ServerName
|
||||||
|
/*if strings.HasSuffix(info.ServerName, pagesDomain) {
|
||||||
|
domain = "*." + pagesDomain
|
||||||
|
}*/
|
||||||
|
|
||||||
|
cert, found := cache.Certificates[info.ServerName]
|
||||||
|
if found {
|
||||||
|
if cert.IsValid() {
|
||||||
|
return cert.TlsCertificate, nil
|
||||||
|
} else {
|
||||||
|
// If we're already working on the domain,
|
||||||
|
// return the old certificate
|
||||||
|
if lockIfUnlockedDomain(domain) {
|
||||||
|
return cert.TlsCertificate, nil
|
||||||
|
}
|
||||||
|
defer unlockDomain(domain)
|
||||||
|
|
||||||
|
// Renew
|
||||||
|
log.Infof("Certificate for %s expired, renewing", domain)
|
||||||
|
newCert, err := certificates.RenewCertificate(&cert, acmeClient)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to renew certificate for %s: %v", domain, err)
|
||||||
|
return cert.TlsCertificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Successfully renewed certificate!")
|
||||||
|
cache.AddCert(newCert, cachePath)
|
||||||
|
return newCert.TlsCertificate, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Don't request if we're already requesting.
|
||||||
|
if lockIfUnlockedDomain(domain) {
|
||||||
|
return cache.FallbackCertificate.TlsCertificate, nil
|
||||||
|
}
|
||||||
|
defer unlockDomain(domain)
|
||||||
|
|
||||||
|
// Request new certificate
|
||||||
|
log.Infof("Obtaining new certificate for %s...", domain)
|
||||||
|
cert, err := certificates.ObtainNewCertificate(
|
||||||
|
[]string{domain},
|
||||||
|
acmeClient,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(
|
||||||
|
"Failed to get certificate for %s: %v",
|
||||||
|
domain,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
return cache.FallbackCertificate.TlsCertificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to cache and flush
|
||||||
|
log.Info("Successfully obtained new certificate!")
|
||||||
|
cache.AddCert(cert, cachePath)
|
||||||
|
return cert.TlsCertificate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("TLS ServerName: %s", info.ServerName)
|
||||||
|
return cache.FallbackCertificate.TlsCertificate, nil
|
||||||
|
},
|
||||||
|
NextProtos: []string{
|
||||||
|
"http/0.9",
|
||||||
|
"http/1.0",
|
||||||
|
"http/1.1",
|
||||||
|
"h2",
|
||||||
|
"h2c",
|
||||||
|
},
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
CipherSuites: []uint16{
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user