serve.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. // Package serve implements the serve command for CFSSL's API.
  2. package serve
  3. import (
  4. "crypto/tls"
  5. "embed"
  6. "errors"
  7. "fmt"
  8. "io/fs"
  9. "net"
  10. "net/http"
  11. "net/url"
  12. "os"
  13. "regexp"
  14. "strconv"
  15. "strings"
  16. "github.com/cloudflare/cfssl/api"
  17. "github.com/cloudflare/cfssl/api/bundle"
  18. "github.com/cloudflare/cfssl/api/certadd"
  19. "github.com/cloudflare/cfssl/api/certinfo"
  20. "github.com/cloudflare/cfssl/api/crl"
  21. "github.com/cloudflare/cfssl/api/gencrl"
  22. "github.com/cloudflare/cfssl/api/generator"
  23. "github.com/cloudflare/cfssl/api/health"
  24. "github.com/cloudflare/cfssl/api/info"
  25. "github.com/cloudflare/cfssl/api/initca"
  26. apiocsp "github.com/cloudflare/cfssl/api/ocsp"
  27. "github.com/cloudflare/cfssl/api/revoke"
  28. "github.com/cloudflare/cfssl/api/scan"
  29. "github.com/cloudflare/cfssl/api/signhandler"
  30. "github.com/cloudflare/cfssl/bundler"
  31. "github.com/cloudflare/cfssl/certdb/dbconf"
  32. certsql "github.com/cloudflare/cfssl/certdb/sql"
  33. "github.com/cloudflare/cfssl/cli"
  34. ocspsign "github.com/cloudflare/cfssl/cli/ocspsign"
  35. "github.com/cloudflare/cfssl/cli/sign"
  36. "github.com/cloudflare/cfssl/helpers"
  37. "github.com/cloudflare/cfssl/log"
  38. "github.com/cloudflare/cfssl/ocsp"
  39. "github.com/cloudflare/cfssl/signer"
  40. "github.com/cloudflare/cfssl/ubiquity"
  41. "github.com/jmoiron/sqlx"
  42. )
  43. // Usage text of 'cfssl serve'
  44. var serverUsageText = `cfssl serve -- set up a HTTP server handles CF SSL requests
  45. Usage of serve:
  46. cfssl serve [-address address] [-min-tls-version version] [-ca cert] [-ca-bundle bundle] \
  47. [-ca-key key] [-int-bundle bundle] [-int-dir dir] [-port port] \
  48. [-metadata file] [-remote remote_host] [-config config] \
  49. [-responder cert] [-responder-key key] \
  50. [-tls-cert cert] [-tls-key key] [-mutual-tls-ca ca] [-mutual-tls-cn regex] \
  51. [-tls-remote-ca ca] [-mutual-tls-client-cert cert] [-mutual-tls-client-key key] \
  52. [-db-config db-config] [-disable endpoint[,endpoint]]
  53. Flags:
  54. `
  55. // Flags used by 'cfssl serve'
  56. var serverFlags = []string{"address", "port", "min-tls-version", "ca", "ca-key", "ca-bundle", "int-bundle", "int-dir",
  57. "metadata", "remote", "config", "responder", "responder-key", "tls-key", "tls-cert", "mutual-tls-ca",
  58. "mutual-tls-cn", "tls-remote-ca", "mutual-tls-client-cert", "mutual-tls-client-key", "db-config", "disable"}
  59. var (
  60. conf cli.Config
  61. s signer.Signer
  62. ocspSigner ocsp.Signer
  63. db *sqlx.DB
  64. )
  65. // V1APIPrefix is the prefix of all CFSSL V1 API Endpoints.
  66. var V1APIPrefix = "/api/v1/cfssl/"
  67. // v1APIPath prepends the V1 API prefix to endpoints not beginning with "/"
  68. func v1APIPath(path string) string {
  69. if !strings.HasPrefix(path, "/") {
  70. path = V1APIPrefix + path
  71. }
  72. return (&url.URL{Path: path}).String()
  73. }
  74. //go:embed static
  75. var staticContent embed.FS
  76. var staticRedirections = map[string]string{
  77. "bundle": "index.html",
  78. "scan": "index.html",
  79. "packages": "index.html",
  80. }
  81. type staticFS struct {
  82. fs fs.FS
  83. redirections map[string]string
  84. }
  85. func (s *staticFS) Open(name string) (fs.File, error) {
  86. if strings.HasPrefix(name, V1APIPrefix) {
  87. return nil, os.ErrNotExist
  88. }
  89. if location, ok := s.redirections[name]; ok {
  90. return s.fs.Open(location)
  91. }
  92. return s.fs.Open(name)
  93. }
  94. var errBadSigner = errors.New("signer not initialized")
  95. var errNoCertDBConfigured = errors.New("cert db not configured (missing -db-config)")
  96. var endpoints = map[string]func() (http.Handler, error){
  97. "sign": func() (http.Handler, error) {
  98. if s == nil {
  99. return nil, errBadSigner
  100. }
  101. h, err := signhandler.NewHandlerFromSigner(s)
  102. if err != nil {
  103. return nil, err
  104. }
  105. if conf.CABundleFile != "" && conf.IntBundleFile != "" {
  106. sh := h.Handler.(*signhandler.Handler)
  107. if err := sh.SetBundler(conf.CABundleFile, conf.IntBundleFile); err != nil {
  108. return nil, err
  109. }
  110. }
  111. return h, nil
  112. },
  113. "authsign": func() (http.Handler, error) {
  114. if s == nil {
  115. return nil, errBadSigner
  116. }
  117. h, err := signhandler.NewAuthHandlerFromSigner(s)
  118. if err != nil {
  119. return nil, err
  120. }
  121. if conf.CABundleFile != "" && conf.IntBundleFile != "" {
  122. sh := h.(*api.HTTPHandler).Handler.(*signhandler.AuthHandler)
  123. if err := sh.SetBundler(conf.CABundleFile, conf.IntBundleFile); err != nil {
  124. return nil, err
  125. }
  126. }
  127. return h, nil
  128. },
  129. "info": func() (http.Handler, error) {
  130. if s == nil {
  131. return nil, errBadSigner
  132. }
  133. return info.NewHandler(s)
  134. },
  135. "crl": func() (http.Handler, error) {
  136. if s == nil {
  137. return nil, errBadSigner
  138. }
  139. if db == nil {
  140. return nil, errNoCertDBConfigured
  141. }
  142. return crl.NewHandler(certsql.NewAccessor(db), conf.CAFile, conf.CAKeyFile)
  143. },
  144. "gencrl": func() (http.Handler, error) {
  145. if s == nil {
  146. return nil, errBadSigner
  147. }
  148. return gencrl.NewHandler(), nil
  149. },
  150. "newcert": func() (http.Handler, error) {
  151. if s == nil {
  152. return nil, errBadSigner
  153. }
  154. h := generator.NewCertGeneratorHandlerFromSigner(generator.CSRValidate, s)
  155. if conf.CABundleFile != "" && conf.IntBundleFile != "" {
  156. cg := h.(api.HTTPHandler).Handler.(*generator.CertGeneratorHandler)
  157. if err := cg.SetBundler(conf.CABundleFile, conf.IntBundleFile); err != nil {
  158. return nil, err
  159. }
  160. }
  161. return h, nil
  162. },
  163. "bundle": func() (http.Handler, error) {
  164. return bundle.NewHandler(conf.CABundleFile, conf.IntBundleFile)
  165. },
  166. "newkey": func() (http.Handler, error) {
  167. return generator.NewHandler(generator.CSRValidate)
  168. },
  169. "init_ca": func() (http.Handler, error) {
  170. return initca.NewHandler(), nil
  171. },
  172. "scan": func() (http.Handler, error) {
  173. return scan.NewHandler(conf.CABundleFile)
  174. },
  175. "scaninfo": func() (http.Handler, error) {
  176. return scan.NewInfoHandler(), nil
  177. },
  178. "certinfo": func() (http.Handler, error) {
  179. if db != nil {
  180. return certinfo.NewAccessorHandler(certsql.NewAccessor(db)), nil
  181. }
  182. return certinfo.NewHandler(), nil
  183. },
  184. "ocspsign": func() (http.Handler, error) {
  185. if ocspSigner == nil {
  186. return nil, errBadSigner
  187. }
  188. return apiocsp.NewHandler(ocspSigner), nil
  189. },
  190. "revoke": func() (http.Handler, error) {
  191. if db == nil {
  192. return nil, errNoCertDBConfigured
  193. }
  194. return revoke.NewHandler(certsql.NewAccessor(db)), nil
  195. },
  196. "/": func() (http.Handler, error) {
  197. subFS, _ := fs.Sub(staticContent, "static")
  198. return http.FileServer(http.FS(&staticFS{fs: subFS, redirections: staticRedirections})), nil
  199. },
  200. "health": func() (http.Handler, error) {
  201. return health.NewHealthCheck(), nil
  202. },
  203. "certadd": func() (http.Handler, error) {
  204. return certadd.NewHandler(certsql.NewAccessor(db), nil), nil
  205. },
  206. }
  207. // registerHandlers instantiates various handlers and associate them to corresponding endpoints.
  208. func registerHandlers() {
  209. disabled := make(map[string]bool)
  210. if conf.Disable != "" {
  211. for _, endpoint := range strings.Split(conf.Disable, ",") {
  212. disabled[endpoint] = true
  213. }
  214. }
  215. for path, getHandler := range endpoints {
  216. log.Debugf("getHandler for %s", path)
  217. if _, ok := disabled[path]; ok {
  218. log.Infof("endpoint '%s' is explicitly disabled", path)
  219. } else if handler, err := getHandler(); err != nil {
  220. log.Warningf("endpoint '%s' is disabled: %v", path, err)
  221. } else {
  222. if path, handler, err = wrapHandler(path, handler, err); err != nil {
  223. log.Warningf("endpoint '%s' is disabled by wrapper: %v", path, err)
  224. } else {
  225. log.Infof("endpoint '%s' is enabled", path)
  226. http.Handle(path, handler)
  227. }
  228. }
  229. }
  230. log.Info("Handler set up complete.")
  231. }
  232. // serverMain is the command line entry point to the API server. It sets up a
  233. // new HTTP server to handle sign, bundle, and validate requests.
  234. func serverMain(args []string, c cli.Config) error {
  235. conf = c
  236. // serve doesn't support arguments.
  237. if len(args) > 0 {
  238. return errors.New("argument is provided but not defined; please refer to the usage by flag -h")
  239. }
  240. bundler.IntermediateStash = conf.IntDir
  241. var err error
  242. if err = ubiquity.LoadPlatforms(conf.Metadata); err != nil {
  243. return err
  244. }
  245. if c.DBConfigFile != "" {
  246. db, err = dbconf.DBFromConfig(c.DBConfigFile)
  247. if err != nil {
  248. return err
  249. }
  250. }
  251. log.Info("Initializing signer")
  252. if s, err = sign.SignerFromConfigAndDB(c, db); err != nil {
  253. log.Warningf("couldn't initialize signer: %v", err)
  254. }
  255. if ocspSigner, err = ocspsign.SignerFromConfig(c); err != nil {
  256. log.Warningf("couldn't initialize ocsp signer: %v", err)
  257. }
  258. registerHandlers()
  259. addr := net.JoinHostPort(conf.Address, strconv.Itoa(conf.Port))
  260. tlscfg := tls.Config{}
  261. if conf.MinTLSVersion != "" {
  262. tlscfg.MinVersion = helpers.StringTLSVersion(conf.MinTLSVersion)
  263. }
  264. if conf.TLSCertFile == "" || conf.TLSKeyFile == "" {
  265. log.Info("Now listening on ", addr)
  266. return http.ListenAndServe(addr, nil)
  267. }
  268. if conf.MutualTLSCAFile != "" {
  269. clientPool, err := helpers.LoadPEMCertPool(conf.MutualTLSCAFile)
  270. if err != nil {
  271. return fmt.Errorf("failed to load mutual TLS CA file: %s", err)
  272. }
  273. tlscfg.ClientAuth = tls.RequireAndVerifyClientCert
  274. tlscfg.ClientCAs = clientPool
  275. server := http.Server{
  276. Addr: addr,
  277. TLSConfig: &tlscfg,
  278. }
  279. if conf.MutualTLSCNRegex != "" {
  280. log.Debugf(`Requiring CN matches regex "%s" for client connections`, conf.MutualTLSCNRegex)
  281. re, err := regexp.Compile(conf.MutualTLSCNRegex)
  282. if err != nil {
  283. return fmt.Errorf("malformed CN regex: %s", err)
  284. }
  285. server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  286. if r != nil && r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
  287. if re.MatchString(r.TLS.PeerCertificates[0].Subject.CommonName) {
  288. http.DefaultServeMux.ServeHTTP(w, r)
  289. return
  290. }
  291. log.Warningf(`Rejected client cert CN "%s" does not match regex %s`,
  292. r.TLS.PeerCertificates[0].Subject.CommonName, conf.MutualTLSCNRegex)
  293. }
  294. http.Error(w, "Invalid CN", http.StatusForbidden)
  295. })
  296. }
  297. log.Info("Now listening with mutual TLS on https://", addr)
  298. return server.ListenAndServeTLS(conf.TLSCertFile, conf.TLSKeyFile)
  299. }
  300. log.Info("Now listening on https://", addr)
  301. server := http.Server{
  302. Addr: addr,
  303. TLSConfig: &tlscfg,
  304. }
  305. return server.ListenAndServeTLS(conf.TLSCertFile, conf.TLSKeyFile)
  306. }
  307. // Command assembles the definition of Command 'serve'
  308. var Command = &cli.Command{UsageText: serverUsageText, Flags: serverFlags, Main: serverMain}
  309. var wrapHandler = defaultWrapHandler
  310. // The default wrapper simply returns the normal handler and prefixes the path appropriately
  311. func defaultWrapHandler(path string, handler http.Handler, err error) (string, http.Handler, error) {
  312. return v1APIPath(path), handler, err
  313. }
  314. // SetWrapHandler sets the wrap handler which is called for all endpoints
  315. // A custom wrap handler may be provided in order to add arbitrary server-side pre or post processing
  316. // of server-side HTTP handling of requests.
  317. func SetWrapHandler(wh func(path string, handler http.Handler, err error) (string, http.Handler, error)) {
  318. wrapHandler = wh
  319. }
  320. // SetEndpoint can be used to add additional routes/endpoints to the HTTP server, or to override an existing route/endpoint
  321. func SetEndpoint(path string, getHandler func() (http.Handler, error)) {
  322. endpoints[path] = getHandler
  323. }