mirror of
https://github.com/1f349/orchid.git
synced 2024-12-21 23:54:12 +00:00
Port previous service code
This commit is contained in:
parent
2f83af0522
commit
2805b72094
12
.idea/dataSources.xml
Normal file
12
.idea/dataSources.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="identifier.sqlite" uuid="c23861d9-b93b-4410-a97e-72d0f511a821">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:identifier.sqlite</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
7
.idea/sqldialects.xml
Normal file
7
.idea/sqldialects.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/renewal/find-next-cert.sql" dialect="GenericSQL" />
|
||||
<file url="PROJECT" dialect="SQLite" />
|
||||
</component>
|
||||
</project>
|
11
go.mod
11
go.mod
@ -9,9 +9,20 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/miekg/dns v1.1.50 // indirect
|
||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
61
go.sum
61
go.sum
@ -1,18 +1,79 @@
|
||||
github.com/MrMelon54/mjwt v0.1.0 h1:x1wBrh9l2CowRekHecxcZaH2zy9Hvqwlp4ppmW1P1OA=
|
||||
github.com/MrMelon54/mjwt v0.1.0/go.mod h1:oYrDBWK09Hju98xb+bRQ0wy+RuAzacxYvKYOZchR2Tk=
|
||||
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
|
||||
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
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/go-acme/lego/v4 v4.12.3 h1:aWPYhBopAZXWBASPgvi1LnWGrr5YiXOsrpVaFaVJipo=
|
||||
github.com/go-acme/lego/v4 v4.12.3/go.mod h1:UZoOlhVmUYP/N0z4tEbfUjoCNHRZNObzqWZtT76DIsc=
|
||||
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/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/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/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.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.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
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.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -1,4 +1,4 @@
|
||||
package renewal
|
||||
package http_acme
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -1,4 +1,4 @@
|
||||
package renewal
|
||||
package http_acme
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
27
pebble-dev/debug.go
Normal file
27
pebble-dev/debug.go
Normal file
@ -0,0 +1,27 @@
|
||||
//go:build DEBUG
|
||||
|
||||
package pebble_dev
|
||||
|
||||
func GetPebbleCert() []byte {
|
||||
return []byte(`
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDCTCCAfGgAwIBAgIIJOLbes8sTr4wDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
|
||||
AxMVbWluaWNhIHJvb3QgY2EgMjRlMmRiMCAXDTE3MTIwNjE5NDIxMFoYDzIxMTcx
|
||||
MjA2MTk0MjEwWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAyNGUyZGIwggEi
|
||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5WgZNoVJandj43kkLyU50vzCZ
|
||||
alozvdRo3OFiKoDtmqKPNWRNO2hC9AUNxTDJco51Yc42u/WV3fPbbhSznTiOOVtn
|
||||
Ajm6iq4I5nZYltGGZetGDOQWr78y2gWY+SG078MuOO2hyDIiKtVc3xiXYA+8Hluu
|
||||
9F8KbqSS1h55yxZ9b87eKR+B0zu2ahzBCIHKmKWgc6N13l7aDxxY3D6uq8gtJRU0
|
||||
toumyLbdzGcupVvjbjDP11nl07RESDWBLG1/g3ktJvqIa4BWgU2HMh4rND6y8OD3
|
||||
Hy3H8MY6CElL+MOCbFJjWqhtOxeFyZZV9q3kYnk9CAuQJKMEGuN4GU6tzhW1AgMB
|
||||
AAGjRTBDMA4GA1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAF85v
|
||||
d40HK1ouDAtWeO1PbnWfGEmC5Xa478s9ddOd9Clvp2McYzNlAFfM7kdcj6xeiNhF
|
||||
WPIfaGAi/QdURSL/6C1KsVDqlFBlTs9zYfh2g0UXGvJtj1maeih7zxFLvet+fqll
|
||||
xseM4P9EVJaQxwuK/F78YBt0tCNfivC6JNZMgxKF59h0FBpH70ytUSHXdz7FKwix
|
||||
Mfn3qEb9BXSk0Q3prNV5sOV3vgjEtB4THfDxSz9z3+DepVnW3vbbqwEbkXdk3j82
|
||||
2muVldgOUgTwK8eT+XdofVdntzU/kzygSAtAQwLJfn51fS1GvEcYGBc1bDryIqmF
|
||||
p9BI7gVKtWSZYegicA==
|
||||
-----END CERTIFICATE-----
|
||||
`)
|
||||
}
|
10
pebble-dev/normal.go
Normal file
10
pebble-dev/normal.go
Normal file
@ -0,0 +1,10 @@
|
||||
//go:build !DEBUG
|
||||
|
||||
package pebble_dev
|
||||
|
||||
import "log"
|
||||
|
||||
func GetPebbleCert() []byte {
|
||||
log.Fatalln("[Renewal] Pebble is selected as the certificate source but this binary was not compiled in debug mode")
|
||||
return nil
|
||||
}
|
16
renewal/account.go
Normal file
16
renewal/account.go
Normal file
@ -0,0 +1,16 @@
|
||||
package renewal
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
email string
|
||||
reg *registration.Resource
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
func (a *Account) GetEmail() string { return a.email }
|
||||
func (a *Account) GetRegistration() *registration.Resource { return a.reg }
|
||||
func (a *Account) GetPrivateKey() crypto.PrivateKey { return a.key }
|
10
renewal/config.go
Normal file
10
renewal/config.go
Normal file
@ -0,0 +1,10 @@
|
||||
package renewal
|
||||
|
||||
type LetsEncryptConfig struct {
|
||||
Account struct {
|
||||
Email string `yaml:"email"`
|
||||
PrivateKey string `yaml:"privateKey"`
|
||||
} `yaml:"account"`
|
||||
Directory string `yaml:"directory"`
|
||||
Certificate string `yaml:"certificate"`
|
||||
}
|
29
renewal/create-tables.sql
Normal file
29
renewal/create-tables.sql
Normal file
@ -0,0 +1,29 @@
|
||||
CREATE TABLE IF NOT EXISTS certificates
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
owner INTEGER,
|
||||
dns INTEGER,
|
||||
auto_renew INTEGER DEFAULT 0,
|
||||
active INTEGER DEFAULT 0,
|
||||
renewing INTEGER DEFAULT 0,
|
||||
renew_failed INTEGER DEFAULT 0,
|
||||
not_after DATETIME,
|
||||
updated_at DATETIME,
|
||||
FOREIGN KEY (dns) REFERENCES dns (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS certificate_domains
|
||||
(
|
||||
domain_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
cert_id INTEGER,
|
||||
domain VARCHAR,
|
||||
FOREIGN KEY (cert_id) REFERENCES certificates (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dns
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
type VARCHAR,
|
||||
email VARCHAR,
|
||||
token VARCHAR
|
||||
);
|
11
renewal/find-next-cert.sql
Normal file
11
renewal/find-next-cert.sql
Normal file
@ -0,0 +1,11 @@
|
||||
select cert.id, certdata.data_id, certdata.not_after, dns.type, dns.token
|
||||
from certificates as cert
|
||||
left outer join certificate_data as certdata on cert.id = certdata.meta_id
|
||||
left outer join dns on cert.dns = dns.id
|
||||
where cert.active = 1
|
||||
and cert.auto_renew = 1
|
||||
and cert.renewing = 0
|
||||
and cert.renew_failed = 0
|
||||
and (certdata.ready IS NULL or certdata.ready = 1)
|
||||
and (certdata.not_after IS NULL or DATETIME(certdata.not_after, 'utc', '-30 days') < DATETIME())
|
||||
order by certdata.not_after DESC NULLS FIRST
|
17
renewal/local.go
Normal file
17
renewal/local.go
Normal file
@ -0,0 +1,17 @@
|
||||
package renewal
|
||||
|
||||
import "time"
|
||||
|
||||
// Contains local types for the renewal service
|
||||
type localCertData struct {
|
||||
id uint64
|
||||
dns struct {
|
||||
name string
|
||||
token string
|
||||
}
|
||||
cert struct {
|
||||
current uint64
|
||||
notAfter time.Time
|
||||
}
|
||||
domains []string
|
||||
}
|
@ -1 +0,0 @@
|
||||
package renewal
|
345
renewal/service.go
Normal file
345
renewal/service.go
Normal file
@ -0,0 +1,345 @@
|
||||
package renewal
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/MrMelon54/orchid/http-acme"
|
||||
"github.com/MrMelon54/orchid/pebble-dev"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/providers/dns/namesilo"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedDNSProvider = errors.New("unsupported DNS provider")
|
||||
//go:embed find-next-cert.sql
|
||||
findNextCertSql string
|
||||
//go:embed create-tables.sql
|
||||
createTableCertificates string
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
db *sql.DB
|
||||
httpAcme *http_acme.HttpAcmeProvider
|
||||
certTicker *time.Ticker
|
||||
certDone chan struct{}
|
||||
caAddr string
|
||||
caCert []byte
|
||||
transport *http.Transport
|
||||
renewLock *sync.Mutex
|
||||
leAccount *Account
|
||||
certDir string
|
||||
keyDir string
|
||||
|
||||
//notify
|
||||
}
|
||||
|
||||
func NewRenewalService(wg *sync.WaitGroup, db *sql.DB, httpAcme *http_acme.HttpAcmeProvider, leConfig LetsEncryptConfig) (*Service, error) {
|
||||
r := &Service{
|
||||
db: db,
|
||||
httpAcme: httpAcme,
|
||||
certTicker: time.NewTicker(time.Minute * 10),
|
||||
certDone: make(chan struct{}),
|
||||
renewLock: &sync.Mutex{},
|
||||
leAccount: &Account{
|
||||
email: leConfig.Account.Email,
|
||||
key: leConfig.Account.PrivateKey,
|
||||
},
|
||||
}
|
||||
|
||||
// init domains table
|
||||
_, err := r.db.Exec(createTableCertificates)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create certificates table: %w", err)
|
||||
}
|
||||
|
||||
// resolve CA information
|
||||
r.resolveCADirectory(leConfig)
|
||||
err = r.resolveCACertificate(leConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve CA certificate: %w", err)
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go r.renewalRoutine(wg)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (s *Service) Shutdown() {
|
||||
log.Println("[Renewal] Shutting down certificate renewal service")
|
||||
close(s.certDone)
|
||||
}
|
||||
|
||||
func (s *Service) resolveCADirectory(conf LetsEncryptConfig) {
|
||||
switch conf.Directory {
|
||||
case "production", "prod":
|
||||
s.caAddr = lego.LEDirectoryProduction
|
||||
case "staging":
|
||||
s.caAddr = lego.LEDirectoryStaging
|
||||
default:
|
||||
s.caAddr = conf.Directory
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) resolveCACertificate(conf LetsEncryptConfig) error {
|
||||
switch conf.Certificate {
|
||||
case "default":
|
||||
// no nothing
|
||||
case "pebble":
|
||||
s.caCert = pebble_dev.GetPebbleCert()
|
||||
default:
|
||||
caGet, err := http.Get(conf.Certificate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download CA certificate: %w", err)
|
||||
}
|
||||
s.caCert, err = io.ReadAll(caGet.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read CA certificate: %w", err)
|
||||
}
|
||||
}
|
||||
if s.caCert != nil {
|
||||
caPool := x509.NewCertPool()
|
||||
if !caPool.AppendCertsFromPEM(s.caCert) {
|
||||
return fmt.Errorf("failed to add certificate to CA cert pool")
|
||||
}
|
||||
t := http.DefaultTransport.(*http.Transport).Clone()
|
||||
t.TLSClientConfig = &tls.Config{RootCAs: caPool}
|
||||
s.transport = t
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrAlreadyRenewing = errors.New("already renewing")
|
||||
|
||||
func (s *Service) renewalRoutine(wg *sync.WaitGroup) {
|
||||
defer func() {
|
||||
s.certTicker.Stop()
|
||||
log.Println("[Renewal] Stopped certificate renewal service")
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
log.Println("[Renewal] Doing quick certificate check before starting...")
|
||||
err := s.renewalCheck()
|
||||
if err != nil {
|
||||
log.Println("[Renewal] Certificate check, should not error first try: ", err)
|
||||
return
|
||||
}
|
||||
log.Println("[Renewal] Initial check complete, continually checking every 4 hours...")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.certDone:
|
||||
return
|
||||
case <-s.certTicker.C:
|
||||
go func() {
|
||||
err := s.renewalCheck()
|
||||
if err != nil && err != ErrAlreadyRenewing {
|
||||
log.Println("[Renewal] Certificate check, an error occurred: ", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) renewalCheck() error {
|
||||
if !s.renewLock.TryLock() {
|
||||
return ErrAlreadyRenewing
|
||||
}
|
||||
defer s.renewLock.Unlock()
|
||||
|
||||
localData, err := s.findNextCertificateToRenew()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find a certificate to renew: %w", err)
|
||||
}
|
||||
|
||||
// no certificates to update
|
||||
if localData == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.renewCert(localData)
|
||||
}
|
||||
|
||||
func (s *Service) findNextCertificateToRenew() (*localCertData, error) {
|
||||
d := &localCertData{}
|
||||
|
||||
row := s.db.QueryRow(findNextCertSql)
|
||||
err := row.Scan(&d.id, &d.cert.current, &d.cert.notAfter, &d.dns.name, &d.dns.token)
|
||||
switch err {
|
||||
case nil:
|
||||
// no nothing
|
||||
break
|
||||
case io.EOF:
|
||||
// no certificate to update
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("failed to scan table row: %w", err)
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (s *Service) fetchDomains(localData *localCertData) ([]string, error) {
|
||||
query, err := s.db.Query(`SELECT domain FROM certificate_domains WHERE cert_id = ?`, localData.id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch domains for certificate: %d: %w", localData.id, err)
|
||||
}
|
||||
|
||||
domains := make([]string, 0)
|
||||
for query.Next() {
|
||||
var domain string
|
||||
err := query.Scan(&domain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan row from domains table: %d: %w", localData.id, err)
|
||||
}
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("no domains registered for certificate: %d", localData.id)
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
func (s *Service) setupLegoClient(localData *localCertData) (*lego.Client, error) {
|
||||
config := lego.NewConfig(s.leAccount)
|
||||
config.CADirURL = s.caAddr
|
||||
if s.transport != nil {
|
||||
config.HTTPClient.Transport = s.transport
|
||||
}
|
||||
dnsProv, err := s.getDnsProvider(localData.dns.name, localData.dns.token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve dns provider: %w", err)
|
||||
}
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate client: %w", err)
|
||||
}
|
||||
|
||||
// set providers - always returns nil so ignore the error
|
||||
_ = client.Challenge.SetHTTP01Provider(s.httpAcme)
|
||||
_ = client.Challenge.SetDNS01Provider(dnsProv)
|
||||
|
||||
register, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update account registration: %w", err)
|
||||
}
|
||||
|
||||
s.leAccount.reg = register
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (s *Service) getDnsProvider(name, token string) (challenge.Provider, error) {
|
||||
switch name {
|
||||
case "namesilo":
|
||||
return namesilo.NewDNSProviderConfig(&namesilo.Config{APIKey: token})
|
||||
default:
|
||||
return nil, ErrUnsupportedDNSProvider
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) getPrivateKey(id uint64) (*rsa.PrivateKey, error) {
|
||||
privKeyBytes, err := os.ReadFile(filepath.Join(s.keyDir, fmt.Sprintf("%d.key.pem", id)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x509.ParsePKCS1PrivateKey(privKeyBytes)
|
||||
}
|
||||
|
||||
func (s *Service) renewCert(localData *localCertData) {
|
||||
s.setRenewing(localData.id, true, false)
|
||||
|
||||
cert, certBytes, err := s.renewCertInternal(localData)
|
||||
if err != nil {
|
||||
log.Printf("[Renewal Failed to renew cert %d: %s\n", localData.id, err)
|
||||
s.setRenewing(localData.id, false, true)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.db.Exec(`UPDATE certificates SET renewing = 0, renew_failed = 0, not_after = ?, updated_at = ? WHERE id = ?`, cert.NotAfter, cert.NotBefore, localData.id)
|
||||
if err != nil {
|
||||
log.Printf("[Renewal] Failed to update certificate %d in database: %s\n", localData.id, err)
|
||||
return
|
||||
}
|
||||
|
||||
oldPath := filepath.Join(s.certDir, fmt.Sprintf("%d-old.cert.pem", localData.id))
|
||||
newPath := filepath.Join(s.certDir, fmt.Sprintf("%d.cert.pem", localData.id))
|
||||
|
||||
err = os.Rename(newPath, oldPath)
|
||||
if err != nil {
|
||||
log.Printf("[Renewal] Failed to rename certificate file '%s' => '%s': %s\n", newPath, oldPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
openCertFile, err := os.Create(newPath)
|
||||
if err != nil {
|
||||
log.Printf("[Renewal] Failed to create certificate file '%s': %s\n", newPath, err)
|
||||
return
|
||||
}
|
||||
defer openCertFile.Close()
|
||||
|
||||
_, err = openCertFile.Write(certBytes)
|
||||
if err != nil {
|
||||
log.Printf("[Renewal] Failed to write certificate file '%s': %s\n", newPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[Renewal] Updated certificate %d successfully\n", localData.id)
|
||||
}
|
||||
|
||||
func (s *Service) renewCertInternal(localData *localCertData) (*x509.Certificate, []byte, error) {
|
||||
// read private key file
|
||||
privKey, err := s.getPrivateKey(localData.id)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to open private key: %w", err)
|
||||
}
|
||||
|
||||
// fetch domains for this certificate
|
||||
domains, err := s.fetchDomains(localData)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to update cert: %w", err)
|
||||
}
|
||||
|
||||
// setup client for requesting a new certificate
|
||||
client, err := s.setupLegoClient(localData)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate a client: %w", err)
|
||||
}
|
||||
|
||||
obtain, err := client.Certificate.Obtain(certificate.ObtainRequest{
|
||||
Domains: domains,
|
||||
PrivateKey: privKey,
|
||||
Bundle: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to obtain replacement certificate: %w", err)
|
||||
}
|
||||
|
||||
parseCert, err := x509.ParseCertificate(obtain.Certificate)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse new certificate: %w", err)
|
||||
}
|
||||
|
||||
return parseCert, obtain.Certificate, nil
|
||||
}
|
||||
|
||||
func (s *Service) setRenewing(id uint64, renewing, failed bool) {
|
||||
_, err := s.db.Exec("UPDATE certificates SET renewing = ?, renew_failed = ? WHERE id = ?", renewing, failed, id)
|
||||
if err != nil {
|
||||
log.Printf("[Renewal] Failed to set renewing/failed mode in database %d: %s\n", id, err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user