commit 4f51af7d86f135f566f8156396dd0c89bc6175aa
| author | Jake Sanders <i@am.so-aweso.me> |
| date | 2021-02-22T15:11:18Z |
| subject | Add example server that passes the test fixtures |
commit 4f51af7d86f135f566f8156396dd0c89bc6175aa
Author: Jake Sanders <i@am.so-aweso.me>
Date: 2021-02-22T15:11:18Z
Add example server that passes the test fixtures
Signed-off-by: Jake Sanders <i@am.so-aweso.me>
---
example/dns.go | 58 ++++++++++++++++++++++++++++++
example/example.go | 60 +++++++++++++++++++++++++++++++
example/example_test.go | 94 +++++++++++++++++++++++++++++++++++++++++++++++++
main_test.go | 20 +++++++++--
4 files changed, 229 insertions(+), 3 deletions(-)
diff --git a/example/dns.go b/example/dns.go
index f7ec372..9b00b79 100644
--- a/example/dns.go
+++ b/example/dns.go
@@ -1 +1,59 @@
package example
+
+import (
+ "fmt"
+ "github.com/miekg/dns"
+)
+
+func (e *exampleSolver) handleDNSRequest(w dns.ResponseWriter, req *dns.Msg) {
+ msg := new(dns.Msg)
+ msg.SetReply(req)
+ switch req.Opcode {
+ case dns.OpcodeQuery:
+ for _, q := range msg.Question {
+ switch q.Qtype {
+ case dns.TypeA:
+ rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN A 127.0.0.1", q.Name))
+ if err != nil {
+ msg.SetRcode(req, dns.RcodeNameError)
+ } else {
+ msg.Answer = append(msg.Answer, rr)
+ }
+ case dns.TypeTXT:
+ // get record
+ e.RLock()
+ record, found := e.txtRecords[q.Name]
+ e.RUnlock()
+ if !found {
+ msg.SetRcode(req, dns.RcodeNameError)
+ } else {
+ rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN TXT %s", q.Name, record))
+ if err != nil {
+ msg.SetRcode(req, dns.RcodeServerFailure)
+ break
+ }
+ msg.Answer = append(msg.Answer, rr)
+ }
+ case dns.TypeNS:
+ rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN NS ns.example-acme-webook.invalid.", q.Name))
+ if err != nil {
+ msg.SetRcode(req, dns.RcodeServerFailure)
+ break
+ } else {
+ msg.Answer = append(msg.Answer, rr)
+ }
+ case dns.TypeSOA:
+ rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN SOA %s 20 5 5 5 5", "ns.example-acme-webook.invalid.", "ns.example-acme-webook.invalid."))
+ if err != nil {
+ msg.SetRcode(req, dns.RcodeServerFailure)
+ break
+ }
+ msg.Answer = append(msg.Answer, rr)
+ default:
+ msg.SetRcode(req, dns.RcodeServerFailure)
+ break
+ }
+ }
+ }
+ w.WriteMsg(msg)
+}
diff --git a/example/example.go b/example/example.go
index f7ec372..49eca8f 100644
--- a/example/example.go
+++ b/example/example.go
@@ -1 +1,61 @@
+// package example contains a self-contained example of a webhook that passes the cert-manager
+// DNS conformance tests
package example
+
+import (
+ "sync"
+
+ "github.com/jetstack/cert-manager/pkg/acme/webhook"
+ acme "github.com/jetstack/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
+ "github.com/miekg/dns"
+ "k8s.io/client-go/rest"
+)
+
+type exampleSolver struct {
+ name string
+ server *dns.Server
+ txtRecords map[string]string
+ sync.RWMutex
+}
+
+func (e exampleSolver) Name() string {
+ return e.name
+}
+
+func (e exampleSolver) Present(ch *acme.ChallengeRequest) error {
+ e.Lock()
+ e.txtRecords[ch.ResolvedFQDN] = ch.Key
+ e.Unlock()
+ return nil
+}
+
+func (e exampleSolver) CleanUp(ch *acme.ChallengeRequest) error {
+ e.Lock()
+ delete(e.txtRecords, ch.ResolvedFQDN)
+ e.Unlock()
+ return nil
+}
+
+func (e exampleSolver) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error {
+ go func(done <-chan struct{}) {
+ <-done
+ e.server.Shutdown()
+ }(stopCh)
+ go func() {
+ e.server.ListenAndServe()
+ }()
+ return nil
+}
+
+func New(port string) webhook.Solver {
+ e := &exampleSolver{
+ name: "example",
+ txtRecords: make(map[string]string),
+ }
+ e.server = &dns.Server{
+ Addr: ":" + port,
+ Net: "udp",
+ Handler: dns.HandlerFunc(e.handleDNSRequest),
+ }
+ return e
+}
diff --git a/example/example_test.go b/example/example_test.go
index f7ec372..2c4a455 100644
--- a/example/example_test.go
+++ b/example/example_test.go
@@ -1 +1,95 @@
package example
+
+import (
+ "crypto/rand"
+ acme "github.com/jetstack/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
+ "github.com/miekg/dns"
+ "github.com/stretchr/testify/assert"
+ "math/big"
+ "testing"
+)
+
+func TestExampleSolver_Name(t *testing.T) {
+ port, _ := rand.Int(rand.Reader, big.NewInt(50000))
+ port = port.Add(port, big.NewInt(15534))
+ solver := New(port.String())
+ assert.Equal(t, "example", solver.Name())
+}
+
+func TestExampleSolver_Initialize(t *testing.T) {
+ port, _ := rand.Int(rand.Reader, big.NewInt(50000))
+ port = port.Add(port, big.NewInt(15534))
+ solver := New(port.String())
+ done := make(chan struct{})
+ err := solver.Initialize(nil, done)
+ assert.NoError(t, err, "Expected Initialize not to error")
+ close(done)
+}
+
+func TestExampleSolver_Present_Cleanup(t *testing.T) {
+ port, _ := rand.Int(rand.Reader, big.NewInt(50000))
+ port = port.Add(port, big.NewInt(15534))
+ solver := New(port.String())
+ done := make(chan struct{})
+ err := solver.Initialize(nil, done)
+ assert.NoError(t, err, "Expected Initialize not to error")
+
+ validTestData := []struct {
+ hostname string
+ record string
+ }{
+ {"test1.example.com.", "testkey1"},
+ {"test2.example.com.", "testkey2"},
+ {"test3.example.com.", "testkey3"},
+ }
+ for _, test := range validTestData {
+ err := solver.Present(&acme.ChallengeRequest{
+ Action: acme.ChallengeActionPresent,
+ Type: "dns-01",
+ ResolvedFQDN: test.hostname,
+ Key: test.record,
+ })
+ assert.NoError(t, err, "Unexpected error while presenting %v", t)
+ }
+
+ // Resolve test data
+ for _, test := range validTestData {
+ msg := new(dns.Msg)
+ msg.Id = dns.Id()
+ msg.RecursionDesired = true
+ msg.Question = make([]dns.Question, 1)
+ msg.Question[0] = dns.Question{dns.Fqdn(test.hostname), dns.TypeTXT, dns.ClassINET}
+ in, err := dns.Exchange(msg, "127.0.0.1:"+port.String())
+
+ assert.NoError(t, err, "Presented record %s not resolvable", test.hostname)
+ assert.Len(t, in.Answer, 1, "RR response is of incorrect length")
+ assert.Equal(t, []string{test.record}, in.Answer[0].(*dns.TXT).Txt, "TXT record returned did not match presented record")
+ }
+
+ // Cleanup test data
+ for _, test := range validTestData {
+ err := solver.CleanUp(&acme.ChallengeRequest{
+ Action: acme.ChallengeActionCleanUp,
+ Type: "dns-01",
+ ResolvedFQDN: test.hostname,
+ Key: test.record,
+ })
+ assert.NoError(t, err, "Unexpected error while cleaning up %v", t)
+ }
+
+ // Resolve test data
+ for _, test := range validTestData {
+ msg := new(dns.Msg)
+ msg.Id = dns.Id()
+ msg.RecursionDesired = true
+ msg.Question = make([]dns.Question, 1)
+ msg.Question[0] = dns.Question{dns.Fqdn(test.hostname), dns.TypeTXT, dns.ClassINET}
+ in, err := dns.Exchange(msg, "127.0.0.1:"+port.String())
+
+ assert.NoError(t, err, "Presented record %s not resolvable", test.hostname)
+ assert.Len(t, in.Answer, 0, "RR response is of incorrect length")
+ assert.Equal(t, dns.RcodeNameError, in.Rcode, "Expexted NXDOMAIN")
+ }
+
+ close(done)
+}
diff --git a/main_test.go b/main_test.go
index 4e32419..c731751 100644
--- a/main_test.go
+++ b/main_test.go
@@ -5,6 +5,8 @@ import (
"testing"
"github.com/jetstack/cert-manager/test/acme/dns"
+
+ "github.com/jetstack/cert-manager-webhook-example/example"
)
var (
@@ -15,11 +17,23 @@ func TestRunsSuite(t *testing.T) {
// The manifest path should contain a file named config.json that is a
// snippet of valid configuration that should be included on the
// ChallengeRequest passed as part of the test cases.
+ //
+
+ // Uncomment the below fixture when implementing your custom DNS provider
+ //fixture := dns.NewFixture(&customDNSProviderSolver{},
+ // dns.SetResolvedZone(zone),
+ // dns.SetAllowAmbientCredentials(false),
+ // dns.SetManifestPath("testdata/my-custom-solver"),
+ // dns.SetBinariesPath("_test/kubebuilder/bin"),
+ //)
- fixture := dns.NewFixture(&customDNSProviderSolver{},
- dns.SetResolvedZone(zone),
- dns.SetAllowAmbientCredentials(false),
+ solver := example.New("59351")
+ fixture := dns.NewFixture(solver,
+ dns.SetResolvedZone("example.com."),
dns.SetManifestPath("testdata/my-custom-solver"),
+ dns.SetBinariesPath("_test/kubebuilder/bin"),
+ dns.SetDNSServer("127.0.0.1:59351"),
+ dns.SetUseAuthoritative(false),
)
fixture.RunConformance(t)