123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- // Package serve implements the serve command for CFSSL's API.
- package serve
- import (
- "crypto/tls"
- "embed"
- "errors"
- "fmt"
- "io/fs"
- "net"
- "net/http"
- "net/url"
- "os"
- "regexp"
- "strconv"
- "strings"
- "github.com/cloudflare/cfssl/api"
- "github.com/cloudflare/cfssl/api/bundle"
- "github.com/cloudflare/cfssl/api/certadd"
- "github.com/cloudflare/cfssl/api/certinfo"
- "github.com/cloudflare/cfssl/api/crl"
- "github.com/cloudflare/cfssl/api/gencrl"
- "github.com/cloudflare/cfssl/api/generator"
- "github.com/cloudflare/cfssl/api/health"
- "github.com/cloudflare/cfssl/api/info"
- "github.com/cloudflare/cfssl/api/initca"
- apiocsp "github.com/cloudflare/cfssl/api/ocsp"
- "github.com/cloudflare/cfssl/api/revoke"
- "github.com/cloudflare/cfssl/api/scan"
- "github.com/cloudflare/cfssl/api/signhandler"
- "github.com/cloudflare/cfssl/bundler"
- "github.com/cloudflare/cfssl/certdb/dbconf"
- certsql "github.com/cloudflare/cfssl/certdb/sql"
- "github.com/cloudflare/cfssl/cli"
- ocspsign "github.com/cloudflare/cfssl/cli/ocspsign"
- "github.com/cloudflare/cfssl/cli/sign"
- "github.com/cloudflare/cfssl/helpers"
- "github.com/cloudflare/cfssl/log"
- "github.com/cloudflare/cfssl/ocsp"
- "github.com/cloudflare/cfssl/signer"
- "github.com/cloudflare/cfssl/ubiquity"
- "github.com/jmoiron/sqlx"
- )
- // Usage text of 'cfssl serve'
- var serverUsageText = `cfssl serve -- set up a HTTP server handles CF SSL requests
- Usage of serve:
- cfssl serve [-address address] [-min-tls-version version] [-ca cert] [-ca-bundle bundle] \
- [-ca-key key] [-int-bundle bundle] [-int-dir dir] [-port port] \
- [-metadata file] [-remote remote_host] [-config config] \
- [-responder cert] [-responder-key key] \
- [-tls-cert cert] [-tls-key key] [-mutual-tls-ca ca] [-mutual-tls-cn regex] \
- [-tls-remote-ca ca] [-mutual-tls-client-cert cert] [-mutual-tls-client-key key] \
- [-db-config db-config] [-disable endpoint[,endpoint]]
- Flags:
- `
- // Flags used by 'cfssl serve'
- var serverFlags = []string{"address", "port", "min-tls-version", "ca", "ca-key", "ca-bundle", "int-bundle", "int-dir",
- "metadata", "remote", "config", "responder", "responder-key", "tls-key", "tls-cert", "mutual-tls-ca",
- "mutual-tls-cn", "tls-remote-ca", "mutual-tls-client-cert", "mutual-tls-client-key", "db-config", "disable"}
- var (
- conf cli.Config
- s signer.Signer
- ocspSigner ocsp.Signer
- db *sqlx.DB
- )
- // V1APIPrefix is the prefix of all CFSSL V1 API Endpoints.
- var V1APIPrefix = "/api/v1/cfssl/"
- // v1APIPath prepends the V1 API prefix to endpoints not beginning with "/"
- func v1APIPath(path string) string {
- if !strings.HasPrefix(path, "/") {
- path = V1APIPrefix + path
- }
- return (&url.URL{Path: path}).String()
- }
- //go:embed static
- var staticContent embed.FS
- var staticRedirections = map[string]string{
- "bundle": "index.html",
- "scan": "index.html",
- "packages": "index.html",
- }
- type staticFS struct {
- fs fs.FS
- redirections map[string]string
- }
- func (s *staticFS) Open(name string) (fs.File, error) {
- if strings.HasPrefix(name, V1APIPrefix) {
- return nil, os.ErrNotExist
- }
- if location, ok := s.redirections[name]; ok {
- return s.fs.Open(location)
- }
- return s.fs.Open(name)
- }
- var errBadSigner = errors.New("signer not initialized")
- var errNoCertDBConfigured = errors.New("cert db not configured (missing -db-config)")
- var endpoints = map[string]func() (http.Handler, error){
- "sign": func() (http.Handler, error) {
- if s == nil {
- return nil, errBadSigner
- }
- h, err := signhandler.NewHandlerFromSigner(s)
- if err != nil {
- return nil, err
- }
- if conf.CABundleFile != "" && conf.IntBundleFile != "" {
- sh := h.Handler.(*signhandler.Handler)
- if err := sh.SetBundler(conf.CABundleFile, conf.IntBundleFile); err != nil {
- return nil, err
- }
- }
- return h, nil
- },
- "authsign": func() (http.Handler, error) {
- if s == nil {
- return nil, errBadSigner
- }
- h, err := signhandler.NewAuthHandlerFromSigner(s)
- if err != nil {
- return nil, err
- }
- if conf.CABundleFile != "" && conf.IntBundleFile != "" {
- sh := h.(*api.HTTPHandler).Handler.(*signhandler.AuthHandler)
- if err := sh.SetBundler(conf.CABundleFile, conf.IntBundleFile); err != nil {
- return nil, err
- }
- }
- return h, nil
- },
- "info": func() (http.Handler, error) {
- if s == nil {
- return nil, errBadSigner
- }
- return info.NewHandler(s)
- },
- "crl": func() (http.Handler, error) {
- if s == nil {
- return nil, errBadSigner
- }
- if db == nil {
- return nil, errNoCertDBConfigured
- }
- return crl.NewHandler(certsql.NewAccessor(db), conf.CAFile, conf.CAKeyFile)
- },
- "gencrl": func() (http.Handler, error) {
- if s == nil {
- return nil, errBadSigner
- }
- return gencrl.NewHandler(), nil
- },
- "newcert": func() (http.Handler, error) {
- if s == nil {
- return nil, errBadSigner
- }
- h := generator.NewCertGeneratorHandlerFromSigner(generator.CSRValidate, s)
- if conf.CABundleFile != "" && conf.IntBundleFile != "" {
- cg := h.(api.HTTPHandler).Handler.(*generator.CertGeneratorHandler)
- if err := cg.SetBundler(conf.CABundleFile, conf.IntBundleFile); err != nil {
- return nil, err
- }
- }
- return h, nil
- },
- "bundle": func() (http.Handler, error) {
- return bundle.NewHandler(conf.CABundleFile, conf.IntBundleFile)
- },
- "newkey": func() (http.Handler, error) {
- return generator.NewHandler(generator.CSRValidate)
- },
- "init_ca": func() (http.Handler, error) {
- return initca.NewHandler(), nil
- },
- "scan": func() (http.Handler, error) {
- return scan.NewHandler(conf.CABundleFile)
- },
- "scaninfo": func() (http.Handler, error) {
- return scan.NewInfoHandler(), nil
- },
- "certinfo": func() (http.Handler, error) {
- if db != nil {
- return certinfo.NewAccessorHandler(certsql.NewAccessor(db)), nil
- }
- return certinfo.NewHandler(), nil
- },
- "ocspsign": func() (http.Handler, error) {
- if ocspSigner == nil {
- return nil, errBadSigner
- }
- return apiocsp.NewHandler(ocspSigner), nil
- },
- "revoke": func() (http.Handler, error) {
- if db == nil {
- return nil, errNoCertDBConfigured
- }
- return revoke.NewHandler(certsql.NewAccessor(db)), nil
- },
- "/": func() (http.Handler, error) {
- subFS, _ := fs.Sub(staticContent, "static")
- return http.FileServer(http.FS(&staticFS{fs: subFS, redirections: staticRedirections})), nil
- },
- "health": func() (http.Handler, error) {
- return health.NewHealthCheck(), nil
- },
- "certadd": func() (http.Handler, error) {
- return certadd.NewHandler(certsql.NewAccessor(db), nil), nil
- },
- }
- // registerHandlers instantiates various handlers and associate them to corresponding endpoints.
- func registerHandlers() {
- disabled := make(map[string]bool)
- if conf.Disable != "" {
- for _, endpoint := range strings.Split(conf.Disable, ",") {
- disabled[endpoint] = true
- }
- }
- for path, getHandler := range endpoints {
- log.Debugf("getHandler for %s", path)
- if _, ok := disabled[path]; ok {
- log.Infof("endpoint '%s' is explicitly disabled", path)
- } else if handler, err := getHandler(); err != nil {
- log.Warningf("endpoint '%s' is disabled: %v", path, err)
- } else {
- if path, handler, err = wrapHandler(path, handler, err); err != nil {
- log.Warningf("endpoint '%s' is disabled by wrapper: %v", path, err)
- } else {
- log.Infof("endpoint '%s' is enabled", path)
- http.Handle(path, handler)
- }
- }
- }
- log.Info("Handler set up complete.")
- }
- // serverMain is the command line entry point to the API server. It sets up a
- // new HTTP server to handle sign, bundle, and validate requests.
- func serverMain(args []string, c cli.Config) error {
- conf = c
- // serve doesn't support arguments.
- if len(args) > 0 {
- return errors.New("argument is provided but not defined; please refer to the usage by flag -h")
- }
- bundler.IntermediateStash = conf.IntDir
- var err error
- if err = ubiquity.LoadPlatforms(conf.Metadata); err != nil {
- return err
- }
- if c.DBConfigFile != "" {
- db, err = dbconf.DBFromConfig(c.DBConfigFile)
- if err != nil {
- return err
- }
- }
- log.Info("Initializing signer")
- if s, err = sign.SignerFromConfigAndDB(c, db); err != nil {
- log.Warningf("couldn't initialize signer: %v", err)
- }
- if ocspSigner, err = ocspsign.SignerFromConfig(c); err != nil {
- log.Warningf("couldn't initialize ocsp signer: %v", err)
- }
- registerHandlers()
- addr := net.JoinHostPort(conf.Address, strconv.Itoa(conf.Port))
- tlscfg := tls.Config{}
- if conf.MinTLSVersion != "" {
- tlscfg.MinVersion = helpers.StringTLSVersion(conf.MinTLSVersion)
- }
- if conf.TLSCertFile == "" || conf.TLSKeyFile == "" {
- log.Info("Now listening on ", addr)
- return http.ListenAndServe(addr, nil)
- }
- if conf.MutualTLSCAFile != "" {
- clientPool, err := helpers.LoadPEMCertPool(conf.MutualTLSCAFile)
- if err != nil {
- return fmt.Errorf("failed to load mutual TLS CA file: %s", err)
- }
- tlscfg.ClientAuth = tls.RequireAndVerifyClientCert
- tlscfg.ClientCAs = clientPool
- server := http.Server{
- Addr: addr,
- TLSConfig: &tlscfg,
- }
- if conf.MutualTLSCNRegex != "" {
- log.Debugf(`Requiring CN matches regex "%s" for client connections`, conf.MutualTLSCNRegex)
- re, err := regexp.Compile(conf.MutualTLSCNRegex)
- if err != nil {
- return fmt.Errorf("malformed CN regex: %s", err)
- }
- server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r != nil && r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
- if re.MatchString(r.TLS.PeerCertificates[0].Subject.CommonName) {
- http.DefaultServeMux.ServeHTTP(w, r)
- return
- }
- log.Warningf(`Rejected client cert CN "%s" does not match regex %s`,
- r.TLS.PeerCertificates[0].Subject.CommonName, conf.MutualTLSCNRegex)
- }
- http.Error(w, "Invalid CN", http.StatusForbidden)
- })
- }
- log.Info("Now listening with mutual TLS on https://", addr)
- return server.ListenAndServeTLS(conf.TLSCertFile, conf.TLSKeyFile)
- }
- log.Info("Now listening on https://", addr)
- server := http.Server{
- Addr: addr,
- TLSConfig: &tlscfg,
- }
- return server.ListenAndServeTLS(conf.TLSCertFile, conf.TLSKeyFile)
- }
- // Command assembles the definition of Command 'serve'
- var Command = &cli.Command{UsageText: serverUsageText, Flags: serverFlags, Main: serverMain}
- var wrapHandler = defaultWrapHandler
- // The default wrapper simply returns the normal handler and prefixes the path appropriately
- func defaultWrapHandler(path string, handler http.Handler, err error) (string, http.Handler, error) {
- return v1APIPath(path), handler, err
- }
- // SetWrapHandler sets the wrap handler which is called for all endpoints
- // A custom wrap handler may be provided in order to add arbitrary server-side pre or post processing
- // of server-side HTTP handling of requests.
- func SetWrapHandler(wh func(path string, handler http.Handler, err error) (string, http.Handler, error)) {
- wrapHandler = wh
- }
- // SetEndpoint can be used to add additional routes/endpoints to the HTTP server, or to override an existing route/endpoint
- func SetEndpoint(path string, getHandler func() (http.Handler, error)) {
- endpoints[path] = getHandler
- }
|