bugfix HTTP-01 relay and update docs

This commit is contained in:
AJ ONeal 2020-11-12 06:30:52 -07:00
parent adb12c43b2
commit 8c8d5b150f
20 changed files with 315 additions and 104 deletions

1
.gitignore vendored
View File

@ -24,6 +24,7 @@ telebit-client-windows.exe
/cmd/wsconnect/wsconnect
/cmd/mgmt/mgmt
/cmd/signjwt/signjwt
/signjwt
/cmd/telebit/telebit
/telebit

View File

@ -161,7 +161,7 @@ go build -mod=vendor -ldflags "-s -w" -o signjwt cmd/signjwt/*.go
To generate an `admin` token:
```bash
VERDOR_ID="test-id"
VENDOR_ID="test-id"
SECRET="xxxxxxxxxxx"
TOKEN=$(./signjwt \
--expires-in 15m \

29
cmd/mgmt/README.md Normal file
View File

@ -0,0 +1,29 @@
# MGMT Server
# Config
```bash
VERBOSE=
PORT=6468
# JWT Verification Secret
#SECRET=XxxxxxxxxxxxxxxX
DB_URL=postgres://postgres:postgres@localhost:5432/postgres
DOMAIN=mgmt.example.com
TUNNEL_DOMAIN=tunnel.example.com
NAMECOM_USERNAME=johndoe
NAMECOM_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
# Build
```bash
go generate -mod vendor ./...
pushd cmd/mgmt
go build -mod vendor -o telebit-mgmt
popd
```

View File

@ -58,6 +58,9 @@ func handleDNSRoutes(r chi.Router) {
r.Delete("/{domain}/{token}/{keyAuth}/{challengeType}", deleteChallenge)
}
// TODO pick one and stick with it
r.Route("/acme-relay", handleACMEChallenges)
r.Route("/acme-solver", handleACMEChallenges)
r.Route("/dns", handleACMEChallenges)
r.Route("/http", handleACMEChallenges)
}

View File

@ -15,6 +15,7 @@ import (
"github.com/go-acme/lego/v3/challenge"
"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/namedotcom"
_ "github.com/joho/godotenv/autoload"
)
@ -43,51 +44,89 @@ func help() {
func main() {
var err error
addr := flag.String("address", "", "IPv4 or IPv6 bind address")
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(
"db-url",
"postgres://postgres:postgres@localhost/postgres",
"database (postgres) connection url",
)
flag.StringVar(&secret, "secret", "", "a >= 16-character random string for JWT key signing")
flag.StringVar(&primaryDomain, "domain", "", "the base domain to use for all clients")
flag.StringVar(&relayDomain, "tunnel-domain", "", "the domain name of the tunnel relay service, if different from base domain")
var port string
var lnAddr string
var dbURL string
var challengesPort string
flag.StringVar(&port, "port", "",
"port to listen to (default localhost 3000)")
flag.StringVar(&lnAddr, "listen", "",
"IPv4 or IPv6 bind address + port (instead of --port)")
flag.StringVar(&challengesPort, "challenges-port", "80",
"port to use to respond to .well-known/acme-challenge tokens")
flag.StringVar(&dbURL, "db-url", "postgres://postgres:postgres@localhost:5432/postgres",
"database (postgres) connection url")
flag.StringVar(&secret, "secret", "",
"a >= 16-character random string for JWT key signing")
flag.StringVar(&primaryDomain, "domain", "",
"the base domain to use for all clients")
flag.StringVar(&relayDomain, "tunnel-domain", "",
"the domain name of the tunnel relay service, if different from base domain")
flag.Parse()
if "" == primaryDomain {
help()
os.Exit(1)
if 0 == len(primaryDomain) {
primaryDomain = os.Getenv("DOMAIN")
}
if "" == relayDomain {
if 0 == len(relayDomain) {
relayDomain = os.Getenv("TUNNEL_DOMAIN")
}
if 0 == len(relayDomain) {
relayDomain = primaryDomain
}
if "" != os.Getenv("GODADDY_API_KEY") {
if 0 == len(dbURL) {
dbURL = os.Getenv("DB_URL")
}
if 0 == len(secret) {
secret = os.Getenv("SECRET")
}
// prefer --listen (with address) over --port (localhost only)
if 0 == len(lnAddr) {
lnAddr = os.Getenv("LISTEN")
}
if 0 == len(lnAddr) {
if 0 == len(port) {
port = os.Getenv("PORT")
}
if 0 == len(port) {
port = "3000"
}
lnAddr = "localhost:" + port
}
if len(os.Getenv("GODADDY_API_KEY")) > 0 {
id := os.Getenv("GODADDY_API_KEY")
apiSecret := os.Getenv("GODADDY_API_SECRET")
if provider, err = newGoDaddyDNSProvider(id, apiSecret); nil != err {
panic(err)
}
} else if "" != os.Getenv("DUCKDNS_TOKEN") {
} else if len(os.Getenv("DUCKDNS_TOKEN")) > 0 {
if provider, err = newDuckDNSProvider(os.Getenv("DUCKDNS_TOKEN")); nil != err {
panic(err)
}
} else if len(os.Getenv("NAMECOM_API_TOKEN")) > 0 {
if provider, err = newNameDotComDNSProvider(
os.Getenv("NAMECOM_USERNAME"),
os.Getenv("NAMECOM_API_TOKEN"),
); nil != err {
panic(err)
}
} else {
panic("Must provide either DUCKDNS or GODADDY credentials")
fmt.Println("DNS-01 relay disabled")
}
if "" == secret {
secret = os.Getenv("SECRET")
}
if "" == secret {
if 0 == len(primaryDomain) || 0 == len(secret) || 0 == len(dbURL) {
help()
os.Exit(1)
return
}
connStr := *dbURL
connStr := dbURL
// TODO url.Parse
if strings.Contains(connStr, "@localhost/") || strings.Contains(connStr, "@localhost:") {
connStr += "?sslmode=disable"
@ -104,16 +143,23 @@ func main() {
defer store.Close()
go func() {
fmt.Println("Listening for ACME challenges on :" + *challengesPort)
if err := http.ListenAndServe(":"+*challengesPort, routeStatic()); nil != err {
fmt.Println("Listening for ACME challenges on :" + challengesPort)
if err := http.ListenAndServe(":"+challengesPort, routeStatic()); nil != err {
log.Fatal(err)
os.Exit(1)
}
}()
bind := *addr + ":" + *port
fmt.Println("Listening on", bind)
fmt.Fprintf(os.Stderr, "failed: %s", http.ListenAndServe(bind, routeAll()))
fmt.Println("Listening on", lnAddr)
fmt.Fprintf(os.Stderr, "failed: %s", http.ListenAndServe(lnAddr, routeAll()))
}
// newNameDotComDNSProvider is for the sake of demoing the tunnel
func newNameDotComDNSProvider(username, apitoken string) (*namedotcom.DNSProvider, error) {
config := namedotcom.NewDefaultConfig()
config.Username = username
config.APIToken = apitoken
return namedotcom.NewDNSProviderConfig(config)
}
// newDuckDNSProvider is for the sake of demoing the tunnel

View File

@ -18,6 +18,7 @@ import (
"github.com/go-chi/chi/middleware"
)
// MgmtClaims includes a Slug, for backwards compatibility
type MgmtClaims struct {
Slug string `json:"slug"`
jwt.StandardClaims
@ -197,7 +198,7 @@ func routeAll() chi.Router {
http.Error(w, msg, http.StatusNotFound)
return
}
if "" != original.MachinePPID {
if len(original.MachinePPID) > 0 {
msg := `{"error":"the presented key has already been used", "code":"E_EXIST"}`
log.Printf("/api/register-device/\n")
log.Println(err)

View File

@ -9,6 +9,7 @@ import (
"fmt"
"os"
"strconv"
"time"
telebit "git.rootprojects.org/root/telebit"
"git.rootprojects.org/root/telebit/dbg"
@ -30,6 +31,7 @@ var durAbbrs = map[byte]bool{
func main() {
var secret, clientSecret, relaySecret string
debug := flag.Bool("debug", true, "show more debug output")
machinePPID := flag.String("machine-ppid", "", "spoof the machine ppid")
machineID := flag.String("machine-id", "", "spoof the raw machine id")
vendorID := flag.String("vendor-id", "", "a unique identifier for a deploy target environment")
@ -39,6 +41,10 @@ func main() {
flag.StringVar(&secret, "secret", "", "either the remote server or the tunnel relay secret (used for JWT authentication)")
flag.Parse()
if *debug {
dbg.Debug = *debug
}
if 0 == len(*authURL) {
*authURL = os.Getenv("AUTH_URL")
}
@ -145,7 +151,7 @@ func main() {
fmt.Fprintf(os.Stderr, "[debug] pub = %s\n", pub)
}
tok, err := authstore.HMACToken(ppid, expNum)
tok, err := authstore.HMACToken(ppid, 15*time.Minute, expNum)
if nil != err {
fmt.Fprintf(os.Stderr, "signing error: %s\n", err)
os.Exit(1)

0
cmd/telebit/README.md Normal file
View File

View File

@ -66,6 +66,14 @@ func InitAdmin(authURL string) {
// Proxy mgmt server ACME DNS 01 Challenges
r.Get("/api/dns/*", proxyHandleFunc)
r.Post("/api/dns/*", proxyHandleFunc)
r.Delete("/api/dns/*", proxyHandleFunc)
r.Get("/api/http/*", proxyHandleFunc)
r.Post("/api/http/*", proxyHandleFunc)
r.Delete("/api/http/*", proxyHandleFunc)
r.Get("/api/acme-relay/*", proxyHandleFunc)
r.Post("/api/acme-relay/*", proxyHandleFunc)
r.Delete("/api/acme-relay/*", proxyHandleFunc)
r.Route("/api", func(r chi.Router) {
// TODO token needs a globally unique subject

View File

@ -18,9 +18,9 @@ import (
"strings"
"time"
telebit "git.rootprojects.org/root/telebit"
"git.rootprojects.org/root/telebit"
"git.rootprojects.org/root/telebit/dbg"
tbDns01 "git.rootprojects.org/root/telebit/internal/dns01"
"git.rootprojects.org/root/telebit/internal/dns01"
"git.rootprojects.org/root/telebit/internal/http01"
"git.rootprojects.org/root/telebit/internal/service"
"git.rootprojects.org/root/telebit/iplist"
@ -28,7 +28,7 @@ import (
"git.rootprojects.org/root/telebit/mgmt/authstore"
"git.rootprojects.org/root/telebit/table"
"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/coolaj86/certmagic"
"github.com/denisbrodbeck/machineid"
@ -36,6 +36,7 @@ import (
"github.com/go-acme/lego/v3/challenge"
"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/namedotcom"
"github.com/joho/godotenv"
_ "github.com/joho/godotenv/autoload"
)
@ -112,6 +113,7 @@ func main() {
var portForwards []Forward
var resolvers []string
debug := flag.Bool("debug", true, "show debug output")
spfDomain := flag.String("spf-domain", "", "domain with SPF-like list of IP addresses which are allowed to connect to clients")
// TODO replace the websocket connection with a mock server
vendorID := flag.String("vendor-id", "", "a unique identifier for a deploy target environment")
@ -133,7 +135,7 @@ func main() {
apiHostname := flag.String("api-hostname", "", "the hostname used to manage clients")
secret := flag.String("secret", "", "the same secret used by telebit-relay (used for JWT authentication)")
token := flag.String("token", "", "an auth token for the server (instead of generating --secret); use --token=false to ignore any $TOKEN in env")
_ = flag.String("leeway", "", "(reserved for future use) allow for time drift / skew (hard-coded to 15 minutes)")
leeway := flag.Duration("leeway", 15*time.Minute, "allow for time drift / skew (hard-coded to 15 minutes)")
bindAddrsStr := flag.String("listen", "", "list of bind addresses on which to listen, such as localhost:80, or :443")
tlsLocals := flag.String("tls-locals", "", "like --locals, but TLS will be used to connect to the local port")
locals := flag.String("locals", "", "a list of <from-domain>:<to-port>")
@ -142,7 +144,7 @@ func main() {
flag.Parse()
if !dbg.Debug {
dbg.Debug = *verbose
dbg.Debug = *verbose || *debug
}
if len(*envpath) > 0 {
@ -253,7 +255,7 @@ func main() {
for _, resolver := range strings.Fields(strings.ReplaceAll(*resolverList, ",", " ")) {
resolvers = append(resolvers, resolver)
}
legoDns01.AddRecursiveNameservers(resolvers)
legoDNS01.AddRecursiveNameservers(resolvers)
}
if 0 == len(*portToPorts) {
@ -325,7 +327,7 @@ func main() {
*token = ""
}
if 0 == len(*token) {
*token, err = authstore.HMACToken(ppid)
*token, err = authstore.HMACToken(ppid, *leeway)
if dbg.Debug {
fmt.Printf("[debug] app_id: %q\n", VendorID)
//fmt.Printf("[debug] client_secret: %q\n", ClientSecret)
@ -384,19 +386,18 @@ func main() {
authorizer = NewAuthorizer(*authURL)
dns01Base := directory.DNS01Proxy.URL
if "" == *acmeRelay {
if 0 == len(*acmeRelay) {
*acmeRelay = dns01Base
} else {
fmt.Println("Suggested ACME DNS 01 Proxy URL:", dns01Base)
fmt.Println("--acme-relay-url ACME DNS 01 Proxy URL:", *acmeRelay)
}
if "" == *acmeRelay {
fmt.Fprintf(os.Stderr, "Discovered Directory Endpoints: %+v\n", directory)
fmt.Fprintf(os.Stderr, "No ACME DNS 01 Proxy URL detected, nor supplied\n")
os.Exit(exitBadConfig)
return
if 0 == len(*acmeHTTP01Relay) {
*acmeHTTP01Relay = directory.HTTP01Proxy.URL
} else {
fmt.Println("Suggested ACME HTTP 01 Proxy URL:", dns01Base)
fmt.Println("--acme-http-01-relay-url ACME DNS 01 Proxy URL:", *acmeRelay)
}
fmt.Println("DNS 01 URL", *acmeRelay)
grants, err = telebit.Inspect(*authURL, *token)
if nil != err {
@ -429,7 +430,19 @@ func main() {
}
authorizer = NewAuthorizer(*authURL)
var dns01Solver *tbDns01.Solver
fmt.Printf("Email: %q\n", *email)
acme := &telebit.ACME{
Email: *email,
StoragePath: *certpath,
Agree: *acmeAgree,
Directory: *acmeDirectory,
EnableTLSALPNChallenge: *enableTLSALPN01,
}
// TODO
// Blog about the stupidity of this typing
// var dns01Solver *dns01.Solver = nil
if len(*acmeRelay) > 0 {
provider, err := getACMEProvider(acmeRelay, token)
if nil != err {
@ -439,33 +452,8 @@ func main() {
os.Exit(exitBadArguments)
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)
acme := &telebit.ACME{
Email: *email,
StoragePath: *certpath,
Agree: *acmeAgree,
Directory: *acmeDirectory,
DNS01Solver: dns01Solver,
/*
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)
if ok && dnsPropagationDelay > 0 {
fmt.Printf("[Telebit-ACME-DNS] sleeping an additional %s\n", dnsPropagationDelay)
@ -474,10 +462,35 @@ func main() {
return ok, err
}),
*/
HTTP01Solver: http01Solver,
//DNSChallengeOption: legoDns01.DNSProviderOption,
EnableHTTPChallenge: *enableHTTP01,
EnableTLSALPNChallenge: *enableTLSALPN01,
//DNSChallengeOption: legoDNS01.DNSProviderOption,
acme.DNS01Solver = dns01.NewSolver(provider)
fmt.Println("Using DNS-01 solver for ACME Challenges")
}
if *enableHTTP01 {
acme.EnableHTTPChallenge = true
}
if len(*acmeHTTP01Relay) > 0 {
acme.EnableHTTPChallenge = true
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,
})
acme.HTTP01Solver = http01Solver
fmt.Println("Using HTTP-01 solver for ACME Challenges")
}
if nil == acme.HTTP01Solver && nil == acme.DNS01Solver {
fmt.Fprintf(os.Stderr, "Neither ACME HTTP 01 nor DNS 01 proxy URL detected, nor supplied\n")
os.Exit(1)
return
}
mux := muxAll(portForwards, forwards, acme, apiHostname, authURL, grants)
@ -533,7 +546,7 @@ func main() {
time.Sleep(10 * time.Minute)
if "" != ClientSecret {
// re-create token unless no secret was supplied
*token, err = authstore.HMACToken(ppid)
*token, err = authstore.HMACToken(ppid, *leeway)
}
err = mgmt.Ping(*authURL, *token)
if nil != err {
@ -831,6 +844,13 @@ func getACMEProvider(acmeRelay, token *string) (challenge.Provider, error) {
if provider, err = newGoDaddyDNSProvider(id, apiSecret); nil != err {
return nil, err
}
} else if "" != os.Getenv("NAMECOM_API_TOKEN") {
if provider, err = newNameDotComDNSProvider(
os.Getenv("NAMECOM_USERNAME"),
os.Getenv("NAMECOM_API_TOKEN"),
); nil != err {
return nil, err
}
} else if "" != os.Getenv("DUCKDNS_TOKEN") {
if provider, err = newDuckDNSProvider(os.Getenv("DUCKDNS_TOKEN")); nil != err {
return nil, err
@ -857,6 +877,14 @@ func getACMEProvider(acmeRelay, token *string) (challenge.Provider, error) {
return provider, nil
}
// newNameDotComDNSProvider is for the sake of demoing the tunnel
func newNameDotComDNSProvider(username, apitoken string) (*namedotcom.DNSProvider, error) {
config := namedotcom.NewDefaultConfig()
config.Username = username
config.APIToken = apitoken
return namedotcom.NewDNSProviderConfig(config)
}
// newDuckDNSProvider is for the sake of demoing the tunnel
func newDuckDNSProvider(token string) (*duckdns.DNSProvider, error) {
config := duckdns.NewDefaultConfig()
@ -873,15 +901,15 @@ func newGoDaddyDNSProvider(id, secret string) (*godaddy.DNSProvider, error) {
}
// newAPIDNSProvider is for the sake of demoing the tunnel
func newAPIDNSProvider(baseURL string, token string) (*tbDns01.DNSProvider, error) {
config := tbDns01.NewDefaultConfig()
func newAPIDNSProvider(baseURL string, token string) (*dns01.DNSProvider, error) {
config := dns01.NewDefaultConfig()
config.Token = token
endpoint, err := url.Parse(baseURL)
if nil != err {
return nil, err
}
config.Endpoint = endpoint
return tbDns01.NewDNSProviderConfig(config)
return dns01.NewDNSProviderConfig(config)
}
/*

View File

@ -1,7 +1,31 @@
CLIENT_SUBJECT=newbie
# TUNNEL_RELAY_URL
# The URL of the Telebit Relay, of course.
# Note that many client configuration details can be preassigned at
# https://devices.example.com/.well-known/telebit.app/index.json
TUNNEL_RELAY_URL=https://devices.example.com/
# VENDOR_ID
# Used to distinguish between different white-labeled Telebit binaries.
# It's just as well to generate a random ID for your organization.
VENDOR_ID=
# Used for Let's Encrypt registration
# ACME_AGREE
ACME_AGREE=true
# ACME_EMAIL
ACME_EMAIL=johndoe@example.com
# CLIENT_SUBJECT (optional)
# NOT used by Telebit.
# This is for the Device Management & Authentication server.
CLIENT_SUBJECT=newbie
CLIENT_SECRET=xxxxxxxxxxxxxxxx
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
#DUCKDNS_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# AUTH_URL
# The client may receive this as `.authn.url`
# through `https://$API_DOMAIN/.well-known/telebit.app/index.json`
# Setting the ENV AUTH_URL or the flag --auth-url overrides the discovery endpoint

View File

@ -1,8 +1,32 @@
# For bash tests
MGMT_SECRET=xxxxxxxxxxxxxxxx
MGMT_URL=https://devices.example.com
# DOMAIN
# This is the base domain from which all devices
# will be given a subdomain (ex: foobar.devices.example.com).
DOMAIN=devices.example.com
# TUNNEL_DOMAIN
# This is the domain that will be used for the wss:// connection URL.
TUNNEL_DOMAIN=new.telebit.cloud
# For mgmt server itself
#SECRET=XxxxxxxxxxxxxxxX
DB_URL=postgres://postgres:postgres@localhost:5432/postgres
# PORT
# the localhost port on which to listen
PORT=6468
# LISTEN
# alternative to PORT, including address
#LISTEN=localhost:6468
DUCKDNS_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
GODADDY_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
GODADDY_API_SECRET=XXXXXXXXXXXXXXXXXXXXXX
#NAMECOM_USERNAME=johndoe
#NAMECOM_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#GODADDY_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#GODADDY_API_SECRET=XXXXXXXXXXXXXXXXXXXXXX
# For bash tests
MGMT_SECRET=XxxxxxxxxxxxxxxX
MGMT_URL=https://devices.example.com

View File

@ -1,21 +1,46 @@
# For Tunnel Relay Service
# SECRET
# This should be the same as the MGMT server secret
# It is used for JWT token creation and verification
SECRET=xxxxxxxxxxxxxxxx
# VERBOSE=true
# This will cause more verbose logs
VERBOSE=true
# API_HOSTNAME
# This is the domain name that should be captured for the API
# (as opposed to being routed downstream)
# If this is not set, the relay will not be active.
API_HOSTNAME=devices.example.com
# LISTEN
# This is the addr:port combo to which telebit should bind and listen.
# Note: a tunnel client can itself still be a relay through the tunnel.
LISTEN=":443"
# To proxy incoming requests for 'https://mgmt.devices.example.com' to localhost:3010
LOCALS=https:mgmt.devices.example.com:3010
# LOCALS
# Act as a reverse proxy for matching incoming requests
# LOCALS=<source-proto>:<source-name>:<destination-port>
# Example: 'https://mgmt.devices.example.com' to localhost:6468
LOCALS=https:mgmt.devices.example.com:6468
# For Device Management & Authorization Server
AUTH_URL=http://localhost:4200/api
# AUTH_URL
# Telebit is narrowly scoped to handle network connections
# The concerns of Device Management & Authorization should
# be handled per each specific use case.
AUTH_URL=http://localhost:6468/api
# For Let's Encrypt ACME registration
# For Let's Encrypt ACME registration of the API_HOSTNAME
# and LOCALS (reverse-proxied traffic).
# This is NOT for the remote telebit clients!
ACME_AGREE=true
ACME_EMAIL=jon.doe@example.com
# For Let's Encrypt ACME Challenges (pick one)
ACME_RELAY_URL=http://localhost:4200/api/dns
SECRET=xxxxxxxxxxxxxxxx
#DUCKDNS_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
ACME_RELAY_URL=http://localhost:6468/api/acme-relay
# (pick ONLY ONE DNS-01 provider)
DUCKDNS_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
#NAMECOM_USERNAME=johndoe
#NAMECOM_API_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#GODADDY_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#GODADDY_API_SECRET=XXXXXXXXXXXXXXXXXXXXXX

1
go.sum
View File

@ -239,6 +239,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g=
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=

View File

@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"path"
@ -202,11 +203,13 @@ type Solver struct {
// Present creates a DNS-01 Challenge Token
func (s *Solver) Present(ctx context.Context, ch acme.Challenge) error {
log.Println("Present DNS-01 challenge solution for", ch.Identifier.Value)
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 {
log.Println("CleanUp DNS-01 challenge solution for", ch.Identifier.Value)
c := make(chan error)
go func() {
c <- s.provider.CleanUp(ch.Identifier.Value, ch.Token, ch.KeyAuthorization)
@ -222,6 +225,7 @@ func (s *Solver) CleanUp(ctx context.Context, ch acme.Challenge) error {
// 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)
func (s *Solver) Wait(ctx context.Context, ch acme.Challenge) error {
log.Println("Wait on DNS-01 challenge self-verification for", ch.Identifier.Value)
return s.dnsChecker.Wait(ctx, ch)
}

View File

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"path"
@ -60,8 +61,9 @@ func NewSolver(config *Config) (*Solver, error) {
return &Solver{config: config}, nil
}
// Present creates a DNS-01 Challenge Token
// Present creates a HTTP-01 Challenge Token
func (s *Solver) Present(ctx context.Context, ch acme.Challenge) error {
log.Println("Present HTTP-01 challenge solution for", ch.Identifier.Value)
msg := &Challenge{
Type: "http-01",
Token: ch.Token,
@ -81,6 +83,7 @@ func (s *Solver) Present(ctx context.Context, ch acme.Challenge) error {
// CleanUp deletes an HTTP-01 Challenge Token
func (s *Solver) CleanUp(ctx context.Context, ch acme.Challenge) error {
log.Println("CleanUp HTTP-01 challenge solution for", ch.Identifier.Value)
msg := &Challenge{
Type: "http-01",
Token: ch.Token,

View File

@ -52,7 +52,7 @@ func ToPublicKeyString(secret string) string {
return pub
}
func HMACToken(secret string, maybeExp ...int) (token string, err error) {
func HMACToken(secret string, leeway time.Duration, maybeExp ...int) (token string, err error) {
keyID := ToPublicKeyString(secret)
if dbg.Debug {
fmt.Fprintf(os.Stderr, "[debug] keyID=%s\n", keyID)
@ -67,7 +67,6 @@ func HMACToken(secret string, maybeExp ...int) (token string, err error) {
b := make([]byte, 16)
_, _ = rand.Read(b)
leeway := 15 * time.Minute
claims := &jwt.StandardClaims{
Id: base64.RawURLEncoding.EncodeToString(b),
Subject: "", // TODO

View File

@ -443,7 +443,7 @@ func NewCertMagic(acme *ACME) (*certmagic.Config, error) {
})
// yes, a circular reference, passing `magic` to its own Issuer
fmt.Printf("ACME Email: %q\n", acme.Email)
magic.Issuer = certmagic.NewACMEManager(magic, certmagic.ACMEManager{
manager := certmagic.ACMEManager{
DNS01Solver: acme.DNS01Solver,
HTTP01Solver: acme.HTTP01Solver,
CA: acme.Directory,
@ -452,7 +452,9 @@ func NewCertMagic(acme *ACME) (*certmagic.Config, error) {
DisableHTTPChallenge: !acme.EnableHTTPChallenge,
DisableTLSALPNChallenge: !acme.EnableTLSALPNChallenge,
// plus any other customizations you need
})
}
magic.Issuer = certmagic.NewACMEManager(magic, manager)
return magic, nil
}

View File

@ -82,7 +82,9 @@ func Discover(relay string) (*Endpoints, error) {
if len(directives.ChallengeProxy.Pathname) > 0 {
directives.ChallengeProxy.URL = endpointToURLString(directives.APIHost, directives.ChallengeProxy)
}
directives.DNS01Proxy.URL = endpointToURLString(directives.APIHost, directives.DNS01Proxy)
if len(directives.DNS01Proxy.Pathname) > 0 {
directives.DNS01Proxy.URL = endpointToURLString(directives.APIHost, directives.DNS01Proxy)
}
if len(directives.HTTP01Proxy.Pathname) > 0 {
directives.HTTP01Proxy.URL = endpointToURLString(directives.APIHost, directives.HTTP01Proxy)
}

5
vendor/modules.txt vendored
View File

@ -34,6 +34,7 @@ github.com/go-acme/lego/v3/platform/tester
github.com/go-acme/lego/v3/platform/wait
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/namedotcom
# github.com/go-chi/chi v4.1.1+incompatible
## explicit
github.com/go-chi/chi
@ -116,8 +117,12 @@ github.com/mholt/acmez
github.com/mholt/acmez/acme
# github.com/miekg/dns v1.1.30
github.com/miekg/dns
# github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04
github.com/namedotcom/go/namecom
# github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e
## explicit
# github.com/pkg/errors v0.9.1
github.com/pkg/errors
# github.com/pmezard/go-difflib v1.0.0
github.com/pmezard/go-difflib/difflib
# github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749