-
Notifications
You must be signed in to change notification settings - Fork 1
/
letse.go
167 lines (147 loc) · 5.56 KB
/
letse.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package letse
import (
"crypto"
"crypto/x509"
"errors"
"log"
"github.com/ericchiang/letsencrypt"
)
const (
prodURL = "https://acme-v01.api.letsencrypt.org/directory"
stagURL = "https://acme-staging.api.letsencrypt.org/directory"
)
var (
// ErrCreatingLEClient is returned when it is unable to initialize LE's client.
ErrCreatingLEClient = errors.New("error creating LetsEncrypt client")
// ErrRequestingAuthz is returned when requesting DNS authorization from LE fails.
ErrRequestingAuthz = errors.New("error requesting DNS authorization challenge from LetsEncrypt servers")
// ErrUnsupportedChallenges ...
ErrUnsupportedChallenges = errors.New("LetsEncrypt servers sent unsupported challenges")
// ErrRetrievingChallengeToken ...
ErrRetrievingChallengeToken = errors.New("error retrieving LetsEncrypt challenge token")
// ErrCreatingDNSTXTRecord ...
ErrCreatingDNSTXTRecord = errors.New("unable to create DNS TXT record in DNS provider service")
// ErrNotifyingChallengeReadiness ...
ErrNotifyingChallengeReadiness = errors.New("error notifying LetsEncrypt servers that challenge is ready to be verified")
// ErrGettingCertificate ...
ErrGettingCertificate = errors.New("error getting new certificate from LetsEncrypt servers")
// ErrRegisteringAccountKey ...
ErrRegisteringAccountKey = errors.New("error registering account key with LetsEncrypt servers")
// ErrRevokingCertificate ...
ErrRevokingCertificate = errors.New("error revoking certificate")
// ErrRenewingCertificate ...
ErrRenewingCertificate = errors.New("error renewing certificate")
)
// supportedChallenges lists challenges supported by this LetsEncrypt client.
var supportedChallenges = []string{
letsencrypt.ChallengeDNS,
}
// DNSProvider is the interface to implement for each DNS Provider supported.
type DNSProvider interface {
AddTXTRecord(name, value string) error
RemoveTXTRecord(name string) error
}
// Client represents an opinionated LetsEncrypt client that only completes
// DNS challenges.
type Client struct {
accountKey crypto.PrivateKey
lc *letsencrypt.Client
la letsencrypt.Authorization
}
// NewClient creates a new instance of Letse's LetsEncrypt client. Allowing
// to use LetsEncrypt staging or production servers.
func NewClient(accountKey crypto.PrivateKey, dryRun bool) (*Client, error) {
var lc *letsencrypt.Client
var err error
if dryRun {
lc, err = letsencrypt.NewClient(stagURL)
} else {
lc, err = letsencrypt.NewClient(prodURL)
}
if err != nil {
log.Printf(`lv=err msg=%q le-err=%q`, ErrCreatingLEClient, err)
return nil, ErrCreatingLEClient
}
return &Client{
lc: lc,
accountKey: accountKey,
}, nil
}
// Register registers account key with LetsEncrypt servers.
func (c *Client) Register() error {
_, err := c.lc.NewRegistration(c.accountKey)
if err != nil {
log.Printf(`lv=err msg=%q le-err=%q`, ErrRegisteringAccountKey, err)
return ErrRegisteringAccountKey
}
return err
}
// RequestAuthz requests authorization from LetsEncrypt servers to issue
// operations on the certificate of the given domain name.
func (c *Client) RequestAuthz(domain string) error {
auth, _, err := c.lc.NewAuthorization(c.accountKey, "dns", domain)
if err != nil {
log.Printf(`lv=err msg=%q le-err=%q`, ErrRequestingAuthz, err)
return ErrRequestingAuthz
}
c.la = auth
return nil
}
// CompleteChallenge completes LetsEncrypt DNS challenge using the passed DNS
// provider.
func (c *Client) CompleteChallenge(provider DNSProvider) error {
chals := c.la.Combinations(supportedChallenges...)
if len(chals) == 0 {
log.Printf(`lv=err msg=%q challenges=%+v`, ErrUnsupportedChallenges, chals)
return ErrUnsupportedChallenges
}
chal := chals[0][0]
subdomain, token, err := chal.DNS(c.accountKey)
if err != nil {
log.Printf(`lv=err msg=%q le-err=%q`, ErrRetrievingChallengeToken, err)
return ErrRetrievingChallengeToken
}
if err := provider.AddTXTRecord(subdomain, token); err != nil {
log.Printf(`lv=err msg=%q le-err=%q`, ErrCreatingDNSTXTRecord, err)
return ErrCreatingDNSTXTRecord
}
defer func() {
err := provider.RemoveTXTRecord(subdomain)
if err != nil {
log.Printf(`lv=warn msg="error deleting TXT record from DNS provider" le-err=%q`, err)
}
}()
// Notifies LetsEncrypt servers that the challenge is ready to be verified.
if err := c.lc.ChallengeReady(c.accountKey, chal); err != nil {
log.Printf(`lv=err msg=%q le-err=%q`, ErrNotifyingChallengeReadiness, err)
return ErrNotifyingChallengeReadiness
}
return nil
}
// NewCert requests a new certificate to LetsEncrypt servers.
func (c *Client) NewCert(csr *x509.CertificateRequest) (*x509.Certificate, error) {
cert, err := c.lc.NewCertificate(c.accountKey, csr)
if err != nil {
log.Printf(`lv=err msg=%q le-err=%q`, ErrGettingCertificate, err)
return nil, ErrGettingCertificate
}
return cert.Certificate, nil
}
// RevokeCert requests LetsEncrypt servers to revoke the given certificate.
func (c *Client) RevokeCert(cert *x509.Certificate) error {
if err := c.lc.RevokeCertificate(c.accountKey, cert.Raw); err != nil {
log.Printf(`lv=err msg=%q le-err=%q`, ErrRevokingCertificate, err)
return ErrRevokingCertificate
}
return nil
}
// RenewCert attempts to renew certificate with LetsEncrypt servers, if expiration
// is not close, LE servers will return the same certificate.
func (c *Client) RenewCert(cert *x509.Certificate) (*x509.Certificate, error) {
newCert, err := c.lc.RenewCertificate("/acme/cert/" + cert.SerialNumber.String())
if err != nil {
log.Printf(`lv=err msg=%q le-err=%q`, ErrRenewingCertificate, err)
return nil, ErrRenewingCertificate
}
return newCert.Certificate, err
}