2
0

api.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. package api
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strconv"
  6. "strings"
  7. "github.com/go-playground/locales/en"
  8. ut "github.com/go-playground/universal-translator"
  9. vr "github.com/go-playground/validator/v10"
  10. "github.com/porter-dev/porter/api/server/shared/config/env"
  11. "github.com/porter-dev/porter/internal/auth/sessionstore"
  12. "github.com/porter-dev/porter/internal/auth/token"
  13. "github.com/porter-dev/porter/internal/kubernetes/local"
  14. "github.com/porter-dev/porter/internal/notifier/sendgrid"
  15. "github.com/porter-dev/porter/internal/oauth"
  16. "golang.org/x/oauth2"
  17. "gorm.io/gorm"
  18. "github.com/gorilla/sessions"
  19. "github.com/porter-dev/porter/internal/helm"
  20. "github.com/porter-dev/porter/internal/helm/loader"
  21. "github.com/porter-dev/porter/internal/kubernetes"
  22. lr "github.com/porter-dev/porter/internal/logger"
  23. notif "github.com/porter-dev/porter/internal/notifier"
  24. "github.com/porter-dev/porter/internal/repository"
  25. "github.com/porter-dev/porter/internal/validator"
  26. "helm.sh/helm/v3/pkg/storage"
  27. "github.com/porter-dev/porter/internal/analytics"
  28. )
  29. // TestAgents are the k8s agents used for testing
  30. type TestAgents struct {
  31. HelmAgent *helm.Agent
  32. HelmTestStorageDriver *storage.Storage
  33. K8sAgent *kubernetes.Agent
  34. }
  35. // AppConfig is the configuration required for creating a new App
  36. type AppConfig struct {
  37. Version string
  38. DB *gorm.DB
  39. Logger *lr.Logger
  40. Repository repository.Repository
  41. ServerConf env.ServerConf
  42. RedisConf *env.RedisConf
  43. DBConf env.DBConf
  44. CapConf config.CapConf
  45. // TestAgents if API is in testing mode
  46. TestAgents *TestAgents
  47. }
  48. // App represents an API instance with handler methods attached, a DB connection
  49. // and a logger instance
  50. type App struct {
  51. // Server configuration
  52. ServerConf env.ServerConf
  53. // Logger for logging
  54. Logger *lr.Logger
  55. // Repo implements a query repository
  56. Repo repository.Repository
  57. // session store for cookie-based sessions
  58. Store sessions.Store
  59. // agents exposed for testing
  60. TestAgents *TestAgents
  61. // An in-cluster agent if service is running in cluster
  62. ProvisionerAgent *kubernetes.Agent
  63. IngressAgent *kubernetes.Agent
  64. // redis client for redis connection
  65. RedisConf *env.RedisConf
  66. // config for db
  67. DBConf env.DBConf
  68. // config for capabilities
  69. Capabilities *AppCapabilities
  70. // ChartLookupURLs contains an in-memory store of Porter chart names matched with
  71. // a repo URL, so that finding a chart does not involve multiple lookups to our
  72. // chart repo's index.yaml file
  73. ChartLookupURLs map[string]string
  74. // oauth-specific clients
  75. GithubUserConf *oauth2.Config
  76. GithubProjectConf *oauth2.Config
  77. GithubAppConf *oauth.GithubAppConf
  78. DOConf *oauth2.Config
  79. GoogleUserConf *oauth2.Config
  80. SlackConf *oauth2.Config
  81. <<<<<<< HEAD
  82. db *gorm.DB
  83. validator *vr.Validate
  84. translator *ut.Translator
  85. tokenConf *token.TokenGeneratorConf
  86. analyticsClient analytics.AnalyticsSegmentClient
  87. notifier notif.UserNotifier
  88. =======
  89. // analytics client for reporting
  90. AnalyticsClient analytics.AnalyticsSegmentClient
  91. db *gorm.DB
  92. validator *vr.Validate
  93. translator *ut.Translator
  94. tokenConf *token.TokenGeneratorConf
  95. >>>>>>> master
  96. }
  97. type AppCapabilities struct {
  98. Version string `json:"version"`
  99. Provisioning bool `json:"provisioner"`
  100. Github bool `json:"github"`
  101. BasicLogin bool `json:"basic_login"`
  102. GithubLogin bool `json:"github_login"`
  103. GoogleLogin bool `json:"google_login"`
  104. SlackNotifications bool `json:"slack_notifs"`
  105. Email bool `json:"email"`
  106. Analytics bool `json:"analytics"`
  107. }
  108. // New returns a new App instance
  109. func New(conf *AppConfig) (*App, error) {
  110. // create a new validator and translator
  111. validator := validator.New()
  112. en := en.New()
  113. uni := ut.New(en, en)
  114. translator, found := uni.GetTranslator("en")
  115. if !found {
  116. return nil, fmt.Errorf("could not find \"en\" translator")
  117. }
  118. app := &App{
  119. Logger: conf.Logger,
  120. Repo: conf.Repository,
  121. ServerConf: conf.ServerConf,
  122. RedisConf: conf.RedisConf,
  123. DBConf: conf.DBConf,
  124. TestAgents: conf.TestAgents,
  125. Capabilities: &AppCapabilities{
  126. Version: conf.Version,
  127. },
  128. db: conf.DB,
  129. validator: validator,
  130. translator: &translator,
  131. }
  132. // if repository not specified, default to in-memory
  133. // if app.Repo == nil {
  134. // app.Repo = test.NewRepository(true)
  135. // }
  136. // create the session store
  137. store, err := sessionstore.NewStore(
  138. &sessionstore.NewStoreOpts{
  139. SessionRepository: app.Repo.Session(),
  140. CookieSecrets: app.ServerConf.CookieSecrets,
  141. },
  142. )
  143. if err != nil {
  144. return nil, err
  145. }
  146. app.Store = store
  147. sc := conf.ServerConf
  148. // get the InClusterAgent from either a file-based kubeconfig or the in-cluster agent
  149. app.assignProvisionerAgent(&sc)
  150. app.assignIngressAgent(&sc)
  151. // if server config contains OAuth client info, create clients
  152. if sc.GithubClientID != "" && sc.GithubClientSecret != "" {
  153. app.Capabilities.Github = true
  154. app.GithubUserConf = oauth.NewGithubClient(&oauth.Config{
  155. ClientID: sc.GithubClientID,
  156. ClientSecret: sc.GithubClientSecret,
  157. Scopes: []string{"read:user", "user:email"},
  158. BaseURL: sc.ServerURL,
  159. })
  160. app.GithubProjectConf = oauth.NewGithubClient(&oauth.Config{
  161. ClientID: sc.GithubClientID,
  162. ClientSecret: sc.GithubClientSecret,
  163. Scopes: []string{"repo", "read:user", "workflow"},
  164. BaseURL: sc.ServerURL,
  165. })
  166. app.Capabilities.GithubLogin = sc.GithubLoginEnabled
  167. }
  168. if sc.GithubAppClientID != "" &&
  169. sc.GithubAppClientSecret != "" &&
  170. sc.GithubAppName != "" &&
  171. sc.GithubAppWebhookSecret != "" &&
  172. sc.GithubAppSecretPath != "" &&
  173. sc.GithubAppID != "" {
  174. if AppID, err := strconv.ParseInt(sc.GithubAppID, 10, 64); err == nil {
  175. app.GithubAppConf = oauth.NewGithubAppClient(&oauth.Config{
  176. ClientID: sc.GithubAppClientID,
  177. ClientSecret: sc.GithubAppClientSecret,
  178. Scopes: []string{"read:user"},
  179. BaseURL: sc.ServerURL,
  180. }, sc.GithubAppName, sc.GithubAppWebhookSecret, sc.GithubAppSecretPath, AppID)
  181. }
  182. }
  183. if sc.GoogleClientID != "" && sc.GoogleClientSecret != "" {
  184. app.Capabilities.GoogleLogin = true
  185. app.GoogleUserConf = oauth.NewGoogleClient(&oauth.Config{
  186. ClientID: sc.GoogleClientID,
  187. ClientSecret: sc.GoogleClientSecret,
  188. Scopes: []string{
  189. "openid",
  190. "profile",
  191. "email",
  192. },
  193. BaseURL: sc.ServerURL,
  194. })
  195. }
  196. if sc.SlackClientID != "" && sc.SlackClientSecret != "" {
  197. app.Capabilities.SlackNotifications = true
  198. app.SlackConf = oauth.NewSlackClient(&oauth.Config{
  199. ClientID: sc.SlackClientID,
  200. ClientSecret: sc.SlackClientSecret,
  201. Scopes: []string{
  202. "incoming-webhook",
  203. "team:read",
  204. },
  205. BaseURL: sc.ServerURL,
  206. })
  207. }
  208. if sc.DOClientID != "" && sc.DOClientSecret != "" {
  209. app.DOConf = oauth.NewDigitalOceanClient(&oauth.Config{
  210. ClientID: sc.DOClientID,
  211. ClientSecret: sc.DOClientSecret,
  212. Scopes: []string{"read", "write"},
  213. BaseURL: sc.ServerURL,
  214. })
  215. }
  216. if sc.SendgridAPIKey != "" {
  217. app.Capabilities.Email = true
  218. sgClient := &sendgrid.Client{
  219. APIKey: sc.SendgridAPIKey,
  220. PWResetTemplateID: sc.SendgridPWResetTemplateID,
  221. PWGHTemplateID: sc.SendgridPWGHTemplateID,
  222. VerifyEmailTemplateID: sc.SendgridVerifyEmailTemplateID,
  223. ProjectInviteTemplateID: sc.SendgridProjectInviteTemplateID,
  224. SenderEmail: sc.SendgridSenderEmail,
  225. }
  226. app.notifier = sendgrid.NewUserNotifier(sgClient)
  227. }
  228. app.Capabilities.Analytics = sc.SegmentClientKey != ""
  229. app.Capabilities.BasicLogin = sc.BasicLoginEnabled
  230. app.tokenConf = &token.TokenGeneratorConf{
  231. TokenSecret: conf.ServerConf.TokenGeneratorSecret,
  232. }
  233. newSegmentClient := analytics.InitializeAnalyticsSegmentClient(sc.SegmentClientKey, app.Logger)
  234. app.AnalyticsClient = newSegmentClient
  235. app.updateChartRepoURLs()
  236. return app, nil
  237. }
  238. func (app *App) assignProvisionerAgent(sc *env.ServerConf) error {
  239. if sc.ProvisionerCluster == "kubeconfig" && sc.SelfKubeconfig != "" {
  240. app.Capabilities.Provisioning = true
  241. agent, err := local.GetSelfAgentFromFileConfig(sc.SelfKubeconfig)
  242. if err != nil {
  243. return fmt.Errorf("could not get in-cluster agent: %v", err)
  244. }
  245. app.ProvisionerAgent = agent
  246. return nil
  247. } else if sc.ProvisionerCluster == "kubeconfig" {
  248. return fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
  249. }
  250. app.Capabilities.Provisioning = true
  251. agent, err := kubernetes.GetAgentInClusterConfig()
  252. if err != nil {
  253. return fmt.Errorf("could not get in-cluster agent: %v", err)
  254. }
  255. app.ProvisionerAgent = agent
  256. return nil
  257. }
  258. func (app *App) assignIngressAgent(sc *env.ServerConf) error {
  259. if sc.IngressCluster == "kubeconfig" && sc.SelfKubeconfig != "" {
  260. agent, err := local.GetSelfAgentFromFileConfig(sc.SelfKubeconfig)
  261. if err != nil {
  262. return fmt.Errorf("could not get in-cluster agent: %v", err)
  263. }
  264. app.IngressAgent = agent
  265. return nil
  266. } else if sc.IngressCluster == "kubeconfig" {
  267. return fmt.Errorf(`"kubeconfig" cluster option requires path to kubeconfig`)
  268. }
  269. agent, err := kubernetes.GetAgentInClusterConfig()
  270. if err != nil {
  271. return fmt.Errorf("could not get in-cluster agent: %v", err)
  272. }
  273. app.IngressAgent = agent
  274. return nil
  275. }
  276. func (app *App) getTokenFromRequest(r *http.Request) *token.Token {
  277. reqToken := r.Header.Get("Authorization")
  278. splitToken := strings.Split(reqToken, "Bearer")
  279. if len(splitToken) != 2 {
  280. return nil
  281. }
  282. reqToken = strings.TrimSpace(splitToken[1])
  283. tok, err := token.GetTokenFromEncoded(reqToken, app.tokenConf)
  284. if err != nil {
  285. return nil
  286. }
  287. return tok
  288. }
  289. func (app *App) updateChartRepoURLs() {
  290. newCharts := make(map[string]string)
  291. for _, chartRepo := range []string{
  292. app.ServerConf.DefaultApplicationHelmRepoURL,
  293. app.ServerConf.DefaultAddonHelmRepoURL,
  294. } {
  295. indexFile, err := loader.LoadRepoIndexPublic(chartRepo)
  296. if err != nil {
  297. continue
  298. }
  299. for chartName, _ := range indexFile.Entries {
  300. newCharts[chartName] = chartRepo
  301. }
  302. }
  303. app.ChartLookupURLs = newCharts
  304. }