acme: Update http-01 challenge tests

Add tests for the starting challenge statuses.
Removed unneeded db write test.
This commit is contained in:
David Cowden 2020-05-19 03:38:04 -07:00
parent 9103880f88
commit 0f63e43b10
2 changed files with 183 additions and 127 deletions

View file

@ -389,17 +389,20 @@ func (hc *http01Challenge) validate(jwk *jose.JSONWebKey, vo validateOptions) (c
if err != nil { if err != nil {
return nil, err return nil, err
} }
if keyAuth != expected {
// add base challenge fail validation // success
e := errors.Errorf("keyAuthorization does not match; expected %s, but got %s", expected, keyAuth) if keyAuth == expected {
up.Error = RejectedIdentifierErr(e).ToACME() up.Validated = clock.Now()
up.Status = StatusInvalid up.Status = StatusValid
up.Error = nil
up.Retry = nil
return up, nil return up, nil
} }
up.Status = StatusValid // fail
up.Validated = clock.Now() up.Status = StatusInvalid
up.Error = nil e := errors.Errorf("keyAuthorization does not match; expected %s, but got %s", expected, keyAuth)
up.Error = RejectedIdentifierErr(e).ToACME()
up.Retry = nil up.Retry = nil
return up, nil return up, nil
} }

View file

@ -790,6 +790,7 @@ func TestHTTP01Validate(t *testing.T) {
err *Error err *Error
} }
tests := map[string]func(t *testing.T) test{ tests := map[string]func(t *testing.T) test{
"ok/status-already-valid": func(t *testing.T) test { "ok/status-already-valid": func(t *testing.T) test {
ch, err := newHTTPCh() ch, err := newHTTPCh()
assert.FatalError(t, err) assert.FatalError(t, err)
@ -801,6 +802,7 @@ func TestHTTP01Validate(t *testing.T) {
res: ch, res: ch,
} }
}, },
"ok/status-already-invalid": func(t *testing.T) test { "ok/status-already-invalid": func(t *testing.T) test {
ch, err := newHTTPCh() ch, err := newHTTPCh()
assert.FatalError(t, err) assert.FatalError(t, err)
@ -812,77 +814,127 @@ func TestHTTP01Validate(t *testing.T) {
res: ch, res: ch,
} }
}, },
"error/status-pending": func(t *testing.T) test {
ch, err := newHTTPCh()
assert.FatalError(t, err)
b := ch.clone()
b.Status = StatusPending
e := errors.New("pending challenges must first be moved to the processing state")
return test{
ch: b.morph(),
err: ServerInternalErr(e),
}
},
"error/status-unknown": func(t *testing.T) test {
ch, err := newHTTPCh()
assert.FatalError(t, err)
b := ch.clone()
b.Status = "unknown"
e := errors.New("unknown challenge state: unknown")
return test{
ch: b.morph(),
err: ServerInternalErr(e),
}
},
"ok/http-get-error": func(t *testing.T) test { "ok/http-get-error": func(t *testing.T) test {
ch, err := newHTTPCh() ch, err := newHTTPCh()
assert.FatalError(t, err) assert.FatalError(t, err)
oldb, err := json.Marshal(ch) up := ch.clone()
up.Retry = &Retry{
Owner: 0,
ProvisionerID: "acme/acme",
NumAttempts: 1,
MaxAttempts: 6,
NextAttempt: time.Now().UTC().Add(time.Minute).Format(time.RFC3339),
}
up.Status = StatusProcessing
ch = up.morph()
chb, err := json.Marshal(ch)
assert.FatalError(t, err) assert.FatalError(t, err)
expErr := ConnectionErr(errors.Errorf("error doing http GET for url "+ rch := ch.clone()
"http://zap.internal/.well-known/acme-challenge/%s: force", ch.getToken())) geterr := errors.New("force")
baseClone := ch.clone() url := fmt.Sprintf("http://%s/.well-known/acme-challenge/%s", ch.getValue(), ch.getToken())
baseClone.Error = expErr.ToACME() e := errors.Wrapf(geterr, "error doing http GET for url %s", url)
newCh := &http01Challenge{baseClone} rch.Error = ConnectionErr(e).ToACME()
newb, err := json.Marshal(newCh)
assert.FatalError(t, err)
return test{ return test{
ch: ch, ch: ch,
vo: validateOptions{ vo: validateOptions{
httpGet: func(url string) (*http.Response, error) { httpGet: func(url string) (*http.Response, error) {
return nil, errors.New("force") return nil, geterr
}, },
}, },
db: &db.MockNoSQLDB{ db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { Ret1: chb,
assert.Equals(t, bucket, challengeTable) Ret2: true,
assert.Equals(t, key, []byte(ch.getID()))
assert.Equals(t, old, oldb)
assert.Equals(t, newval, newb)
return nil, true, nil
},
}, },
res: ch, res: rch,
} }
}, },
"ok/http-get->=400": func(t *testing.T) test { "ok/http-get->=400": func(t *testing.T) test {
ch, err := newHTTPCh() ch, err := newHTTPCh()
assert.FatalError(t, err) assert.FatalError(t, err)
oldb, err := json.Marshal(ch) up := ch.clone()
assert.FatalError(t, err) up.Retry = &Retry{
expErr := ConnectionErr(errors.Errorf("error doing http GET for url "+ Owner: 0,
"http://zap.internal/.well-known/acme-challenge/%s with status code 400", ch.getToken())) ProvisionerID: "acme/acme",
baseClone := ch.clone() NumAttempts: 1,
baseClone.Error = expErr.ToACME() MaxAttempts: 6,
newCh := &http01Challenge{baseClone} NextAttempt: time.Now().UTC().Add(time.Minute).Format(time.RFC3339),
newb, err := json.Marshal(newCh) }
up.Status = StatusProcessing
ch = up.morph()
chb, err := json.Marshal(ch)
assert.FatalError(t, err) assert.FatalError(t, err)
rch := ch.clone()
url := fmt.Sprintf("http://%s/.well-known/acme-challenge/%s", ch.getValue(), ch.getToken())
e := errors.Errorf("error doing http GET for url %s with status code %d", url, http.StatusBadRequest)
rch.Error = ConnectionErr(e).ToACME()
return test{ return test{
ch: ch, ch: ch,
vo: validateOptions{ vo: validateOptions{
httpGet: func(url string) (*http.Response, error) { httpGet: func(url string) (*http.Response, error) {
return &http.Response{ return &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString("")),
StatusCode: http.StatusBadRequest, StatusCode: http.StatusBadRequest,
}, nil }, nil
}, },
}, },
db: &db.MockNoSQLDB{ db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { Ret1: chb,
assert.Equals(t, bucket, challengeTable) Ret2: true,
assert.Equals(t, key, []byte(ch.getID()))
assert.Equals(t, old, oldb)
assert.Equals(t, newval, newb)
return nil, true, nil
},
}, },
res: ch, res: rch,
} }
}, },
"fail/read-body": func(t *testing.T) test { "fail/read-body": func(t *testing.T) test {
ch, err := newHTTPCh() ch, err := newHTTPCh()
assert.FatalError(t, err) assert.FatalError(t, err)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) up := ch.clone()
up.Retry = &Retry{
Owner: 0,
ProvisionerID: "acme/acme",
NumAttempts: 1,
MaxAttempts: 6,
NextAttempt: time.Now().UTC().Add(time.Minute).Format(time.RFC3339),
}
up.Status = StatusProcessing
ch = up.morph()
chb, err := json.Marshal(ch)
assert.FatalError(t, err) assert.FatalError(t, err)
jwk.Key = "foo"
rch := ch.clone()
url := fmt.Sprintf("http://%s/.well-known/acme-challenge/%s", ch.getValue(), ch.getToken())
e := errors.Wrapf(errors.New("force"), "error reading response body for url %s", url)
rch.Error = ServerInternalErr(e).ToACME()
return test{ return test{
ch: ch, ch: ch,
@ -893,18 +945,34 @@ func TestHTTP01Validate(t *testing.T) {
}, nil }, nil
}, },
}, },
jwk: jwk, db: &db.MockNoSQLDB{
err: ServerInternalErr(errors.Errorf("error reading response "+ Ret1: chb,
"body for url http://zap.internal/.well-known/acme-challenge/%s: force", Ret2: true,
ch.getToken())), },
res: rch,
} }
}, },
"fail/key-authorization-gen-error": func(t *testing.T) test { "fail/key-authorization-gen-error": func(t *testing.T) test {
ch, err := newHTTPCh()
assert.FatalError(t, err)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err) assert.FatalError(t, err)
jwk.Key = "foo" jwk.Key = "foo"
ch, err := newHTTPCh()
assert.FatalError(t, err)
b := ch.clone()
b.Retry = &Retry{
Owner: 0,
ProvisionerID: "acme/acme",
NumAttempts: 1,
MaxAttempts: 6,
NextAttempt: time.Now().UTC().Add(time.Minute).Format(time.RFC3339),
}
b.Status = StatusProcessing
ch = b.morph()
chb, err := json.Marshal(ch)
assert.FatalError(t, err)
return test{ return test{
ch: ch, ch: ch,
vo: validateOptions{ vo: validateOptions{
@ -914,30 +982,45 @@ func TestHTTP01Validate(t *testing.T) {
}, nil }, nil
}, },
}, },
db: &db.MockNoSQLDB{
Ret1: chb,
Ret2: true,
},
jwk: jwk, jwk: jwk,
err: ServerInternalErr(errors.New("error generating JWK thumbprint: square/go-jose: unknown key type 'string'")), err: ServerInternalErr(errors.New("error generating JWK thumbprint: square/go-jose: unknown key type 'string'")),
} }
}, },
"ok/key-auth-mismatch": func(t *testing.T) test { "ok/key-auth-mismatch": func(t *testing.T) test {
ch, err := newHTTPCh() jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
oldb, err := json.Marshal(ch)
assert.FatalError(t, err) assert.FatalError(t, err)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) ch, err := newHTTPCh()
assert.FatalError(t, err)
b := ch.clone()
b.Retry = &Retry{
Owner: 0,
ProvisionerID: "acme/acme",
NumAttempts: 1,
MaxAttempts: 6,
NextAttempt: time.Now().UTC().Add(time.Minute).Format(time.RFC3339),
}
b.Status = StatusProcessing
ch = b.morph()
chb, err := json.Marshal(ch)
assert.FatalError(t, err) assert.FatalError(t, err)
expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk) expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk)
assert.FatalError(t, err) assert.FatalError(t, err)
expErr := RejectedIdentifierErr(errors.Errorf("keyAuthorization does not match; "+ b = ch.clone()
"expected %s, but got foo", expKeyAuth)) e := errors.Errorf("keyAuthorization does not match; expected %s, but got foo", expKeyAuth)
baseClone := ch.clone() ae := RejectedIdentifierErr(e)
baseClone.Error = expErr.ToACME() b.Error = ae.ToACME()
baseClone.Error.Subproblems = append(baseClone.Error.Subproblems, expErr) b.Retry = nil
newCh := &http01Challenge{baseClone} b.Status = StatusInvalid
newb, err := json.Marshal(newCh)
assert.FatalError(t, err) rch := b.morph()
return test{ return test{
ch: ch, ch: ch,
@ -950,26 +1033,42 @@ func TestHTTP01Validate(t *testing.T) {
}, },
jwk: jwk, jwk: jwk,
db: &db.MockNoSQLDB{ db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { Ret1: chb,
assert.Equals(t, bucket, challengeTable) Ret2: true,
assert.Equals(t, key, []byte(ch.getID()))
assert.Equals(t, old, oldb)
assert.Equals(t, newval, newb)
return nil, true, nil
},
}, },
res: ch, res: rch,
} }
}, },
"fail/save-error": func(t *testing.T) test {
ch, err := newHTTPCh() "ok": func(t *testing.T) test {
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err) assert.FatalError(t, err)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) ch, err := newHTTPCh()
assert.FatalError(t, err)
b := ch.clone()
b.Retry = &Retry{
Owner: 0,
ProvisionerID: "acme/acme",
NumAttempts: 1,
MaxAttempts: 6,
NextAttempt: time.Now().UTC().Add(time.Minute).Format(time.RFC3339),
}
b.Status = StatusProcessing
ch = b.morph()
chb, err := json.Marshal(ch)
assert.FatalError(t, err) assert.FatalError(t, err)
expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk) expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk)
assert.FatalError(t, err) assert.FatalError(t, err)
b = ch.clone()
b.Validated = clock.Now()
b.Status = StatusValid
b.Error = nil
b.Retry = nil
rch := b.morph()
return test{ return test{
ch: ch, ch: ch,
vo: validateOptions{ vo: validateOptions{
@ -981,64 +1080,14 @@ func TestHTTP01Validate(t *testing.T) {
}, },
jwk: jwk, jwk: jwk,
db: &db.MockNoSQLDB{ db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) { Ret1: chb,
return nil, false, errors.New("force") Ret2: true,
},
},
err: ServerInternalErr(errors.New("error saving acme challenge: force")),
}
},
"ok": func(t *testing.T) test {
ch, err := newHTTPCh()
assert.FatalError(t, err)
_ch, ok := ch.(*http01Challenge)
assert.Fatal(t, ok)
_ch.baseChallenge.Error = MalformedErr(nil).ToACME()
oldb, err := json.Marshal(ch)
assert.FatalError(t, err)
jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0)
assert.FatalError(t, err)
expKeyAuth, err := KeyAuthorization(ch.getToken(), jwk)
assert.FatalError(t, err)
baseClone := ch.clone()
baseClone.Status = StatusValid
baseClone.Error = nil
newCh := &http01Challenge{baseClone}
return test{
ch: ch,
res: newCh,
vo: validateOptions{
httpGet: func(url string) (*http.Response, error) {
return &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(expKeyAuth)),
}, nil
},
},
jwk: jwk,
db: &db.MockNoSQLDB{
MCmpAndSwap: func(bucket, key, old, newval []byte) ([]byte, bool, error) {
assert.Equals(t, bucket, challengeTable)
assert.Equals(t, key, []byte(ch.getID()))
assert.Equals(t, old, oldb)
httpCh, err := unmarshalChallenge(newval)
assert.FatalError(t, err)
assert.Equals(t, httpCh.getStatus(), StatusValid)
assert.True(t, httpCh.getValidated().Before(time.Now().UTC().Add(time.Minute)))
assert.True(t, httpCh.getValidated().After(time.Now().UTC().Add(-1*time.Second)))
baseClone.Validated = httpCh.getValidated()
return nil, true, nil
},
}, },
res: rch,
} }
}, },
} }
for name, run := range tests { for name, run := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
tc := run(t) tc := run(t)
@ -1058,7 +1107,11 @@ func TestHTTP01Validate(t *testing.T) {
assert.Equals(t, tc.res.getStatus(), ch.getStatus()) assert.Equals(t, tc.res.getStatus(), ch.getStatus())
assert.Equals(t, tc.res.getToken(), ch.getToken()) assert.Equals(t, tc.res.getToken(), ch.getToken())
assert.Equals(t, tc.res.getCreated(), ch.getCreated()) assert.Equals(t, tc.res.getCreated(), ch.getCreated())
assert.Equals(t, tc.res.getValidated(), ch.getValidated()) if tc.res.getValidated() != ch.getValidated() {
assert.True(t, ch.getValidated().After(tc.res.getValidated()),
"validated timestamp should come after challenge creation")
}
assert.Equals(t, tc.res.getError(), ch.getError()) assert.Equals(t, tc.res.getError(), ch.getError())
assert.Equals(t, tc.res.getRetry(), ch.getRetry()) assert.Equals(t, tc.res.getRetry(), ch.getRetry())
} }