Implement provider route 53

This commit is contained in:
Christian Simon 2017-08-08 23:05:21 +01:00
parent 8c2e92a5d1
commit 301edc7812
5 changed files with 102 additions and 7 deletions

View File

@ -80,6 +80,7 @@ type ACMEIssuerDNS01Provider struct {
CloudDNS *ACMEIssuerDNS01ProviderCloudDNS
Cloudflare *ACMEIssuerDNS01ProviderCloudflare
Route53 *ACMEIssuerDNS01ProviderRoute53
}
// ACMEIssuerDNS01ProviderCloudDNS is a structure containing the DNS
@ -96,6 +97,16 @@ type ACMEIssuerDNS01ProviderCloudflare struct {
APIKey SecretKeySelector
}
// ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53
// configuration for AWS
type ACMEIssuerDNS01ProviderRoute53 struct {
AccessKeyID string
SecretAccessKey SecretKeySelector
// these following parameters are optional
HostedZoneID string
Region string
}
// +genclient=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -104,6 +104,7 @@ type ACMEIssuerDNS01Provider struct {
CloudDNS *ACMEIssuerDNS01ProviderCloudDNS `json:"clouddns,omitempty"`
Cloudflare *ACMEIssuerDNS01ProviderCloudflare `json:"cloudflare,omitempty"`
Route53 *ACMEIssuerDNS01ProviderRoute53 `json:"route53,omitempty"`
}
// ACMEIssuerDNS01ProviderCloudDNS is a structure containing the DNS
@ -120,6 +121,15 @@ type ACMEIssuerDNS01ProviderCloudflare struct {
APIKey SecretKeySelector `json:"apiKey"`
}
// ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53
// configuration for AWS
type ACMEIssuerDNS01ProviderRoute53 struct {
AccessKeyID string `json:"accessKeyID"`
SecretAccessKey SecretKeySelector `json:"secretAccessKey"`
HostedZoneID string `json:"hostedZoneID"`
Region string `json:"region"`
}
// +genclient=true
// +k8s:openapi-gen=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@ -12,6 +12,7 @@ import (
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/dns/clouddns"
"github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/dns/cloudflare"
"github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/dns/route53"
"github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/dns/util"
)
@ -132,6 +133,26 @@ func (s *Solver) solverFor(crt *v1alpha1.Certificate, domain string) (solver, er
if err != nil {
return nil, fmt.Errorf("error instantiating cloudflare challenge solver: %s", err.Error())
}
case providerConfig.Route53 != nil:
secretAccessKeySecret, err := s.secretLister.Secrets(s.issuer.Namespace).Get(providerConfig.Route53.SecretAccessKey.Name)
if err != nil {
return nil, fmt.Errorf("error getting route53 secret access key: %s", err.Error())
}
secretAccessKeyBytes, ok := secretAccessKeySecret.Data[providerConfig.Cloudflare.APIKey.Key]
if !ok {
return nil, fmt.Errorf("error getting route53 secret access key: key '%s' not found in secret", providerConfig.Route53.SecretAccessKey.Key)
}
impl, err = route53.NewDNSProviderAccessKey(
providerConfig.Route53.AccessKeyID,
string(secretAccessKeyBytes),
providerConfig.Route53.HostedZoneID,
providerConfig.Route53.Region,
)
if err != nil {
return nil, fmt.Errorf("error instantiating route53 challenge solver: %s", err.Error())
}
default:
return nil, fmt.Errorf("no dns provider config specified for domain '%s'", domain)
}

View File

@ -11,10 +11,12 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/xenolf/lego/acme"
"github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/dns/util"
)
const (
@ -22,7 +24,7 @@ const (
route53TTL = 10
)
// DNSProvider implements the acme.ChallengeProvider interface
// DNSProvider implements the util.ChallengeProvider interface
type DNSProvider struct {
client *route53.Route53
hostedZoneID string
@ -78,16 +80,44 @@ func NewDNSProvider() (*DNSProvider, error) {
}, nil
}
// NewDNSProviderAccessKey returns a DNSProvider instance configured for the AWS
// Route 53 service using static credentials from its parameters
func NewDNSProviderAccessKey(accessKeyID, secretAccessKey, hostedZoneID, region string) (*DNSProvider, error) {
creds := credentials.NewStaticCredentials(accessKeyID, secretAccessKey, "")
r := customRetryer{}
r.NumMaxRetries = maxRetries
config := request.WithRetryer(aws.NewConfig(), r).WithCredentials(creds)
if region != "" {
config.WithRegion(region)
}
client := route53.New(session.New(config))
return &DNSProvider{
client: client,
hostedZoneID: hostedZoneID,
}, nil
}
// Timeout returns the timeout and interval to use when checking for DNS
// propagation. Adjusting here to cope with spikes in propagation times.
func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
return 120 * time.Second, 2 * time.Second
}
// Present creates a TXT record using the specified parameters
func (r *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
fqdn, value, _ := util.DNS01Record(domain, keyAuth)
value = `"` + value + `"`
return r.changeRecord("UPSERT", fqdn, value, route53TTL)
}
// CleanUp removes the TXT record matching the specified parameters
func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
fqdn, value, _ := util.DNS01Record(domain, keyAuth)
value = `"` + value + `"`
return r.changeRecord("DELETE", fqdn, value, route53TTL)
}
@ -119,7 +149,7 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
statusID := resp.ChangeInfo.Id
return acme.WaitFor(120*time.Second, 4*time.Second, func() (bool, error) {
return util.WaitFor(120*time.Second, 4*time.Second, func() (bool, error) {
reqParams := &route53.GetChangeInput{
Id: statusID,
}
@ -139,14 +169,14 @@ func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
return r.hostedZoneID, nil
}
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
authZone, err := util.FindZoneByFqdn(fqdn, util.RecursiveNameservers)
if err != nil {
return "", err
}
// .DNSName should not have a trailing dot
reqParams := &route53.ListHostedZonesByNameInput{
DNSName: aws.String(acme.UnFqdn(authZone)),
DNSName: aws.String(util.UnFqdn(authZone)),
}
resp, err := r.client.ListHostedZonesByName(reqParams)
if err != nil {

View File

@ -237,3 +237,26 @@ func UnFqdn(name string) string {
}
return name
}
// WaitFor polls the given function 'f', once every 'interval', up to 'timeout'.
func WaitFor(timeout, interval time.Duration, f func() (bool, error)) error {
var lastErr string
timeup := time.After(timeout)
for {
select {
case <-timeup:
return fmt.Errorf("Time limit exceeded. Last error: %s", lastErr)
default:
}
stop, err := f()
if stop {
return nil
}
if err != nil {
lastErr = err.Error()
}
time.Sleep(interval)
}
}