cluster.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package authz
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "github.com/porter-dev/porter/api/server/shared/apierrors"
  7. "github.com/porter-dev/porter/api/server/shared/config"
  8. "github.com/porter-dev/porter/api/types"
  9. "github.com/porter-dev/porter/internal/helm"
  10. "github.com/porter-dev/porter/internal/kubernetes"
  11. "github.com/porter-dev/porter/internal/models"
  12. "gorm.io/gorm"
  13. "k8s.io/client-go/dynamic"
  14. )
  15. const KubernetesAgentCtxKey string = "k8s-agent"
  16. const KubernetesDynamicClientCtxKey string = "k8s-dyn-client"
  17. const HelmAgentCtxKey string = "helm-agent"
  18. type ClusterScopedFactory struct {
  19. config *config.Config
  20. }
  21. func NewClusterScopedFactory(
  22. config *config.Config,
  23. ) *ClusterScopedFactory {
  24. return &ClusterScopedFactory{config}
  25. }
  26. func (p *ClusterScopedFactory) Middleware(next http.Handler) http.Handler {
  27. return &ClusterScopedMiddleware{next, p.config}
  28. }
  29. type ClusterScopedMiddleware struct {
  30. next http.Handler
  31. config *config.Config
  32. }
  33. func (p *ClusterScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  34. // read the project to check scopes
  35. proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  36. // get the cluster id from the URL param context
  37. reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
  38. clusterID := reqScopes[types.ClusterScope].Resource.UInt
  39. cluster, err := p.config.Repo.Cluster().ReadCluster(proj.ID, clusterID)
  40. if err != nil {
  41. if err == gorm.ErrRecordNotFound {
  42. apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrForbidden(
  43. fmt.Errorf("cluster with id %d not found in project %d", clusterID, proj.ID),
  44. ))
  45. } else {
  46. apierrors.HandleAPIError(p.config, w, r, apierrors.NewErrInternal(err))
  47. }
  48. return
  49. }
  50. ctx := NewClusterContext(r.Context(), cluster)
  51. r = r.WithContext(ctx)
  52. p.next.ServeHTTP(w, r)
  53. }
  54. func NewClusterContext(ctx context.Context, cluster *models.Cluster) context.Context {
  55. return context.WithValue(ctx, types.ClusterScope, cluster)
  56. }
  57. type KubernetesAgentGetter interface {
  58. GetOutOfClusterConfig(cluster *models.Cluster) *kubernetes.OutOfClusterConfig
  59. GetDynamicClient(r *http.Request, cluster *models.Cluster) (dynamic.Interface, error)
  60. GetAgent(r *http.Request, cluster *models.Cluster) (*kubernetes.Agent, error)
  61. GetHelmAgent(r *http.Request, cluster *models.Cluster) (*helm.Agent, error)
  62. }
  63. type OutOfClusterAgentGetter struct {
  64. config *config.Config
  65. }
  66. func NewOutOfClusterAgentGetter(config *config.Config) KubernetesAgentGetter {
  67. return &OutOfClusterAgentGetter{config}
  68. }
  69. func (d *OutOfClusterAgentGetter) GetOutOfClusterConfig(cluster *models.Cluster) *kubernetes.OutOfClusterConfig {
  70. return &kubernetes.OutOfClusterConfig{
  71. Repo: d.config.Repo,
  72. DigitalOceanOAuth: d.config.DOConf,
  73. Cluster: cluster,
  74. }
  75. }
  76. func (d *OutOfClusterAgentGetter) GetAgent(r *http.Request, cluster *models.Cluster) (*kubernetes.Agent, error) {
  77. // look for the agent in context
  78. ctxAgentVal := r.Context().Value(KubernetesAgentCtxKey)
  79. if ctxAgentVal != nil {
  80. if agent, ok := ctxAgentVal.(*kubernetes.Agent); ok {
  81. return agent, nil
  82. }
  83. }
  84. // if agent not found in context, get the agent from out of cluster config
  85. ooc := d.GetOutOfClusterConfig(cluster)
  86. agent, err := kubernetes.GetAgentOutOfClusterConfig(ooc)
  87. if err != nil {
  88. return nil, fmt.Errorf("failed to get agent: %s", err.Error())
  89. }
  90. newCtx := context.WithValue(r.Context(), KubernetesAgentCtxKey, agent)
  91. r = r.WithContext(newCtx)
  92. return agent, nil
  93. }
  94. func (d *OutOfClusterAgentGetter) GetHelmAgent(r *http.Request, cluster *models.Cluster) (*helm.Agent, error) {
  95. // look for the agent in context
  96. ctxAgentVal := r.Context().Value(HelmAgentCtxKey)
  97. if ctxAgentVal != nil {
  98. if agent, ok := ctxAgentVal.(*helm.Agent); ok {
  99. return agent, nil
  100. }
  101. }
  102. // if helm agent not found in context, construct it from k8s agent
  103. k8sAgent, err := d.GetAgent(r, cluster)
  104. if err != nil {
  105. return nil, err
  106. }
  107. // look for namespace in context, otherwise go with default
  108. reqScopes, _ := r.Context().Value(types.RequestScopeCtxKey).(map[types.PermissionScope]*types.RequestAction)
  109. namespace := "default"
  110. if nsPolicy, ok := reqScopes[types.NamespaceScope]; ok && nsPolicy.Resource.Name != "" {
  111. namespace = nsPolicy.Resource.Name
  112. }
  113. helmAgent, err := helm.GetAgentFromK8sAgent("secret", namespace, d.config.Logger, k8sAgent)
  114. if err != nil {
  115. return nil, fmt.Errorf("failed to get Helm agent: %s", err.Error())
  116. }
  117. newCtx := context.WithValue(r.Context(), HelmAgentCtxKey, helmAgent)
  118. r = r.WithContext(newCtx)
  119. return helmAgent, nil
  120. }
  121. func (d *OutOfClusterAgentGetter) GetDynamicClient(r *http.Request, cluster *models.Cluster) (dynamic.Interface, error) {
  122. // look for the agent in context
  123. ctxDynClientVal := r.Context().Value(KubernetesDynamicClientCtxKey)
  124. if ctxDynClientVal != nil {
  125. if dynClient, ok := ctxDynClientVal.(dynamic.Interface); ok {
  126. return dynClient, nil
  127. }
  128. }
  129. return kubernetes.GetDynamicClientOutOfClusterConfig(d.GetOutOfClusterConfig(cluster))
  130. }