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:
Mariano Cano 2022-11-03 16:58:25 -07:00
parent 9d90d0cef3
commit e27c6c529b
No known key found for this signature in database
3 changed files with 171 additions and 2 deletions

View file

@ -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)

View file

@ -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

View file

@ -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