forked from TrueCloudLab/certificates
Add support for custom acme ports
This change adds the flags --acme-http-port, --acme-tls-port, that combined with --insecure can be used to set custom ports for ACME http-01 and tls-alpn-01 challenges. These flags should only be used for testing purposes. Fixes #1015
This commit is contained in:
parent
9d90d0cef3
commit
e27c6c529b
3 changed files with 171 additions and 2 deletions
|
@ -44,6 +44,18 @@ const (
|
||||||
DEVICEATTEST01 ChallengeType = "device-attest-01"
|
DEVICEATTEST01 ChallengeType = "device-attest-01"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// InsecurePortHTTP01 is the port used to verify http-01 challenges. If not set it
|
||||||
|
// defaults to 80.
|
||||||
|
InsecurePortHTTP01 int
|
||||||
|
|
||||||
|
// InsecurePortTLSALPN01 is the port used to verify tls-alpn-01 challenges. If not
|
||||||
|
// set it defaults to 443.
|
||||||
|
//
|
||||||
|
// This variable can be used for testing purposes.
|
||||||
|
InsecurePortTLSALPN01 int
|
||||||
|
)
|
||||||
|
|
||||||
// Challenge represents an ACME response Challenge type.
|
// Challenge represents an ACME response Challenge type.
|
||||||
type Challenge struct {
|
type Challenge struct {
|
||||||
ID string `json:"-"`
|
ID string `json:"-"`
|
||||||
|
@ -93,6 +105,12 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey,
|
||||||
func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey) error {
|
func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey) error {
|
||||||
u := &url.URL{Scheme: "http", Host: http01ChallengeHost(ch.Value), Path: fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Token)}
|
u := &url.URL{Scheme: "http", Host: http01ChallengeHost(ch.Value), Path: fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Token)}
|
||||||
|
|
||||||
|
// Append insecure port if set.
|
||||||
|
// Only used for testing purposes.
|
||||||
|
if InsecurePortHTTP01 != 0 {
|
||||||
|
u.Host += ":" + strconv.Itoa(InsecurePortHTTP01)
|
||||||
|
}
|
||||||
|
|
||||||
vc := MustClientFromContext(ctx)
|
vc := MustClientFromContext(ctx)
|
||||||
resp, err := vc.Get(u.String())
|
resp, err := vc.Get(u.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -165,7 +183,14 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
|
||||||
InsecureSkipVerify: true, //nolint:gosec // we expect a self-signed challenge certificate
|
InsecureSkipVerify: true, //nolint:gosec // we expect a self-signed challenge certificate
|
||||||
}
|
}
|
||||||
|
|
||||||
hostPort := net.JoinHostPort(ch.Value, "443")
|
var hostPort string
|
||||||
|
|
||||||
|
// Allow to change TLS port for testing purposes.
|
||||||
|
if port := InsecurePortTLSALPN01; port == 0 {
|
||||||
|
hostPort = net.JoinHostPort(ch.Value, "443")
|
||||||
|
} else {
|
||||||
|
hostPort = net.JoinHostPort(ch.Value, strconv.Itoa(port))
|
||||||
|
}
|
||||||
|
|
||||||
vc := MustClientFromContext(ctx)
|
vc := MustClientFromContext(ctx)
|
||||||
conn, err := vc.TLSDial("tcp", hostPort, config)
|
conn, err := vc.TLSDial("tcp", hostPort, config)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -370,6 +371,47 @@ func TestChallenge_Validate(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ok/http-01-insecure": func(t *testing.T) test {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
InsecurePortHTTP01 = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
ch := &Challenge{
|
||||||
|
ID: "chID",
|
||||||
|
Status: StatusPending,
|
||||||
|
Type: "http-01",
|
||||||
|
Token: "token",
|
||||||
|
Value: "zap.internal",
|
||||||
|
}
|
||||||
|
|
||||||
|
InsecurePortHTTP01 = 8080
|
||||||
|
|
||||||
|
return test{
|
||||||
|
ch: ch,
|
||||||
|
vc: &mockClient{
|
||||||
|
get: func(url string) (*http.Response, error) {
|
||||||
|
return nil, errors.New("force")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
db: &MockDB{
|
||||||
|
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
|
||||||
|
assert.Equals(t, updch.ID, ch.ID)
|
||||||
|
assert.Equals(t, updch.Token, ch.Token)
|
||||||
|
assert.Equals(t, updch.Type, ch.Type)
|
||||||
|
assert.Equals(t, updch.Status, ch.Status)
|
||||||
|
assert.Equals(t, updch.Value, ch.Value)
|
||||||
|
|
||||||
|
err := NewError(ErrorConnectionType, "error doing http GET for url http://zap.internal:8080/.well-known/acme-challenge/%s: force", ch.Token)
|
||||||
|
assert.HasPrefix(t, updch.Error.Err.Error(), err.Err.Error())
|
||||||
|
assert.Equals(t, updch.Error.Type, err.Type)
|
||||||
|
assert.Equals(t, updch.Error.Detail, err.Detail)
|
||||||
|
assert.Equals(t, updch.Error.Status, err.Status)
|
||||||
|
assert.Equals(t, updch.Error.Detail, err.Detail)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
"fail/dns-01": func(t *testing.T) test {
|
"fail/dns-01": func(t *testing.T) test {
|
||||||
ch := &Challenge{
|
ch := &Challenge{
|
||||||
ID: "chID",
|
ID: "chID",
|
||||||
|
@ -501,6 +543,72 @@ func TestChallenge_Validate(t *testing.T) {
|
||||||
srv, tlsDial := newTestTLSALPNServer(cert)
|
srv, tlsDial := newTestTLSALPNServer(cert)
|
||||||
srv.Start()
|
srv.Start()
|
||||||
|
|
||||||
|
return test{
|
||||||
|
ch: ch,
|
||||||
|
vc: &mockClient{
|
||||||
|
tlsDial: tlsDial,
|
||||||
|
},
|
||||||
|
db: &MockDB{
|
||||||
|
MockUpdateChallenge: func(ctx context.Context, updch *Challenge) error {
|
||||||
|
assert.Equals(t, updch.ID, ch.ID)
|
||||||
|
assert.Equals(t, updch.Token, ch.Token)
|
||||||
|
assert.Equals(t, updch.Status, ch.Status)
|
||||||
|
assert.Equals(t, updch.Type, ch.Type)
|
||||||
|
assert.Equals(t, updch.Value, ch.Value)
|
||||||
|
assert.Equals(t, updch.Error, nil)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
srv: srv,
|
||||||
|
jwk: jwk,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ok/tls-alpn-01-insecure": func(t *testing.T) test {
|
||||||
|
t.Cleanup(func() {
|
||||||
|
InsecurePortTLSALPN01 = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
ch := &Challenge{
|
||||||
|
ID: "chID",
|
||||||
|
Token: "token",
|
||||||
|
Type: "tls-alpn-01",
|
||||||
|
Status: StatusPending,
|
||||||
|
Value: "zap.internal",
|
||||||
|
}
|
||||||
|
|
||||||
|
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
|
expKeyAuth, err := KeyAuthorization(ch.Token, jwk)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
expKeyAuthHash := sha256.Sum256([]byte(expKeyAuth))
|
||||||
|
|
||||||
|
cert, err := newTLSALPNValidationCert(expKeyAuthHash[:], false, true, ch.Value)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
|
||||||
|
t.Fatalf("failed to listen on a port: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, port, err := net.SplitHostPort(l.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to split host port: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use an insecure port
|
||||||
|
InsecurePortTLSALPN01, err = strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to convert port to int: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv, tlsDial := newTestTLSALPNServer(cert, func(srv *httptest.Server) {
|
||||||
|
srv.Listener.Close()
|
||||||
|
srv.Listener = l
|
||||||
|
})
|
||||||
|
srv.Start()
|
||||||
|
|
||||||
return test{
|
return test{
|
||||||
ch: ch,
|
ch: ch,
|
||||||
vc: &mockClient{
|
vc: &mockClient{
|
||||||
|
@ -1248,7 +1356,7 @@ func TestDNS01Validate(t *testing.T) {
|
||||||
|
|
||||||
type tlsDialer func(network, addr string, config *tls.Config) (conn *tls.Conn, err error)
|
type tlsDialer func(network, addr string, config *tls.Config) (conn *tls.Conn, err error)
|
||||||
|
|
||||||
func newTestTLSALPNServer(validationCert *tls.Certificate) (*httptest.Server, tlsDialer) {
|
func newTestTLSALPNServer(validationCert *tls.Certificate, opts ...func(*httptest.Server)) (*httptest.Server, tlsDialer) {
|
||||||
srv := httptest.NewUnstartedServer(http.NewServeMux())
|
srv := httptest.NewUnstartedServer(http.NewServeMux())
|
||||||
|
|
||||||
srv.Config.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){
|
srv.Config.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){
|
||||||
|
@ -1273,6 +1381,11 @@ func newTestTLSALPNServer(validationCert *tls.Certificate) (*httptest.Server, tl
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply options
|
||||||
|
for _, fn := range opts {
|
||||||
|
fn(srv)
|
||||||
|
}
|
||||||
|
|
||||||
srv.Listener = tls.NewListener(srv.Listener, srv.TLS)
|
srv.Listener = tls.NewListener(srv.Listener, srv.TLS)
|
||||||
//srv.Config.ErrorLog = log.New(ioutil.Discard, "", 0) // hush
|
//srv.Config.ErrorLog = log.New(ioutil.Discard, "", 0) // hush
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/smallstep/certificates/acme"
|
||||||
"github.com/smallstep/certificates/authority/config"
|
"github.com/smallstep/certificates/authority/config"
|
||||||
"github.com/smallstep/certificates/authority/provisioner"
|
"github.com/smallstep/certificates/authority/provisioner"
|
||||||
"github.com/smallstep/certificates/ca"
|
"github.com/smallstep/certificates/ca"
|
||||||
|
@ -71,6 +72,19 @@ certificate issuer private key used in the RA mode.`,
|
||||||
Usage: "The name of the authority's context.",
|
Usage: "The name of the authority's context.",
|
||||||
EnvVar: "STEP_CA_CONTEXT",
|
EnvVar: "STEP_CA_CONTEXT",
|
||||||
},
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "acme-http-port",
|
||||||
|
Usage: `The port used on http-01 challenges. It can be changed for testing purposes.
|
||||||
|
Requires **--insecure** flag.`,
|
||||||
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "acme-tls-port",
|
||||||
|
Usage: `The port used on tls-alpn-01 challenges. It can be changed for testing purposes.
|
||||||
|
Requires **--insecure** flag.`,
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "insecure",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +102,23 @@ func appAction(ctx *cli.Context) error {
|
||||||
return errs.TooManyArguments(ctx)
|
return errs.TooManyArguments(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allow custom ACME ports with insecure
|
||||||
|
if acmePort := ctx.Int("acme-http-port"); acmePort != 0 {
|
||||||
|
if ctx.Bool("insecure") {
|
||||||
|
acme.InsecurePortHTTP01 = acmePort
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("flag '--acme-http-port' requires the '--insecure' flag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if acmePort := ctx.Int("acme-tls-port"); acmePort != 0 {
|
||||||
|
if ctx.Bool("insecure") {
|
||||||
|
acme.InsecurePortTLSALPN01 = acmePort
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("flag '--acme-tls-port' requires the '--insecure' flag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow custom contexts.
|
||||||
if caCtx := ctx.String("context"); caCtx != "" {
|
if caCtx := ctx.String("context"); caCtx != "" {
|
||||||
if err := step.Contexts().SetCurrent(caCtx); err != nil {
|
if err := step.Contexts().SetCurrent(caCtx); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in a new issue