add ability to relay http-01 challenges

This commit is contained in:
AJ ONeal 2020-11-05 02:11:17 -07:00
parent d433b987cb
commit 1d71b24ccf
15 changed files with 410 additions and 141 deletions

View File

@ -61,6 +61,7 @@ ACME_AGREE=true
ACME_EMAIL=letsencrypt@example.com ACME_EMAIL=letsencrypt@example.com
# For Let's Encrypt / ACME challenges # For Let's Encrypt / ACME challenges
ACME_HTTP_01_RELAY_URL=http://localhost:3010/api/http
ACME_RELAY_URL=http://localhost:3010/api/dns ACME_RELAY_URL=http://localhost:3010/api/dns
SECRET=xxxxxxxxxxxxxxxx SECRET=xxxxxxxxxxxxxxxx
GODADDY_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx GODADDY_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

View File

@ -5,9 +5,12 @@
"method": "GET", "method": "GET",
"pathname": "" "pathname": ""
}, },
"acme_dns_01_proxy": { "_todo_acme_dns_01_proxy": {
"pathname": "dns" "pathname": "dns"
}, },
"acme_http_01_proxy": {
"pathname": "http"
},
"pair_request": { "pair_request": {
"method": "POST", "method": "POST",
"pathname": "telebit.app/pair_request" "pathname": "telebit.app/pair_request"

View File

@ -10,7 +10,7 @@ import (
"os" "os"
"strings" "strings"
dns01 "git.rootprojects.org/root/telebit/dns01" "git.rootprojects.org/root/telebit/internal/dns01"
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
"github.com/go-acme/lego/v3/challenge" "github.com/go-acme/lego/v3/challenge"

View File

@ -2,22 +2,40 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"os"
"path/filepath"
"strings" "strings"
"github.com/go-acme/lego/v3/challenge" "github.com/go-acme/lego/v3/challenge"
"github.com/go-chi/chi" "github.com/go-chi/chi"
) )
// A Challenge has the data necessary to create an ACME DNS-01 Key Authorization Digest. const (
tmpBase = "acme-tmp"
challengeDir = ".well-known/acme-challenge"
)
// Challenge is an ACME http-01 challenge
type Challenge struct { type Challenge struct {
Domain string `json:"domain"` Type string `json:"type"`
Token string `json:"token"` Token string `json:"token"`
KeyAuth string `json:"key_authorization"` KeyAuth string `json:"key_authorization"`
Identifier Identifier `json:"identifier"`
// for the old one
Domain string `json:"domain"`
error chan error error chan error
} }
// Identifier is restricted to DNS Domain Names for now
type Identifier struct {
Type string `json:"type"`
Value string `json:"value"`
}
type acmeProvider struct { type acmeProvider struct {
BaseURL string BaseURL string
provider challenge.Provider provider challenge.Provider
@ -32,8 +50,19 @@ func (p *acmeProvider) CleanUp(domain, token, keyAuth string) error {
} }
func handleDNSRoutes(r chi.Router) { func handleDNSRoutes(r chi.Router) {
r.Route("/dns", func(r chi.Router) { handleACMEChallenges := func(r chi.Router) {
r.Post("/{domain}", func(w http.ResponseWriter, r *http.Request) { r.Post("/{domain}", createChallenge)
// TODO ugly Delete, but whatever
r.Delete("/{domain}/{token}/{keyAuth}", deleteChallenge)
r.Delete("/{domain}/{token}/{keyAuth}/{challengeType}", deleteChallenge)
}
r.Route("/dns", handleACMEChallenges)
r.Route("/http", handleACMEChallenges)
}
func createChallenge(w http.ResponseWriter, r *http.Request) {
domain := chi.URLParam(r, "domain") domain := chi.URLParam(r, "domain")
ctx := r.Context() ctx := r.Context()
@ -57,13 +86,24 @@ func handleDNSRoutes(r chi.Router) {
//domain := chi.URLParam(r, "*") //domain := chi.URLParam(r, "*")
ch.Domain = domain ch.Domain = domain
ch.Identifier.Value = domain
if "" == ch.Token || "" == ch.KeyAuth {
err = errors.New("missing token and/or key auth")
} else if strings.Contains(ch.Type, "http") {
challengeBase := filepath.Join(tmpBase, ch.Domain, ".well-known/acme-challenge")
_ = os.MkdirAll(challengeBase, 0700)
tokenPath := filepath.Join(challengeBase, ch.Token)
err = ioutil.WriteFile(tokenPath, []byte(ch.KeyAuth), 0600)
} else {
// TODO some additional error checking before the handoff // TODO some additional error checking before the handoff
//ch.error = make(chan error, 1) //ch.error = make(chan error, 1)
ch.error = make(chan error) ch.error = make(chan error)
presenters <- &ch presenters <- &ch
err = <-ch.error err = <-ch.error
if nil != err || "" == ch.Token || "" == ch.KeyAuth { }
if nil != err {
fmt.Println("presenter err", err, ch.Token, ch.KeyAuth) fmt.Println("presenter err", err, ch.Token, ch.KeyAuth)
msg := `{"error":"ACME dns-01 error", "code":"E_SERVER"}` msg := `{"error":"ACME dns-01 error", "code":"E_SERVER"}`
http.Error(w, msg, http.StatusUnprocessableEntity) http.Error(w, msg, http.StatusUnprocessableEntity)
@ -71,10 +111,9 @@ func handleDNSRoutes(r chi.Router) {
} }
w.Write([]byte("{\"success\":true}\n")) w.Write([]byte("{\"success\":true}\n"))
}) }
// TODO ugly Delete, but whatever func deleteChallenge(w http.ResponseWriter, r *http.Request) {
r.Delete("/{domain}/{token}/{keyAuth}", func(w http.ResponseWriter, r *http.Request) {
// TODO authenticate // TODO authenticate
ch := Challenge{ ch := Challenge{
@ -85,15 +124,23 @@ func handleDNSRoutes(r chi.Router) {
//error: make(chan error, 1), //error: make(chan error, 1),
} }
var err error
if "" == ch.Token || "" == ch.KeyAuth {
err = errors.New("missing token and/or key auth")
} else if strings.Contains(ch.Type, "http") {
// always try to remove, as there's no harm
tokenPath := filepath.Join(tmpBase, ch.Domain, challengeDir, ch.Token)
_ = os.Remove(tokenPath)
} else {
cleanups <- &ch cleanups <- &ch
err := <-ch.error err = <-ch.error
if nil != err || "" == ch.Token || "" == ch.KeyAuth { }
if nil != err {
msg := `{"error":"expected json in the format {\"token\":\"xxx\",\"key_authorization\":\"yyy\"}", "code":"E_BAD_REQUEST"}` msg := `{"error":"expected json in the format {\"token\":\"xxx\",\"key_authorization\":\"yyy\"}", "code":"E_BAD_REQUEST"}`
http.Error(w, msg, http.StatusUnprocessableEntity) http.Error(w, msg, http.StatusUnprocessableEntity)
return return
} }
w.Write([]byte("{\"success\":true}\n")) w.Write([]byte("{\"success\":true}\n"))
})
})
} }

View File

@ -27,6 +27,7 @@ var (
GitTimestamp = "0000-00-00T00:00:00+0000" GitTimestamp = "0000-00-00T00:00:00+0000"
) )
// MWKey is a type guard
type MWKey string type MWKey string
var store authstore.Store var store authstore.Store
@ -44,6 +45,7 @@ func main() {
addr := flag.String("address", "", "IPv4 or IPv6 bind address") addr := flag.String("address", "", "IPv4 or IPv6 bind address")
port := flag.String("port", "3000", "port to listen to") port := flag.String("port", "3000", "port to listen to")
challengesPort := flag.String("challenges-port", "80", "port to use to respond to .well-known/acme-challenge tokens")
dbURL := flag.String( dbURL := flag.String(
"db-url", "db-url",
"postgres://postgres:postgres@localhost/postgres", "postgres://postgres:postgres@localhost/postgres",
@ -101,6 +103,10 @@ func main() {
_ = store.SetMaster(secret) _ = store.SetMaster(secret)
defer store.Close() defer store.Close()
go func() {
http.ListenAndServe(":"+challengesPort, routeStatic())
}()
bind := *addr + ":" + *port bind := *addr + ":" + *port
fmt.Println("Listening on", bind) fmt.Println("Listening on", bind)
fmt.Fprintf(os.Stderr, "failed: %s", http.ListenAndServe(bind, routeAll())) fmt.Fprintf(os.Stderr, "failed: %s", http.ListenAndServe(bind, routeAll()))

View File

@ -7,6 +7,7 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath"
"strings" "strings"
"time" "time"
@ -25,6 +26,29 @@ type MgmtClaims struct {
var presenters = make(chan *Challenge) var presenters = make(chan *Challenge)
var cleanups = make(chan *Challenge) var cleanups = make(chan *Challenge)
func routeStatic() chi.Router {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Timeout(15 * time.Second))
r.Use(middleware.Recoverer)
r.Get("/.well-known/acme-challenge/{token}", func(w http.ResponseWriter, r *http.Request) {
//token := chi.URLParam(r, "token")
host := r.Header.Get("Host")
if strings.ContainsAny(host, "/:|\\") {
host = ""
}
tokenPath := filepath.Join(tmpBase, host)
fsrv := http.FileServer(http.Dir(tokenPath))
fsrv.ServeHTTP(w, r)
})
return r
}
func routeAll() chi.Router { func routeAll() chi.Router {
go func() { go func() {

View File

@ -7,7 +7,6 @@ import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -21,14 +20,14 @@ import (
telebit "git.rootprojects.org/root/telebit" telebit "git.rootprojects.org/root/telebit"
"git.rootprojects.org/root/telebit/dbg" "git.rootprojects.org/root/telebit/dbg"
tbDns01 "git.rootprojects.org/root/telebit/dns01" tbDns01 "git.rootprojects.org/root/telebit/internal/dns01"
"git.rootprojects.org/root/telebit/internal/http01"
"git.rootprojects.org/root/telebit/iplist" "git.rootprojects.org/root/telebit/iplist"
"git.rootprojects.org/root/telebit/mgmt" "git.rootprojects.org/root/telebit/mgmt"
"git.rootprojects.org/root/telebit/mgmt/authstore" "git.rootprojects.org/root/telebit/mgmt/authstore"
"git.rootprojects.org/root/telebit/table" "git.rootprojects.org/root/telebit/table"
"git.rootprojects.org/root/telebit/tunnel" "git.rootprojects.org/root/telebit/tunnel"
legoDns01 "github.com/go-acme/lego/v3/challenge/dns01" legoDns01 "github.com/go-acme/lego/v3/challenge/dns01"
"github.com/mholt/acmez/acme"
"github.com/coolaj86/certmagic" "github.com/coolaj86/certmagic"
"github.com/denisbrodbeck/machineid" "github.com/denisbrodbeck/machineid"
@ -81,36 +80,6 @@ var VendorID string
// ClientSecret may be baked in, or supplied via ENVs or --args // ClientSecret may be baked in, or supplied via ENVs or --args
var ClientSecret string var ClientSecret string
type legoWrapper struct {
provider challenge.Provider
//option legoDns01.ChallengeOption
dnsSolver certmagic.DNS01Solver
}
func (lw *legoWrapper) Present(ctx context.Context, ch acme.Challenge) error {
return lw.provider.Present(ch.Identifier.Value, ch.Token, ch.KeyAuthorization)
}
func (lw *legoWrapper) CleanUp(ctx context.Context, ch acme.Challenge) error {
c := make(chan error)
go func() {
c <- lw.provider.CleanUp(ch.Identifier.Value, ch.Token, ch.KeyAuthorization)
}()
select {
case err := <-c:
return err
case <-ctx.Done():
return errors.New("cancelled")
}
}
// Wait blocks until the TXT record created in Present() appears in
// authoritative lookups, i.e. until it has propagated, or until
// timeout, whichever is first.
func (lw *legoWrapper) Wait(ctx context.Context, challenge acme.Challenge) error {
return lw.dnsSolver.Wait(ctx, challenge)
}
func main() { func main() {
var domains []string var domains []string
var forwards []Forward var forwards []Forward
@ -128,6 +97,7 @@ func main() {
enableHTTP01 := flag.Bool("acme-http-01", false, "enable HTTP-01 ACME challenges") enableHTTP01 := flag.Bool("acme-http-01", false, "enable HTTP-01 ACME challenges")
enableTLSALPN01 := flag.Bool("acme-tls-alpn-01", false, "enable TLS-ALPN-01 ACME challenges") enableTLSALPN01 := flag.Bool("acme-tls-alpn-01", false, "enable TLS-ALPN-01 ACME challenges")
acmeRelay := flag.String("acme-relay-url", "", "the base url of the ACME DNS-01 relay, if not the same as the tunnel relay") acmeRelay := flag.String("acme-relay-url", "", "the base url of the ACME DNS-01 relay, if not the same as the tunnel relay")
acmeHTTP01Relay := flag.String("acme-http-01-relay-url", "", "the base url of the ACME HTTP-01 relay, if not the same as the DNS-01 relay")
var dnsPropagationDelay time.Duration var dnsPropagationDelay time.Duration
flag.DurationVar(&dnsPropagationDelay, "dns-01-delay", 0, "add an extra delay after dns self-check to allow DNS-01 challenges to propagate") flag.DurationVar(&dnsPropagationDelay, "dns-01-delay", 0, "add an extra delay after dns self-check to allow DNS-01 challenges to propagate")
resolverList := flag.String("dns-resolvers", "", "a list of resolvers in the format 8.8.8.8:53,8.8.4.4:53") resolverList := flag.String("dns-resolvers", "", "a list of resolvers in the format 8.8.8.8:53,8.8.4.4:53")
@ -181,6 +151,9 @@ func main() {
if 0 == len(*acmeRelay) { if 0 == len(*acmeRelay) {
*acmeRelay = os.Getenv("ACME_RELAY_URL") *acmeRelay = os.Getenv("ACME_RELAY_URL")
} }
if 0 == len(*acmeHTTP01Relay) {
*acmeHTTP01Relay = os.Getenv("ACME_HTTP_01_RELAY_URL")
}
if 0 == len(*email) { if 0 == len(*email) {
*email = os.Getenv("ACME_EMAIL") *email = os.Getenv("ACME_EMAIL")
@ -428,6 +401,8 @@ func main() {
} }
authorizer = NewAuthorizer(*authURL) authorizer = NewAuthorizer(*authURL)
var dns01Solver *tbDns01.Solver
if len(*acmeRelay) > 0 {
provider, err := getACMEProvider(acmeRelay, token) provider, err := getACMEProvider(acmeRelay, token)
if nil != err { if nil != err {
fmt.Fprintf(os.Stderr, "%s\n", err) fmt.Fprintf(os.Stderr, "%s\n", err)
@ -436,14 +411,31 @@ func main() {
os.Exit(exitBadArguments) os.Exit(exitBadArguments)
return return
} }
dns01Solver = tbDns01.NewSolver(provider)
}
var http01Solver *http01.Solver
if len(*acmeHTTP01Relay) > 0 {
endpoint, err := url.Parse(*acmeHTTP01Relay)
if nil != err {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(exitBadArguments)
return
}
http01Solver, err = http01.NewSolver(&http01.Config{
Endpoint: endpoint,
Token: *token,
})
}
fmt.Printf("Email: %q\n", *email) fmt.Printf("Email: %q\n", *email)
acme := &telebit.ACME{ acme := &telebit.ACME{
Email: *email, Email: *email,
StoragePath: *certpath, StoragePath: *certpath,
Agree: *acmeAgree, Agree: *acmeAgree,
Directory: *acmeDirectory, Directory: *acmeDirectory,
DNS01Solver: &legoWrapper{ DNS01Solver: dns01Solver,
provider: provider,
/* /*
options: legoDns01.WrapPreCheck(func(domain, fqdn, value string, orig legoDns01.PreCheckFunc) (bool, error) { options: legoDns01.WrapPreCheck(func(domain, fqdn, value string, orig legoDns01.PreCheckFunc) (bool, error) {
ok, err := orig(fqdn, value) ok, err := orig(fqdn, value)
@ -454,8 +446,7 @@ func main() {
return ok, err return ok, err
}), }),
*/ */
dnsSolver: certmagic.DNS01Solver{}, HTTP01Solver: http01Solver,
},
//DNSChallengeOption: legoDns01.DNSProviderOption, //DNSChallengeOption: legoDns01.DNSProviderOption,
EnableHTTPChallenge: *enableHTTP01, EnableHTTPChallenge: *enableHTTP01,
EnableTLSALPNChallenge: *enableTLSALPN01, EnableTLSALPNChallenge: *enableTLSALPN01,

View File

@ -12,16 +12,14 @@ import (
"os" "os"
"strings" "strings"
"sync" "sync"
"time"
telebit "git.rootprojects.org/root/telebit" telebit "git.rootprojects.org/root/telebit"
tbDns01 "git.rootprojects.org/root/telebit/dns01" tbDns01 "git.rootprojects.org/root/telebit/internal/dns01"
"git.rootprojects.org/root/telebit/table" "git.rootprojects.org/root/telebit/table"
"github.com/coolaj86/certmagic" "github.com/coolaj86/certmagic"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/go-acme/lego/v3/challenge" "github.com/go-acme/lego/v3/challenge"
legoDns01 "github.com/go-acme/lego/v3/challenge/dns01"
"github.com/go-acme/lego/v3/providers/dns/duckdns" "github.com/go-acme/lego/v3/providers/dns/duckdns"
"github.com/go-acme/lego/v3/providers/dns/godaddy" "github.com/go-acme/lego/v3/providers/dns/godaddy"
"github.com/go-chi/chi" "github.com/go-chi/chi"
@ -89,7 +87,8 @@ func main() {
StoragePath: *certpath, StoragePath: *certpath,
Agree: *acmeAgree, Agree: *acmeAgree,
Directory: *acmeDirectory, Directory: *acmeDirectory,
DNSProvider: provider, DNS01Solver: tbDns01.NewSolver(provider),
/*
//DNSChallengeOption: legoDns01.DNSProviderOption, //DNSChallengeOption: legoDns01.DNSProviderOption,
DNSChallengeOption: legoDns01.WrapPreCheck(func(domain, fqdn, value string, orig legoDns01.PreCheckFunc) (bool, error) { DNSChallengeOption: legoDns01.WrapPreCheck(func(domain, fqdn, value string, orig legoDns01.PreCheckFunc) (bool, error) {
ok, err := orig(fqdn, value) ok, err := orig(fqdn, value)
@ -99,6 +98,7 @@ func main() {
} }
return ok, err return ok, err
}), }),
*/
EnableHTTPChallenge: *enableHTTP01, EnableHTTPChallenge: *enableHTTP01,
EnableTLSALPNChallenge: *enableTLSALPN01, EnableTLSALPNChallenge: *enableTLSALPN01,
} }

View File

@ -2,5 +2,6 @@ CLIENT_SUBJECT=newbie
TUNNEL_RELAY_URL=https://devices.example.com/ TUNNEL_RELAY_URL=https://devices.example.com/
CLIENT_SECRET=xxxxxxxxxxxxxxxx CLIENT_SECRET=xxxxxxxxxxxxxxxx
LOCALS=https:$CLIENT_SUBJECT.devices.example.com:3000,https:*.$CLIENT_SUBJECT.devices.example.com:3000 LOCALS=https:$CLIENT_SUBJECT.devices.example.com:3000,https:*.$CLIENT_SUBJECT.devices.example.com:3000
#ACME_HTTP_01_RELAY_URL=http://localhost:4200/api/http
#PORT_FORWARDS=3443:3001,8443:3002 #PORT_FORWARDS=3443:3001,8443:3002
#DUCKDNS_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx #DUCKDNS_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

View File

@ -5,6 +5,7 @@ package dns01
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -14,8 +15,11 @@ import (
"path" "path"
"time" "time"
"github.com/coolaj86/certmagic"
"github.com/go-acme/lego/v3/challenge"
"github.com/go-acme/lego/v3/challenge/dns01" "github.com/go-acme/lego/v3/challenge/dns01"
"github.com/go-acme/lego/v3/platform/config/env" "github.com/go-acme/lego/v3/platform/config/env"
"github.com/mholt/acmez/acme"
) )
// Environment variables names. // Environment variables names.
@ -180,3 +184,44 @@ func (d *DNSProvider) doRequest(method, uri string, msg interface{}) error {
return nil return nil
} }
// NewSolver creates a new Solver
func NewSolver(provider challenge.Provider) (lego *Solver) {
return &Solver{
provider: provider,
dnsChecker: certmagic.DNS01Solver{},
}
}
// Solver wraps a Lego DNS Provider for CertMagic
type Solver struct {
provider challenge.Provider
//option legoDns01.ChallengeOption
dnsChecker certmagic.DNS01Solver
}
// Present creates a DNS-01 Challenge Token
func (s *Solver) Present(ctx context.Context, ch acme.Challenge) error {
return s.provider.Present(ch.Identifier.Value, ch.Token, ch.KeyAuthorization)
}
// CleanUp deletes a DNS-01 Challenge Token
func (s *Solver) CleanUp(ctx context.Context, ch acme.Challenge) error {
c := make(chan error)
go func() {
c <- s.provider.CleanUp(ch.Identifier.Value, ch.Token, ch.KeyAuthorization)
}()
select {
case err := <-c:
return err
case <-ctx.Done():
return errors.New("cancelled")
}
}
// Wait blocks until the TXT record created in Present() appears in
// authoritative lookups, i.e. until it has propagated, or until
// timeout, whichever is first.
func (s *Solver) Wait(ctx context.Context, challenge acme.Challenge) error {
return s.dnsChecker.Wait(ctx, challenge)
}

142
internal/http01/http01.go Normal file
View File

@ -0,0 +1,142 @@
package http01
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"time"
"github.com/mholt/acmez/acme"
)
// Config is used to configure the creation of the HTTP-01 Solver.
type Config struct {
Endpoint *url.URL
Token string
HTTPClient *http.Client
}
// Solver implements the challenge.Provider interface.
type Solver struct {
config *Config
}
// Challenge is an ACME http-01 challenge
type Challenge struct {
Type string `json:"type"`
Token string `json:"token"`
KeyAuthorization string `json:"key_authorization"`
Identifier Identifier `json:"identifier"`
}
// Identifier is restricted to DNS Domain Names for now
type Identifier struct {
Type string `json:"type"`
Value string `json:"value"`
}
// NewSolver return a new HTTP-01 Solver.
func NewSolver(config *Config) (*Solver, error) {
if config == nil {
return nil, errors.New("api: the configuration of the DNS provider is nil")
}
if config.Endpoint == nil {
return nil, errors.New("api: the endpoint is missing")
}
if nil == config.HTTPClient {
config.HTTPClient = &http.Client{
Timeout: 5 * time.Second,
}
}
return &Solver{config: config}, nil
}
// Present creates a DNS-01 Challenge Token
func (s *Solver) Present(ctx context.Context, ch acme.Challenge) error {
msg := &Challenge{
Type: "http-01",
Token: ch.Token,
KeyAuthorization: ch.KeyAuthorization,
Identifier: Identifier{
Type: ch.Identifier.Type,
Value: ch.Identifier.Value,
},
}
err := s.doRequest(http.MethodPost, fmt.Sprintf("/%s", msg.Identifier.Value), msg)
if err != nil {
return fmt.Errorf("api: %w", err)
}
return nil
}
// CleanUp deletes an HTTP-01 Challenge Token
func (s *Solver) CleanUp(ctx context.Context, ch acme.Challenge) error {
msg := &Challenge{
Type: "http-01",
Token: ch.Token,
KeyAuthorization: ch.KeyAuthorization,
Identifier: Identifier{
Type: ch.Identifier.Type,
Value: ch.Identifier.Value,
},
}
err := s.doRequest(
http.MethodDelete,
fmt.Sprintf("/%s/%s/%s/%s", msg.Identifier.Value, msg.Token, msg.KeyAuthorization, msg.Type),
nil,
)
if err != nil {
return fmt.Errorf("api: %w", err)
}
return nil
}
func (s *Solver) doRequest(method, uri string, msg interface{}) error {
data, _ := json.MarshalIndent(msg, "", " ")
reqBody := bytes.NewBuffer(data)
newURI := path.Join(s.config.Endpoint.EscapedPath(), uri)
endpoint, err := s.config.Endpoint.Parse(newURI)
if err != nil {
return err
}
req, err := http.NewRequest(method, endpoint.String(), reqBody)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if len(s.config.Token) > 0 {
req.Header.Set("Authorization", "Bearer "+s.config.Token)
}
resp, err := s.config.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("%d: failed to read response body: %w", resp.StatusCode, err)
}
return fmt.Errorf("%d: request failed: %v", resp.StatusCode, string(body))
}
return nil
}

View File

@ -1,9 +1,10 @@
// +build tools // +build tools
// tools is a faux package for tracking dependencies that don't make it into the code // Package tools is a faux package for tracking dependencies that don't make it into the code
package tools package tools
import ( import (
// these are binaries
_ "git.rootprojects.org/root/go-gitver" _ "git.rootprojects.org/root/go-gitver"
_ "github.com/shurcooL/vfsgen" _ "github.com/shurcooL/vfsgen"
_ "github.com/shurcooL/vfsgen/cmd/vfsgendev" _ "github.com/shurcooL/vfsgen/cmd/vfsgendev"

View File

@ -18,7 +18,9 @@ type Endpoints struct {
APIHost string `json:"api_host"` APIHost string `json:"api_host"`
Tunnel Endpoint `json:"tunnel"` Tunnel Endpoint `json:"tunnel"`
Authenticate Endpoint `json:"authn"` Authenticate Endpoint `json:"authn"`
ChallengeProxy Endpoint `json:"acme_challenge_proxy"`
DNS01Proxy Endpoint `json:"acme_dns_01_proxy"` DNS01Proxy Endpoint `json:"acme_dns_01_proxy"`
HTTP01Proxy Endpoint `json:"acme_http_01_proxy"`
/* /*
{ {
"terms_of_service": ":hostname/tos/", "terms_of_service": ":hostname/tos/",
@ -77,7 +79,13 @@ func Discover(relay string) (*Endpoints, error) {
} }
directives.Authenticate.URL = endpointToURLString(directives.APIHost, directives.Authenticate) directives.Authenticate.URL = endpointToURLString(directives.APIHost, directives.Authenticate)
if len(directives.ChallengeProxy.Pathname) > 0 {
directives.ChallengeProxy.URL = endpointToURLString(directives.APIHost, directives.ChallengeProxy)
}
directives.DNS01Proxy.URL = endpointToURLString(directives.APIHost, directives.DNS01Proxy) directives.DNS01Proxy.URL = endpointToURLString(directives.APIHost, directives.DNS01Proxy)
if len(directives.HTTP01Proxy.Pathname) > 0 {
directives.HTTP01Proxy.URL = endpointToURLString(directives.APIHost, directives.HTTP01Proxy)
}
return directives, nil return directives, nil
} }