diff --git a/acme/api/certificate.go b/acme/api/certificate.go index 5f31968cf..b42296768 100644 --- a/acme/api/certificate.go +++ b/acme/api/certificate.go @@ -2,15 +2,12 @@ package api import ( "bytes" - "crypto/x509" "encoding/pem" "errors" "io" "net/http" "github.com/go-acme/lego/v4/acme" - "github.com/go-acme/lego/v4/certcrypto" - "github.com/go-acme/lego/v4/log" ) // maxBodySize is the maximum size of body that we will read. @@ -77,62 +74,22 @@ func (c *CertificateService) get(certURL string, bundle bool) (*acme.RawCertific return nil, resp.Header, err } - cert := c.getCertificateChain(data, resp.Header, bundle, certURL) + cert := c.getCertificateChain(data, bundle) return cert, resp.Header, err } // getCertificateChain Returns the certificate and the issuer certificate. -func (c *CertificateService) getCertificateChain(cert []byte, headers http.Header, bundle bool, certURL string) *acme.RawCertificate { +func (c *CertificateService) getCertificateChain(cert []byte, bundle bool) *acme.RawCertificate { // Get issuerCert from bundled response from Let's Encrypt // See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962 _, issuer := pem.Decode(cert) - if issuer != nil { - // If bundle is false, we want to return a single certificate. - // To do this, we remove the issuer cert(s) from the issued cert. - if !bundle { - cert = bytes.TrimSuffix(cert, issuer) - } - return &acme.RawCertificate{Cert: cert, Issuer: issuer} - } - // The issuer certificate link may be supplied via an "up" link - // in the response headers of a new certificate. - // See https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2 - up := getLink(headers, "up") - - issuer, err := c.getIssuerFromLink(up) - if err != nil { - // If we fail to acquire the issuer cert, return the issued certificate - do not fail. - log.Warnf("acme: Could not bundle issuer certificate [%s]: %v", certURL, err) - } else if len(issuer) > 0 { - // If bundle is true, we want to return a certificate bundle. - // To do this, we append the issuer cert to the issued cert. - if bundle { - cert = append(cert, issuer...) - } + // If bundle is false, we want to return a single certificate. + // To do this, we remove the issuer cert(s) from the issued cert. + if !bundle { + cert = bytes.TrimSuffix(cert, issuer) } return &acme.RawCertificate{Cert: cert, Issuer: issuer} } - -// getIssuerFromLink requests the issuer certificate. -func (c *CertificateService) getIssuerFromLink(up string) ([]byte, error) { - if up == "" { - return nil, nil - } - - log.Infof("acme: Requesting issuer cert from %s", up) - - cert, _, err := c.get(up, false) - if err != nil { - return nil, err - } - - _, err = x509.ParseCertificate(cert.Cert) - if err != nil { - return nil, err - } - - return certcrypto.PEMEncode(certcrypto.DERCertificateBytes(cert.Cert)), nil -} diff --git a/acme/api/certificate_test.go b/acme/api/certificate_test.go index 9776cccc5..4ff8fec47 100644 --- a/acme/api/certificate_test.go +++ b/acme/api/certificate_test.go @@ -3,11 +3,11 @@ package api import ( "crypto/rand" "crypto/rsa" - "encoding/pem" "net/http" "testing" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -74,25 +74,9 @@ rzFL1KZfz+HZdnFwFW2T2gVW8L3ii1l9AJDuKzlvjUH3p6bgihVq02sjT8mx+GM2 ` func TestCertificateService_Get_issuerRelUp(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Link", "<"+apiURL+`/issuer>; rel="up"`) - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - mux.HandleFunc("/issuer", func(w http.ResponseWriter, _ *http.Request) { - p, _ := pem.Decode([]byte(issuerMock)) - _, err := w.Write(p.Bytes) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -107,15 +91,9 @@ func TestCertificateService_Get_issuerRelUp(t *testing.T) { } func TestCertificateService_Get_embeddedIssuer(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") diff --git a/acme/api/order_test.go b/acme/api/order_test.go index cf28e517b..ebbb7d7ce 100644 --- a/acme/api/order_test.go +++ b/acme/api/order_test.go @@ -11,55 +11,48 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestOrderService_NewWithOptions(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - // small value keeps test fast privateKey, errK := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, errK, "Could not generate test key") - mux.HandleFunc("/newOrder", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } + apiURL := tester.MockACMEServer(). + Route("POST /newOrder", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + body, err := readSignedBody(req, privateKey) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } - body, err := readSignedBody(r, privateKey) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } + order := acme.Order{} + err = json.Unmarshal(body, &order) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } - order := acme.Order{} - err = json.Unmarshal(body, &order) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - err = tester.WriteJSONResponse(w, acme.Order{ - Status: acme.StatusValid, - Expires: order.Expires, - Identifiers: order.Identifiers, - Profile: order.Profile, - NotBefore: order.NotBefore, - NotAfter: order.NotAfter, - Error: order.Error, - Authorizations: order.Authorizations, - Finalize: order.Finalize, - Certificate: order.Certificate, - Replaces: order.Replaces, - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + servermock.JSONEncode(acme.Order{ + Status: acme.StatusValid, + Expires: order.Expires, + Identifiers: order.Identifiers, + Profile: order.Profile, + NotBefore: order.NotBefore, + NotAfter: order.NotAfter, + Error: order.Error, + Authorizations: order.Authorizations, + Finalize: order.Finalize, + Certificate: order.Certificate, + Replaces: order.Replaces, + }).ServeHTTP(rw, req) + })). + Build(t) core, err := New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) diff --git a/certificate/certificates_test.go b/certificate/certificates_test.go index 3be2d606a..ddccd3541 100644 --- a/certificate/certificates_test.go +++ b/certificate/certificates_test.go @@ -3,7 +3,6 @@ package certificate import ( "crypto/rand" "crypto/rsa" - "encoding/pem" "fmt" "net/http" "testing" @@ -12,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -175,15 +175,9 @@ Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ` func Test_checkResponse(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -215,25 +209,9 @@ func Test_checkResponse(t *testing.T) { } func Test_checkResponse_issuerRelUp(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Link", "<"+apiURL+`/issuer>; rel="up"`) - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - mux.HandleFunc("/issuer", func(w http.ResponseWriter, _ *http.Request) { - p, _ := pem.Decode([]byte(issuerMock)) - _, err := w.Write(p.Bytes) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -265,15 +243,9 @@ func Test_checkResponse_issuerRelUp(t *testing.T) { } func Test_checkResponse_no_bundle(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -305,25 +277,16 @@ func Test_checkResponse_no_bundle(t *testing.T) { } func Test_checkResponse_alternate(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer(). + Route("POST /certificate", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Add("Link", + fmt.Sprintf(`;title="foo";rel="alternate"`, req.Context().Value(http.LocalAddrContextKey))) - mux.HandleFunc("/certificate", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Add("Link", fmt.Sprintf(`<%s/certificate/1>;title="foo";rel="alternate"`, apiURL)) - - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - mux.HandleFunc("/certificate/1", func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(certResponseMock2)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + servermock.RawStringResponse(certResponseMock).ServeHTTP(rw, req) + })). + Route("/certificate/1", servermock.RawStringResponse(certResponseMock2)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -358,15 +321,9 @@ func Test_checkResponse_alternate(t *testing.T) { } func Test_Get(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - - mux.HandleFunc("/acme/cert/test-cert", func(w http.ResponseWriter, _ *http.Request) { - _, err := w.Write([]byte(certResponseMock)) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + apiURL := tester.MockACMEServer(). + Route("POST /acme/cert/test-cert", servermock.RawStringResponse(certResponseMock)). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") diff --git a/certificate/renewal_test.go b/certificate/renewal_test.go index 9f20e374e..9cf478600 100644 --- a/certificate/renewal_test.go +++ b/certificate/renewal_test.go @@ -11,6 +11,7 @@ import ( "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -42,26 +43,19 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { require.NoError(t, err) // Test with a fake API. - mux, apiURL := tester.SetupFakeAPI(t) - mux.HandleFunc("/renewalInfo/"+ariLeafCertID, func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Retry-After", "21600") - w.WriteHeader(http.StatusOK) - _, wErr := w.Write([]byte(`{ + apiURL := tester.MockACMEServer(). + Route("GET /renewalInfo/"+ariLeafCertID, + servermock.RawStringResponse(`{ "suggestedWindow": { "start": "2020-03-17T17:51:09Z", "end": "2020-03-17T18:21:09Z" }, - "explanationUrl": "https://aricapable.ca/docs/renewal-advice/" + "explanationUrl": "https://aricapable.ca.example/docs/renewal-advice/" } - }`)) - require.NoError(t, wErr) - }) + }`). + WithHeader("Content-Type", "application/json"). + WithHeader("Retry-After", "21600")). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 2048) require.NoError(t, err, "Could not generate test key") @@ -76,7 +70,7 @@ func TestCertifier_GetRenewalInfo(t *testing.T) { require.NotNil(t, ri) assert.Equal(t, "2020-03-17T17:51:09Z", ri.SuggestedWindow.Start.Format(time.RFC3339)) assert.Equal(t, "2020-03-17T18:21:09Z", ri.SuggestedWindow.End.Format(time.RFC3339)) - assert.Equal(t, "https://aricapable.ca/docs/renewal-advice/", ri.ExplanationURL) + assert.Equal(t, "https://aricapable.ca.example/docs/renewal-advice/", ri.ExplanationURL) assert.Equal(t, time.Duration(21600000000000), ri.RetryAfter) } @@ -117,8 +111,9 @@ func TestCertifier_GetRenewalInfo_errors(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - mux, apiURL := tester.SetupFakeAPI(t) - mux.HandleFunc("/renewalInfo/"+ariLeafCertID, test.handler) + apiURL := tester.MockACMEServer(). + Route("GET /renewalInfo/"+ariLeafCertID, test.handler). + Build(t) core, err := api.New(test.httpClient, "lego-test", apiURL+"/dir", "", key) require.NoError(t, err) diff --git a/challenge/dns01/dns_challenge_test.go b/challenge/dns01/dns_challenge_test.go index c09273c2a..bb0564a81 100644 --- a/challenge/dns01/dns_challenge_test.go +++ b/challenge/dns01/dns_challenge_test.go @@ -32,7 +32,7 @@ func (p *providerTimeoutMock) CleanUp(domain, token, keyAuth string) error { ret func (p *providerTimeoutMock) Timeout() (time.Duration, time.Duration) { return p.timeout, p.interval } func TestChallenge_PreSolve(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) @@ -114,7 +114,7 @@ func TestChallenge_PreSolve(t *testing.T) { } func TestChallenge_Solve(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) @@ -201,7 +201,7 @@ func TestChallenge_Solve(t *testing.T) { } func TestChallenge_CleanUp(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err) diff --git a/challenge/http01/http_challenge_test.go b/challenge/http01/http_challenge_test.go index c29c49068..a5e9cc62f 100644 --- a/challenge/http01/http_challenge_test.go +++ b/challenge/http01/http_challenge_test.go @@ -67,7 +67,7 @@ func TestProviderServer_GetAddress(t *testing.T) { } func TestChallenge(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) providerServer := NewProviderServer("", "23457") @@ -123,7 +123,7 @@ func TestChallengeUnix(t *testing.T) { t.Skip("only for UNIX systems") } - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) dir := t.TempDir() t.Cleanup(func() { _ = os.RemoveAll(dir) }) @@ -188,7 +188,7 @@ func TestChallengeUnix(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -371,7 +371,7 @@ func TestChallengeWithProxy(t *testing.T) { func testServeWithProxy(t *testing.T, header, extra *testProxyHeader, expectError bool) { t.Helper() - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) providerServer := NewProviderServer("localhost", "23457") if header != nil { diff --git a/challenge/resolver/prober_test.go b/challenge/resolver/prober_test.go index 4ee9b1b46..06ff07d2c 100644 --- a/challenge/resolver/prober_test.go +++ b/challenge/resolver/prober_test.go @@ -26,9 +26,9 @@ func TestProber_Solve(t *testing.T) { }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("acme.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("lego.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.com", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.org", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.net", acme.StatusProcessing), }, }, { @@ -41,9 +41,9 @@ func TestProber_Solve(t *testing.T) { }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("acme.wtf", acme.StatusValid), - createStubAuthorizationHTTP01("lego.wtf", acme.StatusValid), - createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusValid), + createStubAuthorizationHTTP01("example.com", acme.StatusValid), + createStubAuthorizationHTTP01("example.org", acme.StatusValid), + createStubAuthorizationHTTP01("example.net", acme.StatusValid), }, }, { @@ -51,23 +51,23 @@ func TestProber_Solve(t *testing.T) { solvers: map[challenge.Type]solver{ challenge.HTTP01: &preSolverMock{ preSolve: map[string]error{ - "acme.wtf": errors.New("preSolve error acme.wtf"), + "example.com": errors.New("preSolve error example.com"), }, solve: map[string]error{ - "acme.wtf": errors.New("solve error acme.wtf"), + "example.com": errors.New("solve error example.com"), }, cleanUp: map[string]error{ - "acme.wtf": errors.New("clean error acme.wtf"), + "example.com": errors.New("clean error example.com"), }, }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("acme.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("lego.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.com", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.org", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.net", acme.StatusProcessing), }, expectedError: `error: one or more domains had a problem: -[acme.wtf] preSolve error acme.wtf +[example.com] preSolve error example.com `, }, { @@ -75,25 +75,25 @@ func TestProber_Solve(t *testing.T) { solvers: map[challenge.Type]solver{ challenge.HTTP01: &preSolverMock{ preSolve: map[string]error{ - "acme.wtf": errors.New("preSolve error acme.wtf"), + "example.com": errors.New("preSolve error example.com"), }, solve: map[string]error{ - "acme.wtf": errors.New("solve error acme.wtf"), - "lego.wtf": errors.New("solve error lego.wtf"), + "example.com": errors.New("solve error example.com"), + "example.org": errors.New("solve error example.org"), }, cleanUp: map[string]error{ - "mydomain.wtf": errors.New("clean error mydomain.wtf"), + "example.net": errors.New("clean error example.net"), }, }, }, authz: []acme.Authorization{ - createStubAuthorizationHTTP01("acme.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("lego.wtf", acme.StatusProcessing), - createStubAuthorizationHTTP01("mydomain.wtf", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.com", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.org", acme.StatusProcessing), + createStubAuthorizationHTTP01("example.net", acme.StatusProcessing), }, expectedError: `error: one or more domains had a problem: -[acme.wtf] preSolve error acme.wtf -[lego.wtf] solve error lego.wtf +[example.com] preSolve error example.com +[example.org] solve error example.org `, }, } diff --git a/challenge/resolver/solver_manager_test.go b/challenge/resolver/solver_manager_test.go index b1e198d3c..a0f4630ed 100644 --- a/challenge/resolver/solver_manager_test.go +++ b/challenge/resolver/solver_manager_test.go @@ -12,6 +12,7 @@ import ( "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -32,64 +33,48 @@ func TestByType(t *testing.T) { } func TestValidate(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) - var statuses []string privateKey, _ := rsa.GenerateKey(rand.Reader, 1024) - mux.HandleFunc("/chlg", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } + apiURL := tester.MockACMEServer(). + Route("POST /chlg", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + if err := validateNoBody(privateKey, req); err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } - if err := validateNoBody(privateKey, r); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } + rw.Header().Set("Link", + fmt.Sprintf(`; rel="up"`, req.Context().Value(http.LocalAddrContextKey))) - w.Header().Set("Link", "<"+apiURL+`/my-authz>; rel="up"`) + st := statuses[0] + statuses = statuses[1:] - st := statuses[0] - statuses = statuses[1:] + chlg := &acme.Challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"} - chlg := &acme.Challenge{Type: "http-01", Status: st, URL: "http://example.com/", Token: "token"} + servermock.JSONEncode(chlg).ServeHTTP(rw, req) + })). + Route("POST /my-authz", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + st := statuses[0] + statuses = statuses[1:] - err := tester.WriteJSONResponse(w, chlg) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + authorization := acme.Authorization{ + Status: st, + Challenges: []acme.Challenge{}, + } - mux.HandleFunc("/my-authz", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } + if st == acme.StatusInvalid { + chlg := acme.Challenge{ + Status: acme.StatusInvalid, + } + authorization.Challenges = append(authorization.Challenges, chlg) + } - st := statuses[0] - statuses = statuses[1:] - - authorization := acme.Authorization{ - Status: st, - Challenges: []acme.Challenge{}, - } - - if st == acme.StatusInvalid { - chlg := acme.Challenge{ - Status: acme.StatusInvalid, - } - authorization.Challenges = append(authorization.Challenges, chlg) - } - - err := tester.WriteJSONResponse(w, authorization) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + servermock.JSONEncode(authorization).ServeHTTP(rw, req) + })). + Build(t) core, err := api.New(http.DefaultClient, "lego-test", apiURL+"/dir", "", privateKey) require.NoError(t, err) diff --git a/challenge/tlsalpn01/tls_alpn_challenge_test.go b/challenge/tlsalpn01/tls_alpn_challenge_test.go index 9f65742f3..5e0469d8c 100644 --- a/challenge/tlsalpn01/tls_alpn_challenge_test.go +++ b/challenge/tlsalpn01/tls_alpn_challenge_test.go @@ -21,7 +21,7 @@ import ( ) func TestChallenge(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) domain := "localhost" port := "24457" @@ -93,7 +93,7 @@ func TestChallenge(t *testing.T) { } func TestChallengeInvalidPort(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) privateKey, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") @@ -123,7 +123,7 @@ func TestChallengeInvalidPort(t *testing.T) { } func TestChallengeIPaddress(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) domain := "127.0.0.1" port := "24457" diff --git a/lego/client_test.go b/lego/client_test.go index 4f07eb1ea..8a18c47c7 100644 --- a/lego/client_test.go +++ b/lego/client_test.go @@ -13,7 +13,7 @@ import ( ) func TestNewClient(t *testing.T) { - _, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer().Build(t) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key") diff --git a/platform/tester/api.go b/platform/tester/api.go index 2084cf1bb..4ffb62a50 100644 --- a/platform/tester/api.go +++ b/platform/tester/api.go @@ -2,53 +2,36 @@ package tester import ( "encoding/json" + "fmt" "net/http" "net/http/httptest" - "testing" "github.com/go-acme/lego/v4/acme" + "github.com/go-acme/lego/v4/platform/tester/servermock" ) -// SetupFakeAPI Minimal stub ACME server for validation. -func SetupFakeAPI(t *testing.T) (*http.ServeMux, string) { - t.Helper() +// MockACMEServer Minimal stub ACME server for validation. +func MockACMEServer() *servermock.Builder[string] { + return servermock.NewBuilder( + func(server *httptest.Server) (string, error) { + return server.URL, nil + }). + Route("GET /dir", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + serverURL := fmt.Sprintf("http://%s", req.Context().Value(http.LocalAddrContextKey)) - mux := http.NewServeMux() - server := httptest.NewServer(mux) - t.Cleanup(server.Close) - - mux.HandleFunc("/dir", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - err := WriteJSONResponse(w, acme.Directory{ - NewNonceURL: server.URL + "/nonce", - NewAccountURL: server.URL + "/account", - NewOrderURL: server.URL + "/newOrder", - RevokeCertURL: server.URL + "/revokeCert", - KeyChangeURL: server.URL + "/keyChange", - RenewalInfo: server.URL + "/renewalInfo", - }) - - mux.HandleFunc("/nonce", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodHead { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - w.Header().Set("Replay-Nonce", "12345") - w.Header().Set("Retry-After", "0") - }) - - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - return mux, server.URL + servermock.JSONEncode(acme.Directory{ + NewNonceURL: serverURL + "/nonce", + NewAccountURL: serverURL + "/account", + NewOrderURL: serverURL + "/newOrder", + RevokeCertURL: serverURL + "/revokeCert", + KeyChangeURL: serverURL + "/keyChange", + RenewalInfo: serverURL + "/renewalInfo", + }).ServeHTTP(rw, req) + })). + Route("HEAD /nonce", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Replay-Nonce", "12345") + rw.Header().Set("Retry-After", "0") + })) } // WriteJSONResponse marshals the body as JSON and writes it to the response. diff --git a/providers/dns/constellix/internal/domains_test.go b/providers/dns/constellix/internal/domains_test.go index 2d92fb8f3..468db4613 100644 --- a/providers/dns/constellix/internal/domains_test.go +++ b/providers/dns/constellix/internal/domains_test.go @@ -30,10 +30,10 @@ func TestDomainService_GetAll(t *testing.T) { require.NoError(t, err) expected := []Domain{ - {ID: 273301, Name: "aaa.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, - {ID: 273302, Name: "bbb.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, - {ID: 273303, Name: "ccc.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, - {ID: 273304, Name: "ddd.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273301, Name: "aaa.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273302, Name: "bbb.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273303, Name: "ccc.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273304, Name: "ddd.example", TypeID: 1, Version: 9, Status: "ACTIVE"}, } assert.Equal(t, expected, data) @@ -44,14 +44,14 @@ func TestDomainService_Search(t *testing.T) { Route("GET /v1/domains/search", servermock.ResponseFromFixture("domains-Search.json"), servermock.CheckQueryParameter().Strict(). - With("exact", "lego.wtf")). + With("exact", "example.com")). Build(t) - data, err := client.Domains.Search(t.Context(), Exact, "lego.wtf") + data, err := client.Domains.Search(t.Context(), Exact, "example.com") require.NoError(t, err) expected := []Domain{ - {ID: 273302, Name: "lego.wtf", TypeID: 1, Version: 9, Status: "ACTIVE"}, + {ID: 273302, Name: "example.com", TypeID: 1, Version: 9, Status: "ACTIVE"}, } assert.Equal(t, expected, data) diff --git a/providers/dns/constellix/internal/fixtures/domains-GetAll.json b/providers/dns/constellix/internal/fixtures/domains-GetAll.json index 5ff2ad41d..8ccb4e52c 100644 --- a/providers/dns/constellix/internal/fixtures/domains-GetAll.json +++ b/providers/dns/constellix/internal/fixtures/domains-GetAll.json @@ -1,7 +1,7 @@ [ { "id": 273301, - "name": "aaa.wtf", + "name": "aaa.example", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", @@ -36,7 +36,7 @@ }, { "id": 273302, - "name": "bbb.wtf", + "name": "bbb.example", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", @@ -71,7 +71,7 @@ }, { "id": 273303, - "name": "ccc.wtf", + "name": "ccc.example", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", @@ -106,7 +106,7 @@ }, { "id": 273304, - "name": "ddd.wtf", + "name": "ddd.example", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", diff --git a/providers/dns/constellix/internal/fixtures/domains-Search.json b/providers/dns/constellix/internal/fixtures/domains-Search.json index 5d018a39a..c33272515 100644 --- a/providers/dns/constellix/internal/fixtures/domains-Search.json +++ b/providers/dns/constellix/internal/fixtures/domains-Search.json @@ -1,7 +1,7 @@ [ { "id": 273302, - "name": "lego.wtf", + "name": "example.com", "soa": { "primaryNameserver": "ns11.constellix.com.", "email": "dns.constellix.com.", diff --git a/providers/dns/gcloud/googlecloud_test.go b/providers/dns/gcloud/googlecloud_test.go index 7fda2f8f6..61d5cc4e5 100644 --- a/providers/dns/gcloud/googlecloud_test.go +++ b/providers/dns/gcloud/googlecloud_test.go @@ -52,7 +52,7 @@ func TestNewDNSProvider(t *testing.T) { envServiceAccountFile: "", // as Travis run on GCE, we have to alter env envGoogleApplicationCredentials: "not-a-secret-file", - envMetadataHost: "http://lego.wtf", // defined here to avoid the client cache. + envMetadataHost: "http://example.com", // defined here to avoid the client cache. }, // the error message varies according to the OS used. expected: "googlecloud: unable to get Google Cloud client: google: error getting credentials using GOOGLE_APPLICATION_CREDENTIALS environment variable: ", @@ -63,7 +63,7 @@ func TestNewDNSProvider(t *testing.T) { EnvProject: "", envServiceAccountFile: "", // as Travis run on GCE, we have to alter env - envMetadataHost: "http://lego.wtf", + envMetadataHost: "http://example.com", }, expected: "googlecloud: project name missing", }, @@ -154,7 +154,7 @@ func TestPresentNoExistingRR(t *testing.T) { }, }), servermock.CheckQueryParameter().Strict(). - With("dnsName", "lego.wtf."). + With("dnsName", "example.com."). With("prettyPrint", "false"). With("alt", "json")). // findTxtRecords @@ -163,7 +163,7 @@ func TestPresentNoExistingRR(t *testing.T) { Rrsets: []*dns.ResourceRecordSet{}, }), servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge.lego.wtf."). + With("name", "_acme-challenge.example.com."). With("type", "TXT"). With("prettyPrint", "false"). With("alt", "json")). @@ -189,7 +189,7 @@ func TestPresentNoExistingRR(t *testing.T) { With("alt", "json")). Build(t) - domain := "lego.wtf" + domain := "example.com" err := provider.Present(domain, "", "") require.NoError(t, err) @@ -205,21 +205,21 @@ func TestPresentWithExistingRR(t *testing.T) { }, }), servermock.CheckQueryParameter().Strict(). - With("dnsName", "lego.wtf."). + With("dnsName", "example.com."). With("prettyPrint", "false"). With("alt", "json")). // findTxtRecords Route("GET /dns/v1/projects/manhattan/managedZones/test/rrsets", servermock.JSONEncode(&dns.ResourceRecordSetsListResponse{ Rrsets: []*dns.ResourceRecordSet{{ - Name: "_acme-challenge.lego.wtf.", + Name: "_acme-challenge.example.com.", Rrdatas: []string{`"X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"huji"`}, Ttl: 120, Type: "TXT", }}, }), servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge.lego.wtf."). + With("name", "_acme-challenge.example.com."). With("type", "TXT"). With("prettyPrint", "false"). With("alt", "json")). @@ -260,7 +260,7 @@ func TestPresentWithExistingRR(t *testing.T) { With("alt", "json")). Build(t) - domain := "lego.wtf" + domain := "example.com" err := provider.Present(domain, "", "") require.NoError(t, err) @@ -276,27 +276,27 @@ func TestPresentSkipExistingRR(t *testing.T) { }, }), servermock.CheckQueryParameter().Strict(). - With("dnsName", "lego.wtf."). + With("dnsName", "example.com."). With("prettyPrint", "false"). With("alt", "json")). // findTxtRecords Route("GET /dns/v1/projects/manhattan/managedZones/test/rrsets", servermock.JSONEncode(&dns.ResourceRecordSetsListResponse{ Rrsets: []*dns.ResourceRecordSet{{ - Name: "_acme-challenge.lego.wtf.", + Name: "_acme-challenge.example.com.", Rrdatas: []string{`"47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"X7DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"`, `"huji"`}, Ttl: 120, Type: "TXT", }}, }), servermock.CheckQueryParameter().Strict(). - With("name", "_acme-challenge.lego.wtf."). + With("name", "_acme-challenge.example.com."). With("type", "TXT"). With("prettyPrint", "false"). With("alt", "json")). Build(t) - domain := "lego.wtf" + domain := "example.com" err := provider.Present(domain, "", "") require.NoError(t, err) diff --git a/providers/dns/servercow/internal/client_test.go b/providers/dns/servercow/internal/client_test.go index 2092bf907..3733ccad1 100644 --- a/providers/dns/servercow/internal/client_test.go +++ b/providers/dns/servercow/internal/client_test.go @@ -29,10 +29,10 @@ func mockBuilder() *servermock.Builder[*Client] { func TestClient_GetRecords(t *testing.T) { client := mockBuilder(). - Route("GET /lego.wtf", servermock.ResponseFromFixture("records-01.json")). + Route("GET /example.com", servermock.ResponseFromFixture("records-01.json")). Build(t) - records, err := client.GetRecords(t.Context(), "lego.wtf") + records, err := client.GetRecords(t.Context(), "example.com") require.NoError(t, err) recordsJSON, err := json.Marshal(records) @@ -46,10 +46,10 @@ func TestClient_GetRecords(t *testing.T) { func TestClient_GetRecords_error(t *testing.T) { client := mockBuilder(). - Route("GET /lego.wtf", servermock.JSONEncode(Message{ErrorMsg: "authentication failed"})). + Route("GET /example.com", servermock.JSONEncode(Message{ErrorMsg: "authentication failed"})). Build(t) - records, err := client.GetRecords(t.Context(), "lego.wtf") + records, err := client.GetRecords(t.Context(), "example.com") require.Error(t, err) assert.Nil(t, records) @@ -57,7 +57,7 @@ func TestClient_GetRecords_error(t *testing.T) { func TestClient_CreateUpdateRecord(t *testing.T) { client := mockBuilder(). - Route("POST /lego.wtf", + Route("POST /example.com", servermock.JSONEncode(Message{Message: "ok"}), servermock.CheckRequestJSONBody(`{"name":"_acme-challenge.www","type":"TXT","ttl":30,"content":["aaa","bbb"]}`)). Build(t) @@ -69,7 +69,7 @@ func TestClient_CreateUpdateRecord(t *testing.T) { Content: Value{"aaa", "bbb"}, } - msg, err := client.CreateUpdateRecord(t.Context(), "lego.wtf", record) + msg, err := client.CreateUpdateRecord(t.Context(), "example.com", record) require.NoError(t, err) expected := &Message{Message: "ok"} @@ -78,7 +78,7 @@ func TestClient_CreateUpdateRecord(t *testing.T) { func TestClient_CreateUpdateRecord_error(t *testing.T) { client := mockBuilder(). - Route("POST /lego.wtf", + Route("POST /example.com", servermock.JSONEncode(Message{ErrorMsg: "parameter type must be cname, txt, tlsa, caa, a or aaaa"})). Build(t) @@ -86,7 +86,7 @@ func TestClient_CreateUpdateRecord_error(t *testing.T) { Name: "_acme-challenge.www", } - msg, err := client.CreateUpdateRecord(t.Context(), "lego.wtf", record) + msg, err := client.CreateUpdateRecord(t.Context(), "example.com", record) require.Error(t, err) assert.Nil(t, msg) @@ -94,7 +94,7 @@ func TestClient_CreateUpdateRecord_error(t *testing.T) { func TestClient_DeleteRecord(t *testing.T) { client := mockBuilder(). - Route("DELETE /lego.wtf", + Route("DELETE /example.com", servermock.JSONEncode(Message{Message: "ok"}), servermock.CheckRequestJSONBody(`{"name":"_acme-challenge.www","type":"TXT"}`)). Build(t) @@ -104,7 +104,7 @@ func TestClient_DeleteRecord(t *testing.T) { Type: "TXT", } - msg, err := client.DeleteRecord(t.Context(), "lego.wtf", record) + msg, err := client.DeleteRecord(t.Context(), "example.com", record) require.NoError(t, err) expected := &Message{Message: "ok"} @@ -113,7 +113,7 @@ func TestClient_DeleteRecord(t *testing.T) { func TestClient_DeleteRecord_error(t *testing.T) { client := mockBuilder(). - Route("DELETE /lego.wtf", + Route("DELETE /example.com", servermock.JSONEncode(Message{ErrorMsg: "parameter type must be cname, txt, tlsa, caa, a or aaaa"})). Build(t) @@ -121,7 +121,7 @@ func TestClient_DeleteRecord_error(t *testing.T) { Name: "_acme-challenge.www", } - msg, err := client.DeleteRecord(t.Context(), "lego.wtf", record) + msg, err := client.DeleteRecord(t.Context(), "example.com", record) require.Error(t, err) assert.Nil(t, msg) diff --git a/registration/registar_test.go b/registration/registar_test.go index 45fe33e82..0b3f88f98 100644 --- a/registration/registar_test.go +++ b/registration/registar_test.go @@ -3,29 +3,28 @@ package registration import ( "crypto/rand" "crypto/rsa" + "fmt" "net/http" "testing" "github.com/go-acme/lego/v4/acme" "github.com/go-acme/lego/v4/acme/api" "github.com/go-acme/lego/v4/platform/tester" + "github.com/go-acme/lego/v4/platform/tester/servermock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRegistrar_ResolveAccountByKey(t *testing.T) { - mux, apiURL := tester.SetupFakeAPI(t) + apiURL := tester.MockACMEServer(). + Route("/account", + http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Location", + fmt.Sprintf("http://%s/account", req.Context().Value(http.LocalAddrContextKey))) - mux.HandleFunc("/account", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Location", apiURL+"/account") - err := tester.WriteJSONResponse(w, acme.Account{ - Status: "valid", - }) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) + servermock.JSONEncode(acme.Account{Status: "valid"}).ServeHTTP(rw, req) + })). + Build(t) key, err := rsa.GenerateKey(rand.Reader, 1024) require.NoError(t, err, "Could not generate test key")