Skip to content

Commit

Permalink
httpcaddyfile: Implement experimental force_automate option (#6712)
Browse files Browse the repository at this point in the history
  • Loading branch information
francislavoie authored Dec 24, 2024
1 parent 5ba1e06 commit afa778a
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 3 deletions.
17 changes: 15 additions & 2 deletions caddyconfig/httpcaddyfile/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {

// parseTLS parses the tls directive. Syntax:
//
// tls [<email>|internal]|[<cert_file> <key_file>] {
// tls [<email>|internal|force_automate]|[<cert_file> <key_file>] {
// protocols <min> [<max>]
// ciphers <cipher_suites...>
// curves <curves...>
Expand All @@ -107,6 +107,7 @@ func parseBind(h Helper) ([]ConfigValue, error) {
// dns_challenge_override_domain <domain>
// on_demand
// reuse_private_keys
// force_automate
// eab <key_id> <mac_key>
// issuer <module_name> [...]
// get_certificate <module_name> [...]
Expand All @@ -126,15 +127,18 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
var certManagers []certmagic.Manager
var onDemand bool
var reusePrivateKeys bool
var forceAutomate bool

firstLine := h.RemainingArgs()
switch len(firstLine) {
case 0:
case 1:
if firstLine[0] == "internal" {
internalIssuer = new(caddytls.InternalIssuer)
} else if firstLine[0] == "force_automate" {
forceAutomate = true
} else if !strings.Contains(firstLine[0], "@") {
return nil, h.Err("single argument must either be 'internal' or an email address")
return nil, h.Err("single argument must either be 'internal', 'force_automate', or an email address")
} else {
acmeIssuer = &caddytls.ACMEIssuer{
Email: firstLine[0],
Expand Down Expand Up @@ -569,6 +573,15 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
})
}

// if enabled, the names in the site addresses will be
// added to the automation policies
if forceAutomate {
configVals = append(configVals, ConfigValue{
Class: "tls.force_automate",
Value: true,
})
}

// custom certificate selection
if len(certSelector.AnyTag) > 0 {
cp.CertSelection = &certSelector
Expand Down
22 changes: 21 additions & 1 deletion caddyconfig/httpcaddyfile/httptype.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,14 @@ func (st *ServerType) serversFromPairings(
}
}

// collect hosts that are forced to be automated
forceAutomatedNames := make(map[string]struct{})
if _, ok := sblock.pile["tls.force_automate"]; ok {
for _, host := range hosts {
forceAutomatedNames[host] = struct{}{}
}
}

// tls: connection policies
if cpVals, ok := sblock.pile["tls.connection_policy"]; ok {
// tls connection policies
Expand Down Expand Up @@ -794,7 +802,7 @@ func (st *ServerType) serversFromPairings(
}

// only append this policy if it actually changes something
if !cp.SettingsEmpty() {
if !cp.SettingsEmpty() || mapContains(forceAutomatedNames, hosts) {
srv.TLSConnPolicies = append(srv.TLSConnPolicies, cp)
hasCatchAllTLSConnPolicy = len(hosts) == 0
}
Expand Down Expand Up @@ -1661,6 +1669,18 @@ func listenersUseAnyPortOtherThan(addresses []string, otherPort string) bool {
return false
}

func mapContains[K comparable, V any](m map[K]V, keys []K) bool {
if len(m) == 0 || len(keys) == 0 {
return false
}
for _, key := range keys {
if _, ok := m[key]; ok {
return true
}
}
return false
}

// specificity returns len(s) minus any wildcards (*) and
// placeholders ({...}). Basically, it's a length count
// that penalizes the use of wildcards and placeholders.
Expand Down
16 changes: 16 additions & 0 deletions caddyconfig/httpcaddyfile/tlsapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ func (st ServerType) buildTLSApp(

// collect all hosts that have a wildcard in them, and arent HTTP
wildcardHosts := []string{}
// hosts that have been explicitly marked to be automated,
// even if covered by another wildcard
forcedAutomatedNames := make(map[string]struct{})
for _, p := range pairings {
var addresses []string
for _, addressWithProtocols := range p.addressesWithProtocols {
Expand Down Expand Up @@ -150,6 +153,13 @@ func (st ServerType) buildTLSApp(
ap.OnDemand = true
}

// collect hosts that are forced to be automated
if _, ok := sblock.pile["tls.force_automate"]; ok {
for _, host := range sblockHosts {
forcedAutomatedNames[host] = struct{}{}
}
}

// reuse private keys tls
if _, ok := sblock.pile["tls.reuse_private_keys"]; ok {
ap.ReusePrivateKeys = true
Expand Down Expand Up @@ -407,6 +417,12 @@ func (st ServerType) buildTLSApp(
}
}
}
for name := range forcedAutomatedNames {
if slices.Contains(al, name) {
continue
}
al = append(al, name)
}
if len(al) > 0 {
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
automated1.example.com {
tls force_automate
respond "Automated!"
}

automated2.example.com {
tls force_automate
respond "Automated!"
}

shadowed.example.com {
respond "Shadowed!"
}

*.example.com {
tls cert.pem key.pem
respond "Wildcard!"
}
----------
{
"apps": {
"http": {
"servers": {
"srv0": {
"listen": [
":443"
],
"routes": [
{
"match": [
{
"host": [
"automated1.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Automated!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"automated2.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Automated!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"shadowed.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Shadowed!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
},
{
"match": [
{
"host": [
"*.example.com"
]
}
],
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": "Wildcard!",
"handler": "static_response"
}
]
}
]
}
],
"terminal": true
}
],
"tls_connection_policies": [
{
"match": {
"sni": [
"automated1.example.com"
]
}
},
{
"match": {
"sni": [
"automated2.example.com"
]
}
},
{
"match": {
"sni": [
"*.example.com"
]
},
"certificate_selection": {
"any_tag": [
"cert0"
]
}
},
{}
]
}
}
},
"tls": {
"certificates": {
"automate": [
"automated1.example.com",
"automated2.example.com"
],
"load_files": [
{
"certificate": "cert.pem",
"key": "key.pem",
"tags": [
"cert0"
]
}
]
}
}
}
}
Loading

0 comments on commit afa778a

Please sign in to comment.