// Setup creates a caddy middleware func Setup(c *setup.Controller) (middleware.Middleware, error) { repoPath, err := parseRepo(c) if err != nil { return nil, err } paths := []string{"/api"} // Runs on Caddy startup, useful for services or other setups. c.Startup = append(c.Startup, func() error { fmt.Println("api middleware is initiated") return nil }) // Runs on Caddy shutdown, useful for cleanups. c.Shutdown = append(c.Shutdown, func() error { fmt.Println("api middleware is cleaning up") return nil }) return func(next middleware.Handler) middleware.Handler { h := &handler{ Paths: paths, Next: next, } repo := api.SimpleRepo(repoPath) server := api.NewServer(repo) h.apihandler = server return h }, nil }
// Git configures a new Git service routine. func Setup(c *setup.Controller) (middleware.Middleware, error) { git, err := parse(c) if err != nil { return nil, err } // repos configured with webhooks var hookRepos []*Repo // functions to execute at startup var startupFuncs []func() error // loop through all repos and and start monitoring for i := range git { repo := git.Repo(i) // If a HookUrl is set, we switch to event based pulling. // Install the url handler if repo.HookUrl != "" { hookRepos = append(hookRepos, repo) startupFuncs = append(startupFuncs, func() error { return repo.Pull() }) } else { startupFuncs = append(startupFuncs, func() error { // Start service routine in background Start(repo) // Do a pull right away to return error return repo.Pull() }) } } // ensure the functions are executed once per server block // for cases like server1.com, server2.com { ... } c.OncePerServerBlock(func() error { c.Startup = append(c.Startup, startupFuncs...) return nil }) // if there are repo(s) with webhook // return handler if len(hookRepos) > 0 { webhook := &WebHook{Repos: hookRepos} return func(next middleware.Handler) middleware.Handler { webhook.Next = next return webhook }, err } return nil, err }
func parseLuaCaddyfile(c *setup.Controller) ([]Rule, error) { var rules []Rule for c.Next() { r := Rule{BasePath: "/"} if c.NextArg() { r.BasePath = c.Val() } if c.NextArg() { return rules, c.ArgErr() } rules = append(rules, r) } return rules, 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.HookUrl = c.Val() // optional secret for validation if c.NextArg() { repo.HookSecret = c.Val() } 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 }
// ParseHugo parses the configuration file func ParseHugo(c *setup.Controller) (*Config, error) { conf := &Config{ Public: strings.Replace(c.Root, "./", "", -1), Path: "./", } for c.Next() { args := c.RemainingArgs() switch len(args) { case 1: conf.Path = args[0] conf.Path = strings.TrimSuffix(conf.Path, "/") conf.Path += "/" } for c.NextBlock() { switch c.Val() { case "styles": if !c.NextArg() { return nil, c.ArgErr() } conf.Styles = c.Val() // Remove the beginning slash if it exists or not conf.Styles = strings.TrimPrefix(conf.Styles, "/") // Add a beginning slash to make a conf.Styles = "/" + conf.Styles default: key := "--" + c.Val() value := "true" if c.NextArg() { value = c.Val() } conf.Args = append(conf.Args, key, value) } } } conf.Args = append([]string{"--source", conf.Path}, conf.Args...) return conf, 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.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 }
// 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 }
// ParseHugo parses the configuration file func ParseHugo(c *setup.Controller) (*Config, error) { conf := &Config{ Public: strings.Replace(c.Root, "./", "", -1), Path: "./", } for c.Next() { args := c.RemainingArgs() switch len(args) { case 1: conf.Path = args[0] conf.Path = strings.TrimSuffix(conf.Path, "/") conf.Path += "/" } for c.NextBlock() { switch c.Val() { case "styles": if !c.NextArg() { return nil, c.ArgErr() } conf.Styles = c.Val() // Remove the beginning slash if it exists or not conf.Styles = strings.TrimPrefix(conf.Styles, "/") // Add a beginning slash to make a conf.Styles = "/" + conf.Styles case "args": if !c.NextArg() { return nil, c.ArgErr() } // Get the arguments and split the array args := strings.Split(c.Val(), " ") for index, element := range args { args[index] = strings.Replace(element, "\"", "", -1) } conf.Args = args } } } return conf, nil }
func parseRepo(c *setup.Controller) (string, error) { var repo string for c.Next() { args := c.RemainingArgs() switch len(args) { case 0: for c.NextBlock() { switch c.Val() { case "repo": repo = c.Val() } } case 1: repo = args[0] default: return repo, c.ArgErr() } } return repo, nil }