forked from TrueCloudLab/certificates
[acme db interface] continuing unit test work
This commit is contained in:
parent
291fd5d45a
commit
20b9785d20
6 changed files with 226 additions and 153 deletions
|
@ -105,7 +105,7 @@ func (h *Handler) NewAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
acc := &acme.Account{
|
acc = &acme.Account{
|
||||||
Key: jwk,
|
Key: jwk,
|
||||||
Contact: nar.Contact,
|
Contact: nar.Contact,
|
||||||
Status: acme.StatusValid,
|
Status: acme.StatusValid,
|
||||||
|
|
|
@ -278,18 +278,13 @@ func TestHandler_GetOrdersByAccountID(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandler_NewAccount(t *testing.T) {
|
func TestHandler_NewAccount(t *testing.T) {
|
||||||
accID := "accountID"
|
|
||||||
acc := acme.Account{
|
|
||||||
ID: accID,
|
|
||||||
Status: "valid",
|
|
||||||
Orders: fmt.Sprintf("https://ca.smallstep.com/acme/account/%s/orders", accID),
|
|
||||||
}
|
|
||||||
prov := newProv()
|
prov := newProv()
|
||||||
provName := url.PathEscape(prov.GetName())
|
provName := url.PathEscape(prov.GetName())
|
||||||
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
|
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
|
||||||
|
|
||||||
type test struct {
|
type test struct {
|
||||||
db acme.DB
|
db acme.DB
|
||||||
|
acc *acme.Account
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
statusCode int
|
statusCode int
|
||||||
err *acme.Error
|
err *acme.Error
|
||||||
|
@ -372,7 +367,7 @@ func TestHandler_NewAccount(t *testing.T) {
|
||||||
err: acme.NewErrorISE("jwk expected in request context"),
|
err: acme.NewErrorISE("jwk expected in request context"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fail/NewAccount-error": func(t *testing.T) test {
|
"fail/db.CreateAccount-error": func(t *testing.T) test {
|
||||||
nar := &NewAccountRequest{
|
nar := &NewAccountRequest{
|
||||||
Contact: []string{"foo", "bar"},
|
Contact: []string{"foo", "bar"},
|
||||||
}
|
}
|
||||||
|
@ -410,20 +405,18 @@ func TestHandler_NewAccount(t *testing.T) {
|
||||||
return test{
|
return test{
|
||||||
db: &acme.MockDB{
|
db: &acme.MockDB{
|
||||||
MockCreateAccount: func(ctx context.Context, acc *acme.Account) error {
|
MockCreateAccount: func(ctx context.Context, acc *acme.Account) error {
|
||||||
|
acc.ID = "accountID"
|
||||||
assert.Equals(t, acc.Contact, nar.Contact)
|
assert.Equals(t, acc.Contact, nar.Contact)
|
||||||
assert.Equals(t, acc.Key, jwk)
|
assert.Equals(t, acc.Key, jwk)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
/*
|
},
|
||||||
getLink: func(ctx context.Context, typ acme.Link, abs bool, in ...string) string {
|
acc: &acme.Account{
|
||||||
assert.Equals(t, typ, acme.AccountLink)
|
ID: "accountID",
|
||||||
assert.True(t, abs)
|
Key: jwk,
|
||||||
assert.True(t, abs)
|
Status: acme.StatusValid,
|
||||||
assert.Equals(t, baseURL, acme.BaseURLFromContext(ctx))
|
Contact: []string{"foo", "bar"},
|
||||||
return fmt.Sprintf("%s/acme/%s/account/%s",
|
Orders: "https://test.ca.smallstep.com/acme/test@acme-provisioner.com/account/accountID/orders",
|
||||||
baseURL.String(), provName, accID)
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
statusCode: 201,
|
statusCode: 201,
|
||||||
|
@ -435,12 +428,21 @@ func TestHandler_NewAccount(t *testing.T) {
|
||||||
}
|
}
|
||||||
b, err := json.Marshal(nar)
|
b, err := json.Marshal(nar)
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
|
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
acc := &acme.Account{
|
||||||
|
ID: "accountID",
|
||||||
|
Key: jwk,
|
||||||
|
Status: acme.StatusValid,
|
||||||
|
Contact: []string{"foo", "bar"},
|
||||||
|
}
|
||||||
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
||||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
|
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{value: b})
|
||||||
ctx = context.WithValue(ctx, accContextKey, &acc)
|
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||||
ctx = context.WithValue(ctx, baseURLContextKey, baseURL)
|
ctx = context.WithValue(ctx, baseURLContextKey, baseURL)
|
||||||
return test{
|
return test{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
acc: acc,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -448,7 +450,7 @@ func TestHandler_NewAccount(t *testing.T) {
|
||||||
for name, run := range tests {
|
for name, run := range tests {
|
||||||
tc := run(t)
|
tc := run(t)
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
h := &Handler{db: tc.db}
|
h := &Handler{db: tc.db, linker: NewLinker("dns", "acme")}
|
||||||
req := httptest.NewRequest("GET", "/foo/bar", nil)
|
req := httptest.NewRequest("GET", "/foo/bar", nil)
|
||||||
req = req.WithContext(tc.ctx)
|
req = req.WithContext(tc.ctx)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
@ -471,19 +473,19 @@ func TestHandler_NewAccount(t *testing.T) {
|
||||||
assert.Equals(t, ae.Subproblems, tc.err.Subproblems)
|
assert.Equals(t, ae.Subproblems, tc.err.Subproblems)
|
||||||
assert.Equals(t, res.Header["Content-Type"], []string{"application/problem+json"})
|
assert.Equals(t, res.Header["Content-Type"], []string{"application/problem+json"})
|
||||||
} else {
|
} else {
|
||||||
expB, err := json.Marshal(acc)
|
expB, err := json.Marshal(tc.acc)
|
||||||
assert.FatalError(t, err)
|
assert.FatalError(t, err)
|
||||||
assert.Equals(t, bytes.TrimSpace(body), expB)
|
assert.Equals(t, bytes.TrimSpace(body), expB)
|
||||||
assert.Equals(t, res.Header["Location"],
|
assert.Equals(t, res.Header["Location"],
|
||||||
[]string{fmt.Sprintf("%s/acme/%s/account/%s", baseURL.String(),
|
[]string{fmt.Sprintf("%s/acme/%s/account/%s", baseURL.String(),
|
||||||
provName, accID)})
|
provName, "accountID")})
|
||||||
assert.Equals(t, res.Header["Content-Type"], []string{"application/json"})
|
assert.Equals(t, res.Header["Content-Type"], []string{"application/json"})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandlerGetUpdateAccount(t *testing.T) {
|
func TestHandler_GetUpdateAccount(t *testing.T) {
|
||||||
accID := "accountID"
|
accID := "accountID"
|
||||||
acc := acme.Account{
|
acc := acme.Account{
|
||||||
ID: accID,
|
ID: accID,
|
||||||
|
@ -594,16 +596,6 @@ func TestHandlerGetUpdateAccount(t *testing.T) {
|
||||||
assert.Equals(t, upd.ID, acc.ID)
|
assert.Equals(t, upd.ID, acc.ID)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
getLink: func(ctx context.Context, typ acme.Link, abs bool, ins ...string) string {
|
|
||||||
assert.Equals(t, typ, acme.AccountLink)
|
|
||||||
assert.True(t, abs)
|
|
||||||
assert.Equals(t, acme.BaseURLFromContext(ctx), baseURL)
|
|
||||||
assert.Equals(t, ins, []string{accID})
|
|
||||||
return fmt.Sprintf("%s/acme/%s/account/%s",
|
|
||||||
baseURL.String(), provName, accID)
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
@ -639,16 +631,6 @@ func TestHandlerGetUpdateAccount(t *testing.T) {
|
||||||
assert.Equals(t, upd.ID, acc.ID)
|
assert.Equals(t, upd.ID, acc.ID)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
getLink: func(ctx context.Context, typ acme.Link, abs bool, ins ...string) string {
|
|
||||||
assert.Equals(t, typ, acme.AccountLink)
|
|
||||||
assert.True(t, abs)
|
|
||||||
assert.Equals(t, acme.BaseURLFromContext(ctx), baseURL)
|
|
||||||
assert.Equals(t, ins, []string{accID})
|
|
||||||
return fmt.Sprintf("%s/acme/%s/account/%s",
|
|
||||||
baseURL.String(), provName, accID)
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
@ -660,18 +642,6 @@ func TestHandlerGetUpdateAccount(t *testing.T) {
|
||||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isPostAsGet: true})
|
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isPostAsGet: true})
|
||||||
ctx = context.WithValue(ctx, baseURLContextKey, baseURL)
|
ctx = context.WithValue(ctx, baseURLContextKey, baseURL)
|
||||||
return test{
|
return test{
|
||||||
/*
|
|
||||||
auth: &mockAcmeAuthority{
|
|
||||||
getLink: func(ctx context.Context, typ acme.Link, abs bool, ins ...string) string {
|
|
||||||
assert.Equals(t, typ, acme.AccountLink)
|
|
||||||
assert.True(t, abs)
|
|
||||||
assert.Equals(t, acme.BaseURLFromContext(ctx), baseURL)
|
|
||||||
assert.Equals(t, ins, []string{accID})
|
|
||||||
return fmt.Sprintf("%s/acme/%s/account/%s",
|
|
||||||
baseURL, provName, accID)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
}
|
}
|
||||||
|
@ -680,7 +650,7 @@ func TestHandlerGetUpdateAccount(t *testing.T) {
|
||||||
for name, run := range tests {
|
for name, run := range tests {
|
||||||
tc := run(t)
|
tc := run(t)
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
h := &Handler{db: tc.db}
|
h := &Handler{db: tc.db, linker: NewLinker("dns", "acme")}
|
||||||
req := httptest.NewRequest("GET", "/foo/bar", nil)
|
req := httptest.NewRequest("GET", "/foo/bar", nil)
|
||||||
req = req.WithContext(tc.ctx)
|
req = req.WithContext(tc.ctx)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
|
@ -38,10 +38,11 @@ type payloadInfo struct {
|
||||||
|
|
||||||
// Handler is the ACME API request handler.
|
// Handler is the ACME API request handler.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
db acme.DB
|
db acme.DB
|
||||||
backdate provisioner.Duration
|
backdate provisioner.Duration
|
||||||
ca acme.CertificateAuthority
|
ca acme.CertificateAuthority
|
||||||
linker Linker
|
linker Linker
|
||||||
|
validateChallengeOptions *acme.ValidateChallengeOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerOptions required to create a new ACME API request handler.
|
// HandlerOptions required to create a new ACME API request handler.
|
||||||
|
@ -63,11 +64,24 @@ type HandlerOptions struct {
|
||||||
|
|
||||||
// NewHandler returns a new ACME API handler.
|
// NewHandler returns a new ACME API handler.
|
||||||
func NewHandler(ops HandlerOptions) api.RouterHandler {
|
func NewHandler(ops HandlerOptions) api.RouterHandler {
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: time.Duration(30 * time.Second),
|
||||||
|
}
|
||||||
|
dialer := &net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}
|
||||||
return &Handler{
|
return &Handler{
|
||||||
ca: ops.CA,
|
ca: ops.CA,
|
||||||
db: ops.DB,
|
db: ops.DB,
|
||||||
backdate: ops.Backdate,
|
backdate: ops.Backdate,
|
||||||
linker: NewLinker(ops.DNS, ops.Prefix),
|
linker: NewLinker(ops.DNS, ops.Prefix),
|
||||||
|
validateChallengeOptions: &acme.ValidateChallengeOptions{
|
||||||
|
HTTPGet: client.Get,
|
||||||
|
LookupTxt: net.LookupTXT,
|
||||||
|
TLSDial: func(network, addr string, config *tls.Config) (*tls.Conn, error) {
|
||||||
|
return tls.DialWithDialer(dialer, network, addr, config)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,24 +226,12 @@ func (h *Handler) GetChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
"account '%s' does not own challenge '%s'", acc.ID, ch.ID))
|
"account '%s' does not own challenge '%s'", acc.ID, ch.ID))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client := http.Client{
|
|
||||||
Timeout: time.Duration(30 * time.Second),
|
|
||||||
}
|
|
||||||
dialer := &net.Dialer{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
}
|
|
||||||
jwk, err := jwkFromContext(ctx)
|
jwk, err := jwkFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.WriteError(w, err)
|
api.WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = ch.Validate(ctx, h.db, jwk, acme.ValidateOptions{
|
if err = ch.Validate(ctx, h.db, jwk, h.validateChallengeOptions); err != nil {
|
||||||
HTTPGet: client.Get,
|
|
||||||
LookupTxt: net.LookupTXT,
|
|
||||||
TLSDial: func(network, addr string, config *tls.Config) (*tls.Conn, error) {
|
|
||||||
return tls.DialWithDialer(dialer, network, addr, config)
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
api.WriteError(w, acme.WrapErrorISE(err, "error validating challenge"))
|
api.WriteError(w, acme.WrapErrorISE(err, "error validating challenge"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,17 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/smallstep/assert"
|
"github.com/smallstep/assert"
|
||||||
"github.com/smallstep/certificates/acme"
|
"github.com/smallstep/certificates/acme"
|
||||||
|
"go.step.sm/crypto/jose"
|
||||||
"go.step.sm/crypto/pemutil"
|
"go.step.sm/crypto/pemutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -438,16 +441,21 @@ func ch() acme.Challenge {
|
||||||
func TestHandler_GetChallenge(t *testing.T) {
|
func TestHandler_GetChallenge(t *testing.T) {
|
||||||
chiCtx := chi.NewRouteContext()
|
chiCtx := chi.NewRouteContext()
|
||||||
chiCtx.URLParams.Add("chID", "chID")
|
chiCtx.URLParams.Add("chID", "chID")
|
||||||
|
chiCtx.URLParams.Add("authzID", "authzID")
|
||||||
prov := newProv()
|
prov := newProv()
|
||||||
provName := url.PathEscape(prov.GetName())
|
provName := url.PathEscape(prov.GetName())
|
||||||
|
|
||||||
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
|
baseURL := &url.URL{Scheme: "https", Host: "test.ca.smallstep.com"}
|
||||||
url := fmt.Sprintf("%s/acme/challenge/%s", baseURL, "chID")
|
|
||||||
|
url := fmt.Sprintf("%s/acme/%s/challenge/%s/%s",
|
||||||
|
baseURL.String(), provName, "authzID", "chID")
|
||||||
|
|
||||||
type test struct {
|
type test struct {
|
||||||
db acme.DB
|
db acme.DB
|
||||||
|
vco *acme.ValidateChallengeOptions
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
statusCode int
|
statusCode int
|
||||||
ch acme.Challenge
|
ch *acme.Challenge
|
||||||
err *acme.Error
|
err *acme.Error
|
||||||
}
|
}
|
||||||
var tests = map[string]func(t *testing.T) test{
|
var tests = map[string]func(t *testing.T) test{
|
||||||
|
@ -485,84 +493,177 @@ func TestHandler_GetChallenge(t *testing.T) {
|
||||||
err: acme.NewError(acme.ErrorMalformedType, "payload expected in request context"),
|
err: acme.NewError(acme.ErrorMalformedType, "payload expected in request context"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/*
|
"fail/db.GetChallenge-error": func(t *testing.T) test {
|
||||||
"fail/validate-challenge-error": func(t *testing.T) test {
|
acc := &acme.Account{ID: "accID"}
|
||||||
acc := &acme.Account{ID: "accID"}
|
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
||||||
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||||
ctx = context.WithValue(ctx, accContextKey, acc)
|
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true})
|
||||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true})
|
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
return test{
|
||||||
return test{
|
db: &acme.MockDB{
|
||||||
db: &acme.MockDB{
|
MockGetChallenge: func(ctx context.Context, chID, azID string) (*acme.Challenge, error) {
|
||||||
MockError: acme.NewError(acme.ErrorUnauthorizedType, "unauthorized"),
|
assert.Equals(t, chID, "chID")
|
||||||
|
assert.Equals(t, azID, "authzID")
|
||||||
|
return nil, acme.NewErrorISE("force")
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
},
|
||||||
statusCode: 401,
|
ctx: ctx,
|
||||||
err: acme.NewError(acme.ErrorUnauthorizedType, "unauthorized"),
|
statusCode: 500,
|
||||||
}
|
err: acme.NewErrorISE("force"),
|
||||||
},
|
}
|
||||||
"fail/get-challenge-error": func(t *testing.T) test {
|
},
|
||||||
acc := &acme.Account{ID: "accID"}
|
"fail/account-id-mismatch": func(t *testing.T) test {
|
||||||
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
acc := &acme.Account{ID: "accID"}
|
||||||
ctx = context.WithValue(ctx, accContextKey, acc)
|
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
||||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isPostAsGet: true})
|
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||||
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true})
|
||||||
return test{
|
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
||||||
db: &acme.MockDB{
|
return test{
|
||||||
MockError: acme.NewError(acme.ErrorUnauthorizedType, "unauthorized"),
|
db: &acme.MockDB{
|
||||||
|
MockGetChallenge: func(ctx context.Context, chID, azID string) (*acme.Challenge, error) {
|
||||||
|
assert.Equals(t, chID, "chID")
|
||||||
|
assert.Equals(t, azID, "authzID")
|
||||||
|
return &acme.Challenge{AccountID: "foo"}, nil
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
},
|
||||||
statusCode: 401,
|
ctx: ctx,
|
||||||
err: acme.NewError(acme.ErrorUnauthorizedType, "unauthorized"),
|
statusCode: 401,
|
||||||
}
|
err: acme.NewError(acme.ErrorUnauthorizedType, "accout id mismatch"),
|
||||||
},
|
}
|
||||||
"ok/validate-challenge": func(t *testing.T) test {
|
},
|
||||||
key, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
"fail/no-jwk": func(t *testing.T) test {
|
||||||
assert.FatalError(t, err)
|
acc := &acme.Account{ID: "accID"}
|
||||||
acc := &acme.Account{ID: "accID", Key: key}
|
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
||||||
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||||
ctx = context.WithValue(ctx, accContextKey, acc)
|
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true})
|
||||||
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true})
|
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
||||||
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
return test{
|
||||||
ctx = context.WithValue(ctx, baseURLContextKey, baseURL)
|
db: &acme.MockDB{
|
||||||
ch := ch()
|
MockGetChallenge: func(ctx context.Context, chID, azID string) (*acme.Challenge, error) {
|
||||||
ch.Status = "valid"
|
assert.Equals(t, chID, "chID")
|
||||||
ch.Validated = time.Now().UTC().Format(time.RFC3339)
|
assert.Equals(t, azID, "authzID")
|
||||||
return test{
|
return &acme.Challenge{AccountID: "accID"}, nil
|
||||||
db: &acme.MockDB{
|
|
||||||
MockGetChallenge: func(ctx context.Context, chID, azID string) (*acme.Challenge, error) {
|
|
||||||
assert.Equals(t, chID, ch.ID)
|
|
||||||
return &ch, nil
|
|
||||||
},
|
|
||||||
getLink: func(ctx context.Context, typ acme.Link, abs bool, in ...string) string {
|
|
||||||
var ret string
|
|
||||||
switch count {
|
|
||||||
case 0:
|
|
||||||
assert.Equals(t, typ, acme.AuthzLink)
|
|
||||||
assert.True(t, abs)
|
|
||||||
assert.Equals(t, in, []string{ch.AuthzID})
|
|
||||||
ret = fmt.Sprintf("%s/acme/%s/authz/%s", baseURL.String(), provName, ch.AuthzID)
|
|
||||||
case 1:
|
|
||||||
assert.Equals(t, typ, acme.ChallengeLink)
|
|
||||||
assert.True(t, abs)
|
|
||||||
assert.Equals(t, in, []string{ch.ID})
|
|
||||||
ret = url
|
|
||||||
}
|
|
||||||
count++
|
|
||||||
return ret
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
},
|
||||||
statusCode: 200,
|
ctx: ctx,
|
||||||
ch: ch,
|
statusCode: 500,
|
||||||
}
|
err: acme.NewErrorISE("missing jwk"),
|
||||||
},
|
}
|
||||||
*/
|
},
|
||||||
|
"fail/nil-jwk": func(t *testing.T) test {
|
||||||
|
acc := &acme.Account{ID: "accID"}
|
||||||
|
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
||||||
|
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||||
|
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true})
|
||||||
|
ctx = context.WithValue(ctx, jwkContextKey, nil)
|
||||||
|
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
||||||
|
return test{
|
||||||
|
db: &acme.MockDB{
|
||||||
|
MockGetChallenge: func(ctx context.Context, chID, azID string) (*acme.Challenge, error) {
|
||||||
|
assert.Equals(t, chID, "chID")
|
||||||
|
assert.Equals(t, azID, "authzID")
|
||||||
|
return &acme.Challenge{AccountID: "accID"}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ctx: ctx,
|
||||||
|
statusCode: 500,
|
||||||
|
err: acme.NewErrorISE("nil jwk"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fail/validate-challenge-error": func(t *testing.T) test {
|
||||||
|
acc := &acme.Account{ID: "accID"}
|
||||||
|
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
||||||
|
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||||
|
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true})
|
||||||
|
_jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
_pub := _jwk.Public()
|
||||||
|
ctx = context.WithValue(ctx, jwkContextKey, &_pub)
|
||||||
|
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
||||||
|
return test{
|
||||||
|
db: &acme.MockDB{
|
||||||
|
MockGetChallenge: func(ctx context.Context, chID, azID string) (*acme.Challenge, error) {
|
||||||
|
assert.Equals(t, chID, "chID")
|
||||||
|
assert.Equals(t, azID, "authzID")
|
||||||
|
return &acme.Challenge{
|
||||||
|
Status: acme.StatusPending,
|
||||||
|
Type: "http-01",
|
||||||
|
AccountID: "accID",
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
|
||||||
|
assert.Equals(t, ch.Status, acme.StatusPending)
|
||||||
|
assert.Equals(t, ch.Type, "http-01")
|
||||||
|
assert.Equals(t, ch.AccountID, "accID")
|
||||||
|
assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String())
|
||||||
|
return acme.NewErrorISE("force")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vco: &acme.ValidateChallengeOptions{
|
||||||
|
HTTPGet: func(string) (*http.Response, error) {
|
||||||
|
return nil, errors.New("force")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ctx: ctx,
|
||||||
|
statusCode: 500,
|
||||||
|
err: acme.NewErrorISE("force"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ok": func(t *testing.T) test {
|
||||||
|
acc := &acme.Account{ID: "accID"}
|
||||||
|
ctx := context.WithValue(context.Background(), provisionerContextKey, prov)
|
||||||
|
ctx = context.WithValue(ctx, accContextKey, acc)
|
||||||
|
ctx = context.WithValue(ctx, payloadContextKey, &payloadInfo{isEmptyJSON: true})
|
||||||
|
_jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
|
||||||
|
assert.FatalError(t, err)
|
||||||
|
_pub := _jwk.Public()
|
||||||
|
ctx = context.WithValue(ctx, jwkContextKey, &_pub)
|
||||||
|
ctx = context.WithValue(ctx, baseURLContextKey, baseURL)
|
||||||
|
ctx = context.WithValue(ctx, chi.RouteCtxKey, chiCtx)
|
||||||
|
return test{
|
||||||
|
db: &acme.MockDB{
|
||||||
|
MockGetChallenge: func(ctx context.Context, chID, azID string) (*acme.Challenge, error) {
|
||||||
|
assert.Equals(t, chID, "chID")
|
||||||
|
assert.Equals(t, azID, "authzID")
|
||||||
|
return &acme.Challenge{
|
||||||
|
ID: "chID",
|
||||||
|
AuthzID: "authzID",
|
||||||
|
Status: acme.StatusPending,
|
||||||
|
Type: "http-01",
|
||||||
|
AccountID: "accID",
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
MockUpdateChallenge: func(ctx context.Context, ch *acme.Challenge) error {
|
||||||
|
assert.Equals(t, ch.Status, acme.StatusPending)
|
||||||
|
assert.Equals(t, ch.Type, "http-01")
|
||||||
|
assert.Equals(t, ch.AccountID, "accID")
|
||||||
|
assert.HasSuffix(t, ch.Error.Type, acme.ErrorConnectionType.String())
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ch: &acme.Challenge{
|
||||||
|
ID: "chID",
|
||||||
|
AuthzID: "authzID",
|
||||||
|
Status: acme.StatusPending,
|
||||||
|
Type: "http-01",
|
||||||
|
AccountID: "accID",
|
||||||
|
URL: url,
|
||||||
|
Error: acme.NewError(acme.ErrorConnectionType, "force"),
|
||||||
|
},
|
||||||
|
vco: &acme.ValidateChallengeOptions{
|
||||||
|
HTTPGet: func(string) (*http.Response, error) {
|
||||||
|
return nil, errors.New("force")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ctx: ctx,
|
||||||
|
statusCode: 200,
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for name, run := range tests {
|
for name, run := range tests {
|
||||||
tc := run(t)
|
tc := run(t)
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
h := &Handler{db: tc.db, linker: NewLinker("dns", "acme")}
|
h := &Handler{db: tc.db, linker: NewLinker("dns", "acme"), validateChallengeOptions: tc.vco}
|
||||||
req := httptest.NewRequest("GET", url, nil)
|
req := httptest.NewRequest("GET", url, nil)
|
||||||
req = req.WithContext(tc.ctx)
|
req = req.WithContext(tc.ctx)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
|
@ -462,7 +462,7 @@ func provisionerFromContext(ctx context.Context) (acme.Provisioner, error) {
|
||||||
func payloadFromContext(ctx context.Context) (*payloadInfo, error) {
|
func payloadFromContext(ctx context.Context) (*payloadInfo, error) {
|
||||||
val, ok := ctx.Value(payloadContextKey).(*payloadInfo)
|
val, ok := ctx.Value(payloadContextKey).(*payloadInfo)
|
||||||
if !ok || val == nil {
|
if !ok || val == nil {
|
||||||
return nil, acme.NewError(acme.ErrorMalformedType, "payload expected in request context")
|
return nil, acme.NewErrorISE("payload expected in request context")
|
||||||
}
|
}
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ func (ch *Challenge) ToLog() (interface{}, error) {
|
||||||
// type using the DB interface.
|
// type using the DB interface.
|
||||||
// satisfactorily validated, the 'status' and 'validated' attributes are
|
// satisfactorily validated, the 'status' and 'validated' attributes are
|
||||||
// updated.
|
// updated.
|
||||||
func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey, vo ValidateOptions) error {
|
func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey, vo *ValidateChallengeOptions) error {
|
||||||
// If already valid or invalid then return without performing validation.
|
// If already valid or invalid then return without performing validation.
|
||||||
if ch.Status == StatusValid || ch.Status == StatusInvalid {
|
if ch.Status == StatusValid || ch.Status == StatusInvalid {
|
||||||
return nil
|
return nil
|
||||||
|
@ -64,7 +64,7 @@ func (ch *Challenge) Validate(ctx context.Context, db DB, jwk *jose.JSONWebKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo ValidateOptions) error {
|
func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo *ValidateChallengeOptions) error {
|
||||||
url := fmt.Sprintf("http://%s/.well-known/acme-challenge/%s", ch.Value, ch.Token)
|
url := fmt.Sprintf("http://%s/.well-known/acme-challenge/%s", ch.Value, ch.Token)
|
||||||
|
|
||||||
resp, err := vo.HTTPGet(url)
|
resp, err := vo.HTTPGet(url)
|
||||||
|
@ -105,7 +105,7 @@ func http01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWeb
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo ValidateOptions) error {
|
func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo *ValidateChallengeOptions) error {
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
NextProtos: []string{"acme-tls/1"},
|
NextProtos: []string{"acme-tls/1"},
|
||||||
ServerName: ch.Value,
|
ServerName: ch.Value,
|
||||||
|
@ -197,7 +197,7 @@ func tlsalpn01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSON
|
||||||
"incorrect certificate for tls-alpn-01 challenge: missing acmeValidationV1 extension"))
|
"incorrect certificate for tls-alpn-01 challenge: missing acmeValidationV1 extension"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo ValidateOptions) error {
|
func dns01Validate(ctx context.Context, ch *Challenge, db DB, jwk *jose.JSONWebKey, vo *ValidateChallengeOptions) error {
|
||||||
// Normalize domain for wildcard DNS names
|
// Normalize domain for wildcard DNS names
|
||||||
// This is done to avoid making TXT lookups for domains like
|
// This is done to avoid making TXT lookups for domains like
|
||||||
// _acme-challenge.*.example.com
|
// _acme-challenge.*.example.com
|
||||||
|
@ -263,8 +263,8 @@ type httpGetter func(string) (*http.Response, error)
|
||||||
type lookupTxt func(string) ([]string, error)
|
type lookupTxt func(string) ([]string, error)
|
||||||
type tlsDialer func(network, addr string, config *tls.Config) (*tls.Conn, error)
|
type tlsDialer func(network, addr string, config *tls.Config) (*tls.Conn, error)
|
||||||
|
|
||||||
// ValidateOptions are ACME challenge validator functions.
|
// ValidateChallengeOptions are ACME challenge validator functions.
|
||||||
type ValidateOptions struct {
|
type ValidateChallengeOptions struct {
|
||||||
HTTPGet httpGetter
|
HTTPGet httpGetter
|
||||||
LookupTxt lookupTxt
|
LookupTxt lookupTxt
|
||||||
TLSDial tlsDialer
|
TLSDial tlsDialer
|
||||||
|
|
Loading…
Reference in a new issue