cert-manager/pkg/controller/acmeorders/sync_test.go
James Munnelly 641fe0da7c Switch to using upstream golang.org/x/crypto
Signed-off-by: James Munnelly <james@munnelly.eu>
2019-12-03 16:49:31 +00:00

360 lines
12 KiB
Go

/*
Copyright 2019 The Jetstack cert-manager contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package acmeorders
import (
"context"
"testing"
"time"
acmeapi "golang.org/x/crypto/acme"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
coretesting "k8s.io/client-go/testing"
fakeclock "k8s.io/utils/clock/testing"
acmecl "github.com/jetstack/cert-manager/pkg/acme/client"
acmefake "github.com/jetstack/cert-manager/pkg/acme/fake"
cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1alpha2"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
"github.com/jetstack/cert-manager/test/unit/gen"
)
func TestSyncHappyPath(t *testing.T) {
nowTime := time.Now()
nowMetaTime := metav1.NewTime(nowTime)
fixedClock := fakeclock.NewFakeClock(nowTime)
testIssuerHTTP01TestCom := gen.Issuer("testissuer", gen.SetIssuerACME(cmacme.ACMEIssuer{
Solvers: []cmacme.ACMEChallengeSolver{
{
Selector: &cmacme.CertificateDNSNameSelector{
DNSNames: []string{"test.com"},
},
HTTP01: &cmacme.ACMEChallengeSolverHTTP01{
Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{},
},
},
},
}))
testOrder := gen.Order("testorder",
gen.SetOrderCommonName("test.com"),
gen.SetOrderIssuer(cmmeta.ObjectReference{
Name: testIssuerHTTP01TestCom.Name,
}),
)
pendingStatus := cmacme.OrderStatus{
State: cmacme.Pending,
URL: "http://testurl.com/abcde",
FinalizeURL: "http://testurl.com/abcde/finalize",
Authorizations: []cmacme.ACMEAuthorization{
{
URL: "http://authzurl",
Identifier: "test.com",
Challenges: []cmacme.ACMEChallenge{
{
URL: "http://chalurl",
Token: "token",
Type: cmacme.ACMEChallengeTypeHTTP01,
},
},
},
},
}
testOrderPending := gen.OrderFrom(testOrder, gen.SetOrderStatus(pendingStatus))
testOrderInvalid := testOrderPending.DeepCopy()
testOrderInvalid.Status.State = cmacme.Invalid
testOrderInvalid.Status.FailureTime = &nowMetaTime
testOrderValid := testOrderPending.DeepCopy()
testOrderValid.Status.State = cmacme.Valid
// pem encoded word 'test'
testOrderValid.Status.Certificate = []byte(`-----BEGIN CERTIFICATE-----
dGVzdA==
-----END CERTIFICATE-----
`)
testOrderReady := testOrderPending.DeepCopy()
testOrderReady.Status.State = cmacme.Ready
fakeHTTP01ACMECl := &acmecl.FakeACME{
FakeHTTP01ChallengeResponse: func(s string) (string, error) {
// TODO: assert s = "token"
return "key", nil
},
}
testAuthorizationChallenge, err := buildChallenge(context.TODO(), fakeHTTP01ACMECl, testIssuerHTTP01TestCom, testOrderPending, testOrderPending.Status.Authorizations[0])
if err != nil {
t.Fatalf("error building Challenge resource test fixture: %v", err)
}
testAuthorizationChallengeValid := testAuthorizationChallenge.DeepCopy()
testAuthorizationChallengeValid.Status.State = cmacme.Valid
testAuthorizationChallengeInvalid := testAuthorizationChallenge.DeepCopy()
testAuthorizationChallengeInvalid.Status.State = cmacme.Invalid
testACMEAuthorizationPending := &acmeapi.Authorization{
URI: "http://authzurl",
Status: acmeapi.StatusPending,
Identifier: acmeapi.AuthzID{
Value: "test.com",
},
Challenges: []*acmeapi.Challenge{
{
Type: "http-01",
Token: "token",
},
},
}
testACMEOrderPending := &acmeapi.Order{
URI: testOrderPending.Status.URL,
Identifiers: []acmeapi.AuthzID{
{
Type: "dns",
Value: "test.com",
},
},
FinalizeURL: testOrderPending.Status.FinalizeURL,
AuthzURLs: []string{"http://authzurl"},
Status: acmeapi.StatusPending,
}
// shallow copy
testACMEOrderValid := &acmeapi.Order{}
*testACMEOrderValid = *testACMEOrderPending
testACMEOrderValid.Status = acmeapi.StatusValid
// shallow copy
testACMEOrderReady := &acmeapi.Order{}
*testACMEOrderReady = *testACMEOrderPending
testACMEOrderReady.Status = acmeapi.StatusReady
// shallow copy
testACMEOrderInvalid := &acmeapi.Order{}
*testACMEOrderInvalid = *testACMEOrderPending
testACMEOrderInvalid.Status = acmeapi.StatusInvalid
tests := map[string]testT{
"create a new order with the acme server, set the order url on the status resource and return nil to avoid cache timing issues": {
order: testOrder,
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{testIssuerHTTP01TestCom, testOrder},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(cmacme.SchemeGroupVersion.WithResource("orders"),
"status",
testOrderPending.Namespace,
gen.OrderFrom(testOrder, gen.SetOrderStatus(cmacme.OrderStatus{
State: cmacme.Pending,
URL: "http://testurl.com/abcde",
FinalizeURL: "http://testurl.com/abcde/finalize",
Authorizations: []cmacme.ACMEAuthorization{
{
URL: "http://authzurl",
},
},
})))),
},
},
acmeClient: &acmecl.FakeACME{
FakeAuthorizeOrder: func(ctx context.Context, id []acmeapi.AuthzID, opt ...acmeapi.OrderOption) (*acmeapi.Order, error) {
return testACMEOrderPending, nil
},
FakeGetAuthorization: func(ctx context.Context, url string) (*acmeapi.Authorization, error) {
// TODO: assert url = "http://authzurl"
return testACMEAuthorizationPending, nil
},
FakeHTTP01ChallengeResponse: func(s string) (string, error) {
// TODO: assert s = "token"
return "key", nil
},
},
},
"create a challenge resource for the test.com dnsName on the order": {
order: testOrderPending,
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{testIssuerHTTP01TestCom, testOrderPending},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewCreateAction(cmacme.SchemeGroupVersion.WithResource("challenges"), testAuthorizationChallenge.Namespace, testAuthorizationChallenge)),
},
ExpectedEvents: []string{
`Normal Created Created Challenge resource "testorder-1335133199" for domain "test.com"`,
},
},
acmeClient: &acmecl.FakeACME{
FakeHTTP01ChallengeResponse: func(s string) (string, error) {
// TODO: assert s = "token"
return "key", nil
},
},
},
"do nothing if the challenge for test.com is still pending": {
order: testOrderPending,
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{testIssuerHTTP01TestCom, testOrderPending, testAuthorizationChallenge},
ExpectedActions: []testpkg.Action{},
},
acmeClient: &acmecl.FakeACME{
FakeHTTP01ChallengeResponse: func(s string) (string, error) {
// TODO: assert s = "token"
return "key", nil
},
},
},
"call GetOrder and update the order state to 'ready' if all challenges are 'valid'": {
order: testOrderPending,
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{testIssuerHTTP01TestCom, testOrderPending, testAuthorizationChallengeValid},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(cmacme.SchemeGroupVersion.WithResource("orders"),
"status",
testOrderReady.Namespace, testOrderReady)),
},
},
acmeClient: &acmecl.FakeACME{
FakeGetOrder: func(_ context.Context, url string) (*acmeapi.Order, error) {
return testACMEOrderReady, nil
},
FakeHTTP01ChallengeResponse: func(s string) (string, error) {
// TODO: assert s = "token"
return "key", nil
},
},
},
"call FinalizeOrder and update the order state to 'valid' if finalize succeeds": {
order: testOrderReady,
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{testIssuerHTTP01TestCom, testOrderReady, testAuthorizationChallengeValid},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(cmacme.SchemeGroupVersion.WithResource("orders"),
"status",
testOrderValid.Namespace, testOrderValid)),
},
ExpectedEvents: []string{
"Normal Complete Order completed successfully",
},
},
acmeClient: &acmecl.FakeACME{
FakeGetOrder: func(_ context.Context, url string) (*acmeapi.Order, error) {
return testACMEOrderValid, nil
},
FakeCreateOrderCert: func(_ context.Context, url string, csr []byte, bundle bool) ([][]byte, string, error) {
testData := []byte("test")
return [][]byte{testData}, "http://testurl", nil
},
FakeHTTP01ChallengeResponse: func(s string) (string, error) {
// TODO: assert s = "token"
return "key", nil
},
},
},
"call GetOrder and update the order state if the challenge is 'failed'": {
order: testOrderPending,
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{testIssuerHTTP01TestCom, testOrderPending, testAuthorizationChallengeInvalid},
ExpectedActions: []testpkg.Action{
testpkg.NewAction(coretesting.NewUpdateSubresourceAction(cmacme.SchemeGroupVersion.WithResource("orders"),
"status",
testOrderInvalid.Namespace, testOrderInvalid)),
},
},
acmeClient: &acmecl.FakeACME{
FakeGetOrder: func(_ context.Context, url string) (*acmeapi.Order, error) {
return testACMEOrderInvalid, nil
},
FakeHTTP01ChallengeResponse: func(s string) (string, error) {
return "key", nil
},
},
},
"should leave the order state as-is if the challenge is marked invalid but the acme order is pending": {
order: testOrderPending,
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{testIssuerHTTP01TestCom, testOrderPending, testAuthorizationChallengeInvalid},
ExpectedActions: []testpkg.Action{},
},
acmeClient: &acmecl.FakeACME{
FakeGetOrder: func(_ context.Context, url string) (*acmeapi.Order, error) {
return testACMEOrderPending, nil
},
FakeHTTP01ChallengeResponse: func(s string) (string, error) {
// TODO: assert s = "token"
return "key", nil
},
},
},
"do nothing if the order is valid": {
order: testOrderValid,
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{testIssuerHTTP01TestCom, testOrderValid},
ExpectedActions: []testpkg.Action{},
},
acmeClient: &acmecl.FakeACME{},
},
"do nothing if the order is failed": {
order: testOrderInvalid,
builder: &testpkg.Builder{
CertManagerObjects: []runtime.Object{testIssuerHTTP01TestCom, testOrderInvalid},
ExpectedActions: []testpkg.Action{},
},
acmeClient: &acmecl.FakeACME{},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
// reset the fixedClock at the start of each test
fixedClock.SetTime(nowTime)
// always use the fixedClock unless otherwise specified
if test.builder.Clock == nil {
test.builder.Clock = fixedClock
}
runTest(t, test)
})
}
}
type testT struct {
order *cmacme.Order
builder *testpkg.Builder
acmeClient acmecl.Interface
expectErr bool
}
func runTest(t *testing.T, test testT) {
test.builder.T = t
test.builder.Init()
defer test.builder.Stop()
c := &controller{}
c.Register(test.builder.Context)
c.acmeHelper = &acmefake.Helper{
ClientForIssuerFunc: func(iss v1alpha2.GenericIssuer) (acmecl.Interface, error) {
return test.acmeClient, nil
},
}
test.builder.Start()
err := c.Sync(context.Background(), test.order)
if err != nil && !test.expectErr {
t.Errorf("Expected function to not error, but got: %v", err)
}
if err == nil && test.expectErr {
t.Errorf("Expected function to get an error, but got: %v", err)
}
test.builder.CheckAndFinish(err)
}