cluster.go 5.6 KB

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