// Setup sets up the TLS configuration and installs certificates that // are specified by the user in the config file. All the automatic HTTPS // stuff comes later outside of this function. func Setup(c *setup.Controller) (middleware.Middleware, error) { if c.Port == "80" || c.Scheme == "http" { c.TLS.Enabled = false log.Printf("[WARNING] TLS disabled for %s://%s.", c.Scheme, c.Address()) return nil, nil } c.TLS.Enabled = true for c.Next() { var certificateFile, keyFile, loadDir, maxCerts string args := c.RemainingArgs() switch len(args) { case 1: c.TLS.LetsEncryptEmail = args[0] // user can force-disable managed TLS this way if c.TLS.LetsEncryptEmail == "off" { c.TLS.Enabled = false return nil, nil } case 2: certificateFile = args[0] keyFile = args[1] c.TLS.Manual = true } // Optional block with extra parameters var hadBlock bool for c.NextBlock() { hadBlock = true switch c.Val() { case "protocols": args := c.RemainingArgs() if len(args) != 2 { return nil, c.ArgErr() } value, ok := supportedProtocols[strings.ToLower(args[0])] if !ok { return nil, c.Errf("Wrong protocol name or protocol not supported '%s'", c.Val()) } c.TLS.ProtocolMinVersion = value value, ok = supportedProtocols[strings.ToLower(args[1])] if !ok { return nil, c.Errf("Wrong protocol name or protocol not supported '%s'", c.Val()) } c.TLS.ProtocolMaxVersion = value case "ciphers": for c.NextArg() { value, ok := supportedCiphersMap[strings.ToUpper(c.Val())] if !ok { return nil, c.Errf("Wrong cipher name or cipher not supported '%s'", c.Val()) } c.TLS.Ciphers = append(c.TLS.Ciphers, value) } case "clients": c.TLS.ClientCerts = c.RemainingArgs() if len(c.TLS.ClientCerts) == 0 { return nil, c.ArgErr() } case "load": c.Args(&loadDir) c.TLS.Manual = true case "max_certs": c.Args(&maxCerts) c.TLS.OnDemand = true default: return nil, c.Errf("Unknown keyword '%s'", c.Val()) } } // tls requires at least one argument if a block is not opened if len(args) == 0 && !hadBlock { return nil, c.ArgErr() } // set certificate limit if on-demand TLS is enabled if maxCerts != "" { maxCertsNum, err := strconv.Atoi(maxCerts) if err != nil || maxCertsNum < 1 { return nil, c.Err("max_certs must be a positive integer") } if onDemandMaxIssue == 0 || int32(maxCertsNum) < onDemandMaxIssue { // keep the minimum; TODO: We have to do this because it is global; should be per-server or per-vhost... onDemandMaxIssue = int32(maxCertsNum) } } // don't try to load certificates unless we're supposed to if !c.TLS.Enabled || !c.TLS.Manual { continue } // load a single certificate and key, if specified if certificateFile != "" && keyFile != "" { err := cacheUnmanagedCertificatePEMFile(certificateFile, keyFile) if err != nil { return nil, c.Errf("Unable to load certificate and key files for %s: %v", c.Host, err) } log.Printf("[INFO] Successfully loaded TLS assets from %s and %s", certificateFile, keyFile) } // load a directory of certificates, if specified if loadDir != "" { err := loadCertsInDir(c, loadDir) if err != nil { return nil, err } } } setDefaultTLSParams(c.Config) return nil, nil }
func parse(c *setup.Controller) (Git, error) { var git Git for c.Next() { repo := &Repo{Branch: "master", Interval: DefaultInterval, Path: c.Root} args := c.RemainingArgs() switch len(args) { case 2: repo.Path = filepath.Clean(c.Root + string(filepath.Separator) + args[1]) fallthrough case 1: repo.URL = args[0] } for c.NextBlock() { switch c.Val() { case "repo": if !c.NextArg() { return nil, c.ArgErr() } repo.URL = c.Val() case "path": if !c.NextArg() { return nil, c.ArgErr() } repo.Path = filepath.Clean(c.Root + string(filepath.Separator) + c.Val()) case "branch": if !c.NextArg() { return nil, c.ArgErr() } repo.Branch = c.Val() case "key": if !c.NextArg() { return nil, c.ArgErr() } repo.KeyPath = c.Val() case "interval": if !c.NextArg() { return nil, c.ArgErr() } t, _ := strconv.Atoi(c.Val()) if t > 0 { repo.Interval = time.Duration(t) * time.Second } case "hook": if !c.NextArg() { return nil, c.ArgErr() } repo.Hook.Url = c.Val() // optional secret for validation if c.NextArg() { repo.Hook.Secret = c.Val() } case "hook_type": if !c.NextArg() { return nil, c.ArgErr() } t := c.Val() if _, ok := handlers[t]; !ok { return nil, c.Errf("invalid hook type %v", t) } repo.Hook.Type = t case "then": if !c.NextArg() { return nil, c.ArgErr() } command := c.Val() args := c.RemainingArgs() repo.Then = append(repo.Then, NewThen(command, args...)) case "then_long": if !c.NextArg() { return nil, c.ArgErr() } command := c.Val() args := c.RemainingArgs() repo.Then = append(repo.Then, NewLongThen(command, args...)) default: return nil, c.ArgErr() } } // if repo is not specified, return error if repo.URL == "" { return nil, c.ArgErr() } // if private key is not specified, convert repository URL to https // to avoid ssh authentication // else validate git URL // Note: private key support not yet available on Windows var err error if repo.KeyPath == "" { repo.URL, repo.Host, err = sanitizeHTTP(repo.URL) } else { repo.URL, repo.Host, err = sanitizeGit(repo.URL) // TODO add Windows support for private repos if runtime.GOOS == "windows" { return nil, fmt.Errorf("private repository not yet supported on Windows") } } if err != nil { return nil, err } // validate git requirements if err = Init(); err != nil { return nil, err } // prepare repo for use if err = repo.Prepare(); err != nil { return nil, err } git = append(git, repo) } return git, nil }
// loadCertsInDir loads all the certificates/keys in dir, as long as // the file ends with .pem. This method of loading certificates is // modeled after haproxy, which expects the certificate and key to // be bundled into the same file: // https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#5.1-crt // // This function may write to the log as it walks the directory tree. func loadCertsInDir(c *setup.Controller, dir string) error { return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if err != nil { log.Printf("[WARNING] Unable to traverse into %s; skipping", path) return nil } if info.IsDir() { return nil } if strings.HasSuffix(strings.ToLower(info.Name()), ".pem") { certBuilder, keyBuilder := new(bytes.Buffer), new(bytes.Buffer) var foundKey bool // use only the first key in the file bundle, err := ioutil.ReadFile(path) if err != nil { return err } for { // Decode next block so we can see what type it is var derBlock *pem.Block derBlock, bundle = pem.Decode(bundle) if derBlock == nil { break } if derBlock.Type == "CERTIFICATE" { // Re-encode certificate as PEM, appending to certificate chain pem.Encode(certBuilder, derBlock) } else if derBlock.Type == "EC PARAMETERS" { // EC keys generated from openssl can be composed of two blocks: // parameters and key (parameter block should come first) if !foundKey { // Encode parameters pem.Encode(keyBuilder, derBlock) // Key must immediately follow derBlock, bundle = pem.Decode(bundle) if derBlock == nil || derBlock.Type != "EC PRIVATE KEY" { return c.Errf("%s: expected elliptic private key to immediately follow EC parameters", path) } pem.Encode(keyBuilder, derBlock) foundKey = true } } else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") { // RSA key if !foundKey { pem.Encode(keyBuilder, derBlock) foundKey = true } } else { return c.Errf("%s: unrecognized PEM block type: %s", path, derBlock.Type) } } certPEMBytes, keyPEMBytes := certBuilder.Bytes(), keyBuilder.Bytes() if len(certPEMBytes) == 0 { return c.Errf("%s: failed to parse PEM data", path) } if len(keyPEMBytes) == 0 { return c.Errf("%s: no private key block found", path) } err = cacheUnmanagedCertificatePEMBytes(certPEMBytes, keyPEMBytes) if err != nil { return c.Errf("%s: failed to load cert and key for %s: %v", path, c.Host, err) } log.Printf("[INFO] Successfully loaded TLS assets from %s", path) } return nil }) }
// Setup sets up the TLS configuration and installs certificates that // are specified by the user in the config file. All the automatic HTTPS // stuff comes later outside of this function. func Setup(c *setup.Controller) (middleware.Middleware, error) { if c.Scheme == "http" { c.TLS.Enabled = false log.Printf("[WARNING] TLS disabled for %s://%s.", c.Scheme, c.Address()) } else { c.TLS.Enabled = true } for c.Next() { var certificateFile, keyFile, loadDir, maxCerts string args := c.RemainingArgs() switch len(args) { case 1: c.TLS.LetsEncryptEmail = args[0] // user can force-disable managed TLS this way if c.TLS.LetsEncryptEmail == "off" { c.TLS.Enabled = false } case 2: certificateFile = args[0] keyFile = args[1] c.TLS.Manual = true } // Optional block with extra parameters var hadBlock bool for c.NextBlock() { hadBlock = true switch c.Val() { case "protocols": args := c.RemainingArgs() if len(args) != 2 { return nil, c.ArgErr() } value, ok := supportedProtocols[strings.ToLower(args[0])] if !ok { return nil, c.Errf("Wrong protocol name or protocol not supported '%s'", c.Val()) } c.TLS.ProtocolMinVersion = value value, ok = supportedProtocols[strings.ToLower(args[1])] if !ok { return nil, c.Errf("Wrong protocol name or protocol not supported '%s'", c.Val()) } c.TLS.ProtocolMaxVersion = value case "ciphers": for c.NextArg() { value, ok := supportedCiphersMap[strings.ToUpper(c.Val())] if !ok { return nil, c.Errf("Wrong cipher name or cipher not supported '%s'", c.Val()) } c.TLS.Ciphers = append(c.TLS.Ciphers, value) } case "clients": c.TLS.ClientCerts = c.RemainingArgs() if len(c.TLS.ClientCerts) == 0 { return nil, c.ArgErr() } case "load": c.Args(&loadDir) c.TLS.Manual = true case "max_certs": c.Args(&maxCerts) c.TLS.OnDemand = true default: return nil, c.Errf("Unknown keyword '%s'", c.Val()) } } // tls requires at least one argument if a block is not opened if len(args) == 0 && !hadBlock { return nil, c.ArgErr() } // set certificate limit if on-demand TLS is enabled if maxCerts != "" { maxCertsNum, err := strconv.Atoi(maxCerts) if err != nil || maxCertsNum < 1 { return nil, c.Err("max_certs must be a positive integer") } if onDemandMaxIssue == 0 || int32(maxCertsNum) < onDemandMaxIssue { // keep the minimum; TODO: We have to do this because it is global; should be per-server or per-vhost... onDemandMaxIssue = int32(maxCertsNum) } } // don't try to load certificates unless we're supposed to if !c.TLS.Enabled || !c.TLS.Manual { continue } // load a single certificate and key, if specified if certificateFile != "" && keyFile != "" { err := cacheUnmanagedCertificatePEMFile(certificateFile, keyFile) if err != nil { return nil, c.Errf("Unable to load certificate and key files for %s: %v", c.Host, err) } log.Printf("[INFO] Successfully loaded TLS assets from %s and %s", certificateFile, keyFile) } // load a directory of certificates, if specified // modeled after haproxy: https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#5.1-crt if loadDir != "" { err := filepath.Walk(loadDir, func(path string, info os.FileInfo, err error) error { if err != nil { log.Printf("[WARNING] Unable to traverse into %s; skipping", path) return nil } if info.IsDir() { return nil } if strings.HasSuffix(strings.ToLower(info.Name()), ".pem") { certBuilder, keyBuilder := new(bytes.Buffer), new(bytes.Buffer) var foundKey bool bundle, err := ioutil.ReadFile(path) if err != nil { return err } for { // Decode next block so we can see what type it is var derBlock *pem.Block derBlock, bundle = pem.Decode(bundle) if derBlock == nil { break } if derBlock.Type == "CERTIFICATE" { // Re-encode certificate as PEM, appending to certificate chain pem.Encode(certBuilder, derBlock) } else if derBlock.Type == "EC PARAMETERS" { // EC keys are composed of two blocks: parameters and key // (parameter block should come first) if !foundKey { // Encode parameters pem.Encode(keyBuilder, derBlock) // Key must immediately follow derBlock, bundle = pem.Decode(bundle) if derBlock == nil || derBlock.Type != "EC PRIVATE KEY" { return c.Errf("%s: expected elliptic private key to immediately follow EC parameters", path) } pem.Encode(keyBuilder, derBlock) foundKey = true } } else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") { // RSA key if !foundKey { pem.Encode(keyBuilder, derBlock) foundKey = true } } else { return c.Errf("%s: unrecognized PEM block type: %s", path, derBlock.Type) } } certPEMBytes, keyPEMBytes := certBuilder.Bytes(), keyBuilder.Bytes() if len(certPEMBytes) == 0 { return c.Errf("%s: failed to parse PEM data", path) } if len(keyPEMBytes) == 0 { return c.Errf("%s: no private key block found", path) } err = cacheUnmanagedCertificatePEMBytes(certPEMBytes, keyPEMBytes) if err != nil { return c.Errf("%s: failed to load cert and key for %s: %v", path, c.Host, err) } log.Printf("[INFO] Successfully loaded TLS assets from %s", path) } return nil }) if err != nil { return nil, err } } } setDefaultTLSParams(c.Config) return nil, nil }