Add example server that passes the test fixtures

Signed-off-by: Jake Sanders <i@am.so-aweso.me>
This commit is contained in:
Jake Sanders 2021-02-22 15:11:18 +00:00
parent 9b61d24773
commit 4f51af7d86
No known key found for this signature in database
GPG Key ID: 7E708D7933B84690
4 changed files with 229 additions and 3 deletions

View File

@ -1 +1,59 @@
package example 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)
}

View File

@ -1 +1,61 @@
// package example contains a self-contained example of a webhook that passes the cert-manager
// DNS conformance tests
package example 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
}

View File

@ -1 +1,95 @@
package example 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)
}

View File

@ -5,6 +5,8 @@ import (
"testing" "testing"
"github.com/jetstack/cert-manager/test/acme/dns" "github.com/jetstack/cert-manager/test/acme/dns"
"github.com/jetstack/cert-manager-webhook-example/example"
) )
var ( var (
@ -15,11 +17,23 @@ func TestRunsSuite(t *testing.T) {
// The manifest path should contain a file named config.json that is a // The manifest path should contain a file named config.json that is a
// snippet of valid configuration that should be included on the // snippet of valid configuration that should be included on the
// ChallengeRequest passed as part of the test cases. // ChallengeRequest passed as part of the test cases.
//
fixture := dns.NewFixture(&customDNSProviderSolver{}, // Uncomment the below fixture when implementing your custom DNS provider
dns.SetResolvedZone(zone), //fixture := dns.NewFixture(&customDNSProviderSolver{},
dns.SetAllowAmbientCredentials(false), // dns.SetResolvedZone(zone),
// dns.SetAllowAmbientCredentials(false),
// dns.SetManifestPath("testdata/my-custom-solver"),
// dns.SetBinariesPath("_test/kubebuilder/bin"),
//)
solver := example.New("59351")
fixture := dns.NewFixture(solver,
dns.SetResolvedZone("example.com."),
dns.SetManifestPath("testdata/my-custom-solver"), dns.SetManifestPath("testdata/my-custom-solver"),
dns.SetBinariesPath("_test/kubebuilder/bin"),
dns.SetDNSServer("127.0.0.1:59351"),
dns.SetUseAuthoritative(false),
) )
fixture.RunConformance(t) fixture.RunConformance(t)