Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into cpu-acme-dns-provider
Browse files Browse the repository at this point in the history
  • Loading branch information
cpu committed Jul 1, 2018
2 parents 8d3e310 + e0d5121 commit d9bdd50
Show file tree
Hide file tree
Showing 26 changed files with 1,806 additions and 38 deletions.
8 changes: 7 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 26 additions & 8 deletions acme/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -662,9 +663,18 @@ func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, pr

// determine certificate name(s) based on the authorization resources
commonName := order.Domains[0]
var san []string

// ACME draft Section 7.4 "Applying for Certificate Issuance"
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4
// says:
// Clients SHOULD NOT make any assumptions about the sort order of
// "identifiers" or "authorizations" elements in the returned order
// object.
san := []string{commonName}
for _, auth := range order.Identifiers {
san = append(san, auth.Value)
if auth.Value != commonName {
san = append(san, auth.Value)
}
}

// TODO: should the CSR be customizable?
Expand All @@ -681,13 +691,13 @@ func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr

csrString := base64.RawURLEncoding.EncodeToString(csr)
var retOrder orderMessage
_, error := postJSON(c.jws, order.Finalize, csrMessage{Csr: csrString}, &retOrder)
if error != nil {
return nil, error
_, err := postJSON(c.jws, order.Finalize, csrMessage{Csr: csrString}, &retOrder)
if err != nil {
return nil, err
}

if retOrder.Status == "invalid" {
return nil, error
return nil, err
}

certRes := CertificateResource{
Expand Down Expand Up @@ -753,8 +763,9 @@ func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResou
return false, err
}

// The issuer certificate link is always supplied via an "up" link
// in the response headers of a new certificate.
// The issuer certificate link may be supplied via an "up" link
// in the response headers of a new certificate. See
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4.2
links := parseLinks(resp.Header["Link"])
if link, ok := links["up"]; ok {
issuerCert, err := c.getIssuerCertificate(link)
Expand All @@ -773,6 +784,13 @@ func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResou

certRes.IssuerCertificate = issuerCert
}
} else {
// Get issuerCert from bundled response from Let's Encrypt
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
_, rest := pem.Decode(cert)
if rest != nil {
certRes.IssuerCertificate = rest
}
}

certRes.Certificate = cert
Expand Down
4 changes: 1 addition & 3 deletions acme/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,7 @@ func generatePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {

func generateCsr(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
template := x509.CertificateRequest{
Subject: pkix.Name{
CommonName: domain,
},
Subject: pkix.Name{CommonName: domain},
}

if len(san) > 0 {
Expand Down
13 changes: 10 additions & 3 deletions acme/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ var (
HTTPClient = http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
}).DialContext,
TLSHandshakeTimeout: 15 * time.Second,
ResponseHeaderTimeout: 15 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
RootCAs: initCertPool(),
ServerName: os.Getenv(caServerNameEnvVar),
RootCAs: initCertPool(),
},
},
}
Expand All @@ -53,6 +54,12 @@ const (
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
// the system-wide trusted root list.
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"

// caServerNameEnvVar is the environment variable name that can be used to
// specify the CA server name that can be used to
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
// the system-wide trusted root list.
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
)

// initCertPool creates a *x509.CertPool populated with the PEM certificates
Expand Down
2 changes: 2 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,12 @@ Here is an example bash command using the CloudFlare DNS provider:
fmt.Fprintln(w, "\tmanual:\tnone")
fmt.Fprintln(w, "\tnamecheap:\tNAMECHEAP_API_USER, NAMECHEAP_API_KEY")
fmt.Fprintln(w, "\tnamedotcom:\tNAMECOM_USERNAME, NAMECOM_API_TOKEN")
fmt.Fprintln(w, "\tnifcloud:\tNIFCLOUD_ACCESS_KEY_ID, NIFCLOUD_SECRET_ACCESS_KEY")
fmt.Fprintln(w, "\trackspace:\tRACKSPACE_USER, RACKSPACE_API_KEY")
fmt.Fprintln(w, "\trfc2136:\tRFC2136_TSIG_KEY, RFC2136_TSIG_SECRET,\n\t\tRFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER")
fmt.Fprintln(w, "\troute53:\tAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_HOSTED_ZONE_ID")
fmt.Fprintln(w, "\tdyn:\tDYN_CUSTOMER_NAME, DYN_USER_NAME, DYN_PASSWORD")
fmt.Fprintln(w, "\tvegadns:\tSECRET_VEGADNS_KEY, SECRET_VEGADNS_SECRET, VEGADNS_URL")
fmt.Fprintln(w, "\tvultr:\tVULTR_API_KEY")
fmt.Fprintln(w, "\tovh:\tOVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY")
fmt.Fprintln(w, "\tpdns:\tPDNS_API_KEY, PDNS_API_URL")
Expand Down
6 changes: 6 additions & 0 deletions providers/dns/dns_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/xenolf/lego/providers/dns/linode"
"github.com/xenolf/lego/providers/dns/namecheap"
"github.com/xenolf/lego/providers/dns/namedotcom"
"github.com/xenolf/lego/providers/dns/nifcloud"
"github.com/xenolf/lego/providers/dns/ns1"
"github.com/xenolf/lego/providers/dns/otc"
"github.com/xenolf/lego/providers/dns/ovh"
Expand All @@ -36,6 +37,7 @@ import (
"github.com/xenolf/lego/providers/dns/rfc2136"
"github.com/xenolf/lego/providers/dns/route53"
"github.com/xenolf/lego/providers/dns/sakuracloud"
"github.com/xenolf/lego/providers/dns/vegadns"
"github.com/xenolf/lego/providers/dns/vultr"
)

Expand Down Expand Up @@ -90,6 +92,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
return namecheap.NewDNSProvider()
case "namedotcom":
return namedotcom.NewDNSProvider()
case "nifcloud":
return nifcloud.NewDNSProvider()
case "rackspace":
return rackspace.NewDNSProvider()
case "route53":
Expand All @@ -110,6 +114,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
return otc.NewDNSProvider()
case "exec":
return exec.NewDNSProvider()
case "vegadns":
return vegadns.NewDNSProvider()
default:
return nil, fmt.Errorf("unrecognised DNS provider: %s", name)
}
Expand Down
20 changes: 14 additions & 6 deletions providers/dns/lightsail/lightsail.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package lightsail

import (
"math/rand"
"os"
"time"

"github.com/aws/aws-sdk-go/aws"
Expand All @@ -20,7 +21,8 @@ const (

// DNSProvider implements the acme.ChallengeProvider interface
type DNSProvider struct {
client *lightsail.Lightsail
client *lightsail.Lightsail
dnsZone string
}

// customRetryer implements the client.Retryer interface by composing the
Expand Down Expand Up @@ -61,18 +63,24 @@ func (c customRetryer) RetryRules(r *request.Request) time.Duration {
func NewDNSProvider() (*DNSProvider, error) {
r := customRetryer{}
r.NumMaxRetries = maxRetries
config := request.WithRetryer(aws.NewConfig(), r)
client := lightsail.New(session.New(config))

config := aws.NewConfig().WithRegion("us-east-1")
sess, err := session.NewSession(request.WithRetryer(config, r))
if err != nil {
return nil, err
}

return &DNSProvider{
client: client,
dnsZone: os.Getenv("DNS_ZONE"),
client: lightsail.New(sess),
}, nil
}

// Present creates a TXT record using the specified parameters
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
value = `"` + value + `"`

err := d.newTxtRecord(domain, fqdn, value)
return err
}
Expand All @@ -82,7 +90,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
value = `"` + value + `"`
params := &lightsail.DeleteDomainEntryInput{
DomainName: aws.String(domain),
DomainName: aws.String(d.dnsZone),
DomainEntry: &lightsail.DomainEntry{
Name: aws.String(fqdn),
Type: aws.String("TXT"),
Expand All @@ -95,7 +103,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {

func (d *DNSProvider) newTxtRecord(domain string, fqdn string, value string) error {
params := &lightsail.CreateDomainEntryInput{
DomainName: aws.String(domain),
DomainName: aws.String(d.dnsZone),
DomainEntry: &lightsail.DomainEntry{
Name: aws.String(fqdn),
Target: aws.String(value),
Expand Down
5 changes: 4 additions & 1 deletion providers/dns/lightsail/lightsail_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ func TestLightsailTTL(t *testing.T) {
// we need a separate Lightshail client here as the one in the DNS provider is
// unexported.
fqdn := "_acme-challenge." + m["lightsailDomain"]
svc := lightsail.New(session.New())
sess, err := session.NewSession()
require.NoError(t, err)

svc := lightsail.New(sess)
if err != nil {
provider.CleanUp(m["lightsailDomain"], "foo", "bar")
t.Fatal(err)
Expand Down
32 changes: 20 additions & 12 deletions providers/dns/lightsail/lightsail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/lightsail"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var (
Expand All @@ -30,16 +30,21 @@ func restoreEnv() {
os.Setenv("AWS_HOSTED_ZONE_ID", lightsailZone)
}

func makeLightsailProvider(ts *httptest.Server) *DNSProvider {
func makeLightsailProvider(ts *httptest.Server) (*DNSProvider, error) {
config := &aws.Config{
Credentials: credentials.NewStaticCredentials("abc", "123", " "),
Endpoint: aws.String(ts.URL),
Region: aws.String("mock-region"),
MaxRetries: aws.Int(1),
}

client := lightsail.New(session.New(config))
return &DNSProvider{client: client}
sess, err := session.NewSession(config)
if err != nil {
return nil, err
}

client := lightsail.New(sess)
return &DNSProvider{client: client}, nil
}

func TestCredentialsFromEnv(t *testing.T) {
Expand All @@ -52,24 +57,27 @@ func TestCredentialsFromEnv(t *testing.T) {
CredentialsChainVerboseErrors: aws.Bool(true),
}

sess := session.New(config)
_, err := sess.Config.Credentials.Get()
assert.NoError(t, err, "Expected credentials to be set from environment")
sess, err := session.NewSession(config)
require.NoError(t, err)

_, err = sess.Config.Credentials.Get()
require.NoError(t, err, "Expected credentials to be set from environment")
}

func TestLightsailPresent(t *testing.T) {
mockResponses := MockResponseMap{
"/": MockResponse{StatusCode: 200, Body: ""},
mockResponses := map[string]MockResponse{
"/": {StatusCode: 200, Body: ""},
}

ts := newMockServer(t, mockResponses)
defer ts.Close()

provider := makeLightsailProvider(ts)
provider, err := makeLightsailProvider(ts)
require.NoError(t, err)

domain := "example.com"
keyAuth := "123456d=="

err := provider.Present(domain, "", keyAuth)
assert.NoError(t, err, "Expected Present to return no error")
err = provider.Present(domain, "", keyAuth)
require.NoError(t, err, "Expected Present to return no error")
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ type MockResponse struct {
Body string
}

// MockResponseMap maps request paths to responses
type MockResponseMap map[string]MockResponse

func newMockServer(t *testing.T, responses MockResponseMap) *httptest.Server {
func newMockServer(t *testing.T, responses map[string]MockResponse) *httptest.Server {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
resp, ok := responses[path]
Expand Down
Loading

0 comments on commit d9bdd50

Please sign in to comment.