serve.go 11 KB

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