@marcel91200 / caddy Mirror · read-only / commits / 5a19db5d
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 14, 05:15 UTC

v2: Implement 'pki' app powered by Smallstep for localhost certificates (#3125)

* pki: Initial commit of PKI app (WIP) (see #2502 and #3021) * pki: Ability to use root/intermediates, and sign with root * pki: Fix benign misnamings left over from copy+paste * pki: Only install root if not already trusted * Make HTTPS port the default; all names use auto-HTTPS; bug fixes * Fix build - what happened to our CI tests?? * Fix go.mod

Matt Holt committed Mar 13, 2020 at 11:06 UTC 5a19db5dc2db7c02d0f99630a07a64cacb7f7b44
21 files changed +1292 -175
caddyconfig/httpcaddyfile/addresses.go
+4 -21
@@ -172,20 +172,14 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
172 httpsPort = strconv.Itoa(hsport.(int))
173 }
174
175 - lnPort := DefaultPort
175 + // default port is the HTTPS port
176 + lnPort := httpsPort
177 if addr.Port != "" {
178 // port explicitly defined
179 lnPort = addr.Port
179 - } else if addr.Scheme != "" {
180 + } else if addr.Scheme == "http" {
181 // port inferred from scheme
181 - if addr.Scheme == "http" {
182 - lnPort = httpPort
183 - } else if addr.Scheme == "https" {
184 - lnPort = httpsPort
185 - }
186 - } else if certmagic.HostQualifies(addr.Host) {
187 - // automatic HTTPS
188 - lnPort = httpsPort
182 + lnPort = httpPort
183 }
184
185 // error if scheme and port combination violate convention
@@ -213,7 +207,6 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
207 for lnStr := range listeners {
208 listenersList = append(listenersList, lnStr)
209 }
216 - // sort.Strings(listenersList) // TODO: is sorting necessary?
210
211 return listenersList, nil
212 }
@@ -317,9 +310,6 @@ func (a Address) String() string {
310 // Normalize returns a normalized version of a.
311 func (a Address) Normalize() Address {
312 path := a.Path
320 - if !caseSensitivePath {
321 - path = strings.ToLower(path)
322 - }
313
314 // ensure host is normalized if it's an IP address
315 host := a.Host
@@ -357,10 +347,3 @@ func (a Address) Key() string {
347 }
348 return res
349 }
360 -
361 -const (
362 - // DefaultPort is the default port to use.
363 - DefaultPort = "2015"
364 -
365 - caseSensitivePath = false // TODO: Used?
366 -)
caddyconfig/httpcaddyfile/addresses_test.go
+2 -10
@@ -1,7 +1,6 @@
1 package httpcaddyfile
2
3 import (
4 - "strings"
4 "testing"
5 )
6
@@ -156,15 +155,8 @@ func TestKeyNormalization(t *testing.T) {
155 t.Errorf("Test %d: Parsing address '%s': %v", i, tc.input, err)
156 continue
157 }
159 - expect := tc.expect
160 - if !caseSensitivePath {
161 - // every other part of the address should be lowercased when normalized,
162 - // so simply lower-case the whole thing to do case-insensitive comparison
163 - // of the path as well
164 - expect = strings.ToLower(expect)
165 - }
166 - if actual := addr.Normalize().Key(); actual != expect {
167 - t.Errorf("Test %d: Normalized key for address '%s' was '%s' but expected '%s'", i, tc.input, actual, expect)
158 + if actual := addr.Normalize().Key(); actual != tc.expect {
159 + t.Errorf("Test %d: Normalized key for address '%s' was '%s' but expected '%s'", i, tc.input, actual, tc.expect)
160 }
161
162 }
caddyconfig/httpcaddyfile/builtins.go
+54 -27
@@ -95,7 +95,7 @@ func parseRoot(h Helper) ([]ConfigValue, error) {
95
96 // parseTLS parses the tls directive. Syntax:
97 //
98 -// tls [<email>]|[<cert_file> <key_file>] {
98 +// tls [<email>|internal]|[<cert_file> <key_file>] {
99 // protocols <min> [<max>]
100 // ciphers <cipher_suites...>
101 // curves <curves...>
@@ -106,23 +106,11 @@ func parseRoot(h Helper) ([]ConfigValue, error) {
106 // }
107 //
108 func parseTLS(h Helper) ([]ConfigValue, error) {
109 - var configVals []ConfigValue
110 -
109 var cp *caddytls.ConnectionPolicy
110 var fileLoader caddytls.FileLoader
111 var folderLoader caddytls.FolderLoader
114 - var mgr caddytls.ACMEIssuer
115 -
116 - // fill in global defaults, if configured
117 - if email := h.Option("email"); email != nil {
118 - mgr.Email = email.(string)
119 - }
120 - if acmeCA := h.Option("acme_ca"); acmeCA != nil {
121 - mgr.CA = acmeCA.(string)
122 - }
123 - if caPemFile := h.Option("acme_ca_root"); caPemFile != nil {
124 - mgr.TrustedRootsPEMFiles = append(mgr.TrustedRootsPEMFiles, caPemFile.(string))
125 - }
112 + var acmeIssuer *caddytls.ACMEIssuer
113 + var internalIssuer *caddytls.InternalIssuer
114
115 for h.Next() {
116 // file certificate loader
@@ -130,10 +118,17 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
118 switch len(firstLine) {
119 case 0:
120 case 1:
133 - if !strings.Contains(firstLine[0], "@") {
134 - return nil, h.Err("single argument must be an email address")
121 + if firstLine[0] == "internal" {
122 + internalIssuer = new(caddytls.InternalIssuer)
123 + } else if !strings.Contains(firstLine[0], "@") {
124 + return nil, h.Err("single argument must either be 'internal' or an email address")
125 + } else {
126 + if acmeIssuer == nil {
127 + acmeIssuer = new(caddytls.ACMEIssuer)
128 + }
129 + acmeIssuer.Email = firstLine[0]
130 }
136 - mgr.Email = firstLine[0]
131 +
132 case 2:
133 certFilename := firstLine[0]
134 keyFilename := firstLine[1]
@@ -143,7 +138,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
138 // https://github.com/caddyserver/caddy/issues/2588 ... but we
139 // must be careful about how we do this; being careless will
140 // lead to failed handshakes
146 -
141 + //
142 // we need to remember which cert files we've seen, since we
143 // must load each cert only once; otherwise, they each get a
144 // different tag... since a cert loaded twice has the same
@@ -152,7 +147,7 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
147 // policy that is looking for any tag but the last one to be
148 // loaded won't find it, and TLS handshakes will fail (see end)
149 // of issue #3004)
155 -
150 + //
151 // tlsCertTags maps certificate filenames to their tag.
152 // This is used to remember which tag is used for each
153 // certificate files, since we need to avoid loading
@@ -256,29 +251,38 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
251 if len(arg) != 1 {
252 return nil, h.ArgErr()
253 }
259 - mgr.CA = arg[0]
254 + if acmeIssuer == nil {
255 + acmeIssuer = new(caddytls.ACMEIssuer)
256 + }
257 + acmeIssuer.CA = arg[0]
258
259 // DNS provider for ACME DNS challenge
260 case "dns":
261 if !h.Next() {
262 return nil, h.ArgErr()
263 }
264 + if acmeIssuer == nil {
265 + acmeIssuer = new(caddytls.ACMEIssuer)
266 + }
267 provName := h.Val()
267 - if mgr.Challenges == nil {
268 - mgr.Challenges = new(caddytls.ChallengesConfig)
268 + if acmeIssuer.Challenges == nil {
269 + acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
270 }
271 dnsProvModule, err := caddy.GetModule("tls.dns." + provName)
272 if err != nil {
273 return nil, h.Errf("getting DNS provider module named '%s': %v", provName, err)
274 }
274 - mgr.Challenges.DNSRaw = caddyconfig.JSONModuleObject(dnsProvModule.New(), "provider", provName, h.warnings)
275 + acmeIssuer.Challenges.DNSRaw = caddyconfig.JSONModuleObject(dnsProvModule.New(), "provider", provName, h.warnings)
276
277 case "ca_root":
278 arg := h.RemainingArgs()
279 if len(arg) != 1 {
280 return nil, h.ArgErr()
281 }
281 - mgr.TrustedRootsPEMFiles = append(mgr.TrustedRootsPEMFiles, arg[0])
282 + if acmeIssuer == nil {
283 + acmeIssuer = new(caddytls.ACMEIssuer)
284 + }
285 + acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, arg[0])
286
287 default:
288 return nil, h.Errf("unknown subdirective: %s", h.Val())
@@ -291,6 +295,9 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
295 }
296 }
297
298 + // begin building the final config values
299 + var configVals []ConfigValue
300 +
301 // certificate loaders
302 if len(fileLoader) > 0 {
303 configVals = append(configVals, ConfigValue{
@@ -322,10 +329,30 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
329 }
330
331 // automation policy
325 - if !reflect.DeepEqual(mgr, caddytls.ACMEIssuer{}) {
332 + if acmeIssuer != nil && internalIssuer != nil {
333 + // the logic to support this would be complex
334 + return nil, h.Err("cannot use both ACME and internal issuers in same server block")
335 + }
336 + if acmeIssuer != nil {
337 + // fill in global defaults, if configured
338 + if email := h.Option("email"); email != nil && acmeIssuer.Email == "" {
339 + acmeIssuer.Email = email.(string)
340 + }
341 + if acmeCA := h.Option("acme_ca"); acmeCA != nil && acmeIssuer.CA == "" {
342 + acmeIssuer.CA = acmeCA.(string)
343 + }
344 + if caPemFile := h.Option("acme_ca_root"); caPemFile != nil {
345 + acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, caPemFile.(string))
346 + }
347 +
348 + configVals = append(configVals, ConfigValue{
349 + Class: "tls.cert_issuer",
350 + Value: acmeIssuer,
351 + })
352 + } else if internalIssuer != nil {
353 configVals = append(configVals, ConfigValue{
354 Class: "tls.cert_issuer",
328 - Value: mgr,
355 + Value: internalIssuer,
356 })
357 }
358
caddyconfig/httpcaddyfile/httptype.go
+9 -27
@@ -185,10 +185,10 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
185 for _, p := range pairings {
186 for i, sblock := range p.serverBlocks {
187 // tls automation policies
188 - if mmVals, ok := sblock.pile["tls.cert_issuer"]; ok {
189 - for _, mmVal := range mmVals {
190 - mm := mmVal.Value.(certmagic.Issuer)
191 - sblockHosts, err := st.autoHTTPSHosts(sblock)
188 + if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
189 + for _, issuerVal := range issuerVals {
190 + issuer := issuerVal.Value.(certmagic.Issuer)
191 + sblockHosts, err := st.hostsFromServerBlockKeys(sblock.block)
192 if err != nil {
193 return nil, warnings, err
194 }
@@ -198,7 +198,7 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
198 }
199 tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, &caddytls.AutomationPolicy{
200 Hosts: sblockHosts,
201 - IssuerRaw: caddyconfig.JSONModuleObject(mm, "module", mm.(caddy.Module).CaddyModule().ID.Name(), &warnings),
201 + IssuerRaw: caddyconfig.JSONModuleObject(issuer, "module", issuer.(caddy.Module).CaddyModule().ID.Name(), &warnings),
202 })
203 } else {
204 warnings = append(warnings, caddyconfig.Warning{
@@ -500,16 +500,13 @@ func (st *ServerType) serversFromPairings(
500
501 // tls: connection policies and toggle auto HTTPS
502 defaultSNI := tryString(options["default_sni"], warnings)
503 - autoHTTPSQualifiedHosts, err := st.autoHTTPSHosts(sblock)
504 - if err != nil {
505 - return nil, err
506 - }
507 - if _, ok := sblock.pile["tls.off"]; ok && len(autoHTTPSQualifiedHosts) > 0 {
503 + if _, ok := sblock.pile["tls.off"]; ok {
504 + // TODO: right now, no directives yield any tls.off value...
505 // tls off: disable TLS (and automatic HTTPS) for server block's names
506 if srv.AutoHTTPS == nil {
507 srv.AutoHTTPS = new(caddyhttp.AutoHTTPSConfig)
508 }
512 - srv.AutoHTTPS.Skip = append(srv.AutoHTTPS.Skip, autoHTTPSQualifiedHosts...)
509 + srv.AutoHTTPS.Disabled = true
510 } else if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
511 // tls connection policies
512
@@ -741,25 +738,10 @@ func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subro
738 return subroute, nil
739 }
740
744 -func (st ServerType) autoHTTPSHosts(sb serverBlock) ([]string, error) {
745 - // get the hosts for this server block...
746 - hosts, err := st.hostsFromServerBlockKeys(sb.block)
747 - if err != nil {
748 - return nil, err
749 - }
750 - // ...and of those, which ones qualify for auto HTTPS
751 - var autoHTTPSQualifiedHosts []string
752 - for _, h := range hosts {
753 - if certmagic.HostQualifies(h) {
754 - autoHTTPSQualifiedHosts = append(autoHTTPSQualifiedHosts, h)
755 - }
756 - }
757 - return autoHTTPSQualifiedHosts, nil
758 -}
759 -
741 // consolidateRoutes combines routes with the same properties
742 // (same matchers, same Terminal and Group settings) for a
743 // cleaner overall output.
744 +// FIXME: See caddyserver/caddy#3108
745 func consolidateRoutes(routes caddyhttp.RouteList) caddyhttp.RouteList {
746 for i := 0; i < len(routes)-1; i++ {
747 if reflect.DeepEqual(routes[i].MatcherSetsRaw, routes[i+1].MatcherSetsRaw) &&
go.mod
+4 -4
@@ -4,9 +4,9 @@ go 1.14
4
5 require (
6 github.com/Masterminds/sprig/v3 v3.0.2
7 - github.com/alecthomas/chroma v0.7.1
7 + github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a
8 github.com/andybalholm/brotli v1.0.0
9 - github.com/caddyserver/certmagic v0.10.0
9 + github.com/caddyserver/certmagic v0.10.1
10 github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
11 github.com/go-acme/lego/v3 v3.4.0
12 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
@@ -24,8 +24,8 @@ require (
24 github.com/smallstep/cli v0.14.0-rc.3
25 github.com/smallstep/truststore v0.9.4
26 github.com/vulcand/oxy v1.0.0
27 - github.com/yuin/goldmark v1.1.24
28 - github.com/yuin/goldmark-highlighting v0.0.0-20200218065240-d1af22c1126f
27 + github.com/yuin/goldmark v1.1.25
28 + github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
29 go.uber.org/zap v1.14.0
30 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
31 golang.org/x/net v0.0.0-20200301022130-244492dfa37a
go.sum
+8 -8
@@ -72,8 +72,8 @@ github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75 h1:3ILj
72 github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE=
73 github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
74 github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
75 -github.com/alecthomas/chroma v0.7.1 h1:G1i02OhUbRi2nJxcNkwJaY/J1gHXj9tt72qN6ZouLFQ=
76 -github.com/alecthomas/chroma v0.7.1/go.mod h1:gHw09mkX1Qp80JlYbmN9L3+4R5o6DJJ3GRShh+AICNc=
75 +github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a h1:3v1NrYWWqp2S72e4HLgxKt83B3l0lnORDholH/ihoMM=
76 +github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s=
77 github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
78 github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
79 github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
@@ -108,8 +108,8 @@ github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTK
108 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
109 github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
110 github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
111 -github.com/caddyserver/certmagic v0.10.0 h1:kbQsqN5RmdUMClVUNd8svTzemCo8W6NNc8UJOXnUIu0=
112 -github.com/caddyserver/certmagic v0.10.0/go.mod h1:Y8jcUBctgk/IhpAzlHKfimZNyXCkfGgRTC0orl8gROQ=
111 +github.com/caddyserver/certmagic v0.10.1 h1:k9E+C4b8WM3sTs3PSfmWIAwxtO9cXtr0bDHX2Bc0RIM=
112 +github.com/caddyserver/certmagic v0.10.1/go.mod h1:Y8jcUBctgk/IhpAzlHKfimZNyXCkfGgRTC0orl8gROQ=
113 github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
114 github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
115 github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -730,10 +730,10 @@ github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4m
730 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
731 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
732 github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
733 -github.com/yuin/goldmark v1.1.24 h1:K4FemPDr4x/ZcqldoXWnexTLfdMIy2eEfXxsLnotTRI=
734 -github.com/yuin/goldmark v1.1.24/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
735 -github.com/yuin/goldmark-highlighting v0.0.0-20200218065240-d1af22c1126f h1:5295skDVJn90SXIYI22jOMeR9XbnuN76y/V1m9N8ITQ=
736 -github.com/yuin/goldmark-highlighting v0.0.0-20200218065240-d1af22c1126f/go.mod h1:9yW2CHuRSORvHgw7YfybB09PqUZTbzERyW3QFvd8+0Q=
733 +github.com/yuin/goldmark v1.1.25 h1:isv+Q6HQAmmL2Ofcmg8QauBmDPlUUnSoNhEcC940Rds=
734 +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
735 +github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
736 +github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
737 go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
738 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
739 go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
modules/caddyhttp/autohttps.go
+104 -56
@@ -4,6 +4,7 @@ import (
4 "fmt"
5 "net/http"
6 "strconv"
7 + "strings"
8
9 "github.com/caddyserver/caddy/v2"
10 "github.com/caddyserver/caddy/v2/modules/caddytls"
@@ -130,8 +131,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
131 return fmt.Errorf("%s: route %d, matcher set %d, matcher %d, host matcher %d: %v",
132 srvName, routeIdx, matcherSetIdx, matcherIdx, hostMatcherIdx, err)
133 }
133 - if certmagic.HostQualifies(d) &&
134 - !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) {
134 + if !srv.AutoHTTPS.Skipped(d, srv.AutoHTTPS.Skip) {
135 serverDomainSet[d] = struct{}{}
136 }
137 }
@@ -161,6 +161,15 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
161 )
162 continue
163 }
164 +
165 + // most clients don't accept wildcards like *.tld... we
166 + // can handle that, but as a courtesy, warn the user
167 + if strings.Contains(d, "*") &&
168 + strings.Count(strings.Trim(d, "."), ".") == 1 {
169 + app.logger.Warn("most clients do not trust second-level wildcard certificates (*.tld)",
170 + zap.String("domain", d))
171 + }
172 +
173 uniqueDomainsForCerts[d] = struct{}{}
174 }
175 }
@@ -202,12 +211,18 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
211 // we now have a list of all the unique names for which we need certs;
212 // turn the set into a slice so that phase 2 can use it
213 app.allCertDomains = make([]string, 0, len(uniqueDomainsForCerts))
214 + var internal, external []string
215 for d := range uniqueDomainsForCerts {
216 + if certmagic.SubjectQualifiesForPublicCert(d) {
217 + external = append(external, d)
218 + } else {
219 + internal = append(internal, d)
220 + }
221 app.allCertDomains = append(app.allCertDomains, d)
222 }
223
224 // ensure there is an automation policy to handle these certs
210 - err := app.createAutomationPolicy(ctx)
225 + err := app.createAutomationPolicies(ctx, external, internal)
226 if err != nil {
227 return err
228 }
@@ -354,23 +369,29 @@ redirServersLoop:
369 return nil
370 }
371
357 -// createAutomationPolicy ensures that certificates for this app are
358 -// managed properly; for example, it's implied that the HTTPPort
359 -// should also be the port the HTTP challenge is solved on; the same
360 -// for HTTPS port and TLS-ALPN challenge also. We need to tell the
361 -// TLS app to manage these certs by honoring those port configurations,
362 -// so we either find an existing matching automation policy with an
363 -// ACME issuer, or make a new one and append it.
364 -func (app *App) createAutomationPolicy(ctx caddy.Context) error {
372 +// createAutomationPolicy ensures that automated certificates for this
373 +// app are managed properly. This adds up to two automation policies:
374 +// one for the public names, and one for the internal names. If a catch-all
375 +// automation policy exists, it will be shallow-copied and used as the
376 +// base for the new ones (this is important for preserving behavior the
377 +// user intends to be "defaults").
378 +func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, internalNames []string) error {
379 + // nothing to do if no names to manage certs for
380 + if len(publicNames) == 0 && len(internalNames) == 0 {
381 + return nil
382 + }
383 +
384 + // start by finding a base policy that the user may have defined
385 + // which should, in theory, apply to any policies derived from it;
386 + // typically this would be a "catch-all" policy with no host filter
387 var matchingPolicy *caddytls.AutomationPolicy
366 - var acmeIssuer *caddytls.ACMEIssuer
388 if app.tlsApp.Automation != nil {
368 - // maybe we can find an exisitng one that matches; this is
369 - // useful if the user made a single automation policy to
370 - // set the CA endpoint to a test/staging endpoint (very
371 - // common), but forgot to customize the ports here, while
372 - // setting them in the HTTP app instead (I did this too
373 - // many times)
389 + // if an existing policy matches (specifically, a catch-all policy),
390 + // we should inherit from it, because that is what the user expects;
391 + // this is very common for user setting a default issuer, with a
392 + // custom CA endpoint, for example - whichever one we choose must
393 + // have a host list that is a superset of the policy we make...
394 + // the policy with no host filter is guaranteed to qualify
395 for _, ap := range app.tlsApp.Automation.Policies {
396 if len(ap.Hosts) == 0 {
397 matchingPolicy = ap
@@ -378,51 +399,78 @@ func (app *App) createAutomationPolicy(ctx caddy.Context) error {
399 }
400 }
401 }
381 - if matchingPolicy != nil {
382 - // if it has an ACME issuer, maybe we can just use that
383 - acmeIssuer, _ = matchingPolicy.Issuer.(*caddytls.ACMEIssuer)
384 - }
385 - if acmeIssuer == nil {
386 - acmeIssuer = new(caddytls.ACMEIssuer)
387 - }
388 - if acmeIssuer.Challenges == nil {
389 - acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
390 - }
391 - if acmeIssuer.Challenges.HTTP == nil {
392 - acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
393 - }
394 - if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
395 - // don't overwrite existing explicit config
396 - acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
397 - }
398 - if acmeIssuer.Challenges.TLSALPN == nil {
399 - acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
402 + if matchingPolicy == nil {
403 + matchingPolicy = new(caddytls.AutomationPolicy)
404 }
401 - if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
402 - // don't overwrite existing explicit config
403 - acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
405 +
406 + // addPolicy adds an automation policy that uses issuer for hosts.
407 + addPolicy := func(issuer certmagic.Issuer, hosts []string) error {
408 + // shallow-copy the matching policy; we want to inherit
409 + // from it, not replace it... this takes two lines to
410 + // overrule compiler optimizations
411 + policyCopy := *matchingPolicy
412 + newPolicy := &policyCopy
413 +
414 + // very important to provision it, since we are
415 + // bypassing the JSON-unmarshaling step
416 + if prov, ok := issuer.(caddy.Provisioner); ok {
417 + err := prov.Provision(ctx)
418 + if err != nil {
419 + return err
420 + }
421 + }
422 + newPolicy.Issuer = issuer
423 + newPolicy.Hosts = hosts
424 +
425 + return app.tlsApp.AddAutomationPolicy(newPolicy)
426 }
427
406 - if matchingPolicy == nil {
407 - // if there was no matching policy, we'll have to append our own
408 - err := app.tlsApp.AddAutomationPolicy(&caddytls.AutomationPolicy{
409 - Hosts: app.allCertDomains,
410 - Issuer: acmeIssuer,
411 - })
412 - if err != nil {
428 + if len(publicNames) > 0 {
429 + var acmeIssuer *caddytls.ACMEIssuer
430 + // if it has an ACME issuer, maybe we can just use that
431 + // TODO: we might need a deep copy here, like a Clone() method on ACMEIssuer...
432 + acmeIssuer, _ = matchingPolicy.Issuer.(*caddytls.ACMEIssuer)
433 + if acmeIssuer == nil {
434 + acmeIssuer = new(caddytls.ACMEIssuer)
435 + }
436 + if app.HTTPPort > 0 || app.HTTPSPort > 0 {
437 + if acmeIssuer.Challenges == nil {
438 + acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
439 + }
440 + }
441 + if app.HTTPPort > 0 {
442 + if acmeIssuer.Challenges.HTTP == nil {
443 + acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
444 + }
445 + // don't overwrite existing explicit config
446 + if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
447 + acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
448 + }
449 + }
450 + if app.HTTPSPort > 0 {
451 + if acmeIssuer.Challenges.TLSALPN == nil {
452 + acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
453 + }
454 + // don't overwrite existing explicit config
455 + if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
456 + acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
457 + }
458 + }
459 + if err := addPolicy(acmeIssuer, publicNames); err != nil {
460 return err
461 }
415 - } else {
416 - // if there was an existing matching policy, we need to reprovision
417 - // its issuer (because we just changed its port settings and it has
418 - // to re-build its stored certmagic config template with the new
419 - // values), then re-assign the Issuer pointer on the policy struct
420 - // because our type assertion changed the address
421 - err := acmeIssuer.Provision(ctx)
422 - if err != nil {
462 + }
463 +
464 + if len(internalNames) > 0 {
465 + internalIssuer := new(caddytls.InternalIssuer)
466 + if err := addPolicy(internalIssuer, internalNames); err != nil {
467 return err
468 }
425 - matchingPolicy.Issuer = acmeIssuer
469 + }
470 +
471 + err := app.tlsApp.Validate()
472 + if err != nil {
473 + return err
474 }
475
476 return nil
modules/caddyhttp/caddyhttp.go
+8 -2
@@ -286,8 +286,8 @@ func (app *App) Start() error {
286 }
287
288 // enable TLS if there is a policy and if this is not the HTTP port
289 - if len(srv.TLSConnPolicies) > 0 &&
290 - int(listenAddr.StartPort+portOffset) != app.httpPort() {
289 + useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
290 + if useTLS {
291 // create TLS listener
292 tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
293 ln = tls.NewListener(ln, tlsCfg)
@@ -317,6 +317,12 @@ func (app *App) Start() error {
317 /////////
318 }
319
320 + app.logger.Debug("starting server loop",
321 + zap.String("address", lnAddr),
322 + zap.Bool("http3", srv.ExperimentalHTTP3),
323 + zap.Bool("tls", useTLS),
324 + )
325 +
326 go s.Serve(ln)
327 app.servers = append(app.servers, s)
328 }
modules/caddyhttp/fileserver/command.go
+1 -6
@@ -23,7 +23,6 @@ import (
23
24 "github.com/caddyserver/caddy/v2"
25 "github.com/caddyserver/caddy/v2/caddyconfig"
26 - "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
26 caddycmd "github.com/caddyserver/caddy/v2/cmd"
27 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
28 "github.com/caddyserver/certmagic"
@@ -90,11 +89,7 @@ func cmdFileServer(fs caddycmd.Flags) (int, error) {
89 Routes: caddyhttp.RouteList{route},
90 }
91 if listen == "" {
93 - if certmagic.HostQualifies(domain) {
94 - listen = ":" + strconv.Itoa(certmagic.HTTPSPort)
95 - } else {
96 - listen = ":" + httpcaddyfile.DefaultPort
97 - }
92 + listen = ":" + strconv.Itoa(certmagic.HTTPSPort)
93 }
94 server.Listen = []string{listen}
95
modules/caddyhttp/reverseproxy/command.go
+2 -6
@@ -25,11 +25,9 @@ import (
25
26 "github.com/caddyserver/caddy/v2"
27 "github.com/caddyserver/caddy/v2/caddyconfig"
28 - "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
28 caddycmd "github.com/caddyserver/caddy/v2/cmd"
29 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
30 "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
32 - "github.com/caddyserver/certmagic"
31 )
32
33 func init() {
@@ -67,7 +65,7 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
65 changeHost := fs.Bool("change-host-header")
66
67 if from == "" {
70 - from = "localhost:" + httpcaddyfile.DefaultPort
68 + from = "localhost:443"
69 }
70
71 // URLs need a scheme in order to parse successfully
@@ -129,11 +127,9 @@ func cmdReverseProxy(fs caddycmd.Flags) (int, error) {
127 }
128 }
129
132 - listen := ":80"
130 + listen := ":443"
131 if urlPort := fromURL.Port(); urlPort != "" {
132 listen = ":" + urlPort
135 - } else if certmagic.HostQualifies(urlHost) {
136 - listen = ":443"
133 }
134
135 server := &caddyhttp.Server{
modules/caddypki/ca.go new
+334
@@ -0,0 +1,334 @@
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 caddypki
16 +
17 +import (
18 + "crypto/x509"
19 + "encoding/json"
20 + "fmt"
21 + "path"
22 + "sync"
23 + "time"
24 +
25 + "github.com/caddyserver/caddy/v2"
26 + "github.com/caddyserver/certmagic"
27 + "go.uber.org/zap"
28 +)
29 +
30 +// CA describes a certificate authority, which consists of
31 +// root/signing certificates and various settings pertaining
32 +// to the issuance of certificates and trusting them.
33 +type CA struct {
34 + // The user-facing name of the certificate authority.
35 + Name string `json:"name,omitempty"`
36 +
37 + // The name to put in the CommonName field of the
38 + // root certificate.
39 + RootCommonName string `json:"root_common_name,omitempty"`
40 +
41 + // The name to put in the CommonName field of the
42 + // intermediate certificates.
43 + IntermediateCommonName string `json:"intermediate_common_name,omitempty"`
44 +
45 + // Whether Caddy will attempt to install the CA's root
46 + // into the system trust store, as well as into Java
47 + // and Mozilla Firefox trust stores. Default: true.
48 + InstallTrust *bool `json:"install_trust,omitempty"`
49 +
50 + Root *KeyPair `json:"root,omitempty"`
51 + Intermediate *KeyPair `json:"intermediate,omitempty"`
52 +
53 + // Optionally configure a separate storage module associated with this
54 + // issuer, instead of using Caddy's global/default-configured storage.
55 + // This can be useful if you want to keep your signing keys in a
56 + // separate location from your leaf certificates.
57 + StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
58 +
59 + id string
60 + storage certmagic.Storage
61 + root, inter *x509.Certificate
62 + interKey interface{} // TODO: should we just store these as crypto.Signer?
63 + mu *sync.RWMutex
64 +
65 + rootCertPath string // mainly used for logging purposes if trusting
66 + log *zap.Logger
67 +}
68 +
69 +// Provision sets up the CA.
70 +func (ca *CA) Provision(ctx caddy.Context, id string, log *zap.Logger) error {
71 + ca.mu = new(sync.RWMutex)
72 + ca.log = log.Named("ca." + id)
73 +
74 + if id == "" {
75 + return fmt.Errorf("CA ID is required (use 'local' for the default CA)")
76 + }
77 + ca.mu.Lock()
78 + ca.id = id
79 + ca.mu.Unlock()
80 +
81 + if ca.StorageRaw != nil {
82 + val, err := ctx.LoadModule(ca, "StorageRaw")
83 + if err != nil {
84 + return fmt.Errorf("loading storage module: %v", err)
85 + }
86 + cmStorage, err := val.(caddy.StorageConverter).CertMagicStorage()
87 + if err != nil {
88 + return fmt.Errorf("creating storage configuration: %v", err)
89 + }
90 + ca.storage = cmStorage
91 + }
92 + if ca.storage == nil {
93 + ca.storage = ctx.Storage()
94 + }
95 +
96 + if ca.Name == "" {
97 + ca.Name = defaultCAName
98 + }
99 + if ca.RootCommonName == "" {
100 + ca.RootCommonName = defaultRootCommonName
101 + }
102 + if ca.IntermediateCommonName == "" {
103 + ca.IntermediateCommonName = defaultIntermediateCommonName
104 + }
105 +
106 + // load the certs and key that will be used for signing
107 + var rootCert, interCert *x509.Certificate
108 + var rootKey, interKey interface{}
109 + var err error
110 + if ca.Root != nil {
111 + if ca.Root.Format == "" || ca.Root.Format == "pem_file" {
112 + ca.rootCertPath = ca.Root.Certificate
113 + }
114 + rootCert, rootKey, err = ca.Root.Load()
115 + } else {
116 + ca.rootCertPath = "storage:" + ca.storageKeyRootCert()
117 + rootCert, rootKey, err = ca.loadOrGenRoot()
118 + }
119 + if err != nil {
120 + return err
121 + }
122 + if ca.Intermediate != nil {
123 + interCert, interKey, err = ca.Intermediate.Load()
124 + } else {
125 + interCert, interKey, err = ca.loadOrGenIntermediate(rootCert, rootKey)
126 + }
127 + if err != nil {
128 + return err
129 + }
130 +
131 + ca.mu.Lock()
132 + ca.root, ca.inter, ca.interKey = rootCert, interCert, interKey
133 + ca.mu.Unlock()
134 +
135 + return nil
136 +}
137 +
138 +// ID returns the CA's ID, as given by the user in the config.
139 +func (ca CA) ID() string {
140 + return ca.id
141 +}
142 +
143 +// RootCertificate returns the CA's root certificate (public key).
144 +func (ca CA) RootCertificate() *x509.Certificate {
145 + ca.mu.RLock()
146 + defer ca.mu.RUnlock()
147 + return ca.root
148 +}
149 +
150 +// RootKey returns the CA's root private key. Since the root key is
151 +// not cached in memory long-term, it needs to be loaded from storage,
152 +// which could yield an error.
153 +func (ca CA) RootKey() (interface{}, error) {
154 + _, rootKey, err := ca.loadOrGenRoot()
155 + return rootKey, err
156 +}
157 +
158 +// IntermediateCertificate returns the CA's intermediate
159 +// certificate (public key).
160 +func (ca CA) IntermediateCertificate() *x509.Certificate {
161 + ca.mu.RLock()
162 + defer ca.mu.RUnlock()
163 + return ca.inter
164 +}
165 +
166 +// IntermediateKey returns the CA's intermediate private key.
167 +func (ca CA) IntermediateKey() interface{} {
168 + ca.mu.RLock()
169 + defer ca.mu.RUnlock()
170 + return ca.interKey
171 +}
172 +
173 +func (ca CA) loadOrGenRoot() (rootCert *x509.Certificate, rootKey interface{}, err error) {
174 + rootCertPEM, err := ca.storage.Load(ca.storageKeyRootCert())
175 + if err != nil {
176 + if _, ok := err.(certmagic.ErrNotExist); !ok {
177 + return nil, nil, fmt.Errorf("loading root cert: %v", err)
178 + }
179 +
180 + // TODO: should we require that all or none of the assets are required before overwriting anything?
181 + rootCert, rootKey, err = ca.genRoot()
182 + if err != nil {
183 + return nil, nil, fmt.Errorf("generating root: %v", err)
184 + }
185 + }
186 +
187 + if rootCert == nil {
188 + rootCert, err = pemDecodeSingleCert(rootCertPEM)
189 + if err != nil {
190 + return nil, nil, fmt.Errorf("parsing root certificate PEM: %v", err)
191 + }
192 + }
193 + if rootKey == nil {
194 + rootKeyPEM, err := ca.storage.Load(ca.storageKeyRootKey())
195 + if err != nil {
196 + return nil, nil, fmt.Errorf("loading root key: %v", err)
197 + }
198 + rootKey, err = pemDecodePrivateKey(rootKeyPEM)
199 + if err != nil {
200 + return nil, nil, fmt.Errorf("decoding root key: %v", err)
201 + }
202 + }
203 +
204 + return rootCert, rootKey, nil
205 +}
206 +
207 +func (ca CA) genRoot() (rootCert *x509.Certificate, rootKey interface{}, err error) {
208 + repl := ca.newReplacer()
209 +
210 + rootCert, rootKey, err = generateRoot(repl.ReplaceAll(ca.RootCommonName, ""))
211 + if err != nil {
212 + return nil, nil, fmt.Errorf("generating CA root: %v", err)
213 + }
214 + rootCertPEM, err := pemEncodeCert(rootCert.Raw)
215 + if err != nil {
216 + return nil, nil, fmt.Errorf("encoding root certificate: %v", err)
217 + }
218 + err = ca.storage.Store(ca.storageKeyRootCert(), rootCertPEM)
219 + if err != nil {
220 + return nil, nil, fmt.Errorf("saving root certificate: %v", err)
221 + }
222 + rootKeyPEM, err := pemEncodePrivateKey(rootKey)
223 + if err != nil {
224 + return nil, nil, fmt.Errorf("encoding root key: %v", err)
225 + }
226 + err = ca.storage.Store(ca.storageKeyRootKey(), rootKeyPEM)
227 + if err != nil {
228 + return nil, nil, fmt.Errorf("saving root key: %v", err)
229 + }
230 +
231 + return rootCert, rootKey, nil
232 +}
233 +
234 +func (ca CA) loadOrGenIntermediate(rootCert *x509.Certificate, rootKey interface{}) (interCert *x509.Certificate, interKey interface{}, err error) {
235 + interCertPEM, err := ca.storage.Load(ca.storageKeyIntermediateCert())
236 + if err != nil {
237 + if _, ok := err.(certmagic.ErrNotExist); !ok {
238 + return nil, nil, fmt.Errorf("loading intermediate cert: %v", err)
239 + }
240 +
241 + // TODO: should we require that all or none of the assets are required before overwriting anything?
242 + interCert, interKey, err = ca.genIntermediate(rootCert, rootKey)
243 + if err != nil {
244 + return nil, nil, fmt.Errorf("generating new intermediate cert: %v", err)
245 + }
246 + }
247 +
248 + if interCert == nil {
249 + interCert, err = pemDecodeSingleCert(interCertPEM)
250 + if err != nil {
251 + return nil, nil, fmt.Errorf("decoding intermediate certificate PEM: %v", err)
252 + }
253 + }
254 +
255 + if interKey == nil {
256 + interKeyPEM, err := ca.storage.Load(ca.storageKeyIntermediateKey())
257 + if err != nil {
258 + return nil, nil, fmt.Errorf("loading intermediate key: %v", err)
259 + }
260 + interKey, err = pemDecodePrivateKey(interKeyPEM)
261 + if err != nil {
262 + return nil, nil, fmt.Errorf("decoding intermediate key: %v", err)
263 + }
264 + }
265 +
266 + return interCert, interKey, nil
267 +}
268 +
269 +func (ca CA) genIntermediate(rootCert *x509.Certificate, rootKey interface{}) (interCert *x509.Certificate, interKey interface{}, err error) {
270 + repl := ca.newReplacer()
271 +
272 + rootKeyPEM, err := ca.storage.Load(ca.storageKeyRootKey())
273 + if err != nil {
274 + return nil, nil, fmt.Errorf("loading root key to sign new intermediate: %v", err)
275 + }
276 + rootKey, err = pemDecodePrivateKey(rootKeyPEM)
277 + if err != nil {
278 + return nil, nil, fmt.Errorf("decoding root key: %v", err)
279 + }
280 + interCert, interKey, err = generateIntermediate(repl.ReplaceAll(ca.IntermediateCommonName, ""), rootCert, rootKey)
281 + if err != nil {
282 + return nil, nil, fmt.Errorf("generating CA intermediate: %v", err)
283 + }
284 + interCertPEM, err := pemEncodeCert(interCert.Raw)
285 + if err != nil {
286 + return nil, nil, fmt.Errorf("encoding intermediate certificate: %v", err)
287 + }
288 + err = ca.storage.Store(ca.storageKeyIntermediateCert(), interCertPEM)
289 + if err != nil {
290 + return nil, nil, fmt.Errorf("saving intermediate certificate: %v", err)
291 + }
292 + interKeyPEM, err := pemEncodePrivateKey(interKey)
293 + if err != nil {
294 + return nil, nil, fmt.Errorf("encoding intermediate key: %v", err)
295 + }
296 + err = ca.storage.Store(ca.storageKeyIntermediateKey(), interKeyPEM)
297 + if err != nil {
298 + return nil, nil, fmt.Errorf("saving intermediate key: %v", err)
299 + }
300 +
301 + return interCert, interKey, nil
302 +}
303 +
304 +func (ca CA) storageKeyCAPrefix() string {
305 + return path.Join("pki", "authorities", certmagic.StorageKeys.Safe(ca.id))
306 +}
307 +func (ca CA) storageKeyRootCert() string {
308 + return path.Join(ca.storageKeyCAPrefix(), "root.crt")
309 +}
310 +func (ca CA) storageKeyRootKey() string {
311 + return path.Join(ca.storageKeyCAPrefix(), "root.key")
312 +}
313 +func (ca CA) storageKeyIntermediateCert() string {
314 + return path.Join(ca.storageKeyCAPrefix(), "intermediate.crt")
315 +}
316 +func (ca CA) storageKeyIntermediateKey() string {
317 + return path.Join(ca.storageKeyCAPrefix(), "intermediate.key")
318 +}
319 +
320 +func (ca CA) newReplacer() *caddy.Replacer {
321 + repl := caddy.NewReplacer()
322 + repl.Set("pki.ca.name", ca.Name)
323 + return repl
324 +}
325 +
326 +const (
327 + defaultCAID = "local"
328 + defaultCAName = "Caddy Local Authority"
329 + defaultRootCommonName = "{pki.ca.name} - {time.now.year} ECC Root"
330 + defaultIntermediateCommonName = "{pki.ca.name} - ECC Intermediate"
331 +
332 + defaultRootLifetime = 24 * time.Hour * 30 * 12 * 10
333 + defaultIntermediateLifetime = 24 * time.Hour * 7
334 +)
modules/caddypki/certificates.go new
+50
@@ -0,0 +1,50 @@
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 caddypki
16 +
17 +import (
18 + "crypto/x509"
19 + "time"
20 +
21 + "github.com/smallstep/cli/crypto/x509util"
22 +)
23 +
24 +func generateRoot(commonName string) (rootCrt *x509.Certificate, privateKey interface{}, err error) {
25 + rootProfile, err := x509util.NewRootProfile(commonName)
26 + if err != nil {
27 + return
28 + }
29 + rootProfile.Subject().NotAfter = time.Now().Add(defaultRootLifetime) // TODO: make configurable
30 + return newCert(rootProfile)
31 +}
32 +
33 +func generateIntermediate(commonName string, rootCrt *x509.Certificate, rootKey interface{}) (cert *x509.Certificate, privateKey interface{}, err error) {
34 + interProfile, err := x509util.NewIntermediateProfile(commonName, rootCrt, rootKey)
35 + if err != nil {
36 + return
37 + }
38 + interProfile.Subject().NotAfter = time.Now().Add(defaultIntermediateLifetime) // TODO: make configurable
39 + return newCert(interProfile)
40 +}
41 +
42 +func newCert(profile x509util.Profile) (cert *x509.Certificate, privateKey interface{}, err error) {
43 + certBytes, err := profile.CreateCertificate()
44 + if err != nil {
45 + return
46 + }
47 + privateKey = profile.SubjectPrivateKey()
48 + cert, err = x509.ParseCertificate(certBytes)
49 + return
50 +}
modules/caddypki/command.go new
+89
@@ -0,0 +1,89 @@
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 caddypki
16 +
17 +import (
18 + "flag"
19 + "fmt"
20 + "os"
21 + "path/filepath"
22 +
23 + "github.com/caddyserver/caddy/v2"
24 + caddycmd "github.com/caddyserver/caddy/v2/cmd"
25 + "github.com/smallstep/truststore"
26 +)
27 +
28 +func init() {
29 + caddycmd.RegisterCommand(caddycmd.Command{
30 + Name: "untrust",
31 + Func: cmdUntrust,
32 + Usage: "[--ca <id> | --cert <path>]",
33 + Short: "Untrusts a locally-trusted CA certificate",
34 + Long: `
35 +Untrusts a root certificate from the local trust store(s). Intended
36 +for development environments only.
37 +
38 +This command uninstalls trust; it does not necessarily delete the
39 +root certificate from trust stores entirely. Thus, repeatedly
40 +trusting and untrusting new certificates can fill up trust databases.
41 +
42 +This command does not delete or modify certificate files.
43 +
44 +Specify which certificate to untrust either by the ID of its CA with
45 +the --ca flag, or the direct path to the certificate file with the
46 +--cert flag. If the --ca flag is used, only the default storage paths
47 +are assumed (i.e. using --ca flag with custom storage backends or file
48 +paths will not work).
49 +
50 +If no flags are specified, --ca=local is assumed.`,
51 + Flags: func() *flag.FlagSet {
52 + fs := flag.NewFlagSet("untrust", flag.ExitOnError)
53 + fs.String("ca", "", "The ID of the CA to untrust")
54 + fs.String("cert", "", "The path to the CA certificate to untrust")
55 + return fs
56 + }(),
57 + })
58 +}
59 +
60 +func cmdUntrust(fs caddycmd.Flags) (int, error) {
61 + ca := fs.String("ca")
62 + cert := fs.String("cert")
63 +
64 + if ca != "" && cert != "" {
65 + return caddy.ExitCodeFailedStartup, fmt.Errorf("conflicting command line arguments")
66 + }
67 + if ca == "" && cert == "" {
68 + ca = defaultCAID
69 + }
70 + if ca != "" {
71 + cert = filepath.Join(caddy.AppDataDir(), "pki", "authorities", ca, "root.crt")
72 + }
73 +
74 + // sanity check, make sure cert file exists first
75 + _, err := os.Stat(cert)
76 + if err != nil {
77 + return caddy.ExitCodeFailedStartup, fmt.Errorf("accessing certificate file: %v", err)
78 + }
79 +
80 + err = truststore.UninstallFile(cert,
81 + truststore.WithDebug(),
82 + truststore.WithFirefox(),
83 + truststore.WithJava())
84 + if err != nil {
85 + return caddy.ExitCodeFailedStartup, err
86 + }
87 +
88 + return caddy.ExitCodeSuccess, nil
89 +}
modules/caddypki/crypto.go new
+155
@@ -0,0 +1,155 @@
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 caddypki
16 +
17 +import (
18 + "bytes"
19 + "crypto"
20 + "crypto/ecdsa"
21 + "crypto/ed25519"
22 + "crypto/rsa"
23 + "crypto/x509"
24 + "encoding/pem"
25 + "fmt"
26 + "io/ioutil"
27 + "strings"
28 +)
29 +
30 +func pemDecodeSingleCert(pemDER []byte) (*x509.Certificate, error) {
31 + pemBlock, remaining := pem.Decode(pemDER)
32 + if pemBlock == nil {
33 + return nil, fmt.Errorf("no PEM block found")
34 + }
35 + if len(remaining) > 0 {
36 + return nil, fmt.Errorf("input contained more than a single PEM block")
37 + }
38 + if pemBlock.Type != "CERTIFICATE" {
39 + return nil, fmt.Errorf("expected PEM block type to be CERTIFICATE, but got '%s'", pemBlock.Type)
40 + }
41 + return x509.ParseCertificate(pemBlock.Bytes)
42 +}
43 +
44 +func pemEncodeCert(der []byte) ([]byte, error) {
45 + return pemEncode("CERTIFICATE", der)
46 +}
47 +
48 +// pemEncodePrivateKey marshals a EC or RSA private key into a PEM-encoded array of bytes.
49 +// TODO: this is the same thing as in certmagic. Should we reuse that code somehow? It's unexported.
50 +func pemEncodePrivateKey(key crypto.PrivateKey) ([]byte, error) {
51 + var pemType string
52 + var keyBytes []byte
53 + switch key := key.(type) {
54 + case *ecdsa.PrivateKey:
55 + var err error
56 + pemType = "EC"
57 + keyBytes, err = x509.MarshalECPrivateKey(key)
58 + if err != nil {
59 + return nil, err
60 + }
61 + case *rsa.PrivateKey:
62 + pemType = "RSA"
63 + keyBytes = x509.MarshalPKCS1PrivateKey(key)
64 + case *ed25519.PrivateKey:
65 + var err error
66 + pemType = "ED25519"
67 + keyBytes, err = x509.MarshalPKCS8PrivateKey(key)
68 + if err != nil {
69 + return nil, err
70 + }
71 + default:
72 + return nil, fmt.Errorf("unsupported key type: %T", key)
73 + }
74 + return pemEncode(pemType+" PRIVATE KEY", keyBytes)
75 +}
76 +
77 +// pemDecodePrivateKey loads a PEM-encoded ECC/RSA private key from an array of bytes.
78 +// Borrowed from Go standard library, to handle various private key and PEM block types.
79 +// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L291-L308
80 +// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238)
81 +// TODO: this is the same thing as in certmagic. Should we reuse that code somehow? It's unexported.
82 +func pemDecodePrivateKey(keyPEMBytes []byte) (crypto.PrivateKey, error) {
83 + keyBlockDER, _ := pem.Decode(keyPEMBytes)
84 +
85 + if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
86 + return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
87 + }
88 +
89 + if key, err := x509.ParsePKCS1PrivateKey(keyBlockDER.Bytes); err == nil {
90 + return key, nil
91 + }
92 +
93 + if key, err := x509.ParsePKCS8PrivateKey(keyBlockDER.Bytes); err == nil {
94 + switch key := key.(type) {
95 + case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey:
96 + return key, nil
97 + default:
98 + return nil, fmt.Errorf("found unknown private key type in PKCS#8 wrapping: %T", key)
99 + }
100 + }
101 +
102 + if key, err := x509.ParseECPrivateKey(keyBlockDER.Bytes); err == nil {
103 + return key, nil
104 + }
105 +
106 + return nil, fmt.Errorf("unknown private key type")
107 +}
108 +
109 +func pemEncode(blockType string, b []byte) ([]byte, error) {
110 + var buf bytes.Buffer
111 + err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: b})
112 + return buf.Bytes(), err
113 +}
114 +
115 +func trusted(cert *x509.Certificate) bool {
116 + chains, err := cert.Verify(x509.VerifyOptions{})
117 + return len(chains) > 0 && err == nil
118 +}
119 +
120 +// KeyPair represents a public-private key pair, where the
121 +// public key is also called a certificate.
122 +type KeyPair struct {
123 + Certificate string `json:"certificate,omitempty"`
124 + PrivateKey string `json:"private_key,omitempty"`
125 + Format string `json:"format,omitempty"`
126 +}
127 +
128 +// Load loads the certificate and key.
129 +func (kp KeyPair) Load() (*x509.Certificate, interface{}, error) {
130 + switch kp.Format {
131 + case "", "pem_file":
132 + certData, err := ioutil.ReadFile(kp.Certificate)
133 + if err != nil {
134 + return nil, nil, err
135 + }
136 + keyData, err := ioutil.ReadFile(kp.PrivateKey)
137 + if err != nil {
138 + return nil, nil, err
139 + }
140 +
141 + cert, err := pemDecodeSingleCert(certData)
142 + if err != nil {
143 + return nil, nil, err
144 + }
145 + key, err := pemDecodePrivateKey(keyData)
146 + if err != nil {
147 + return nil, nil, err
148 + }
149 +
150 + return cert, key, nil
151 +
152 + default:
153 + return nil, nil, fmt.Errorf("unsupported format: %s", kp.Format)
154 + }
155 +}
modules/caddypki/maintain.go new
+99
@@ -0,0 +1,99 @@
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 caddypki
16 +
17 +import (
18 + "crypto/x509"
19 + "fmt"
20 + "time"
21 +
22 + "go.uber.org/zap"
23 +)
24 +
25 +func (p *PKI) maintenance() {
26 + ticker := time.NewTicker(10 * time.Minute) // TODO: make configurable
27 + defer ticker.Stop()
28 +
29 + for {
30 + select {
31 + case <-ticker.C:
32 + p.renewCerts()
33 + case <-p.ctx.Done():
34 + return
35 + }
36 + }
37 +}
38 +
39 +func (p *PKI) renewCerts() {
40 + for _, ca := range p.CAs {
41 + err := p.renewCertsForCA(ca)
42 + if err != nil {
43 + p.log.Error("renewing intermediate certificates",
44 + zap.Error(err),
45 + zap.String("ca", ca.id))
46 + }
47 + }
48 +}
49 +
50 +func (p *PKI) renewCertsForCA(ca *CA) error {
51 + ca.mu.Lock()
52 + defer ca.mu.Unlock()
53 +
54 + log := p.log.With(zap.String("ca", ca.id))
55 +
56 + // only maintain the root if it's not manually provided in the config
57 + if ca.Root == nil {
58 + if needsRenewal(ca.root) {
59 + // TODO: implement root renewal (use same key)
60 + log.Warn("root certificate expiring soon (FIXME: ROOT RENEWAL NOT YET IMPLEMENTED)",
61 + zap.Duration("time_remaining", time.Until(ca.inter.NotAfter)),
62 + )
63 + }
64 + }
65 +
66 + // only maintain the intermediate if it's not manually provided in the config
67 + if ca.Intermediate == nil {
68 + if needsRenewal(ca.inter) {
69 + log.Info("intermediate expires soon; renewing",
70 + zap.Duration("time_remaining", time.Until(ca.inter.NotAfter)),
71 + )
72 +
73 + rootCert, rootKey, err := ca.loadOrGenRoot()
74 + if err != nil {
75 + return fmt.Errorf("loading root key: %v", err)
76 + }
77 + interCert, interKey, err := ca.genIntermediate(rootCert, rootKey)
78 + if err != nil {
79 + return fmt.Errorf("generating new certificate: %v", err)
80 + }
81 + ca.inter, ca.interKey = interCert, interKey
82 +
83 + log.Info("renewed intermediate",
84 + zap.Time("new_expiration", ca.inter.NotAfter),
85 + )
86 + }
87 + }
88 +
89 + return nil
90 +}
91 +
92 +func needsRenewal(cert *x509.Certificate) bool {
93 + lifetime := cert.NotAfter.Sub(cert.NotBefore)
94 + renewalWindow := time.Duration(float64(lifetime) * renewalWindowRatio)
95 + renewalWindowStart := cert.NotAfter.Add(-renewalWindow)
96 + return time.Now().After(renewalWindowStart)
97 +}
98 +
99 +const renewalWindowRatio = 0.2 // TODO: make configurable
modules/caddypki/pki.go new
+117
@@ -0,0 +1,117 @@
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 caddypki
16 +
17 +import (
18 + "fmt"
19 +
20 + "github.com/caddyserver/caddy/v2"
21 + "github.com/smallstep/truststore"
22 + "go.uber.org/zap"
23 +)
24 +
25 +func init() {
26 + caddy.RegisterModule(PKI{})
27 +}
28 +
29 +// PKI provides Public Key Infrastructure facilities for Caddy.
30 +type PKI struct {
31 + // The CAs to manage. Each CA is keyed by an ID that is used
32 + // to uniquely identify it from other CAs. The default CA ID
33 + // is "local".
34 + CAs map[string]*CA `json:"certificate_authorities,omitempty"`
35 +
36 + ctx caddy.Context
37 + log *zap.Logger
38 +}
39 +
40 +// CaddyModule returns the Caddy module information.
41 +func (PKI) CaddyModule() caddy.ModuleInfo {
42 + return caddy.ModuleInfo{
43 + ID: "pki",
44 + New: func() caddy.Module { return new(PKI) },
45 + }
46 +}
47 +
48 +// Provision sets up the configuration for the PKI app.
49 +func (p *PKI) Provision(ctx caddy.Context) error {
50 + p.ctx = ctx
51 + p.log = ctx.Logger(p)
52 +
53 + // if this app is initialized at all, ensure there's
54 + // at least a default CA that can be used
55 + if len(p.CAs) == 0 {
56 + p.CAs = map[string]*CA{defaultCAID: new(CA)}
57 + }
58 +
59 + for caID, ca := range p.CAs {
60 + err := ca.Provision(ctx, caID, p.log)
61 + if err != nil {
62 + return fmt.Errorf("provisioning CA '%s': %v", caID, err)
63 + }
64 + }
65 +
66 + return nil
67 +}
68 +
69 +// Start starts the PKI app.
70 +func (p *PKI) Start() error {
71 + // install roots to trust store, if not disabled
72 + for _, ca := range p.CAs {
73 + if ca.InstallTrust != nil && !*ca.InstallTrust {
74 + ca.log.Warn("root certificate trust store installation disabled; clients will show warnings without intervention",
75 + zap.String("path", ca.rootCertPath))
76 + continue
77 + }
78 +
79 + // avoid password prompt if already trusted
80 + if trusted(ca.root) {
81 + ca.log.Info("root certificate is already trusted by system",
82 + zap.String("path", ca.rootCertPath))
83 + continue
84 + }
85 +
86 + ca.log.Warn("trusting root certificate (you might be prompted for password)",
87 + zap.String("path", ca.rootCertPath))
88 +
89 + err := truststore.Install(ca.root,
90 + truststore.WithDebug(),
91 + truststore.WithFirefox(),
92 + truststore.WithJava(),
93 + )
94 + if err != nil {
95 + return fmt.Errorf("adding root certificate to trust store: %v", err)
96 + }
97 + }
98 +
99 + // see if root/intermediates need renewal...
100 + p.renewCerts()
101 +
102 + // ...and keep them renewed
103 + go p.maintenance()
104 +
105 + return nil
106 +}
107 +
108 +// Stop stops the PKI app.
109 +func (p *PKI) Stop() error {
110 + return nil
111 +}
112 +
113 +// Interface guards
114 +var (
115 + _ caddy.Provisioner = (*PKI)(nil)
116 + _ caddy.App = (*PKI)(nil)
117 +)
modules/caddytls/acmeissuer.go
+3 -2
@@ -145,7 +145,7 @@ func (m *ACMEIssuer) SetConfig(cfg *certmagic.Config) {
145 }
146
147 // PreCheck implements the certmagic.PreChecker interface.
148 -func (m *ACMEIssuer) PreCheck(names []string, interactive bool) (skip bool, err error) {
148 +func (m *ACMEIssuer) PreCheck(names []string, interactive bool) error {
149 return certmagic.NewACMEManager(m.magic, m.template).PreCheck(names, interactive)
150 }
151
@@ -200,8 +200,9 @@ type DNSProviderMaker interface {
200
201 // Interface guards
202 var (
203 + _ certmagic.PreChecker = (*ACMEIssuer)(nil)
204 _ certmagic.Issuer = (*ACMEIssuer)(nil)
205 _ certmagic.Revoker = (*ACMEIssuer)(nil)
205 - _ certmagic.PreChecker = (*ACMEIssuer)(nil)
206 + _ caddy.Provisioner = (*ACMEIssuer)(nil)
207 _ ConfigSetter = (*ACMEIssuer)(nil)
208 )
modules/caddytls/internalissuer.go new
+199
@@ -0,0 +1,199 @@
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 + "bytes"
19 + "context"
20 + "crypto"
21 + "crypto/x509"
22 + "encoding/pem"
23 + "fmt"
24 + "time"
25 +
26 + "github.com/caddyserver/caddy/v2"
27 + "github.com/caddyserver/caddy/v2/modules/caddypki"
28 + "github.com/caddyserver/certmagic"
29 + "github.com/smallstep/certificates/authority"
30 + "github.com/smallstep/certificates/authority/provisioner"
31 + "github.com/smallstep/cli/crypto/x509util"
32 +)
33 +
34 +func init() {
35 + caddy.RegisterModule(InternalIssuer{})
36 +}
37 +
38 +// InternalIssuer is a certificate issuer that generates
39 +// certificates internally using a locally-configured
40 +// CA which can be customized using the `pki` app.
41 +type InternalIssuer struct {
42 + // The ID of the CA to use for signing. The default
43 + // CA ID is "local". The CA can be configured with the
44 + // `pki` app.
45 + CA string `json:"ca,omitempty"`
46 +
47 + // The validity period of certificates.
48 + Lifetime caddy.Duration `json:"lifetime,omitempty"`
49 +
50 + // If true, the root will be the issuer instead of
51 + // the intermediate. This is NOT recommended and should
52 + // only be used when devices/clients do not properly
53 + // validate certificate chains.
54 + SignWithRoot bool `json:"sign_with_root,omitempty"`
55 +
56 + ca *caddypki.CA
57 +}
58 +
59 +// CaddyModule returns the Caddy module information.
60 +func (InternalIssuer) CaddyModule() caddy.ModuleInfo {
61 + return caddy.ModuleInfo{
62 + ID: "tls.issuance.internal",
63 + New: func() caddy.Module { return new(InternalIssuer) },
64 + }
65 +}
66 +
67 +// Provision sets up the issuer.
68 +func (li *InternalIssuer) Provision(ctx caddy.Context) error {
69 + // get a reference to the configured CA
70 + appModule, err := ctx.App("pki")
71 + if err != nil {
72 + return err
73 + }
74 + pkiApp := appModule.(*caddypki.PKI)
75 + if li.CA == "" {
76 + li.CA = defaultInternalCAName
77 + }
78 + ca, ok := pkiApp.CAs[li.CA]
79 + if !ok {
80 + return fmt.Errorf("no certificate authority configured with id: %s", li.CA)
81 + }
82 + li.ca = ca
83 +
84 + // set any other default values
85 + if li.Lifetime == 0 {
86 + li.Lifetime = caddy.Duration(defaultInternalCertLifetime)
87 + }
88 +
89 + return nil
90 +}
91 +
92 +// IssuerKey returns the unique issuer key for the
93 +// confgured CA endpoint.
94 +func (li InternalIssuer) IssuerKey() string {
95 + return li.ca.ID()
96 +}
97 +
98 +// Issue issues a certificate to satisfy the CSR.
99 +func (li InternalIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
100 + // prepare the signing authority
101 + // TODO: eliminate placeholders / needless values
102 + cfg := &authority.Config{
103 + Address: "placeholder_Address:1",
104 + Root: []string{"placeholder_Root"},
105 + IntermediateCert: "placeholder_IntermediateCert",
106 + IntermediateKey: "placeholder_IntermediateKey",
107 + DNSNames: []string{"placeholder_DNSNames"},
108 + AuthorityConfig: &authority.AuthConfig{
109 + Provisioners: provisioner.List{},
110 + },
111 + }
112 +
113 + // get the root certificate and the issuer cert+key
114 + rootCert := li.ca.RootCertificate()
115 + var issuerCert *x509.Certificate
116 + var issuerKey interface{}
117 + if li.SignWithRoot {
118 + issuerCert = rootCert
119 + var err error
120 + issuerKey, err = li.ca.RootKey()
121 + if err != nil {
122 + return nil, fmt.Errorf("loading signing key: %v", err)
123 + }
124 + } else {
125 + issuerCert = li.ca.IntermediateCertificate()
126 + issuerKey = li.ca.IntermediateKey()
127 + }
128 +
129 + auth, err := authority.New(cfg,
130 + authority.WithX509Signer(issuerCert, issuerKey.(crypto.Signer)),
131 + authority.WithX509RootCerts(rootCert),
132 + )
133 + if err != nil {
134 + return nil, fmt.Errorf("initializing certificate authority: %v", err)
135 + }
136 +
137 + // ensure issued certificate does not expire later than its issuer
138 + lifetime := time.Duration(li.Lifetime)
139 + if time.Now().Add(lifetime).After(issuerCert.NotAfter) {
140 + // TODO: log this
141 + lifetime = issuerCert.NotAfter.Sub(time.Now())
142 + }
143 +
144 + certChain, err := auth.Sign(csr, provisioner.Options{},
145 + profileDefaultDuration(li.Lifetime),
146 + )
147 + if err != nil {
148 + return nil, err
149 + }
150 +
151 + var buf bytes.Buffer
152 + for _, cert := range certChain {
153 + err := pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
154 + if err != nil {
155 + return nil, err
156 + }
157 + }
158 +
159 + return &certmagic.IssuedCertificate{
160 + Certificate: buf.Bytes(),
161 + }, nil
162 +}
163 +
164 +// TODO: borrowing from https://github.com/smallstep/certificates/blob/806abb6232a5691198b891d76b9898ea7f269da0/authority/provisioner/sign_options.go#L191-L211
165 +// as per https://github.com/smallstep/certificates/issues/198.
166 +// profileDefaultDuration is a wrapper against x509util.WithOption to conform
167 +// the SignOption interface.
168 +type profileDefaultDuration time.Duration
169 +
170 +// TODO: is there a better way to set cert lifetimes than copying from the smallstep libs?
171 +func (d profileDefaultDuration) Option(so provisioner.Options) x509util.WithOption {
172 + var backdate time.Duration
173 + notBefore := so.NotBefore.Time()
174 + if notBefore.IsZero() {
175 + notBefore = time.Now().Truncate(time.Second)
176 + backdate = -1 * so.Backdate
177 + }
178 + notAfter := so.NotAfter.RelativeTime(notBefore)
179 + return func(p x509util.Profile) error {
180 + fn := x509util.WithNotBeforeAfterDuration(notBefore, notAfter, time.Duration(d))
181 + if err := fn(p); err != nil {
182 + return err
183 + }
184 + crt := p.Subject()
185 + crt.NotBefore = crt.NotBefore.Add(backdate)
186 + return nil
187 + }
188 +}
189 +
190 +const (
191 + defaultInternalCAName = "local"
192 + defaultInternalCertLifetime = 12 * time.Hour
193 +)
194 +
195 +// Interface guards
196 +var (
197 + _ caddy.Provisioner = (*InternalIssuer)(nil)
198 + _ certmagic.Issuer = (*InternalIssuer)(nil)
199 +)
modules/caddytls/tls.go
+46 -6
@@ -175,6 +175,26 @@ func (t *TLS) Provision(ctx caddy.Context) error {
175 return nil
176 }
177
178 +// Validate validates t's configuration.
179 +func (t *TLS) Validate() error {
180 + if t.Automation != nil {
181 + // ensure that host aren't repeated; since only the first
182 + // automation policy is used, repeating a host in the lists
183 + // isn't useful and is probably a mistake
184 + // TODO: test this
185 + hostSet := make(map[string]int)
186 + for i, ap := range t.Automation.Policies {
187 + for _, h := range ap.Hosts {
188 + if first, ok := hostSet[h]; ok {
189 + return fmt.Errorf("automation policy %d: cannot apply more than one automation policy to host: %s (first match in policy %d)", i, h, first)
190 + }
191 + hostSet[h] = i
192 + }
193 + }
194 + }
195 + return nil
196 +}
197 +
198 // Start activates the TLS module.
199 func (t *TLS) Start() error {
200 // now that we are running, and all manual certificates have
@@ -266,7 +286,10 @@ func (t *TLS) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
286 }
287
288 // AddAutomationPolicy provisions and adds ap to the list of the app's
269 -// automation policies.
289 +// automation policies. If an existing automation policy exists that has
290 +// fewer hosts in its list than ap does, ap will be inserted before that
291 +// other policy (this helps ensure that ap will be prioritized/chosen
292 +// over, say, a catch-all policy).
293 func (t *TLS) AddAutomationPolicy(ap *AutomationPolicy) error {
294 if t.Automation == nil {
295 t.Automation = new(AutomationConfig)
@@ -275,6 +298,16 @@ func (t *TLS) AddAutomationPolicy(ap *AutomationPolicy) error {
298 if err != nil {
299 return err
300 }
301 + for i, other := range t.Automation.Policies {
302 + // if a catch-all policy (or really, any policy with
303 + // fewer names) exists, prioritize this new policy
304 + if len(other.Hosts) < len(ap.Hosts) {
305 + t.Automation.Policies = append(t.Automation.Policies[:i],
306 + append([]*AutomationPolicy{ap}, t.Automation.Policies[i+1:]...)...)
307 + return nil
308 + }
309 + }
310 + // otherwise just append the new one
311 t.Automation.Policies = append(t.Automation.Policies, ap)
312 return nil
313 }
@@ -444,6 +477,7 @@ type AutomationPolicy struct {
477 // obtaining or renewing certificates. This is often
478 // not desirable, especially when serving sites out
479 // of your control. Default: false
480 + // TODO: is this really necessary per-policy? why not a global setting...
481 ManageSync bool `json:"manage_sync,omitempty"`
482
483 Issuer certmagic.Issuer `json:"-"`
@@ -510,8 +544,7 @@ func (ap *AutomationPolicy) provision(tlsApp *TLS) error {
544 OnDemand: ond,
545 Storage: storage,
546 }
513 - cfg := certmagic.New(tlsApp.certCache, template)
514 - ap.magic = cfg
547 + ap.magic = certmagic.New(tlsApp.certCache, template)
548
549 if ap.IssuerRaw != nil {
550 val, err := tlsApp.ctx.LoadModule(ap, "IssuerRaw")
@@ -527,12 +560,12 @@ func (ap *AutomationPolicy) provision(tlsApp *TLS) error {
560 // ACME challenges -- it's an annoying, inelegant circular
561 // dependency that I don't know how to resolve nicely!)
562 if configger, ok := ap.Issuer.(ConfigSetter); ok {
530 - configger.SetConfig(cfg)
563 + configger.SetConfig(ap.magic)
564 }
565
533 - cfg.Issuer = ap.Issuer
566 + ap.magic.Issuer = ap.Issuer
567 if rev, ok := ap.Issuer.(certmagic.Revoker); ok {
535 - cfg.Revoker = rev
568 + ap.magic.Revoker = rev
569 }
570
571 return nil
@@ -789,3 +822,10 @@ func (t *TLS) moveCertificates() error {
822
823 return nil
824 }
825 +
826 +// Interface guards
827 +var (
828 + _ caddy.Provisioner = (*TLS)(nil)
829 + _ caddy.Validator = (*TLS)(nil)
830 + _ caddy.App = (*TLS)(nil)
831 +)
modules/standard/import.go
+1
@@ -6,6 +6,7 @@ import (
6 _ "github.com/caddyserver/caddy/v2/caddyconfig/json5"
7 _ "github.com/caddyserver/caddy/v2/caddyconfig/jsonc"
8 _ "github.com/caddyserver/caddy/v2/modules/caddyhttp/standard"
9 + _ "github.com/caddyserver/caddy/v2/modules/caddypki"
10 _ "github.com/caddyserver/caddy/v2/modules/caddytls"
11 _ "github.com/caddyserver/caddy/v2/modules/caddytls/distributedstek"
12 _ "github.com/caddyserver/caddy/v2/modules/caddytls/standardstek"
replacer.go
+3
@@ -19,6 +19,7 @@ import (
19 "os"
20 "path/filepath"
21 "runtime"
22 + "strconv"
23 "strings"
24 "time"
25 )
@@ -236,6 +237,8 @@ func globalDefaultReplacements(key string) (string, bool) {
237 return runtime.GOARCH, true
238 case "time.now.common_log":
239 return nowFunc().Format("02/Jan/2006:15:04:05 -0700"), true
240 + case "time.now.year":
241 + return strconv.Itoa(nowFunc().Year()), true
242 }
243
244 return "", false