cluster.go 6.6 KB

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