Add more code
This commit is contained in:
parent
68b568c014
commit
da30ec9fa6
126
account.go
Normal file
126
account.go
Normal file
@ -0,0 +1,126 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/go-acme/lego/v4/acme"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
|
||||
type AcmeAccount struct {
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
Key crypto.PrivateKey
|
||||
}
|
||||
|
||||
func (a *AcmeAccount) GetEmail() string {
|
||||
return a.Email
|
||||
}
|
||||
|
||||
func (a *AcmeAccount) GetPrivateKey() crypto.PrivateKey {
|
||||
return a.Key
|
||||
}
|
||||
|
||||
func (a *AcmeAccount) GetRegistration() *registration.Resource {
|
||||
return a.Registration
|
||||
}
|
||||
|
||||
func (a *AcmeAccount) FlushToDisk(storage string) {
|
||||
data := map[string]interface{}{
|
||||
"email": a.Email,
|
||||
"reg": a.Registration,
|
||||
"private_key_encoded": base64.StdEncoding.EncodeToString(
|
||||
certcrypto.PEMEncode(a.Key),
|
||||
),
|
||||
}
|
||||
raw, _ := json.Marshal(data)
|
||||
ioutil.WriteFile(storage, raw, 0600)
|
||||
}
|
||||
|
||||
func ClientFromFile(storage, acmeServer string) (*lego.Client, error) {
|
||||
file, err := ioutil.ReadFile(storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var data map[string]interface{}
|
||||
_ = json.Unmarshal(file, &data)
|
||||
reg, _ := data["reg"].(map[string]interface{})
|
||||
accountRaw := reg["body"].(map[string]interface{})
|
||||
contact := accountRaw["contact"].([]interface{})
|
||||
contacts := make([]string, 0)
|
||||
for _, v := range contact {
|
||||
contacts = append(contacts, v.(string))
|
||||
}
|
||||
|
||||
|
||||
registration := registration.Resource{
|
||||
URI: reg["uri"].(string),
|
||||
Body: acme.Account{
|
||||
Status: accountRaw["status"].(string),
|
||||
Contact: contacts,
|
||||
TermsOfServiceAgreed: true,
|
||||
//Orders: accountRaw["orders"].(string),
|
||||
},
|
||||
}
|
||||
|
||||
pkEncoded, err := base64.StdEncoding.DecodeString(data["private_key_encoded"].(string))
|
||||
pk, err := certcrypto.ParsePEMPrivateKey(pkEncoded)
|
||||
account := AcmeAccount{
|
||||
Email: data["email"].(string),
|
||||
Key: pk.(*ecdsa.PrivateKey),
|
||||
Registration: ®istration,
|
||||
}
|
||||
config := lego.NewConfig(&account)
|
||||
config.CADirURL = acmeServer
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func GenerateNewAccount(email, storage, acmeServer string) (*lego.Client, error) {
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
account := AcmeAccount{
|
||||
Email: email,
|
||||
Key: privateKey,
|
||||
}
|
||||
config := lego.NewConfig(&account)
|
||||
config.CADirURL = acmeServer
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Register it
|
||||
req, err := client.Registration.Register(
|
||||
registration.RegisterOptions{
|
||||
TermsOfServiceAgreed: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account.Registration = req
|
||||
account.FlushToDisk(storage)
|
||||
|
||||
return client, nil
|
||||
}
|
343
acme.go
Normal file
343
acme.go
Normal file
@ -0,0 +1,343 @@
|
||||
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
|
||||
}
|
41
certificate.go
Normal file
41
certificate.go
Normal file
@ -0,0 +1,41 @@
|
||||
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
|
||||
}
|
39
dns.go
39
dns.go
@ -1,25 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/patrickmn/go-cache"
|
||||
)
|
||||
|
||||
const (
|
||||
TxtRepoKey = "repo="
|
||||
)
|
||||
|
||||
var (
|
||||
cnameCache = cache.New(1 * time.Hour, 1 * time.Hour)
|
||||
txtRepoCache = cache.New(1 * time.Hour, 1 * time.Hour)
|
||||
)
|
||||
|
||||
func lookupRepoTXT(domain string) (string, error) {
|
||||
repoLookup, found := txtRepoCache.Get(domain)
|
||||
if found {
|
||||
return repoLookup.(string), nil
|
||||
}
|
||||
|
||||
txts, err := net.LookupTXT("_rio-pages." + domain)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
repo := ""
|
||||
for _, txt := range txts {
|
||||
if !strings.HasPrefix(txt, TxtRepoKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
repo = strings.TrimPrefix(txt, TxtRepoKey)
|
||||
break
|
||||
}
|
||||
|
||||
txtRepoCache.Set(domain, repo, cache.DefaultExpiration)
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func lookupCNAME(domain string) (string, error) {
|
||||
cname, found := cnameCache.Get(domain)
|
||||
if found {
|
||||
if cname == "" {
|
||||
return "", errors.New("Previous request failure")
|
||||
}
|
||||
|
||||
return cname.(string), nil
|
||||
}
|
||||
|
||||
cname, err := net.LookupCNAME(domain)
|
||||
if err == nil {
|
||||
cnameCache.Set(domain, cname, cache.DefaultExpiration)
|
||||
return cname.(string), nil
|
||||
}
|
||||
|
||||
cnameCache.Set(domain, "", cache.DefaultExpiration)
|
||||
return "", err
|
||||
}
|
||||
|
12
go.mod
12
go.mod
@ -4,18 +4,26 @@ go 1.20
|
||||
|
||||
require (
|
||||
code.gitea.io/sdk/gitea v0.17.0
|
||||
github.com/go-acme/lego/v4 v4.14.2
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/urfave/cli/v2 v2.27.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/hashicorp/go-version v1.5.0 // indirect
|
||||
github.com/miekg/dns v1.1.55 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/crypto v0.10.0 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.11.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
)
|
||||
|
24
go.sum
24
go.sum
@ -1,15 +1,24 @@
|
||||
code.gitea.io/sdk/gitea v0.17.0 h1:8JPBss4+Jf7AE1YcfyiGrngTXE8dFSG3si/bypsTH34=
|
||||
code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
|
||||
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/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/davecgh/go-spew v1.1.0/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/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
||||
github.com/go-acme/lego/v4 v4.14.2 h1:/D/jqRgLi8Cbk33sLGtu2pX2jEg3bGJWHyV8kFuUHGM=
|
||||
github.com/go-acme/lego/v4 v4.14.2/go.mod h1:kBXxbeTg0x9AgaOYjPSwIeJy3Y33zTz+tMD16O4MO6c=
|
||||
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-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/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.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||
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/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@ -18,19 +27,27 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
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-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-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -39,10 +56,17 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7
|
||||
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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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/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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
106
main.go
106
main.go
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"os"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -10,13 +11,12 @@ import (
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
//PagesBranch "pages"
|
||||
// TODO: Change back for testing
|
||||
PagesBranch = "main"
|
||||
PagesBranch = "pages"
|
||||
)
|
||||
|
||||
func handleSubdomain(domain string, cname string, path string, giteaClient *gitea.Client, w http.ResponseWriter) {
|
||||
@ -50,6 +50,10 @@ func Handler(pagesDomain string, giteaClient *gitea.Client) http.HandlerFunc {
|
||||
w.Header().Set("Server", "rio")
|
||||
|
||||
if strings.HasSuffix(req.Host, pagesDomain){
|
||||
if handleLetsEncryptChallenge(w, req) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("Domain can be directly handled")
|
||||
handleSubdomain(req.Host, "", req.URL.Path, giteaClient, w)
|
||||
return
|
||||
@ -65,6 +69,11 @@ func Handler(pagesDomain string, giteaClient *gitea.Client) http.HandlerFunc {
|
||||
|
||||
if strings.HasSuffix(cname, pagesDomain) {
|
||||
log.Debugf("%s is alias of %s", req.Host, cname)
|
||||
if handleLetsEncryptChallenge(w, req) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("Domain can be handled after a CNAME query")
|
||||
handleSubdomain(cname, cname, req.URL.Path, giteaClient, w)
|
||||
return
|
||||
}
|
||||
@ -75,11 +84,56 @@ func Handler(pagesDomain string, giteaClient *gitea.Client) http.HandlerFunc {
|
||||
}
|
||||
|
||||
func runServer(ctx *cli.Context) error {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
giteaUrl := ctx.String("gitea-url")
|
||||
addr := ctx.String("listen-host") + ":" + ctx.String("listen-port")
|
||||
domain := ctx.String("pages-domain")
|
||||
certsFile := ctx.String("certs-file")
|
||||
acmeEmail := ctx.String("acme-email")
|
||||
acmeServer := ctx.String("acme-server")
|
||||
acmeFile := ctx.String("acme-file")
|
||||
acmeHost := ctx.String("acme-host")
|
||||
acmePort := ctx.String("acme-port")
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
err := LoadCertificateStoreFromFile(certsFile)
|
||||
if err != nil {
|
||||
log.Debugf("Generating cert")
|
||||
err := InitialiseFallbackCert(domain)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate fallback certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
FlushCertificateStoreToFile(certsFile)
|
||||
log.Debug("Certificate wrote to disk")
|
||||
} else {
|
||||
log.Debug("Certificate store read from disk")
|
||||
}
|
||||
|
||||
// Create an ACME client, if we failed to load one
|
||||
acmeClient, err := ClientFromFile(acmeFile, acmeServer)
|
||||
if err != nil {
|
||||
log.Warn("Failed to load ACME client data from disk. Generating new account")
|
||||
acmeClient, err = GenerateNewAccount(acmeEmail, acmeFile, acmeServer)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate new ACME client: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Info("ACME account registered")
|
||||
} else {
|
||||
log.Info("ACME client data read from disk")
|
||||
}
|
||||
|
||||
// Set up the HTTP01 listener
|
||||
err = acmeClient.Challenge.SetHTTP01Provider(
|
||||
http01.NewProviderServer(acmeHost, acmePort),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to setup HTTP01 challenge listener: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup the HTTPS stuff
|
||||
httpClient := http.Client{Timeout: 10 * time.Second}
|
||||
client, err := gitea.NewClient(
|
||||
giteaUrl,
|
||||
@ -93,6 +147,14 @@ func runServer(ctx *cli.Context) error {
|
||||
fmt.Errorf("Failed to create listener: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
tlsConfig := makeTlsConfig(
|
||||
domain,
|
||||
certsFile,
|
||||
acmeClient,
|
||||
)
|
||||
listener = tls.NewListener(listener, tlsConfig)
|
||||
|
||||
if err := http.Serve(listener, Handler(domain, client)); err != nil {
|
||||
fmt.Printf("Listening failed")
|
||||
return err
|
||||
@ -123,12 +185,48 @@ func main() {
|
||||
EnvVars: []string{"PORT"},
|
||||
Value: "8888",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "acme-host",
|
||||
Usage: "The host to bind to for ACME challenges",
|
||||
EnvVars: []string{"ACME_HOST"},
|
||||
Value: "",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "acme-port",
|
||||
Usage: "The port to listen on for ACME challenges",
|
||||
EnvVars: []string{"ACME_PORT"},
|
||||
Value: "8889",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "pages-domain",
|
||||
Usage: "The domain on which the server is reachable",
|
||||
EnvVars: []string{"PAGES_DOMAIN"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "certs-file",
|
||||
Usage: "File to store certificates in",
|
||||
EnvVars: []string{"CERTS_FILE"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "acme-file",
|
||||
Usage: "File to store ACME configuration in",
|
||||
EnvVars: []string{"ACME_FILE"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "acme-email",
|
||||
Usage: "Email to use for an ACME account",
|
||||
EnvVars: []string{"ACME_EMAIL"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "acme-server",
|
||||
Usage: "CA Directory to use",
|
||||
EnvVars: []string{"ACME_SERVER"},
|
||||
Value: "https://acme-staging-v02.api.letsencrypt.org/directory",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
18
repo.go
18
repo.go
@ -59,7 +59,6 @@ func lookupRepositoryAndCache(username, reponame, host, domain, path, cname stri
|
||||
}
|
||||
|
||||
// Cache data
|
||||
cnameCache.Set(host, domain, cache.DefaultExpiration)
|
||||
pathCache.Set(
|
||||
makePageCacheKey(domain, path),
|
||||
PageCacheEntry{
|
||||
@ -100,9 +99,24 @@ func RepoFromPath(username, host, cname, path string, giteaClient *gitea.Client)
|
||||
}
|
||||
}
|
||||
|
||||
// Allow naming the repository "example.org"
|
||||
// Allow specifying the repository name in the TXT record
|
||||
reponame := domain
|
||||
lookupDomain := domain
|
||||
if cname != "" {
|
||||
lookupDomain = cname
|
||||
}
|
||||
repoLookup, err := lookupRepoTXT(lookupDomain)
|
||||
if err != nil || repoLookup != "" {
|
||||
log.Infof(
|
||||
"TXT lookup for %s resulted in choosing repository %s",
|
||||
lookupDomain,
|
||||
repoLookup,
|
||||
)
|
||||
reponame = repoLookup
|
||||
}
|
||||
|
||||
// Allow naming the repository "example.org" (But give the TXT record preference)
|
||||
if cname != "" && repoLookup == "" && err == nil {
|
||||
reponame = cname;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user