@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 256 lines 7.37 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddytls
16
17 import (
18 "context"
19 "crypto/x509"
20 "fmt"
21 "strconv"
22 "time"
23
24 "github.com/caddyserver/certmagic"
25 "go.uber.org/zap"
26
27 "github.com/caddyserver/caddy/v2"
28 "github.com/caddyserver/caddy/v2/caddyconfig"
29 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
30 )
31
32 func init() {
33 caddy.RegisterModule(new(ZeroSSLIssuer))
34 }
35
36 // ZeroSSLIssuer uses the ZeroSSL API to get certificates.
37 // Note that this is distinct from ZeroSSL's ACME endpoint.
38 // To use ZeroSSL's ACME endpoint, use the ACMEIssuer
39 // configured with ZeroSSL's ACME directory endpoint.
40 type ZeroSSLIssuer struct {
41 // The API key (or "access key") for using the ZeroSSL API.
42 // REQUIRED.
43 APIKey string `json:"api_key,omitempty"` //nolint:gosec // false positive... yes this is exported, for JSON interop
44
45 // How many days the certificate should be valid for.
46 // Only certain values are accepted; see ZeroSSL docs.
47 ValidityDays int `json:"validity_days,omitempty"`
48
49 // The host to bind to when opening a listener for
50 // verifying domain names (or IPs).
51 ListenHost string `json:"listen_host,omitempty"`
52
53 // If HTTP is forwarded from port 80, specify the
54 // forwarded port here.
55 AlternateHTTPPort int `json:"alternate_http_port,omitempty"`
56
57 // Use CNAME validation instead of HTTP. ZeroSSL's
58 // API uses CNAME records for DNS validation, similar
59 // to how Let's Encrypt uses TXT records for the
60 // DNS challenge.
61 CNAMEValidation *DNSChallengeConfig `json:"cname_validation,omitempty"`
62
63 logger *zap.Logger
64 storage certmagic.Storage
65 issuer *certmagic.ZeroSSLIssuer
66 }
67
68 // CaddyModule returns the Caddy module information.
69 func (*ZeroSSLIssuer) CaddyModule() caddy.ModuleInfo {
70 return caddy.ModuleInfo{
71 ID: "tls.issuance.zerossl",
72 New: func() caddy.Module { return new(ZeroSSLIssuer) },
73 }
74 }
75
76 // Provision sets up the issuer.
77 func (iss *ZeroSSLIssuer) Provision(ctx caddy.Context) error {
78 iss.logger = ctx.Logger()
79 iss.storage = ctx.Storage()
80 repl := caddy.NewReplacer()
81
82 var dnsManager *certmagic.DNSManager
83 if iss.CNAMEValidation != nil && len(iss.CNAMEValidation.ProviderRaw) > 0 {
84 val, err := ctx.LoadModule(iss.CNAMEValidation, "ProviderRaw")
85 if err != nil {
86 return fmt.Errorf("loading DNS provider module: %v", err)
87 }
88 dnsManager = &certmagic.DNSManager{
89 DNSProvider: val.(certmagic.DNSProvider),
90 TTL: time.Duration(iss.CNAMEValidation.TTL),
91 PropagationDelay: time.Duration(iss.CNAMEValidation.PropagationDelay),
92 PropagationTimeout: time.Duration(iss.CNAMEValidation.PropagationTimeout),
93 Resolvers: iss.CNAMEValidation.Resolvers,
94 OverrideDomain: iss.CNAMEValidation.OverrideDomain,
95 Logger: iss.logger.Named("cname"),
96 }
97 }
98
99 iss.issuer = &certmagic.ZeroSSLIssuer{
100 APIKey: repl.ReplaceAll(iss.APIKey, ""),
101 ValidityDays: iss.ValidityDays,
102 ListenHost: iss.ListenHost,
103 AltHTTPPort: iss.AlternateHTTPPort,
104 Storage: iss.storage,
105 CNAMEValidation: dnsManager,
106 Logger: iss.logger,
107 }
108
109 return nil
110 }
111
112 // Issue obtains a certificate for the given csr.
113 func (iss *ZeroSSLIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
114 return iss.issuer.Issue(ctx, csr)
115 }
116
117 // IssuerKey returns the unique issuer key for the configured CA endpoint.
118 func (iss *ZeroSSLIssuer) IssuerKey() string {
119 return iss.issuer.IssuerKey()
120 }
121
122 // Revoke revokes the given certificate.
123 func (iss *ZeroSSLIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource, reason int) error {
124 return iss.issuer.Revoke(ctx, cert, reason)
125 }
126
127 // UnmarshalCaddyfile deserializes Caddyfile tokens into iss.
128 //
129 // ... zerossl <api_key> {
130 // validity_days <days>
131 // alt_http_port <port>
132 // dns <provider_name> ...
133 // propagation_delay <duration>
134 // propagation_timeout <duration>
135 // resolvers <list...>
136 // dns_ttl <duration>
137 // }
138 func (iss *ZeroSSLIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
139 d.Next() // consume issuer name
140
141 // API key is required
142 if !d.NextArg() {
143 return d.ArgErr()
144 }
145 iss.APIKey = d.Val()
146 if d.NextArg() {
147 return d.ArgErr()
148 }
149
150 for nesting := d.Nesting(); d.NextBlock(nesting); {
151 switch d.Val() {
152 case "validity_days":
153 if iss.ValidityDays != 0 {
154 return d.Errf("validity days is already specified: %d", iss.ValidityDays)
155 }
156 days, err := strconv.Atoi(d.Val())
157 if err != nil {
158 return d.Errf("invalid number of days %s: %v", d.Val(), err)
159 }
160 iss.ValidityDays = days
161
162 case "alt_http_port":
163 if !d.NextArg() {
164 return d.ArgErr()
165 }
166 port, err := strconv.Atoi(d.Val())
167 if err != nil {
168 return d.Errf("invalid port %s: %v", d.Val(), err)
169 }
170 iss.AlternateHTTPPort = port
171
172 case "dns":
173 if !d.NextArg() {
174 return d.ArgErr()
175 }
176 provName := d.Val()
177 if iss.CNAMEValidation == nil {
178 iss.CNAMEValidation = new(DNSChallengeConfig)
179 }
180 unm, err := caddyfile.UnmarshalModule(d, "dns.providers."+provName)
181 if err != nil {
182 return err
183 }
184 iss.CNAMEValidation.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, nil)
185
186 case "propagation_delay":
187 if !d.NextArg() {
188 return d.ArgErr()
189 }
190 delayStr := d.Val()
191 delay, err := caddy.ParseDuration(delayStr)
192 if err != nil {
193 return d.Errf("invalid propagation_delay duration %s: %v", delayStr, err)
194 }
195 if iss.CNAMEValidation == nil {
196 iss.CNAMEValidation = new(DNSChallengeConfig)
197 }
198 iss.CNAMEValidation.PropagationDelay = caddy.Duration(delay)
199
200 case "propagation_timeout":
201 if !d.NextArg() {
202 return d.ArgErr()
203 }
204 timeoutStr := d.Val()
205 var timeout time.Duration
206 if timeoutStr == "-1" {
207 timeout = time.Duration(-1)
208 } else {
209 var err error
210 timeout, err = caddy.ParseDuration(timeoutStr)
211 if err != nil {
212 return d.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err)
213 }
214 }
215 if iss.CNAMEValidation == nil {
216 iss.CNAMEValidation = new(DNSChallengeConfig)
217 }
218 iss.CNAMEValidation.PropagationTimeout = caddy.Duration(timeout)
219
220 case "resolvers":
221 if iss.CNAMEValidation == nil {
222 iss.CNAMEValidation = new(DNSChallengeConfig)
223 }
224 iss.CNAMEValidation.Resolvers = d.RemainingArgs()
225 if len(iss.CNAMEValidation.Resolvers) == 0 {
226 return d.ArgErr()
227 }
228
229 case "dns_ttl":
230 if !d.NextArg() {
231 return d.ArgErr()
232 }
233 ttlStr := d.Val()
234 ttl, err := caddy.ParseDuration(ttlStr)
235 if err != nil {
236 return d.Errf("invalid dns_ttl duration %s: %v", ttlStr, err)
237 }
238 if iss.CNAMEValidation == nil {
239 iss.CNAMEValidation = new(DNSChallengeConfig)
240 }
241 iss.CNAMEValidation.TTL = caddy.Duration(ttl)
242
243 default:
244 return d.Errf("unrecognized zerossl issuer property: %s", d.Val())
245 }
246 }
247
248 return nil
249 }
250
251 // Interface guards
252 var (
253 _ certmagic.Issuer = (*ZeroSSLIssuer)(nil)
254 _ certmagic.Revoker = (*ZeroSSLIssuer)(nil)
255 _ caddy.Provisioner = (*ZeroSSLIssuer)(nil)
256 )