patch with rfc2136

Signed-off-by: splashx <splash@gmail.com>
This commit is contained in:
splashx 2018-09-06 16:34:50 +02:00
parent 834fda15a1
commit 41111f7879
No known key found for this signature in database
GPG Key ID: ECDEC3A849D92F1E
11 changed files with 689 additions and 3 deletions

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(function(){navData = {"toc":[{"section":"-strong-field-definitions-strong-","subsections":[{"section":"vaultissuer-v1alpha1"},{"section":"vaultauth-v1alpha1"},{"section":"vaultapprole-v1alpha1"},{"section":"time-v1"},{"section":"statusdetails-v1"},{"section":"statuscause-v1"},{"section":"status-v1"},{"section":"selfsignedissuer-v1alpha1"},{"section":"secretkeyselector-v1alpha1"},{"section":"ownerreference-v1"},{"section":"objectreference-v1alpha1"},{"section":"objectmeta-v1"},{"section":"listmeta-v1"},{"section":"issuercondition-v1alpha1"},{"section":"initializers-v1"},{"section":"initializer-v1"},{"section":"http01solverconfig-v1alpha1"},{"section":"domainsolverconfig-v1alpha1"},{"section":"dns01solverconfig-v1alpha1"},{"section":"certificatecondition-v1alpha1"},{"section":"certificateacmestatus-v1alpha1"},{"section":"caissuer-v1alpha1"},{"section":"acmeorderstatus-v1alpha1"},{"section":"acmeorderchallenge-v1alpha1"},{"section":"acmeissuerhttp01config-v1alpha1"},{"section":"acmeissuerdns01providerroute53-v1alpha1"},{"section":"acmeissuerdns01providercloudflare-v1alpha1"},{"section":"acmeissuerdns01providerclouddns-v1alpha1"},{"section":"acmeissuerdns01providerazuredns-v1alpha1"},{"section":"acmeissuerdns01providerakamai-v1alpha1"},{"section":"acmeissuerdns01provideracmedns-v1alpha1"},{"section":"acmeissuerdns01provider-v1alpha1"},{"section":"acmeissuerdns01config-v1alpha1"},{"section":"acmeissuer-v1alpha1"},{"section":"acmecertificateconfig-v1alpha1"}]},{"section":"-strong-old-api-versions-strong-","subsections":[]},{"section":"issuer-v1alpha1","subsections":[]},{"section":"clusterissuer-v1alpha1","subsections":[]},{"section":"certificate-v1alpha1","subsections":[]},{"section":"-strong-cert-manager-strong-","subsections":[]}],"flatToc":["vaultissuer-v1alpha1","vaultauth-v1alpha1","vaultapprole-v1alpha1","time-v1","statusdetails-v1","statuscause-v1","status-v1","selfsignedissuer-v1alpha1","secretkeyselector-v1alpha1","ownerreference-v1","objectreference-v1alpha1","objectmeta-v1","listmeta-v1","issuercondition-v1alpha1","initializers-v1","initializer-v1","http01solverconfig-v1alpha1","domainsolverconfig-v1alpha1","dns01solverconfig-v1alpha1","certificatecondition-v1alpha1","certificateacmestatus-v1alpha1","caissuer-v1alpha1","acmeorderstatus-v1alpha1","acmeorderchallenge-v1alpha1","acmeissuerhttp01config-v1alpha1","acmeissuerdns01providerroute53-v1alpha1","acmeissuerdns01providercloudflare-v1alpha1","acmeissuerdns01providerclouddns-v1alpha1","acmeissuerdns01providerazuredns-v1alpha1","acmeissuerdns01providerakamai-v1alpha1","acmeissuerdns01provideracmedns-v1alpha1","acmeissuerdns01provider-v1alpha1","acmeissuerdns01config-v1alpha1","acmeissuer-v1alpha1","acmecertificateconfig-v1alpha1","-strong-field-definitions-strong-","-strong-old-api-versions-strong-","issuer-v1alpha1","clusterissuer-v1alpha1","certificate-v1alpha1","-strong-cert-manager-strong-"]};})();
(function(){navData = {"toc":[{"section":"-strong-field-definitions-strong-","subsections":[{"section":"vaultissuer-v1alpha1"},{"section":"vaultauth-v1alpha1"},{"section":"vaultapprole-v1alpha1"},{"section":"time-v1"},{"section":"statusdetails-v1"},{"section":"statuscause-v1"},{"section":"status-v1"},{"section":"selfsignedissuer-v1alpha1"},{"section":"secretkeyselector-v1alpha1"},{"section":"ownerreference-v1"},{"section":"objectreference-v1alpha1"},{"section":"objectmeta-v1"},{"section":"listmeta-v1"},{"section":"issuercondition-v1alpha1"},{"section":"initializers-v1"},{"section":"initializer-v1"},{"section":"http01solverconfig-v1alpha1"},{"section":"domainsolverconfig-v1alpha1"},{"section":"dns01solverconfig-v1alpha1"},{"section":"certificatecondition-v1alpha1"},{"section":"certificateacmestatus-v1alpha1"},{"section":"caissuer-v1alpha1"},{"section":"acmeorderstatus-v1alpha1"},{"section":"acmeorderchallenge-v1alpha1"},{"section":"acmeissuerhttp01config-v1alpha1"},{"section":"acmeissuerdns01providerroute53-v1alpha1"},{"section":"acmeissuerdns01providerrfc2136-v1alpha1"},{"section":"acmeissuerdns01providercloudflare-v1alpha1"},{"section":"acmeissuerdns01providerclouddns-v1alpha1"},{"section":"acmeissuerdns01providerazuredns-v1alpha1"},{"section":"acmeissuerdns01providerakamai-v1alpha1"},{"section":"acmeissuerdns01provideracmedns-v1alpha1"},{"section":"acmeissuerdns01provider-v1alpha1"},{"section":"acmeissuerdns01config-v1alpha1"},{"section":"acmeissuer-v1alpha1"},{"section":"acmecertificateconfig-v1alpha1"}]},{"section":"-strong-old-api-versions-strong-","subsections":[]},{"section":"issuer-v1alpha1","subsections":[]},{"section":"clusterissuer-v1alpha1","subsections":[]},{"section":"certificate-v1alpha1","subsections":[]},{"section":"-strong-cert-manager-strong-","subsections":[]}],"flatToc":["vaultissuer-v1alpha1","vaultauth-v1alpha1","vaultapprole-v1alpha1","time-v1","statusdetails-v1","statuscause-v1","status-v1","selfsignedissuer-v1alpha1","secretkeyselector-v1alpha1","ownerreference-v1","objectreference-v1alpha1","objectmeta-v1","listmeta-v1","issuercondition-v1alpha1","initializers-v1","initializer-v1","http01solverconfig-v1alpha1","domainsolverconfig-v1alpha1","dns01solverconfig-v1alpha1","certificatecondition-v1alpha1","certificateacmestatus-v1alpha1","caissuer-v1alpha1","acmeorderstatus-v1alpha1","acmeorderchallenge-v1alpha1","acmeissuerhttp01config-v1alpha1","acmeissuerdns01providerroute53-v1alpha1","acmeissuerdns01providerrfc2136-v1alpha1","acmeissuerdns01providercloudflare-v1alpha1","acmeissuerdns01providerclouddns-v1alpha1","acmeissuerdns01providerazuredns-v1alpha1","acmeissuerdns01providerakamai-v1alpha1","acmeissuerdns01provideracmedns-v1alpha1","acmeissuerdns01provider-v1alpha1","acmeissuerdns01config-v1alpha1","acmeissuer-v1alpha1","acmecertificateconfig-v1alpha1","-strong-field-definitions-strong-","-strong-old-api-versions-strong-","issuer-v1alpha1","clusterissuer-v1alpha1","certificate-v1alpha1","-strong-cert-manager-strong-"]};})();

View File

@ -142,6 +142,19 @@ Akamai FastDNS
name: akamai-dns
key: accessToken
RFC2136
========
.. code-block:: yaml
rfc2136:
nameserver: 192.168.0.1
tsigKeyName: myzone-tsig
tsigAlgorithm: HMACMD5
tsigSecretSecretRef:
name: my-secret
key: tsigkey
ACME-DNS
========

View File

@ -16,7 +16,9 @@ limitations under the License.
package v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +genclient:nonNamespaced
@ -150,6 +152,7 @@ type ACMEIssuerDNS01Provider struct {
Route53 *ACMEIssuerDNS01ProviderRoute53 `json:"route53,omitempty"`
AzureDNS *ACMEIssuerDNS01ProviderAzureDNS `json:"azuredns,omitempty"`
AcmeDNS *ACMEIssuerDNS01ProviderAcmeDNS `json:"acmedns,omitempty"`
RFC2136 *ACMEIssuerDNS01ProviderRFC2136 `json:"rfc2136,omitempty"`
}
// ACMEIssuerDNS01ProviderAkamai is a structure containing the DNS
@ -204,6 +207,29 @@ type ACMEIssuerDNS01ProviderAcmeDNS struct {
AccountSecret SecretKeySelector `json:"accountSecretRef"`
}
// ACMEIssuerDNS01ProviderRFC2136 is a structure containing the
// configuration for RFC2136 DNS
type ACMEIssuerDNS01ProviderRFC2136 struct {
// The IP address of the DNS supporting RFC2136. Required.
Nameserver string `json:"nameserver"`
// The name of the secret containing the TSIG value.
// If ``tsigSecretSecretRef`` is not defined, ``tsigKey`` is ignored.
// +optional
TSIGSecret SecretKeySelector `json:"tsigSecretSecretRef"`
// The TSIG Key name configured in the DNS. If ``tsigKeyName`` is not defined,
// ``tsigSecretSecretRef`` is ignored
// +optional
TSIGKeyName string `json:"tsigKeyName"`
// The TSIG Algorithm configured in the DNS supporting RFC2136. Acceptable
// values are (case-insensitive): ``HMACMD5`` (default), ``HMACSHA1``,
// ``HMACSHA256`` or ``HMACSHA512``
// +optional
TSIGAlgorithm string `json:"tsigAlgorithm"`
}
// IssuerStatus contains status information about an Issuer
type IssuerStatus struct {
Conditions []IssuerCondition `json:"conditions"`

View File

@ -162,6 +162,15 @@ func (in *ACMEIssuerDNS01Provider) DeepCopyInto(out *ACMEIssuerDNS01Provider) {
**out = **in
}
}
if in.RFC2136 != nil {
in, out := &in.RFC2136, &out.RFC2136
if *in == nil {
*out = nil
} else {
*out = new(ACMEIssuerDNS01ProviderRFC2136)
**out = **in
}
}
return
}
@ -262,6 +271,23 @@ func (in *ACMEIssuerDNS01ProviderCloudflare) DeepCopy() *ACMEIssuerDNS01Provider
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopyInto(out *ACMEIssuerDNS01ProviderRFC2136) {
*out = *in
out.TSIGSecret = in.TSIGSecret
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderRFC2136.
func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC2136 {
if in == nil {
return nil
}
out := new(ACMEIssuerDNS01ProviderRFC2136)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) {
*out = *in

View File

@ -17,6 +17,10 @@ limitations under the License.
package validation
import (
"strings"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/rfc2136"
"k8s.io/apimachinery/pkg/util/validation/field"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
@ -202,6 +206,23 @@ func ValidateACMEIssuerDNS01Config(iss *v1alpha1.ACMEIssuerDNS01Config, fldPath
el = append(el, field.Required(fldPath.Child("acmedns", "host"), ""))
}
}
if p.RFC2136 != nil {
if numProviders > 0 {
el = append(el, field.Forbidden(fldPath.Child("rfc2136"), "may not specify more than one provider type"))
} else {
numProviders++
// Nameserver is the only required field for RFC2136
if len(p.RFC2136.Nameserver) == 0 {
el = append(el, field.Required(fldPath.Child("rfc2136", "nameserver"), ""))
}
if len(p.RFC2136.TSIGAlgorithm) > 0 {
_, ok := rfc2136.SupportedAlgorithms[strings.ToUpper(p.RFC2136.TSIGAlgorithm)]
if !ok {
el = append(el, field.Forbidden(fldPath.Child("rfc2136", "tsigSecretSecretRef"), ""))
}
}
}
}
if numProviders == 0 {
el = append(el, field.Required(fldPath, "at least one provider must be configured"))
}

View File

@ -403,6 +403,48 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) {
},
errs: []*field.Error{},
},
"valid rfc2136 config": {
cfg: &v1alpha1.ACMEIssuerDNS01Config{
Providers: []v1alpha1.ACMEIssuerDNS01Provider{
{
Name: "a name",
RFC2136: &v1alpha1.ACMEIssuerDNS01ProviderRFC2136{
Nameserver: "127.0.0.1",
},
},
},
},
errs: []*field.Error{},
},
"missing rfc2136 required field": {
cfg: &v1alpha1.ACMEIssuerDNS01Config{
Providers: []v1alpha1.ACMEIssuerDNS01Provider{
{
Name: "a name",
RFC2136: &v1alpha1.ACMEIssuerDNS01ProviderRFC2136{},
},
},
},
errs: []*field.Error{
field.Required(providersPath.Index(0).Child("rfc2136", "nameserver"), ""),
},
},
"rfc2136 provider using non-supported algorithm": {
cfg: &v1alpha1.ACMEIssuerDNS01Config{
Providers: []v1alpha1.ACMEIssuerDNS01Provider{
{
Name: "a name",
RFC2136: &v1alpha1.ACMEIssuerDNS01ProviderRFC2136{
Nameserver: "127.0.0.1",
TSIGAlgorithm: "HAMMOCK",
},
},
},
},
errs: []*field.Error{
field.Forbidden(providersPath.Index(0).Child("rfc2136", "tsigSecretSecretRef"), ""),
},
},
"multiple providers configured": {
cfg: &v1alpha1.ACMEIssuerDNS01Config{
Providers: []v1alpha1.ACMEIssuerDNS01Provider{

View File

@ -33,6 +33,7 @@ import (
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/azuredns"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/clouddns"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/cloudflare"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/rfc2136"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/route53"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/util"
)
@ -56,6 +57,7 @@ type dnsProviderConstructors struct {
route53 func(accessKey, secretKey, hostedZoneID, region string, ambient bool, dns01Nameservers []string) (*route53.DNSProvider, error)
azureDNS func(clientID, clientSecret, subscriptionID, tenentID, resourceGroupName, hostedZoneName string, dns01Nameservers []string) (*azuredns.DNSProvider, error)
acmeDNS func(host string, accountJson []byte, dns01Nameservers []string) (*acmedns.DNSProvider, error)
rfc2136 func(nameserver, tsigAlgorithm, tsigKeyName, tsigSecret string) (*rfc2136.DNSProvider, error)
}
// Solver is a solver for the acme dns01 challenge.
@ -265,6 +267,29 @@ func (s *Solver) solverForIssuerProvider(issuer v1alpha1.GenericIssuer, provider
if err != nil {
return nil, fmt.Errorf("error instantiating acmedns challenge solver: %s", err)
}
case providerConfig.RFC2136 != nil:
var secret string
if len(providerConfig.RFC2136.TSIGSecret.Name) > 0 {
tsigSecret, err := s.secretLister.Secrets(resourceNamespace).Get(providerConfig.RFC2136.TSIGSecret.Name)
if err != nil {
return nil, fmt.Errorf("error getting rfc2136 service account: %s", err.Error())
}
secretBytes, ok := tsigSecret.Data[providerConfig.RFC2136.TSIGSecret.Key]
if !ok {
return nil, fmt.Errorf("error getting rfc2136 secret key: key '%s' not found in secret", providerConfig.RFC2136.TSIGSecret.Key)
}
secret = string(secretBytes)
}
impl, err = s.dnsProviderConstructors.rfc2136(
providerConfig.RFC2136.Nameserver,
string(providerConfig.RFC2136.TSIGAlgorithm),
providerConfig.RFC2136.TSIGKeyName,
secret,
)
if err != nil {
return nil, fmt.Errorf("error instantiating rfc2136 challenge solver: %s", err.Error())
}
default:
return nil, fmt.Errorf("no dns provider config specified for provider %q", providerName)
}
@ -282,6 +307,7 @@ func NewSolver(ctx *controller.Context) *Solver {
route53.NewDNSProvider,
azuredns.NewDNSProviderCredentials,
acmedns.NewDNSProviderHostBytes,
rfc2136.NewDNSProviderCredentials,
},
}
}

View File

@ -0,0 +1,188 @@
/*
Copyright 2018 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 rfc2136 implements a DNS provider for solving the DNS-01 challenge
// using the rfc2136 dynamic update.
// This code was adapted from lego:
// https://github.com/xenolf/lego
package rfc2136
import (
"fmt"
"net"
"os"
"strings"
"time"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/util"
"github.com/miekg/dns"
)
// SupportedAlgorithms should refer to https://tools.ietf.org/html/rfc4635#section-2
// but miekd/dns supports only the ones below
var SupportedAlgorithms = map[string]string{
"HMACMD5": dns.HmacMD5,
"HMACSHA1": dns.HmacSHA1,
"HMACSHA256": dns.HmacSHA256,
"HMACSHA512": dns.HmacSHA512,
}
// DNSProvider is an implementation of the acme.ChallengeProvider interface that
// uses dynamic DNS updates (RFC 2136) to create TXT records on a nameserver.
type DNSProvider struct {
nameserver string
tsigAlgorithm string
tsigKeyName string
tsigSecret string
}
// NewDNSProvider returns a DNSProvider instance configured for rfc2136
// dynamic update. Configured with environment variables:
// RFC2136_NAMESERVER: Network address in the form "host" or "host:port".
// RFC2136_TSIG_ALGORITHM: Defaults to hmac-md5.sig-alg.reg.int. (HMAC-MD5).
// See https://github.com/miekg/dns/blob/master/tsig.go for supported values.
// RFC2136_TSIG_KEY: Name of the secret key as defined in DNS server configuration.
// RFC2136_TSIG_SECRET: Secret key payload.
// To disable TSIG authentication, leave the RFC2136_TSIG* variables unset.
func NewDNSProvider() (*DNSProvider, error) {
nameserver := os.Getenv("RFC2136_NAMESERVER")
tsigAlgorithm := os.Getenv("RFC2136_TSIG_ALGORITHM")
tsigKeyName := os.Getenv("RFC2136_TSIG_KEY_NAME")
tsigSecret := os.Getenv("RFC2136_TSIG_SECRET")
return NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKeyName, tsigSecret)
}
// NewDNSProviderCredentials uses the supplied credentials to return a
// DNSProvider instance configured for rfc2136 dynamic update. To disable TSIG
// authentication, leave the TSIG parameters as empty strings.
// nameserver must be a network address in the form "IP" or "IP:port".
func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKeyName, tsigSecret string) (*DNSProvider, error) {
if nameserver == "" {
return nil, fmt.Errorf("RFC2136 nameserver missing")
}
// Append the default DNS port if none is specified.
if _, _, err := net.SplitHostPort(nameserver); err != nil {
if strings.Contains(err.Error(), "missing port") {
host := nameserver
if ipaddr := net.ParseIP(host); ipaddr != nil {
nameserver = net.JoinHostPort(host, "53")
} else {
return nil, fmt.Errorf("RFC2136 nameserver must be a valid IP Address, not %v", nameserver)
}
} else {
return nil, err
}
}
d := &DNSProvider{
nameserver: nameserver,
}
if tsigAlgorithm == "" {
tsigAlgorithm = dns.HmacMD5
} else {
if value, ok := SupportedAlgorithms[strings.ToUpper(tsigAlgorithm)]; ok {
tsigAlgorithm = value
} else {
return nil, fmt.Errorf("The algorithm '%v' is not supported", tsigAlgorithm)
}
}
d.tsigAlgorithm = tsigAlgorithm
if len(tsigKeyName) > 0 && len(tsigSecret) > 0 {
d.tsigKeyName = tsigKeyName
d.tsigSecret = tsigSecret
}
return d, nil
}
// Timeout returns the timeout and interval to use when checking for DNS
// propagation. 300s (5m) is usually a default time for TTL in DNS
func (r *DNSProvider) Timeout() (timeout, interval time.Duration) {
return 300 * time.Second, 5 * time.Second
}
// Present creates a TXT record using the specified parameters
func (r *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl, err := util.DNS01Record(domain, keyAuth, strings.Fields(r.nameserver))
if err != nil {
return err
}
return r.changeRecord("INSERT", fqdn, value, ttl)
}
// CleanUp removes the TXT record matching the specified parameters
func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value, ttl, err := util.DNS01Record(domain, keyAuth, strings.Fields(r.nameserver))
if err != nil {
return err
}
return r.changeRecord("REMOVE", fqdn, value, ttl)
}
func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
// Find the zone for the given fqdn
zone, err := util.FindZoneByFqdn(fqdn, []string{r.nameserver})
if err != nil {
return err
}
// Create RR
rr := new(dns.TXT)
rr.Hdr = dns.RR_Header{Name: fqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: uint32(ttl)}
rr.Txt = []string{value}
rrs := []dns.RR{rr}
// Create dynamic update packet
m := new(dns.Msg)
m.SetUpdate(zone)
switch action {
case "INSERT":
// Always remove old challenge left over from who knows what.
m.RemoveRRset(rrs)
m.Insert(rrs)
case "REMOVE":
m.Remove(rrs)
default:
return fmt.Errorf("Unexpected action: %s", action)
}
// Setup client
c := new(dns.Client)
c.SingleInflight = true
// TSIG authentication / msg signing
if len(r.tsigKeyName) > 0 && len(r.tsigSecret) > 0 {
m.SetTsig(dns.Fqdn(r.tsigKeyName), r.tsigAlgorithm, 300, time.Now().Unix())
c.TsigSecret = map[string]string{dns.Fqdn(r.tsigKeyName): r.tsigSecret}
}
// Send the query
reply, _, err := c.Exchange(m, r.nameserver)
if err != nil {
return fmt.Errorf("DNS update failed: %v", err)
}
if reply != nil && reply.Rcode != dns.RcodeSuccess {
return fmt.Errorf("DNS update failed. Server replied: %s", dns.RcodeToString[reply.Rcode])
}
return nil
}

View File

@ -0,0 +1,283 @@
/*
Copyright 2018 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 rfc2136 implements a DNS provider for solving the DNS-01 challenge
// using the rfc2136 dynamic update.
// This code was adapted from lego:
// https://github.com/xenolf/lego
package rfc2136
import (
"fmt"
"net"
"strings"
"sync"
"testing"
"time"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
)
var (
rfc2136TestDomain = "123456789.www.example.com"
rfc2136TestKeyAuth = "123d=="
rfc2136TestValue = "Now36o-3BmlB623-0c1qCIUmgWVVmDJb88KGl24pqpo"
rfc2136TestFqdn = "_acme-challenge.123456789.www.example.com."
rfc2136TestZone = "example.com."
rfc2136TestTsigKeyName = "example.com."
rfc2136TestTTL = 60
rfc2136TestTsigSecret = "IwBTJx9wrDp4Y1RyC3H0gA=="
)
var reqChan = make(chan *dns.Msg, 10)
func TestRFC2136CanaryLocalTestServer(t *testing.T) {
dns.HandleFunc("example.com.", serverHandlerHello)
defer dns.HandleRemove("example.com.")
server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false)
if err != nil {
t.Fatalf("Failed to start test server: %v", err)
}
defer server.Shutdown()
c := new(dns.Client)
m := new(dns.Msg)
m.SetQuestion("example.com.", dns.TypeTXT)
r, _, err := c.Exchange(m, addrstr)
if err != nil || len(r.Extra) == 0 {
t.Fatalf("Failed to communicate with test server: %v", err)
}
txt := r.Extra[0].(*dns.TXT).Txt[0]
if txt != "Hello world" {
t.Error("Expected test server to return 'Hello world' but got: ", txt)
}
}
func TestRFC2136ServerSuccess(t *testing.T) {
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnSuccess)
defer dns.HandleRemove(rfc2136TestZone)
server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false)
if err != nil {
t.Fatalf("Failed to start test server: %v", err)
}
defer server.Shutdown()
provider, err := NewDNSProviderCredentials(addrstr, "", "", "")
if err != nil {
t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err)
}
if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err != nil {
t.Errorf("Expected Present() to return no error but the error was -> %v", err)
}
}
func TestRFC2136ServerError(t *testing.T) {
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnErr)
defer dns.HandleRemove(rfc2136TestZone)
server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false)
if err != nil {
t.Fatalf("Failed to start test server: %v", err)
}
defer server.Shutdown()
provider, err := NewDNSProviderCredentials(addrstr, "", "", "")
if err != nil {
t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err)
}
if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err == nil {
t.Errorf("Expected Present() to return an error but it did not.")
} else if !strings.Contains(err.Error(), "NOTZONE") {
t.Errorf("Expected Present() to return an error with the 'NOTZONE' rcode string but it did not.")
}
}
func TestRFC2136TsigClient(t *testing.T) {
dns.HandleFunc(rfc2136TestZone, serverHandlerReturnSuccess)
defer dns.HandleRemove(rfc2136TestZone)
server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", true)
if err != nil {
t.Fatalf("Failed to start test server: %v", err)
}
defer server.Shutdown()
provider, err := NewDNSProviderCredentials(addrstr, "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret)
if err != nil {
t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err)
}
if err := provider.Present(rfc2136TestDomain, "", rfc2136TestKeyAuth); err != nil {
t.Errorf("Expected Present() to return no error but the error was -> %v", err)
}
}
func TestRFC2136InvalidNameserver(t *testing.T) {
_, err := NewDNSProviderCredentials("dns01.example.org", "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret)
assert.Error(t, err)
}
func TestRFC2136DefaultTSIGAlgorithm(t *testing.T) {
provider, err := NewDNSProviderCredentials("127.0.0.1:0", "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret)
if err != nil {
assert.Equal(t, provider.tsigAlgorithm, dns.HmacMD5, "Default TSIG must match")
}
}
func TestRFC2136InvalidTSIGAlgorithm(t *testing.T) {
_, err := NewDNSProviderCredentials("127.0.0.1:0", "HAMMOCK", rfc2136TestTsigKeyName, rfc2136TestTsigSecret)
assert.Error(t, err)
}
func TestRFC2136NamserverWithoutPort(t *testing.T) {
_, err := NewDNSProviderCredentials("127.0.0.1", "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret)
assert.NoError(t, err)
}
func TestRFC2136ValidUpdatePacket(t *testing.T) {
dns.HandleFunc(rfc2136TestZone, serverHandlerPassBackRequest)
defer dns.HandleRemove(rfc2136TestZone)
server, addrstr, err := runLocalDNSTestServer("127.0.0.1:0", false)
if err != nil {
t.Fatalf("Failed to start test server: %v", err)
}
defer server.Shutdown()
txtRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN TXT %s", rfc2136TestFqdn, rfc2136TestTTL, rfc2136TestValue))
rrs := []dns.RR{txtRR}
m := new(dns.Msg)
m.SetUpdate(rfc2136TestZone)
m.RemoveRRset(rrs)
m.Insert(rrs)
//expectstr := m.String()
//expect, err := m.Pack()
if err != nil {
t.Fatalf("Error packing expect msg: %v", err)
}
provider, err := NewDNSProviderCredentials(addrstr, "", "", "")
if err != nil {
t.Fatalf("Expected NewDNSProviderCredentials() to return no error but the error was -> %v", err)
}
if err := provider.Present(rfc2136TestDomain, "", rfc2136TestValue); err != nil {
t.Errorf("Expected Present() to return no error but the error was -> %v", err)
}
assert.NoError(t, err)
//rcvMsg := <-reqChan
//rcvMsg.Id = m.Id
//actual, err := rcvMsg.Pack()
//if err != nil {
// t.Fatalf("Error packing actual msg: %v", err)
//}
//if !bytes.Equal(actual, expect) {
// tmp := new(dns.Msg)
// if err := tmp.Unpack(actual); err != nil {
// t.Fatalf("Error unpacking actual msg: %v", err)
// }
// t.Errorf("Expected msg:\n%s", expectstr)
// t.Errorf("Actual msg:\n%v", tmp)
//}
}
func runLocalDNSTestServer(listenAddr string, tsig bool) (*dns.Server, string, error) {
pc, err := net.ListenPacket("udp", listenAddr)
if err != nil {
return nil, "", err
}
server := &dns.Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour}
if tsig {
server.TsigSecret = map[string]string{rfc2136TestTsigKeyName: rfc2136TestTsigSecret}
}
waitLock := sync.Mutex{}
waitLock.Lock()
server.NotifyStartedFunc = waitLock.Unlock
go func() {
server.ActivateAndServe()
pc.Close()
}()
waitLock.Lock()
return server, pc.LocalAddr().String(), nil
}
func serverHandlerHello(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
m.Extra = make([]dns.RR, 1)
m.Extra[0] = &dns.TXT{
Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0},
Txt: []string{"Hello world"},
}
w.WriteMsg(m)
}
func serverHandlerReturnSuccess(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
if req.Opcode == dns.OpcodeQuery && req.Question[0].Qtype == dns.TypeSOA && req.Question[0].Qclass == dns.ClassINET {
// Return SOA to appease findZoneByFqdn()
soaRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN SOA ns1.%s admin.%s 2016022801 28800 7200 2419200 1200", rfc2136TestZone, rfc2136TestTTL, rfc2136TestZone, rfc2136TestZone))
m.Answer = []dns.RR{soaRR}
}
if t := req.IsTsig(); t != nil {
if w.TsigStatus() == nil {
// Validated
m.SetTsig(rfc2136TestZone, dns.HmacMD5, 300, time.Now().Unix())
}
}
w.WriteMsg(m)
}
func serverHandlerReturnErr(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetRcode(req, dns.RcodeNotZone)
w.WriteMsg(m)
}
func serverHandlerPassBackRequest(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
if req.Opcode == dns.OpcodeQuery && req.Question[0].Qtype == dns.TypeSOA && req.Question[0].Qclass == dns.ClassINET {
// Return SOA to appease findZoneByFqdn()
soaRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN SOA ns1.%s admin.%s 2016022801 28800 7200 2419200 1200", rfc2136TestZone, rfc2136TestTTL, rfc2136TestZone, rfc2136TestZone))
m.Answer = []dns.RR{soaRR}
}
if t := req.IsTsig(); t != nil {
if w.TsigStatus() == nil {
// Validated
m.SetTsig(rfc2136TestZone, dns.HmacMD5, 300, time.Now().Unix())
}
}
w.WriteMsg(m)
if req.Opcode != dns.OpcodeQuery || req.Question[0].Qtype != dns.TypeSOA || req.Question[0].Qclass != dns.ClassINET {
// Only talk back when it is not the SOA RR.
reqChan <- req
}
}

View File

@ -28,6 +28,7 @@ import (
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/azuredns"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/clouddns"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/cloudflare"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/rfc2136"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/route53"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/util"
)
@ -159,6 +160,10 @@ func newFakeDNSProviders() *fakeDNSProviders {
f.call("acmedns", host, accountJson, dns01Nameservers)
return nil, nil
},
rfc2136: func(nameserver, tsigAlgorithm, tsigKeyName, tsigSecret string) (*rfc2136.DNSProvider, error) {
f.call("rfc2136", nameserver, tsigAlgorithm, tsigKeyName, tsigSecret)
return nil, nil
},
}
return f
}