router.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package router
  2. import (
  3. "net/http"
  4. "os"
  5. "path"
  6. "strings"
  7. "github.com/go-chi/chi"
  8. chiMiddleware "github.com/go-chi/chi/middleware"
  9. "github.com/porter-dev/porter/api/server/authn"
  10. "github.com/porter-dev/porter/api/server/authz"
  11. "github.com/porter-dev/porter/api/server/authz/policy"
  12. "github.com/porter-dev/porter/api/server/router/middleware"
  13. "github.com/porter-dev/porter/api/server/shared"
  14. "github.com/porter-dev/porter/api/server/shared/config"
  15. "github.com/porter-dev/porter/api/types"
  16. )
  17. func NewAPIRouter(config *config.Config) *chi.Mux {
  18. r := chi.NewRouter()
  19. endpointFactory := shared.NewAPIObjectEndpointFactory(config)
  20. baseRegisterer := NewBaseRegisterer()
  21. oauthCallbackRegisterer := NewOAuthCallbackRegisterer()
  22. releaseRegisterer := NewReleaseScopedRegisterer()
  23. namespaceRegisterer := NewNamespaceScopedRegisterer(releaseRegisterer)
  24. clusterRegisterer := NewClusterScopedRegisterer(namespaceRegisterer)
  25. infraRegisterer := NewInfraScopedRegisterer()
  26. gitInstallationRegisterer := NewGitInstallationScopedRegisterer()
  27. registryRegisterer := NewRegistryScopedRegisterer()
  28. helmRepoRegisterer := NewHelmRepoScopedRegisterer()
  29. inviteRegisterer := NewInviteScopedRegisterer()
  30. projectIntegrationRegisterer := NewProjectIntegrationScopedRegisterer()
  31. projectOAuthRegisterer := NewProjectOAuthScopedRegisterer()
  32. slackIntegrationRegisterer := NewSlackIntegrationScopedRegisterer()
  33. projRegisterer := NewProjectScopedRegisterer(
  34. clusterRegisterer,
  35. registryRegisterer,
  36. helmRepoRegisterer,
  37. inviteRegisterer,
  38. gitInstallationRegisterer,
  39. infraRegisterer,
  40. projectIntegrationRegisterer,
  41. projectOAuthRegisterer,
  42. slackIntegrationRegisterer,
  43. )
  44. userRegisterer := NewUserScopedRegisterer(projRegisterer)
  45. panicMW := middleware.NewPanicMiddleware(config)
  46. if config.ServerConf.PprofEnabled {
  47. r.Mount("/debug", chiMiddleware.Profiler())
  48. }
  49. r.Route("/api", func(r chi.Router) {
  50. // set panic middleware for all API endpoints to catch panics
  51. r.Use(panicMW.Middleware)
  52. // set the content type for all API endpoints and log all request info
  53. r.Use(middleware.ContentTypeJSON)
  54. baseRoutes := baseRegisterer.GetRoutes(
  55. r,
  56. config,
  57. &types.Path{
  58. RelativePath: "",
  59. },
  60. endpointFactory,
  61. )
  62. oauthCallbackRoutes := oauthCallbackRegisterer.GetRoutes(
  63. r,
  64. config,
  65. &types.Path{
  66. RelativePath: "",
  67. },
  68. endpointFactory,
  69. )
  70. userRoutes := userRegisterer.GetRoutes(
  71. r,
  72. config,
  73. &types.Path{
  74. RelativePath: "",
  75. },
  76. endpointFactory,
  77. userRegisterer.Children...,
  78. )
  79. routes := [][]*Route{
  80. baseRoutes,
  81. userRoutes,
  82. oauthCallbackRoutes,
  83. }
  84. var allRoutes []*Route
  85. for _, r := range routes {
  86. allRoutes = append(allRoutes, r...)
  87. }
  88. registerRoutes(config, allRoutes)
  89. })
  90. staticFilePath := config.ServerConf.StaticFilePath
  91. fs := http.FileServer(http.Dir(staticFilePath))
  92. r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
  93. w.Header().Set("X-Frame-Options", "DENY")
  94. if _, err := os.Stat(staticFilePath + r.RequestURI); os.IsNotExist(err) {
  95. w.Header().Set("Cache-Control", "no-cache")
  96. http.StripPrefix(r.URL.Path, fs).ServeHTTP(w, r)
  97. } else {
  98. // Set static files involving html, js, or empty cache to "no-cache", which means they must be validated
  99. // for changes before the browser uses the cache
  100. if base := path.Base(r.URL.Path); strings.Contains(base, "html") || strings.Contains(base, "js") || base == "." || base == "/" {
  101. w.Header().Set("Cache-Control", "no-cache")
  102. }
  103. fs.ServeHTTP(w, r)
  104. }
  105. })
  106. return r
  107. }
  108. type Route struct {
  109. Endpoint *shared.APIEndpoint
  110. Handler http.Handler
  111. Router chi.Router
  112. }
  113. type Registerer struct {
  114. GetRoutes func(
  115. r chi.Router,
  116. config *config.Config,
  117. basePath *types.Path,
  118. factory shared.APIEndpointFactory,
  119. children ...*Registerer,
  120. ) []*Route
  121. Children []*Registerer
  122. }
  123. func registerRoutes(config *config.Config, routes []*Route) {
  124. // Create a new "user-scoped" factory which will create a new user-scoped request
  125. // after authentication. Each subsequent http.Handler can lookup the user in context.
  126. authNFactory := authn.NewAuthNFactory(config)
  127. // Create a new "project-scoped" factory which will create a new project-scoped request
  128. // after authorization. Each subsequent http.Handler can lookup the project in context.
  129. projFactory := authz.NewProjectScopedFactory(config)
  130. // Create a new "cluster-scoped" factory which will create a new cluster-scoped request
  131. // after authorization. Each subsequent http.Handler can lookup the cluster in context.
  132. clusterFactory := authz.NewClusterScopedFactory(config)
  133. // Create a new "namespace-scoped" factory which will create a new namespace-scoped request
  134. // after authorization. Each subsequent http.Handler can lookup the namespace in context.
  135. namespaceFactory := authz.NewNamespaceScopedFactory(config)
  136. // Create a new "helmrepo-scoped" factory which will create a new helmrepo-scoped request
  137. // after authorization. Each subsequent http.Handler can lookup the helm repo in context.
  138. helmRepoFactory := authz.NewHelmRepoScopedFactory(config)
  139. // Create a new "registry-scoped" factory which will create a new registry-scoped request
  140. // after authorization. Each subsequent http.Handler can lookup the registry in context.
  141. registryFactory := authz.NewRegistryScopedFactory(config)
  142. // Create a new "gitinstallation-scoped" factory which will create a new gitinstallation-scoped request
  143. // after authorization. Each subsequent http.Handler can lookup the gitinstallation in context.
  144. gitInstallationFactory := authz.NewGitInstallationScopedFactory(config)
  145. // Create a new "invite-scoped" factory which will create a new invite-scoped request
  146. // after authorization. Each subsequent http.Handler can lookup the invite in context.
  147. inviteFactory := authz.NewInviteScopedFactory(config)
  148. // Create a new "infra-scoped" factory which will create a new infra-scoped request
  149. // after authorization. Each subsequent http.Handler can lookup the infra in context.
  150. infraFactory := authz.NewInfraScopedFactory(config)
  151. // Create a new "operation-scoped" factory which will create a new operation-scoped request
  152. // after authorization. Each subsequent http.Handler can lookup the operation in context.
  153. operationFactory := authz.NewOperationScopedFactory(config)
  154. // Create a new "release-scoped" factory which will create a new release-scoped request
  155. // after authorization. Each subsequent http.Handler can lookup the release in context.
  156. releaseFactory := authz.NewReleaseScopedFactory(config)
  157. // Policy doc loader loads the policy documents for a specific project.
  158. policyDocLoader := policy.NewBasicPolicyDocumentLoader(config.Repo.Project())
  159. // set up logging middleware to log information about the request
  160. loggerMw := middleware.NewRequestLoggerMiddleware(config.Logger)
  161. // websocket middleware for upgrading requests
  162. websocketMw := middleware.NewWebsocketMiddleware(config)
  163. for _, route := range routes {
  164. atomicGroup := route.Router.Group(nil)
  165. for _, scope := range route.Endpoint.Metadata.Scopes {
  166. switch scope {
  167. case types.UserScope:
  168. // if the endpoint should redirect when authn fails, attach redirect handler
  169. if route.Endpoint.Metadata.ShouldRedirect {
  170. atomicGroup.Use(authNFactory.NewAuthenticatedWithRedirect)
  171. } else {
  172. atomicGroup.Use(authNFactory.NewAuthenticated)
  173. }
  174. case types.ProjectScope:
  175. policyFactory := authz.NewPolicyMiddleware(config, *route.Endpoint.Metadata, policyDocLoader)
  176. atomicGroup.Use(policyFactory.Middleware)
  177. atomicGroup.Use(projFactory.Middleware)
  178. case types.ClusterScope:
  179. atomicGroup.Use(clusterFactory.Middleware)
  180. case types.NamespaceScope:
  181. atomicGroup.Use(namespaceFactory.Middleware)
  182. case types.HelmRepoScope:
  183. atomicGroup.Use(helmRepoFactory.Middleware)
  184. case types.RegistryScope:
  185. atomicGroup.Use(registryFactory.Middleware)
  186. case types.InviteScope:
  187. atomicGroup.Use(inviteFactory.Middleware)
  188. case types.GitInstallationScope:
  189. atomicGroup.Use(gitInstallationFactory.Middleware)
  190. case types.InfraScope:
  191. atomicGroup.Use(infraFactory.Middleware)
  192. case types.OperationScope:
  193. atomicGroup.Use(operationFactory.Middleware)
  194. case types.ReleaseScope:
  195. atomicGroup.Use(releaseFactory.Middleware)
  196. }
  197. }
  198. if !route.Endpoint.Metadata.Quiet {
  199. atomicGroup.Use(loggerMw.Middleware)
  200. }
  201. if route.Endpoint.Metadata.IsWebsocket {
  202. atomicGroup.Use(websocketMw.Middleware)
  203. }
  204. if route.Endpoint.Metadata.CheckUsage && config.ServerConf.UsageTrackingEnabled {
  205. usageMW := middleware.NewUsageMiddleware(config, route.Endpoint.Metadata.UsageMetric)
  206. atomicGroup.Use(usageMW.Middleware)
  207. }
  208. atomicGroup.Method(
  209. string(route.Endpoint.Metadata.Method),
  210. route.Endpoint.Metadata.Path.RelativePath,
  211. route.Handler,
  212. )
  213. }
  214. }