@marcel91200 / caddy Mirror · read-only / commits / 3579815a
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 12, 10:45 UTC

caddytls: Caddyfile support for TLS conn and cert sel policies (#6462)

* Caddyfile support for TLS custom certificate selection policy * Caddyfile support for TLS connection policy

vnxme committed Jul 24, 2024 at 20:01 UTC 3579815a6c099bfbcff0143f7a251a92b4fa4198
2 files changed +251 -1
modules/caddytls/certselection.go
+78
@@ -22,6 +22,8 @@ import (
22 "math/big"
23
24 "github.com/caddyserver/certmagic"
25 +
26 + "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
27 )
28
29 // CustomCertSelectionPolicy represents a policy for selecting the certificate
@@ -122,6 +124,79 @@ nextChoice:
124 return certmagic.DefaultCertificateSelector(hello, viable)
125 }
126
127 +// UnmarshalCaddyfile sets up the CustomCertSelectionPolicy from Caddyfile tokens. Syntax:
128 +//
129 +// cert_selection {
130 +// all_tags <values...>
131 +// any_tag <values...>
132 +// public_key_algorithm <dsa|ecdsa|rsa>
133 +// serial_number <big_integers...>
134 +// subject_organization <values...>
135 +// }
136 +func (p *CustomCertSelectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
137 + _, wrapper := d.Next(), d.Val() // consume wrapper name
138 +
139 + // No same-line options are supported
140 + if d.CountRemainingArgs() > 0 {
141 + return d.ArgErr()
142 + }
143 +
144 + var hasPublicKeyAlgorithm bool
145 + for nesting := d.Nesting(); d.NextBlock(nesting); {
146 + optionName := d.Val()
147 + switch optionName {
148 + case "all_tags":
149 + if d.CountRemainingArgs() == 0 {
150 + return d.ArgErr()
151 + }
152 + p.AllTags = append(p.AllTags, d.RemainingArgs()...)
153 + case "any_tag":
154 + if d.CountRemainingArgs() == 0 {
155 + return d.ArgErr()
156 + }
157 + p.AnyTag = append(p.AnyTag, d.RemainingArgs()...)
158 + case "public_key_algorithm":
159 + if hasPublicKeyAlgorithm {
160 + return d.Errf("duplicate %s option '%s'", wrapper, optionName)
161 + }
162 + if d.CountRemainingArgs() != 1 {
163 + return d.ArgErr()
164 + }
165 + d.NextArg()
166 + if err := p.PublicKeyAlgorithm.UnmarshalJSON([]byte(d.Val())); err != nil {
167 + return d.Errf("parsing %s option '%s': %v", wrapper, optionName, err)
168 + }
169 + hasPublicKeyAlgorithm = true
170 + case "serial_number":
171 + if d.CountRemainingArgs() == 0 {
172 + return d.ArgErr()
173 + }
174 + for d.NextArg() {
175 + val, bi := d.Val(), bigInt{}
176 + _, ok := bi.SetString(val, 10)
177 + if !ok {
178 + return d.Errf("parsing %s option '%s': invalid big.int value %s", wrapper, optionName, val)
179 + }
180 + p.SerialNumber = append(p.SerialNumber, bi)
181 + }
182 + case "subject_organization":
183 + if d.CountRemainingArgs() == 0 {
184 + return d.ArgErr()
185 + }
186 + p.SubjectOrganization = append(p.SubjectOrganization, d.RemainingArgs()...)
187 + default:
188 + return d.ArgErr()
189 + }
190 +
191 + // No nested blocks are supported
192 + if d.NextBlock(nesting + 1) {
193 + return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
194 + }
195 + }
196 +
197 + return nil
198 +}
199 +
200 // bigInt is a big.Int type that interops with JSON encodings as a string.
201 type bigInt struct{ big.Int }
202
@@ -144,3 +219,6 @@ func (bi *bigInt) UnmarshalJSON(p []byte) error {
219 }
220 return nil
221 }
222 +
223 +// Interface guard
224 +var _ caddyfile.Unmarshaler = (*CustomCertSelectionPolicy)(nil)
modules/caddytls/connpolicy.go
+173 -1
@@ -363,6 +363,136 @@ func (p ConnectionPolicy) SettingsEmpty() bool {
363 p.InsecureSecretsLog == ""
364 }
365
366 +// UnmarshalCaddyfile sets up the ConnectionPolicy from Caddyfile tokens. Syntax:
367 +//
368 +// connection_policy {
369 +// alpn <values...>
370 +// cert_selection {
371 +// ...
372 +// }
373 +// ciphers <cipher_suites...>
374 +// client_auth {
375 +// ...
376 +// }
377 +// curves <curves...>
378 +// default_sni <server_name>
379 +// match {
380 +// ...
381 +// }
382 +// protocols <min> [<max>]
383 +// # EXPERIMENTAL:
384 +// drop
385 +// fallback_sni <server_name>
386 +// insecure_secrets_log <log_file>
387 +// }
388 +func (cp *ConnectionPolicy) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
389 + _, wrapper := d.Next(), d.Val()
390 +
391 + // No same-line options are supported
392 + if d.CountRemainingArgs() > 0 {
393 + return d.ArgErr()
394 + }
395 +
396 + var hasCertSelection, hasClientAuth, hasDefaultSNI, hasDrop,
397 + hasFallbackSNI, hasInsecureSecretsLog, hasMatch, hasProtocols bool
398 + for nesting := d.Nesting(); d.NextBlock(nesting); {
399 + optionName := d.Val()
400 + switch optionName {
401 + case "alpn":
402 + if d.CountRemainingArgs() == 0 {
403 + return d.ArgErr()
404 + }
405 + cp.ALPN = append(cp.ALPN, d.RemainingArgs()...)
406 + case "cert_selection":
407 + if hasCertSelection {
408 + return d.Errf("duplicate %s option '%s'", wrapper, optionName)
409 + }
410 + p := &CustomCertSelectionPolicy{}
411 + if err := p.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil {
412 + return err
413 + }
414 + cp.CertSelection, hasCertSelection = p, true
415 + case "client_auth":
416 + if hasClientAuth {
417 + return d.Errf("duplicate %s option '%s'", wrapper, optionName)
418 + }
419 + ca := &ClientAuthentication{}
420 + if err := ca.UnmarshalCaddyfile(d.NewFromNextSegment()); err != nil {
421 + return err
422 + }
423 + cp.ClientAuthentication, hasClientAuth = ca, true
424 + case "ciphers":
425 + if d.CountRemainingArgs() == 0 {
426 + return d.ArgErr()
427 + }
428 + cp.CipherSuites = append(cp.CipherSuites, d.RemainingArgs()...)
429 + case "curves":
430 + if d.CountRemainingArgs() == 0 {
431 + return d.ArgErr()
432 + }
433 + cp.Curves = append(cp.Curves, d.RemainingArgs()...)
434 + case "default_sni":
435 + if hasDefaultSNI {
436 + return d.Errf("duplicate %s option '%s'", wrapper, optionName)
437 + }
438 + if d.CountRemainingArgs() != 1 {
439 + return d.ArgErr()
440 + }
441 + _, cp.DefaultSNI, hasDefaultSNI = d.NextArg(), d.Val(), true
442 + case "drop": // EXPERIMENTAL
443 + if hasDrop {
444 + return d.Errf("duplicate %s option '%s'", wrapper, optionName)
445 + }
446 + cp.Drop, hasDrop = true, true
447 + case "fallback_sni": // EXPERIMENTAL
448 + if hasFallbackSNI {
449 + return d.Errf("duplicate %s option '%s'", wrapper, optionName)
450 + }
451 + if d.CountRemainingArgs() != 1 {
452 + return d.ArgErr()
453 + }
454 + _, cp.FallbackSNI, hasFallbackSNI = d.NextArg(), d.Val(), true
455 + case "insecure_secrets_log": // EXPERIMENTAL
456 + if hasInsecureSecretsLog {
457 + return d.Errf("duplicate %s option '%s'", wrapper, optionName)
458 + }
459 + if d.CountRemainingArgs() != 1 {
460 + return d.ArgErr()
461 + }
462 + _, cp.InsecureSecretsLog, hasInsecureSecretsLog = d.NextArg(), d.Val(), true
463 + case "match":
464 + if hasMatch {
465 + return d.Errf("duplicate %s option '%s'", wrapper, optionName)
466 + }
467 + matcherSet, err := ParseCaddyfileNestedMatcherSet(d)
468 + if err != nil {
469 + return err
470 + }
471 + cp.MatchersRaw, hasMatch = matcherSet, true
472 + case "protocols":
473 + if hasProtocols {
474 + return d.Errf("duplicate %s option '%s'", wrapper, optionName)
475 + }
476 + if d.CountRemainingArgs() == 0 || d.CountRemainingArgs() > 2 {
477 + return d.ArgErr()
478 + }
479 + _, cp.ProtocolMin, hasProtocols = d.NextArg(), d.Val(), true
480 + if d.NextArg() {
481 + cp.ProtocolMax = d.Val()
482 + }
483 + default:
484 + return d.ArgErr()
485 + }
486 +
487 + // No nested blocks are supported
488 + if d.NextBlock(nesting + 1) {
489 + return d.Errf("malformed %s option '%s': blocks are not supported", wrapper, optionName)
490 + }
491 + }
492 +
493 + return nil
494 +}
495 +
496 // ClientAuthentication configures TLS client auth.
497 type ClientAuthentication struct {
498 // Certificate authority module which provides the certificate pool of trusted certificates
@@ -819,4 +949,46 @@ func (d destructableWriter) Destruct() error { return d.Close() }
949
950 var secretsLogPool = caddy.NewUsagePool()
951
822 -var _ caddyfile.Unmarshaler = (*ClientAuthentication)(nil)
952 +// Interface guards
953 +var (
954 + _ caddyfile.Unmarshaler = (*ClientAuthentication)(nil)
955 + _ caddyfile.Unmarshaler = (*ConnectionPolicy)(nil)
956 +)
957 +
958 +// ParseCaddyfileNestedMatcherSet parses the Caddyfile tokens for a nested
959 +// matcher set, and returns its raw module map value.
960 +func ParseCaddyfileNestedMatcherSet(d *caddyfile.Dispenser) (caddy.ModuleMap, error) {
961 + matcherMap := make(map[string]ConnectionMatcher)
962 +
963 + tokensByMatcherName := make(map[string][]caddyfile.Token)
964 + for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); {
965 + matcherName := d.Val()
966 + tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...)
967 + }
968 +
969 + for matcherName, tokens := range tokensByMatcherName {
970 + dd := caddyfile.NewDispenser(tokens)
971 + dd.Next() // consume wrapper name
972 +
973 + unm, err := caddyfile.UnmarshalModule(dd, "tls.handshake_match."+matcherName)
974 + if err != nil {
975 + return nil, err
976 + }
977 + cm, ok := unm.(ConnectionMatcher)
978 + if !ok {
979 + return nil, fmt.Errorf("matcher module '%s' is not a connection matcher", matcherName)
980 + }
981 + matcherMap[matcherName] = cm
982 + }
983 +
984 + matcherSet := make(caddy.ModuleMap)
985 + for name, matcher := range matcherMap {
986 + jsonBytes, err := json.Marshal(matcher)
987 + if err != nil {
988 + return nil, fmt.Errorf("marshaling %T matcher: %v", matcher, err)
989 + }
990 + matcherSet[name] = jsonBytes
991 + }
992 +
993 + return matcherSet, nil
994 +}