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
| 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 httpcaddyfile |
| 16 | |
| 17 | import ( |
| 18 | "slices" |
| 19 | "strconv" |
| 20 | |
| 21 | "github.com/caddyserver/caddy/v2" |
| 22 | "github.com/caddyserver/caddy/v2/caddyconfig" |
| 23 | "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" |
| 24 | "github.com/caddyserver/caddy/v2/modules/caddypki" |
| 25 | "github.com/caddyserver/caddy/v2/modules/caddytls" |
| 26 | ) |
| 27 | |
| 28 | func init() { |
| 29 | RegisterGlobalOption("pki", parsePKIApp) |
| 30 | } |
| 31 | |
| 32 | // parsePKIApp parses the global pki option. Syntax: |
| 33 | // |
| 34 | // pki { |
| 35 | // ca [<id>] { |
| 36 | // name <name> |
| 37 | // root_cn <name> |
| 38 | // intermediate_cn <name> |
| 39 | // intermediate_lifetime <duration> |
| 40 | // maintenance_interval <duration> |
| 41 | // renewal_window_ratio <ratio> |
| 42 | // root { |
| 43 | // cert <path> |
| 44 | // key <path> |
| 45 | // format <format> |
| 46 | // } |
| 47 | // intermediate { |
| 48 | // cert <path> |
| 49 | // key <path> |
| 50 | // format <format> |
| 51 | // } |
| 52 | // } |
| 53 | // } |
| 54 | // |
| 55 | // When the CA ID is unspecified, 'local' is assumed. |
| 56 | func parsePKIApp(d *caddyfile.Dispenser, existingVal any) (any, error) { |
| 57 | d.Next() // consume app name |
| 58 | |
| 59 | pki := &caddypki.PKI{ |
| 60 | CAs: make(map[string]*caddypki.CA), |
| 61 | } |
| 62 | for d.NextBlock(0) { |
| 63 | switch d.Val() { |
| 64 | case "ca": |
| 65 | pkiCa := new(caddypki.CA) |
| 66 | if d.NextArg() { |
| 67 | pkiCa.ID = d.Val() |
| 68 | if d.NextArg() { |
| 69 | return nil, d.ArgErr() |
| 70 | } |
| 71 | } |
| 72 | if pkiCa.ID == "" { |
| 73 | pkiCa.ID = caddypki.DefaultCAID |
| 74 | } |
| 75 | |
| 76 | for nesting := d.Nesting(); d.NextBlock(nesting); { |
| 77 | switch d.Val() { |
| 78 | case "name": |
| 79 | if !d.NextArg() { |
| 80 | return nil, d.ArgErr() |
| 81 | } |
| 82 | pkiCa.Name = d.Val() |
| 83 | |
| 84 | case "root_cn": |
| 85 | if !d.NextArg() { |
| 86 | return nil, d.ArgErr() |
| 87 | } |
| 88 | pkiCa.RootCommonName = d.Val() |
| 89 | |
| 90 | case "intermediate_cn": |
| 91 | if !d.NextArg() { |
| 92 | return nil, d.ArgErr() |
| 93 | } |
| 94 | pkiCa.IntermediateCommonName = d.Val() |
| 95 | |
| 96 | case "intermediate_lifetime": |
| 97 | if !d.NextArg() { |
| 98 | return nil, d.ArgErr() |
| 99 | } |
| 100 | dur, err := caddy.ParseDuration(d.Val()) |
| 101 | if err != nil { |
| 102 | return nil, err |
| 103 | } |
| 104 | pkiCa.IntermediateLifetime = caddy.Duration(dur) |
| 105 | |
| 106 | case "maintenance_interval": |
| 107 | if !d.NextArg() { |
| 108 | return nil, d.ArgErr() |
| 109 | } |
| 110 | dur, err := caddy.ParseDuration(d.Val()) |
| 111 | if err != nil { |
| 112 | return nil, err |
| 113 | } |
| 114 | pkiCa.MaintenanceInterval = caddy.Duration(dur) |
| 115 | |
| 116 | case "renewal_window_ratio": |
| 117 | if !d.NextArg() { |
| 118 | return nil, d.ArgErr() |
| 119 | } |
| 120 | ratio, err := strconv.ParseFloat(d.Val(), 64) |
| 121 | if err != nil || ratio <= 0 || ratio > 1 { |
| 122 | return nil, d.Errf("renewal_window_ratio must be a number in (0, 1], got %s", d.Val()) |
| 123 | } |
| 124 | pkiCa.RenewalWindowRatio = ratio |
| 125 | |
| 126 | case "root": |
| 127 | if pkiCa.Root == nil { |
| 128 | pkiCa.Root = new(caddypki.KeyPair) |
| 129 | } |
| 130 | for nesting := d.Nesting(); d.NextBlock(nesting); { |
| 131 | switch d.Val() { |
| 132 | case "cert": |
| 133 | if !d.NextArg() { |
| 134 | return nil, d.ArgErr() |
| 135 | } |
| 136 | pkiCa.Root.Certificate = d.Val() |
| 137 | |
| 138 | case "key": |
| 139 | if !d.NextArg() { |
| 140 | return nil, d.ArgErr() |
| 141 | } |
| 142 | pkiCa.Root.PrivateKey = d.Val() |
| 143 | |
| 144 | case "format": |
| 145 | if !d.NextArg() { |
| 146 | return nil, d.ArgErr() |
| 147 | } |
| 148 | pkiCa.Root.Format = d.Val() |
| 149 | |
| 150 | default: |
| 151 | return nil, d.Errf("unrecognized pki ca root option '%s'", d.Val()) |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | case "intermediate": |
| 156 | if pkiCa.Intermediate == nil { |
| 157 | pkiCa.Intermediate = new(caddypki.KeyPair) |
| 158 | } |
| 159 | for nesting := d.Nesting(); d.NextBlock(nesting); { |
| 160 | switch d.Val() { |
| 161 | case "cert": |
| 162 | if !d.NextArg() { |
| 163 | return nil, d.ArgErr() |
| 164 | } |
| 165 | pkiCa.Intermediate.Certificate = d.Val() |
| 166 | |
| 167 | case "key": |
| 168 | if !d.NextArg() { |
| 169 | return nil, d.ArgErr() |
| 170 | } |
| 171 | pkiCa.Intermediate.PrivateKey = d.Val() |
| 172 | |
| 173 | case "format": |
| 174 | if !d.NextArg() { |
| 175 | return nil, d.ArgErr() |
| 176 | } |
| 177 | pkiCa.Intermediate.Format = d.Val() |
| 178 | |
| 179 | default: |
| 180 | return nil, d.Errf("unrecognized pki ca intermediate option '%s'", d.Val()) |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | default: |
| 185 | return nil, d.Errf("unrecognized pki ca option '%s'", d.Val()) |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | pki.CAs[pkiCa.ID] = pkiCa |
| 190 | |
| 191 | default: |
| 192 | return nil, d.Errf("unrecognized pki option '%s'", d.Val()) |
| 193 | } |
| 194 | } |
| 195 | return pki, nil |
| 196 | } |
| 197 | |
| 198 | func (st ServerType) buildPKIApp( |
| 199 | pairings []sbAddrAssociation, |
| 200 | options map[string]any, |
| 201 | warnings []caddyconfig.Warning, |
| 202 | ) (*caddypki.PKI, []caddyconfig.Warning, error) { |
| 203 | skipInstallTrust := false |
| 204 | if _, ok := options["skip_install_trust"]; ok { |
| 205 | skipInstallTrust = true |
| 206 | } |
| 207 | |
| 208 | // check if auto_https is off - in that case we should not create |
| 209 | // any PKI infrastructure even with skip_install_trust directive |
| 210 | autoHTTPS := []string{} |
| 211 | if ah, ok := options["auto_https"].([]string); ok { |
| 212 | autoHTTPS = ah |
| 213 | } |
| 214 | autoHTTPSOff := slices.Contains(autoHTTPS, "off") |
| 215 | |
| 216 | falseBool := false |
| 217 | |
| 218 | // Load the PKI app configured via global options |
| 219 | var pkiApp *caddypki.PKI |
| 220 | unwrappedPki, ok := options["pki"].(*caddypki.PKI) |
| 221 | if ok { |
| 222 | pkiApp = unwrappedPki |
| 223 | } else { |
| 224 | pkiApp = &caddypki.PKI{CAs: make(map[string]*caddypki.CA)} |
| 225 | } |
| 226 | for _, ca := range pkiApp.CAs { |
| 227 | if skipInstallTrust { |
| 228 | ca.InstallTrust = &falseBool |
| 229 | } |
| 230 | pkiApp.CAs[ca.ID] = ca |
| 231 | } |
| 232 | |
| 233 | // Add in the CAs configured via directives |
| 234 | for _, p := range pairings { |
| 235 | for _, sblock := range p.serverBlocks { |
| 236 | // find all the CAs that were defined and add them to the app config |
| 237 | // i.e. from any "acme_server" directives |
| 238 | for _, caCfgValue := range sblock.pile["pki.ca"] { |
| 239 | ca := caCfgValue.Value.(*caddypki.CA) |
| 240 | if skipInstallTrust { |
| 241 | ca.InstallTrust = &falseBool |
| 242 | } |
| 243 | |
| 244 | // the CA might already exist from global options, so |
| 245 | // don't overwrite it in that case |
| 246 | if _, ok := pkiApp.CAs[ca.ID]; !ok { |
| 247 | pkiApp.CAs[ca.ID] = ca |
| 248 | } |
| 249 | } |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | // if there was no CAs defined in any of the servers, |
| 254 | // and we were requested to not install trust, then |
| 255 | // add one for the default/local CA to do so |
| 256 | // only if auto_https is not completely disabled |
| 257 | if len(pkiApp.CAs) == 0 && skipInstallTrust && !autoHTTPSOff { |
| 258 | ca := new(caddypki.CA) |
| 259 | ca.ID = caddypki.DefaultCAID |
| 260 | ca.InstallTrust = &falseBool |
| 261 | pkiApp.CAs[ca.ID] = ca |
| 262 | } |
| 263 | |
| 264 | // an explicit site-level internal issuer creates its own CA |
| 265 | // regardless of `auto_https`, so make sure `skip_install_trust` |
| 266 | // is honored for it too; otherwise the CA is created later at |
| 267 | // runtime with trust installation left on |
| 268 | if skipInstallTrust { |
| 269 | for _, p := range pairings { |
| 270 | for _, sblock := range p.serverBlocks { |
| 271 | for _, issuerCfgValue := range sblock.pile["tls.cert_issuer"] { |
| 272 | internalIssuer, ok := issuerCfgValue.Value.(*caddytls.InternalIssuer) |
| 273 | if !ok { |
| 274 | continue |
| 275 | } |
| 276 | caID := internalIssuer.CA |
| 277 | if caID == "" { |
| 278 | caID = caddypki.DefaultCAID |
| 279 | } |
| 280 | if _, ok := pkiApp.CAs[caID]; ok { |
| 281 | continue |
| 282 | } |
| 283 | ca := new(caddypki.CA) |
| 284 | ca.ID = caID |
| 285 | ca.InstallTrust = &falseBool |
| 286 | pkiApp.CAs[ca.ID] = ca |
| 287 | } |
| 288 | } |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | return pkiApp, warnings, nil |
| 293 | } |