mirror of
https://github.com/1f349/orchid.git
synced 2025-01-10 09:16:25 +00:00
Partial work
This commit is contained in:
parent
8b9b503741
commit
58e426a3f3
8
cmd/orchid/conf.go
Normal file
8
cmd/orchid/conf.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type startUpConfig struct {
|
||||||
|
Database string `json:"db"`
|
||||||
|
PrivKey string `json:"priv_key"`
|
||||||
|
PubKey string `json:"pub_key"`
|
||||||
|
Listen string `json:"listen"`
|
||||||
|
}
|
@ -1,4 +1,20 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"github.com/google/subcommands"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
subcommands.Register(subcommands.HelpCommand(), "")
|
||||||
|
subcommands.Register(subcommands.FlagsCommand(), "")
|
||||||
|
subcommands.Register(subcommands.CommandsCommand(), "")
|
||||||
|
subcommands.Register(&serveCmd{}, "")
|
||||||
|
subcommands.Register(&setupCmd{}, "")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
ctx := context.Background()
|
||||||
|
os.Exit(int(subcommands.Execute(ctx)))
|
||||||
}
|
}
|
||||||
|
63
cmd/orchid/serve.go
Normal file
63
cmd/orchid/serve.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"github.com/MrMelon54/mjwt"
|
||||||
|
"github.com/google/subcommands"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serveCmd struct{ configPath string }
|
||||||
|
|
||||||
|
func (s *serveCmd) Name() string { return "serve" }
|
||||||
|
func (s *serveCmd) Synopsis() string { return "Serve certificate renewal service" }
|
||||||
|
func (s *serveCmd) SetFlags(f *flag.FlagSet) {
|
||||||
|
f.StringVar(&s.configPath, "conf", "", "/path/to/config.json : path to the config file")
|
||||||
|
}
|
||||||
|
func (s *serveCmd) Usage() string {
|
||||||
|
return `serve [-conf <config file>]
|
||||||
|
Serve certificate renewal service using information from config file
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serveCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
|
||||||
|
log.Println("[Orchid] Starting...")
|
||||||
|
|
||||||
|
if s.configPath == "" {
|
||||||
|
log.Println("[Orchid] Error: config flag is missing")
|
||||||
|
return subcommands.ExitUsageError
|
||||||
|
}
|
||||||
|
|
||||||
|
openConf, err := os.Open(s.configPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
log.Println("[Orchid] Error: missing config file")
|
||||||
|
} else {
|
||||||
|
log.Println("[Orchid] Error: open config file: ", err)
|
||||||
|
}
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
var conf startUpConfig
|
||||||
|
err = json.NewDecoder(openConf).Decode(&conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[Orchid] Error: invalid config file: ", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
normalLoad(conf)
|
||||||
|
return subcommands.ExitSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalLoad(conf startUpConfig) {
|
||||||
|
os.ReadFile()
|
||||||
|
x509.ParsePKCS1PrivateKey()
|
||||||
|
mjwtVerify, err := mjwt.NewMJwtVerifierFromFile(conf.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
53
cmd/orchid/setup.go
Normal file
53
cmd/orchid/setup.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/google/subcommands"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type setupCmd struct{ wdPath string }
|
||||||
|
|
||||||
|
func (s *setupCmd) Name() string { return "setup" }
|
||||||
|
func (s *setupCmd) Synopsis() string { return "Setup certificate renewal service" }
|
||||||
|
func (s *setupCmd) SetFlags(f *flag.FlagSet) {
|
||||||
|
f.StringVar(&s.wdPath, "wd", ".", "Path to the directory to create config files in (defaults to the working directory)")
|
||||||
|
}
|
||||||
|
func (s *setupCmd) Usage() string {
|
||||||
|
return `setup [-wd <directory>]
|
||||||
|
Setup Orchid automatically by answering questions.
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *setupCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
|
||||||
|
// get absolute path to specify files
|
||||||
|
wdAbs, err := filepath.Abs(s.wdPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[Orchid] Failed to get full directory path: ", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
// ask about running the setup steps
|
||||||
|
createFile := false
|
||||||
|
err = survey.AskOne(&survey.Confirm{Message: fmt.Sprintf("Create Orchid config files in this directory: '%s'?", wdAbs)}, &createFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[Orchid] Error: ", err)
|
||||||
|
return subcommands.ExitFailure
|
||||||
|
}
|
||||||
|
if !createFile {
|
||||||
|
fmt.Println("[Orchid] Goodbye")
|
||||||
|
return subcommands.ExitSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
var answers struct {
|
||||||
|
ApiListen string
|
||||||
|
FirstDomains []string
|
||||||
|
}
|
||||||
|
_ = answers
|
||||||
|
|
||||||
|
// ask main questions
|
||||||
|
return subcommands.ExitUsageError
|
||||||
|
}
|
9
go.mod
9
go.mod
@ -3,9 +3,11 @@ module github.com/MrMelon54/orchid
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/MrMelon54/certgen v0.0.1
|
github.com/MrMelon54/certgen v0.0.1
|
||||||
github.com/MrMelon54/mjwt v0.1.0
|
github.com/MrMelon54/mjwt v0.1.1
|
||||||
github.com/go-acme/lego/v4 v4.12.3
|
github.com/go-acme/lego/v4 v4.12.3
|
||||||
|
github.com/google/subcommands v1.2.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.17
|
github.com/mattn/go-sqlite3 v1.14.17
|
||||||
github.com/miekg/dns v1.1.50
|
github.com/miekg/dns v1.1.50
|
||||||
@ -18,6 +20,10 @@ require (
|
|||||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
|
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
github.com/nrdcg/namesilo v0.2.1 // indirect
|
github.com/nrdcg/namesilo v0.2.1 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
@ -25,6 +31,7 @@ require (
|
|||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/net v0.8.0 // indirect
|
golang.org/x/net v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.6.0 // indirect
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
|
golang.org/x/term v0.6.0 // indirect
|
||||||
golang.org/x/text v0.8.0 // indirect
|
golang.org/x/text v0.8.0 // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
39
go.sum
39
go.sum
@ -1,9 +1,17 @@
|
|||||||
|
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||||
|
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||||
github.com/MrMelon54/certgen v0.0.1 h1:ycWdZ2RlxQ5qSuejeBVv4aXjGo5hdqqL4j4EjrXnFMk=
|
github.com/MrMelon54/certgen v0.0.1 h1:ycWdZ2RlxQ5qSuejeBVv4aXjGo5hdqqL4j4EjrXnFMk=
|
||||||
github.com/MrMelon54/certgen v0.0.1/go.mod h1:GHflVlSbtFLJZLpN1oWyUvDBRrR8qCWiwZLXCCnS2Gc=
|
github.com/MrMelon54/certgen v0.0.1/go.mod h1:GHflVlSbtFLJZLpN1oWyUvDBRrR8qCWiwZLXCCnS2Gc=
|
||||||
github.com/MrMelon54/mjwt v0.1.0 h1:x1wBrh9l2CowRekHecxcZaH2zy9Hvqwlp4ppmW1P1OA=
|
github.com/MrMelon54/mjwt v0.1.0 h1:x1wBrh9l2CowRekHecxcZaH2zy9Hvqwlp4ppmW1P1OA=
|
||||||
github.com/MrMelon54/mjwt v0.1.0/go.mod h1:oYrDBWK09Hju98xb+bRQ0wy+RuAzacxYvKYOZchR2Tk=
|
github.com/MrMelon54/mjwt v0.1.0/go.mod h1:oYrDBWK09Hju98xb+bRQ0wy+RuAzacxYvKYOZchR2Tk=
|
||||||
|
github.com/MrMelon54/mjwt v0.1.1 h1:m+aTpxbhQCrOPKHN170DQMFR5r938LkviU38unob5Jw=
|
||||||
|
github.com/MrMelon54/mjwt v0.1.1/go.mod h1:oYrDBWK09Hju98xb+bRQ0wy+RuAzacxYvKYOZchR2Tk=
|
||||||
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
|
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/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
|
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||||
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
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 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=
|
||||||
@ -19,10 +27,23 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
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 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
|
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||||
|
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
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/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 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg=
|
||||||
@ -32,46 +53,64 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/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/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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.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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
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=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
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/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-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-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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
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.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
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-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.0.0-20210220032951-036812b2e83c/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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20210330210617-4fbd30eecc44/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-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-20210510120138-977fb7262007/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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/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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||||
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
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.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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
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/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-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.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.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
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/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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package http_acme
|
package http_acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -9,26 +10,26 @@ import (
|
|||||||
|
|
||||||
var _ challenge.Provider = &HttpAcmeProvider{}
|
var _ challenge.Provider = &HttpAcmeProvider{}
|
||||||
|
|
||||||
|
// HttpAcmeProvider sends HTTP requests to an API updating the outputted
|
||||||
|
// `.wellknown/acme-challenges` data
|
||||||
type HttpAcmeProvider struct {
|
type HttpAcmeProvider struct {
|
||||||
accessToken, refreshToken string
|
accessToken, refreshToken string
|
||||||
apiUrlPresent, apiUrlCleanUp string
|
apiUrlPresent, apiUrlCleanUp string
|
||||||
|
apiUrlRefreshToken string
|
||||||
trip http.RoundTripper
|
trip http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCustomHTTPProvider(accessToken, refreshToken, apiUrlPresent, apiUrlCleanUp string) *HttpAcmeProvider {
|
// NewHttpAcmeProvider creates a new HttpAcmeProvider using http.DefaultTransport
|
||||||
return &HttpAcmeProvider{accessToken, refreshToken, apiUrlPresent, apiUrlCleanUp, http.DefaultTransport}
|
// as the transport
|
||||||
|
func NewHttpAcmeProvider(accessToken, refreshToken, apiUrlPresent, apiUrlCleanUp, apiUrlRefreshToken string) *HttpAcmeProvider {
|
||||||
|
return &HttpAcmeProvider{accessToken, refreshToken, apiUrlPresent, apiUrlCleanUp, apiUrlRefreshToken, http.DefaultTransport}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Present implements challenge.Provider and sends a put request to the specified
|
||||||
|
// path along with a bearer token to authenticate
|
||||||
func (h *HttpAcmeProvider) Present(domain, token, keyAuth string) error {
|
func (h *HttpAcmeProvider) Present(domain, token, keyAuth string) error {
|
||||||
v := strings.NewReplacer("%domain%", domain, "%token%", token, "%content%", keyAuth).Replace(h.apiUrlPresent)
|
|
||||||
req, err := http.NewRequest(http.MethodPut, v, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("Authorization", "Bearer "+h.accessToken)
|
|
||||||
|
|
||||||
// round trip
|
// round trip
|
||||||
trip, err := h.trip.RoundTrip(req)
|
trip, err := h.authCheckRequest(http.MethodPut, h.apiUrlPresent, domain, token, keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -38,16 +39,11 @@ func (h *HttpAcmeProvider) Present(domain, token, keyAuth string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CleanUp implements challenge.Provider and sends a delete request to the
|
||||||
|
// specified path along with a bearer token to authenticate
|
||||||
func (h *HttpAcmeProvider) CleanUp(domain, token, keyAuth string) error {
|
func (h *HttpAcmeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
v := strings.NewReplacer("%domain%", domain, "%token%", token, "%content%", keyAuth).Replace(h.apiUrlCleanUp)
|
|
||||||
req, err := http.NewRequest(http.MethodPut, v, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.Header.Set("Authorization", "Bearer "+h.accessToken)
|
|
||||||
|
|
||||||
// round trip
|
// round trip
|
||||||
trip, err := h.trip.RoundTrip(req)
|
trip, err := h.authCheckRequest(http.MethodDelete, h.apiUrlCleanUp, domain, token, keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -56,3 +52,69 @@ func (h *HttpAcmeProvider) CleanUp(domain, token, keyAuth string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// authCheckRequest call internalRequest and renews the access token if it is
|
||||||
|
// outdated and calls internalRequest again
|
||||||
|
func (h *HttpAcmeProvider) authCheckRequest(method, url, domain, token, keyAuth string) (*http.Response, error) {
|
||||||
|
// call internal request and check the status code
|
||||||
|
resp, err := h.internalRequest(method, url, domain, token, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusOK:
|
||||||
|
// just return
|
||||||
|
return resp, nil
|
||||||
|
case http.StatusForbidden:
|
||||||
|
// send request to get renewed access and refresh tokens
|
||||||
|
req, err := http.NewRequest(http.MethodPost, h.apiUrlRefreshToken, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("refresh token request failed: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+h.refreshToken)
|
||||||
|
|
||||||
|
// round trip and status check
|
||||||
|
trip, err := h.trip.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("refresh token request failed: %w", err)
|
||||||
|
}
|
||||||
|
if trip.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("refresh token request failed: due to invalid status code, expected 200 got %d", trip.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse tokens from response body
|
||||||
|
var tokens struct {
|
||||||
|
Access string `json:"access"`
|
||||||
|
Refresh string `json:"refresh"`
|
||||||
|
}
|
||||||
|
if json.NewDecoder(trip.Body).Decode(&tokens) != nil {
|
||||||
|
return nil, fmt.Errorf("refresh token parsing failed: %w", err)
|
||||||
|
}
|
||||||
|
h.accessToken = tokens.Access
|
||||||
|
h.refreshToken = tokens.Refresh
|
||||||
|
|
||||||
|
// call internal request again
|
||||||
|
resp, err = h.internalRequest(method, url, domain, token, keyAuth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
// just return
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("invalid status code, expected 200 got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
// first request had an invalid status code
|
||||||
|
return nil, fmt.Errorf("invalid status code, expected 200/403 got %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// internalRequest sends a request to the acme challenge hosting api
|
||||||
|
func (h *HttpAcmeProvider) internalRequest(method, url, domain, token, keyAuth string) (*http.Response, error) {
|
||||||
|
v := strings.NewReplacer("%domain%", domain, "%token%", token, "%content%", keyAuth).Replace(url)
|
||||||
|
req, err := http.NewRequest(method, v, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+h.accessToken)
|
||||||
|
return h.trip.RoundTrip(req)
|
||||||
|
}
|
||||||
|
@ -16,6 +16,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func makeQuickHttpProv(accessToken string, ft http.RoundTripper) *HttpAcmeProvider {
|
||||||
|
return &HttpAcmeProvider{
|
||||||
|
accessToken,
|
||||||
|
"",
|
||||||
|
"https://api.example.com/acme/present/%domain%/%token%/%content%",
|
||||||
|
"https://api.example.com/acme/clean/%domain%/%token%",
|
||||||
|
"https://api.example.com/acme/token",
|
||||||
|
ft,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fakeTransport captures any requests and responds with a successful answer if
|
||||||
|
// applicable
|
||||||
type fakeTransport struct {
|
type fakeTransport struct {
|
||||||
verify mjwt.Verifier
|
verify mjwt.Verifier
|
||||||
req *http.Request
|
req *http.Request
|
||||||
@ -23,6 +36,7 @@ type fakeTransport struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (f *fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
// check bearer token and extract claims
|
||||||
bearer := req.Header.Get("Authorization")
|
bearer := req.Header.Get("Authorization")
|
||||||
if !strings.HasPrefix(bearer, "Bearer ") {
|
if !strings.HasPrefix(bearer, "Bearer ") {
|
||||||
return nil, fmt.Errorf("invalid bearer token")
|
return nil, fmt.Errorf("invalid bearer token")
|
||||||
@ -59,13 +73,7 @@ func TestHttpAcmeProvider_Present(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
ft := &fakeTransport{verify: signer}
|
ft := &fakeTransport{verify: signer}
|
||||||
prov := &HttpAcmeProvider{
|
prov := makeQuickHttpProv(accessToken, ft)
|
||||||
accessToken,
|
|
||||||
"",
|
|
||||||
"https://api.example.com/acme/present/%domain%/%token%/%content%",
|
|
||||||
"https://api.example.com/acme/clean/%domain%/%token%",
|
|
||||||
ft,
|
|
||||||
}
|
|
||||||
assert.NoError(t, prov.Present("example.com", "1234", "1234abcd"))
|
assert.NoError(t, prov.Present("example.com", "1234", "1234abcd"))
|
||||||
assert.Equal(t, *ft.req.URL, url.URL{
|
assert.Equal(t, *ft.req.URL, url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
@ -88,13 +96,7 @@ func TestHttpAcmeProvider_CleanUp(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
ft := &fakeTransport{verify: signer, clean: true}
|
ft := &fakeTransport{verify: signer, clean: true}
|
||||||
prov := &HttpAcmeProvider{
|
prov := makeQuickHttpProv(accessToken, ft)
|
||||||
accessToken,
|
|
||||||
"",
|
|
||||||
"https://api.example.com/acme/present/%domain%/%token%/%content%",
|
|
||||||
"https://api.example.com/acme/clean/%domain%/%token%",
|
|
||||||
ft,
|
|
||||||
}
|
|
||||||
assert.NoError(t, prov.CleanUp("example.com", "1234", "1234abcd"))
|
assert.NoError(t, prov.CleanUp("example.com", "1234", "1234abcd"))
|
||||||
assert.Equal(t, *ft.req.URL, url.URL{
|
assert.Equal(t, *ft.req.URL, url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Account stores the information required for the lego library to use the
|
||||||
|
// LetsEncrypt account details.
|
||||||
type Account struct {
|
type Account struct {
|
||||||
email string
|
email string
|
||||||
reg *registration.Resource
|
reg *registration.Resource
|
||||||
|
@ -35,11 +35,20 @@ var (
|
|||||||
createTableCertificates string
|
createTableCertificates string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// overrides only used in testing
|
||||||
var testDnsOptions interface {
|
var testDnsOptions interface {
|
||||||
challenge.Provider
|
challenge.Provider
|
||||||
GetDnsAddrs() []string
|
GetDnsAddrs() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Service manages the scheduled renewal of certificates stored in the database
|
||||||
|
// and outputs the latest certificates to the certDir folder. If the certificate
|
||||||
|
// does not have a key already defined in keyDir then a new key is generated.
|
||||||
|
//
|
||||||
|
// The service makes use of an HTTP ACME challenge provider, and a DNS ACME
|
||||||
|
// challenge provider. These ensure the `.wellknown/acme-challenges` files and
|
||||||
|
// `_acme-challenges` TXT records are updated to validate the ownership of the
|
||||||
|
// specified domains.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
httpAcme challenge.Provider
|
httpAcme challenge.Provider
|
||||||
@ -55,7 +64,8 @@ type Service struct {
|
|||||||
insecure bool
|
insecure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRenewalService(wg *sync.WaitGroup, db *sql.DB, httpAcme challenge.Provider, leConfig LetsEncryptConfig, certDir, keyDir string) (*Service, error) {
|
// NewService creates a new certificate renewal service.
|
||||||
|
func NewService(wg *sync.WaitGroup, db *sql.DB, httpAcme challenge.Provider, leConfig LetsEncryptConfig, certDir, keyDir string) (*Service, error) {
|
||||||
r := &Service{
|
r := &Service{
|
||||||
db: db,
|
db: db,
|
||||||
httpAcme: httpAcme,
|
httpAcme: httpAcme,
|
||||||
@ -99,16 +109,20 @@ func NewRenewalService(wg *sync.WaitGroup, db *sql.DB, httpAcme challenge.Provid
|
|||||||
return nil, fmt.Errorf("failed to resolve CA certificate: %w", err)
|
return nil, fmt.Errorf("failed to resolve CA certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start the background routine
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go r.renewalRoutine(wg)
|
go r.renewalRoutine(wg)
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown the renewal service.
|
||||||
func (s *Service) Shutdown() {
|
func (s *Service) Shutdown() {
|
||||||
log.Println("[Renewal] Shutting down certificate renewal service")
|
log.Println("[Renewal] Shutting down certificate renewal service")
|
||||||
close(s.certDone)
|
close(s.certDone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveLEPrivKey resolves the private key for the LetsEncrypt account.
|
||||||
|
// If the string is a path to a file then the contents of the file is read.
|
||||||
func (s *Service) resolveLEPrivKey(a string) error {
|
func (s *Service) resolveLEPrivKey(a string) error {
|
||||||
key, err := x509.ParsePKCS1PrivateKey([]byte(a))
|
key, err := x509.ParsePKCS1PrivateKey([]byte(a))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -122,6 +136,14 @@ func (s *Service) resolveLEPrivKey(a string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveCADirectory resolves the certificate authority directory URL.
|
||||||
|
//
|
||||||
|
// If "production" or "prod" then the LetsEncrypt production directory is used.
|
||||||
|
//
|
||||||
|
// If "staging" then the LetsEncrypt staging directory is used.
|
||||||
|
//
|
||||||
|
// Otherwise, the string is assumed to be a value directory URL (used for testing
|
||||||
|
// with pebble).
|
||||||
func (s *Service) resolveCADirectory(dir string) {
|
func (s *Service) resolveCADirectory(dir string) {
|
||||||
switch dir {
|
switch dir {
|
||||||
case "production", "prod":
|
case "production", "prod":
|
||||||
@ -133,6 +155,12 @@ func (s *Service) resolveCADirectory(dir string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveCACertificate resolves the certificate authority root certificate.
|
||||||
|
//
|
||||||
|
// If "default" is used then the internal library lego defaults to the
|
||||||
|
// LetsEncrypt production root certificate.
|
||||||
|
//
|
||||||
|
// If "pebble" is used then the pebble certificate is used.
|
||||||
func (s *Service) resolveCACertificate(cert string) error {
|
func (s *Service) resolveCACertificate(cert string) error {
|
||||||
switch cert {
|
switch cert {
|
||||||
case "default":
|
case "default":
|
||||||
@ -164,27 +192,37 @@ func (s *Service) resolveCACertificate(cert string) error {
|
|||||||
|
|
||||||
var ErrAlreadyRenewing = errors.New("already renewing")
|
var ErrAlreadyRenewing = errors.New("already renewing")
|
||||||
|
|
||||||
|
// renewalRoutine is the main loop which makes used of certTicker to constantly
|
||||||
|
// check if the existing certificates are up-to-date.
|
||||||
func (s *Service) renewalRoutine(wg *sync.WaitGroup) {
|
func (s *Service) renewalRoutine(wg *sync.WaitGroup) {
|
||||||
|
// Upon leaving the function stop the ticker and clear the WaitGroup.
|
||||||
defer func() {
|
defer func() {
|
||||||
s.certTicker.Stop()
|
s.certTicker.Stop()
|
||||||
log.Println("[Renewal] Stopped certificate renewal service")
|
log.Println("[Renewal] Stopped certificate renewal service")
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Do an initial check and refuse to start if any errors occur.
|
||||||
log.Println("[Renewal] Doing quick certificate check before starting...")
|
log.Println("[Renewal] Doing quick certificate check before starting...")
|
||||||
err := s.renewalCheck()
|
err := s.renewalCheck()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[Renewal] Certificate check, should not error first try: ", err)
|
log.Println("[Renewal] Certificate check, should not error first try: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("[Renewal] Initial check complete, continually checking every 4 hours...")
|
|
||||||
|
|
||||||
|
// Logging or something
|
||||||
|
log.Println("[Renewal] Initial check complete, continually checking every 10 minutes...")
|
||||||
|
|
||||||
|
// Main loop
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-s.certDone:
|
case <-s.certDone:
|
||||||
|
// Exit if certDone has closed
|
||||||
return
|
return
|
||||||
case <-s.certTicker.C:
|
case <-s.certTicker.C:
|
||||||
|
// Start a new check in a separate routine
|
||||||
go func() {
|
go func() {
|
||||||
|
// run a renewal check and log errors, but ignore ErrAlreadyRenewing
|
||||||
err := s.renewalCheck()
|
err := s.renewalCheck()
|
||||||
if err != nil && err != ErrAlreadyRenewing {
|
if err != nil && err != ErrAlreadyRenewing {
|
||||||
log.Println("[Renewal] Certificate check, an error occurred: ", err)
|
log.Println("[Renewal] Certificate check, an error occurred: ", err)
|
||||||
@ -194,12 +232,15 @@ func (s *Service) renewalRoutine(wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renewalCheck runs a locked renewal check, this only returns and unlocks once a
|
||||||
|
// renewal finishes or if no certificate needs to renew.
|
||||||
func (s *Service) renewalCheck() error {
|
func (s *Service) renewalCheck() error {
|
||||||
if !s.renewLock.TryLock() {
|
if !s.renewLock.TryLock() {
|
||||||
return ErrAlreadyRenewing
|
return ErrAlreadyRenewing
|
||||||
}
|
}
|
||||||
defer s.renewLock.Unlock()
|
defer s.renewLock.Unlock()
|
||||||
|
|
||||||
|
// check for running out certificates in the database
|
||||||
localData, err := s.findNextCertificateToRenew()
|
localData, err := s.findNextCertificateToRenew()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to find a certificate to renew: %w", err)
|
return fmt.Errorf("failed to find a certificate to renew: %w", err)
|
||||||
@ -210,19 +251,35 @@ func (s *Service) renewalCheck() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renew the certificate from the collected data
|
||||||
err = s.renewCert(localData)
|
err = s.renewCert(localData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renew succeeded
|
||||||
log.Printf("[Renewal] Updated certificate %d successfully\n", localData.id)
|
log.Printf("[Renewal] Updated certificate %d successfully\n", localData.id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findNextCertificateToRenew finds a certificate to update
|
||||||
func (s *Service) findNextCertificateToRenew() (*localCertData, error) {
|
func (s *Service) findNextCertificateToRenew() (*localCertData, error) {
|
||||||
d := &localCertData{}
|
d := &localCertData{}
|
||||||
|
|
||||||
row := s.db.QueryRow(findNextCertSql)
|
// sql or something, the query is in `find-next-cert.sql`
|
||||||
err := row.Scan(&d.id, &d.notAfter, &d.dns.name, &d.dns.token)
|
row, err := s.db.Query(findNextCertSql)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to run query: %w", err)
|
||||||
|
}
|
||||||
|
defer row.Close()
|
||||||
|
|
||||||
|
// if next fails no rows were found
|
||||||
|
if !row.Next() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan the first row
|
||||||
|
err = row.Scan(&d.id, &d.notAfter, &d.dns.name, &d.dns.token)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
// no nothing
|
// no nothing
|
||||||
@ -238,11 +295,13 @@ func (s *Service) findNextCertificateToRenew() (*localCertData, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) fetchDomains(localData *localCertData) ([]string, error) {
|
func (s *Service) fetchDomains(localData *localCertData) ([]string, error) {
|
||||||
|
// more sql: this one just grabs all the domains for a certificate
|
||||||
query, err := s.db.Query(`SELECT domain FROM certificate_domains WHERE cert_id = ?`, localData.id)
|
query, err := s.db.Query(`SELECT domain FROM certificate_domains WHERE cert_id = ?`, localData.id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch domains for certificate: %d: %w", localData.id, err)
|
return nil, fmt.Errorf("failed to fetch domains for certificate: %d: %w", localData.id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert query responses to a string slice
|
||||||
domains := make([]string, 0)
|
domains := make([]string, 0)
|
||||||
for query.Next() {
|
for query.Next() {
|
||||||
var domain string
|
var domain string
|
||||||
@ -252,6 +311,7 @@ func (s *Service) fetchDomains(localData *localCertData) ([]string, error) {
|
|||||||
}
|
}
|
||||||
domains = append(domains, domain)
|
domains = append(domains, domain)
|
||||||
}
|
}
|
||||||
|
// if no domains were found then the renewal will fail
|
||||||
if len(domains) == 0 {
|
if len(domains) == 0 {
|
||||||
return nil, fmt.Errorf("no domains registered for certificate: %d", localData.id)
|
return nil, fmt.Errorf("no domains registered for certificate: %d", localData.id)
|
||||||
}
|
}
|
||||||
@ -259,12 +319,15 @@ func (s *Service) fetchDomains(localData *localCertData) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) setupLegoClient(localData *localCertData) (*lego.Client, error) {
|
func (s *Service) setupLegoClient(localData *localCertData) (*lego.Client, error) {
|
||||||
|
// create lego config and change the certificate authority directory URL and the
|
||||||
|
// http.Client transport if an alternative is provided
|
||||||
config := lego.NewConfig(s.leAccount)
|
config := lego.NewConfig(s.leAccount)
|
||||||
config.CADirURL = s.caAddr
|
config.CADirURL = s.caAddr
|
||||||
if s.transport != nil {
|
if s.transport != nil {
|
||||||
config.HTTPClient.Transport = s.transport
|
config.HTTPClient.Transport = s.transport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create lego client from the config
|
||||||
client, err := lego.NewClient(config)
|
client, err := lego.NewClient(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate client: %w", err)
|
return nil, fmt.Errorf("failed to generate client: %w", err)
|
||||||
@ -275,6 +338,8 @@ func (s *Service) setupLegoClient(localData *localCertData) (*lego.Client, error
|
|||||||
|
|
||||||
// if testDnsOptions is defined then set up the test provider
|
// if testDnsOptions is defined then set up the test provider
|
||||||
if testDnsOptions != nil {
|
if testDnsOptions != nil {
|
||||||
|
// set up the dns provider used during tests and disable propagation as no dns
|
||||||
|
// will validate these tests
|
||||||
dnsAddrs := testDnsOptions.GetDnsAddrs()
|
dnsAddrs := testDnsOptions.GetDnsAddrs()
|
||||||
log.Printf("Using testDnsOptions with DNS server: %v\n", dnsAddrs)
|
log.Printf("Using testDnsOptions with DNS server: %v\n", dnsAddrs)
|
||||||
_ = client.Challenge.SetDNS01Provider(testDnsOptions, dns01.AddRecursiveNameservers(dnsAddrs), dns01.DisableCompletePropagationRequirement())
|
_ = client.Challenge.SetDNS01Provider(testDnsOptions, dns01.AddRecursiveNameservers(dnsAddrs), dns01.DisableCompletePropagationRequirement())
|
||||||
@ -288,15 +353,19 @@ func (s *Service) setupLegoClient(localData *localCertData) (*lego.Client, error
|
|||||||
_ = client.Challenge.SetDNS01Provider(dnsProv)
|
_ = client.Challenge.SetDNS01Provider(dnsProv)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure the LetsEncrypt account is registered
|
||||||
register, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
register, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to update account registration: %w", err)
|
return nil, fmt.Errorf("failed to update account registration: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return and use the client
|
||||||
s.leAccount.reg = register
|
s.leAccount.reg = register
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getDnsProvider loads a DNS challenge provider using the provided name and
|
||||||
|
// token
|
||||||
func (s *Service) getDnsProvider(name, token string) (challenge.Provider, error) {
|
func (s *Service) getDnsProvider(name, token string) (challenge.Provider, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case "duckdns":
|
case "duckdns":
|
||||||
@ -312,6 +381,7 @@ func (s *Service) getDnsProvider(name, token string) (challenge.Provider, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getPrivateKey reads the private key for the specified certificate id
|
||||||
func (s *Service) getPrivateKey(id uint64) (*rsa.PrivateKey, error) {
|
func (s *Service) getPrivateKey(id uint64) (*rsa.PrivateKey, error) {
|
||||||
privKeyBytes, err := os.ReadFile(filepath.Join(s.keyDir, fmt.Sprintf("%d.key.pem", id)))
|
privKeyBytes, err := os.ReadFile(filepath.Join(s.keyDir, fmt.Sprintf("%d.key.pem", id)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -320,20 +390,27 @@ func (s *Service) getPrivateKey(id uint64) (*rsa.PrivateKey, error) {
|
|||||||
return x509.ParsePKCS1PrivateKey(privKeyBytes)
|
return x509.ParsePKCS1PrivateKey(privKeyBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renewCert sets the renewing state in the database, calls renewCertInternal,
|
||||||
|
// updates the NotAfter/NotBefore columns in the database and writes the
|
||||||
|
// certificate to the certDir directory.
|
||||||
func (s *Service) renewCert(localData *localCertData) error {
|
func (s *Service) renewCert(localData *localCertData) error {
|
||||||
|
// database synchronous state
|
||||||
s.setRenewing(localData.id, true, false)
|
s.setRenewing(localData.id, true, false)
|
||||||
|
|
||||||
|
// run internal renewal code and log errors
|
||||||
cert, certBytes, err := s.renewCertInternal(localData)
|
cert, certBytes, err := s.renewCertInternal(localData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.setRenewing(localData.id, false, true)
|
s.setRenewing(localData.id, false, true)
|
||||||
return fmt.Errorf("failed to renew cert %d: %w", localData.id, err)
|
return fmt.Errorf("failed to renew cert %d: %w", localData.id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set the NotAfter/NotBefore in the database
|
||||||
_, err = s.db.Exec(`UPDATE certificates SET renewing = 0, renew_failed = 0, not_after = ?, updated_at = ? WHERE id = ?`, cert.NotAfter, cert.NotBefore, localData.id)
|
_, 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update cert %d in database: %w", localData.id, err)
|
return fmt.Errorf("failed to update cert %d in database: %w", localData.id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// write out the certificate file
|
||||||
err = s.writeCertFile(localData.id, certBytes)
|
err = s.writeCertFile(localData.id, certBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write cert file: %w", err)
|
return fmt.Errorf("failed to write cert file: %w", err)
|
||||||
@ -342,6 +419,9 @@ func (s *Service) renewCert(localData *localCertData) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renewCertInternal handles each stage of fetching the certificate private key,
|
||||||
|
// fetching the domains slice, setting up the lego client, obtaining a renewed
|
||||||
|
// certificate, decoding and parsing the certificate.
|
||||||
func (s *Service) renewCertInternal(localData *localCertData) (*x509.Certificate, []byte, error) {
|
func (s *Service) renewCertInternal(localData *localCertData) (*x509.Certificate, []byte, error) {
|
||||||
// read private key file
|
// read private key file
|
||||||
privKey, err := s.getPrivateKey(localData.id)
|
privKey, err := s.getPrivateKey(localData.id)
|
||||||
@ -387,6 +467,8 @@ func (s *Service) renewCertInternal(localData *localCertData) (*x509.Certificate
|
|||||||
return parseCert, obtain.Certificate, nil
|
return parseCert, obtain.Certificate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setRenewing sets the renewing and failed states in the database for a
|
||||||
|
// specified certifcate id.
|
||||||
func (s *Service) setRenewing(id uint64, renewing, failed bool) {
|
func (s *Service) setRenewing(id uint64, renewing, failed bool) {
|
||||||
_, err := s.db.Exec("UPDATE certificates SET renewing = ?, renew_failed = ? WHERE id = ?", renewing, failed, id)
|
_, err := s.db.Exec("UPDATE certificates SET renewing = ?, renew_failed = ? WHERE id = ?", renewing, failed, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -394,6 +476,8 @@ func (s *Service) setRenewing(id uint64, renewing, failed bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeCertFile writes the output certificate file and renames the current one
|
||||||
|
// to include `-old` in the name.
|
||||||
func (s *Service) writeCertFile(id uint64, certBytes []byte) error {
|
func (s *Service) writeCertFile(id uint64, certBytes []byte) error {
|
||||||
oldPath := filepath.Join(s.certDir, fmt.Sprintf("%d-old.cert.pem", id))
|
oldPath := filepath.Join(s.certDir, fmt.Sprintf("%d-old.cert.pem", id))
|
||||||
newPath := filepath.Join(s.certDir, fmt.Sprintf("%d.cert.pem", id))
|
newPath := filepath.Join(s.certDir, fmt.Sprintf("%d.cert.pem", id))
|
||||||
|
@ -121,7 +121,7 @@ func setupPebbleTest(t *testing.T, serverTls *certgen.CertGen) *Service {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
acmeProv := test.MakeFakeAcmeProv(serverTls.GetCertPem())
|
acmeProv := test.MakeFakeAcmeProv(serverTls.GetCertPem())
|
||||||
service, err := NewRenewalService(wg, db, acmeProv, LetsEncryptConfig{
|
service, err := NewService(wg, db, acmeProv, LetsEncryptConfig{
|
||||||
Account: struct {
|
Account: struct {
|
||||||
Email string `yaml:"email"`
|
Email string `yaml:"email"`
|
||||||
PrivateKey string `yaml:"privateKey"`
|
PrivateKey string `yaml:"privateKey"`
|
||||||
@ -144,6 +144,9 @@ func setupPebbleTest(t *testing.T, serverTls *certgen.CertGen) *Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPebbleRenewal(t *testing.T) {
|
func TestPebbleRenewal(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("Skipping renewal tests in short mode")
|
||||||
|
}
|
||||||
serverTls, cancel := setupPebbleSuite(t)
|
serverTls, cancel := setupPebbleSuite(t)
|
||||||
t.Cleanup(cancel)
|
t.Cleanup(cancel)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user