-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial challtestsrv package & vendored deps. (#1)
Boulder has a nice handy [`challtestsrv` package and command][1] used for integration tests. It's small, stand-alone, and useful enough to live in its own repo. This will make it easy for Boulder's load-generator to use the common package and for Pebble's pebble-challtestsrv command to use it as well. The `challtestsrv` package is ported over from Boulder mostly-as is with a few small improvements. Notably: * The TLS-ALPN-01 and HTTPS HTTP-01 features were split into two separate binds. This helps us preserve strict TLS-ALPN-01 challenge responses while also supporting HTTP-01 -> HTTPS HTTP-01 redirects. (See letsencrypt/boulder#3962) * The "FAKE_DNS" env var is removed. Now there is a default IPv4 and a default IPv6 address that can be set via the management API. These default addresses are used for A/AAAA query responses when there is not a more specific mock. * Hardcoded Boulder specific mock DNS data is removed. In its place are new management API functions for adding/removing mock A, AAAA, and CAA records * The output is less noisy now. The DNS server no longer prints a line per reply. [1]: https://github.com/letsencrypt/boulder/tree/9e39680e3f78c410e2d780a7badfe200a31698eb/test/challtestsrv
- Loading branch information
Showing
579 changed files
with
231,643 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
language: go | ||
go: | ||
- "1.11.x" | ||
env: | ||
- GO111MODULE=on | ||
|
||
# Override the base install phase so that the project can be installed using | ||
# `-mod=vendor` to use the vendored dependencies | ||
install: | ||
- go install -mod=vendor -v -race ./... | ||
|
||
script: | ||
- go vet -mod=vendor -v ./... | ||
- go test -mod=vendor -v -race ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,60 @@ | ||
# challtestsrv | ||
Small TEST-ONLY server for mock DNS & responding to HTTP-01, DNS-01, and TLS-ALPN-01 ACME challenges. | ||
# Challenge Test Server | ||
|
||
The `challtestsrv` package offers a library/command that can be used by test | ||
code to respond to HTTP-01, DNS-01, and TLS-ALPN-01 ACME challenges. The | ||
`challtestsrv` package can also be used as a mock DNS server letting | ||
developers mock `A`, `AAAA`, and `CAA` DNS data for specific hostnames. | ||
|
||
**Important note: The `challtestsrv` command and library are for TEST USAGE | ||
ONLY. It is trivially insecure, offering no authentication. Only use | ||
`challtestsrv` in a controlled test environment.** | ||
|
||
For example this package is used by the Boulder | ||
[`load-generator`](https://github.com/letsencrypt/boulder/tree/9e39680e3f78c410e2d780a7badfe200a31698eb/test/load-generator) | ||
command to manage its own in-process HTTP-01 challenge server. | ||
|
||
### Usage | ||
|
||
Create a challenge server responding to HTTP-01 challenges on ":8888" and | ||
DNS-01 challenges on ":9999" and "10.0.0.1:9998": | ||
|
||
``` | ||
import "github.com/letsencrypt/pebble/challtestsrv" | ||
challSrv, err := challtestsrv.New(challsrv.Config{ | ||
HTTPOneAddr: []string{":8888"}, | ||
DNSOneAddr: []string{":9999", "10.0.0.1:9998"}, | ||
}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
``` | ||
|
||
Run the Challenge server and subservers: | ||
``` | ||
// Start the Challenge server in its own Go routine | ||
go challSrv.Run() | ||
``` | ||
|
||
Add an HTTP-01 response for the token `"aaa"` and the value `"bbb"`, defer | ||
cleaning it up again: | ||
``` | ||
challSrv.AddHTTPOneChallenge("aaa", "bbb") | ||
defer challSrv.DeleteHTTPOneChallenge("aaa") | ||
``` | ||
|
||
Add a DNS-01 TXT response for the host `"_acme-challenge.example.com."` and the | ||
value `"bbb"`, defer cleaning it up again: | ||
``` | ||
challSrv.AddDNSOneChallenge("_acme-challenge.example.com.", "bbb") | ||
defer challSrv.DeleteHTTPOneChallenge("_acme-challenge.example.com.") | ||
``` | ||
|
||
Stop the Challenge server and subservers: | ||
``` | ||
// Shutdown the Challenge server | ||
challSrv.Shutdown() | ||
``` | ||
|
||
For more information on the package API see Godocs and the associated package | ||
sourcecode. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
// Package challtestsrv provides a trivially insecure acme challenge response | ||
// server for rapidly testing HTTP-01, DNS-01 and TLS-ALPN-01 challenge types. | ||
package challtestsrv | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"os" | ||
"sync" | ||
) | ||
|
||
const ( | ||
// Default to using localhost for both A and AAAA queries that don't match | ||
// more specific mock host data. | ||
defaultIPv4 = "127.0.0.1" | ||
defaultIPv6 = "::1" | ||
) | ||
|
||
// challengeServers offer common functionality to start up and shutdown. | ||
type challengeServer interface { | ||
ListenAndServe() error | ||
Shutdown() error | ||
} | ||
|
||
// ChallSrv is a multi-purpose challenge server. Each ChallSrv may have one or | ||
// more ACME challenges it provides servers for. It is safe to use concurrently. | ||
type ChallSrv struct { | ||
log *log.Logger | ||
|
||
// servers are the individual challenge server listeners started in New() and | ||
// closed in Shutdown(). | ||
servers []challengeServer | ||
|
||
// challMu is a RWMutex used to control concurrent updates to the challenge | ||
// response data maps below. | ||
challMu sync.RWMutex | ||
|
||
// httpOne is a map of token values to key authorizations used for HTTP-01 | ||
// responses. | ||
httpOne map[string]string | ||
|
||
// dnsOne is a map of DNS host values to key authorizations used for DNS-01 | ||
// responses. | ||
dnsOne map[string][]string | ||
|
||
// dnsMocks holds mock DNS data used to respond to DNS queries other than | ||
// DNS-01 TXT challenge lookups. | ||
dnsMocks mockDNSData | ||
|
||
// tlsALPNOne is a map of token values to key authorizations used for TLS-ALPN-01 | ||
// responses. | ||
tlsALPNOne map[string]string | ||
|
||
// redirects is a map of paths to URLs. HTTP challenge servers respond to | ||
// requests for these paths with a 301 to the corresponding URL. | ||
redirects map[string]string | ||
} | ||
|
||
// mockDNSData holds mock respones for DNS A, AAAA, and CAA lookups. | ||
type mockDNSData struct { | ||
// The IPv4 address used for all A record responses that don't match a host in | ||
// aRecords. | ||
defaultIPv4 string | ||
// The IPv6 address used for all AAAA record responses that don't match a host | ||
// in aaaaRecords. | ||
defaultIPv6 string | ||
// A map of host to IPv4 addressess in string form for A record responses. | ||
aRecords map[string][]string | ||
// A map of host to IPv6 addresses in string form for AAAA record responses. | ||
aaaaRecords map[string][]string | ||
// A map of host to CAA policies for CAA responses. | ||
caaRecords map[string][]MockCAAPolicy | ||
} | ||
|
||
// MockCAAPolicy holds a tag and a value for a CAA record. See | ||
// https://tools.ietf.org/html/rfc6844 | ||
type MockCAAPolicy struct { | ||
Tag string | ||
Value string | ||
} | ||
|
||
// Config holds challenge server configuration | ||
type Config struct { | ||
Log *log.Logger | ||
// HTTPOneAddrs are the HTTP-01 challenge server bind addresses/ports | ||
HTTPOneAddrs []string | ||
// HTTPSOneAddrs are the HTTPS HTTP-01 challenge server bind addresses/ports | ||
HTTPSOneAddrs []string | ||
// DNSOneAddrs are the DNS-01 challenge server bind addresses/ports | ||
DNSOneAddrs []string | ||
// TLSALPNOneAddrs are the TLS-ALPN-01 challenge server bind addresses/ports | ||
TLSALPNOneAddrs []string | ||
} | ||
|
||
// validate checks that a challenge server Config is valid. To be valid it must | ||
// specify a bind address for at least one challenge type. If there is no | ||
// configured log in the config a default is provided. | ||
func (c *Config) validate() error { | ||
// There needs to be at least one challenge type with a bind address | ||
if len(c.HTTPOneAddrs) < 1 && | ||
len(c.HTTPSOneAddrs) < 1 && | ||
len(c.DNSOneAddrs) < 1 && | ||
len(c.TLSALPNOneAddrs) < 1 { | ||
return fmt.Errorf( | ||
"config must specify at least one HTTPOneAddrs entry, one HTTPSOneAddr " + | ||
"entry, one DNSOneAddrs entry, or one TLSALPNOneAddrs entry") | ||
} | ||
// If there is no configured log make a default with a prefix | ||
if c.Log == nil { | ||
c.Log = log.New(os.Stdout, "challtestsrv - ", log.LstdFlags) | ||
} | ||
return nil | ||
} | ||
|
||
// New constructs and returns a new ChallSrv instance with the given Config. | ||
func New(config Config) (*ChallSrv, error) { | ||
// Validate the provided configuration | ||
if err := config.validate(); err != nil { | ||
return nil, err | ||
} | ||
|
||
challSrv := &ChallSrv{ | ||
log: config.Log, | ||
httpOne: make(map[string]string), | ||
dnsOne: make(map[string][]string), | ||
tlsALPNOne: make(map[string]string), | ||
redirects: make(map[string]string), | ||
dnsMocks: mockDNSData{ | ||
defaultIPv4: defaultIPv4, | ||
defaultIPv6: defaultIPv6, | ||
aRecords: make(map[string][]string), | ||
aaaaRecords: make(map[string][]string), | ||
caaRecords: make(map[string][]MockCAAPolicy), | ||
}, | ||
} | ||
|
||
// If there are HTTP-01 addresses configured, create HTTP-01 servers with | ||
// HTTPS disabled. | ||
for _, address := range config.HTTPOneAddrs { | ||
challSrv.log.Printf("Creating HTTP-01 challenge server on %s\n", address) | ||
challSrv.servers = append(challSrv.servers, httpOneServer(address, challSrv, false)) | ||
} | ||
|
||
// If there are HTTPS HTTP-01 addresses configured, create HTTP-01 servers | ||
// with HTTPS enabled. | ||
for _, address := range config.HTTPSOneAddrs { | ||
challSrv.log.Printf("Creating HTTPS HTTP-01 challenge server on %s\n", address) | ||
challSrv.servers = append(challSrv.servers, httpOneServer(address, challSrv, true)) | ||
} | ||
|
||
// If there are DNS-01 addresses configured, create DNS-01 servers | ||
for _, address := range config.DNSOneAddrs { | ||
challSrv.log.Printf("Creating TCP and UDP DNS-01 challenge server on %s\n", address) | ||
challSrv.servers = append(challSrv.servers, | ||
dnsOneServer(address, challSrv.dnsHandler)...) | ||
} | ||
|
||
// If there are TLS-ALPN-01 addresses configured, create TLS-ALPN-01 servers | ||
for _, address := range config.TLSALPNOneAddrs { | ||
challSrv.log.Printf("Creating TLS-ALPN-01 challenge server on %s\n", address) | ||
challSrv.servers = append(challSrv.servers, tlsALPNOneServer(address, challSrv)) | ||
} | ||
|
||
return challSrv, nil | ||
} | ||
|
||
// Run starts each of the ChallSrv's challengeServers. | ||
func (s *ChallSrv) Run() { | ||
s.log.Printf("Starting challenge servers") | ||
|
||
// Start each server in their own dedicated Go routine | ||
for _, srv := range s.servers { | ||
go func(srv challengeServer) { | ||
err := srv.ListenAndServe() | ||
if err != nil { | ||
s.log.Print(err) | ||
} | ||
}(srv) | ||
} | ||
} | ||
|
||
// Shutdown gracefully stops each of the ChallSrv's challengeServers. | ||
func (s *ChallSrv) Shutdown() { | ||
for _, srv := range s.servers { | ||
if err := srv.Shutdown(); err != nil { | ||
s.log.Printf("err in Shutdown(): %s\n", err.Error()) | ||
} | ||
} | ||
} |
Oops, something went wrong.