diff --git a/api/argoApplication/ArgoApplicationRestHandler.go b/api/argoApplication/ArgoApplicationRestHandler.go index edcbabdca9..5b816db2cd 100644 --- a/api/argoApplication/ArgoApplicationRestHandler.go +++ b/api/argoApplication/ArgoApplicationRestHandler.go @@ -21,8 +21,10 @@ import ( "errors" "github.com/devtron-labs/devtron/api/restHandler/common" "github.com/devtron-labs/devtron/pkg/argoApplication" + "github.com/devtron-labs/devtron/pkg/argoApplication/bean" "github.com/devtron-labs/devtron/pkg/argoApplication/read" "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" + "github.com/devtron-labs/devtron/util/rbac" "go.uber.org/zap" "net/http" "strconv" @@ -39,26 +41,24 @@ type ArgoApplicationRestHandlerImpl struct { readService read.ArgoApplicationReadService logger *zap.SugaredLogger enforcer casbin.Enforcer + enforcerUtilGitOps rbac.EnforcerUtilGitOps } func NewArgoApplicationRestHandlerImpl(argoApplicationService argoApplication.ArgoApplicationService, - readService read.ArgoApplicationReadService, logger *zap.SugaredLogger, enforcer casbin.Enforcer) *ArgoApplicationRestHandlerImpl { + readService read.ArgoApplicationReadService, logger *zap.SugaredLogger, enforcer casbin.Enforcer, + enforcerUtilGitOps rbac.EnforcerUtilGitOps) *ArgoApplicationRestHandlerImpl { return &ArgoApplicationRestHandlerImpl{ argoApplicationService: argoApplicationService, readService: readService, logger: logger, enforcer: enforcer, + enforcerUtilGitOps: enforcerUtilGitOps, } } func (handler *ArgoApplicationRestHandlerImpl) ListApplications(w http.ResponseWriter, r *http.Request) { - // handle super-admin RBAC token := r.Header.Get("token") - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { - common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) - return - } v := r.URL.Query() clusterIdString := v.Get("clusterIds") var clusterIds []int @@ -80,16 +80,34 @@ func (handler *ArgoApplicationRestHandlerImpl) ListApplications(w http.ResponseW common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) return } - common.WriteJsonResp(w, nil, resp, http.StatusOK) + // RBAC enforcer applying: filter the listing to the applications the caller may see. + // Batched rather than a per-app Enforce loop; an app whose object cannot be built is dropped. + objects := make([]string, 0, len(resp)) + objectByApp := make(map[*bean.ArgoApplicationListDto]string, len(resp)) + for _, app := range resp { + object := handler.enforcerUtilGitOps.GetExternalGitOpsAppObjectByClusterName(app.ClusterName, app.Namespace, app.Name) + if len(object) == 0 { + continue + } + objectByApp[app] = object + objects = append(objects, object) + } + authorisedObjects := make(map[string]bool) + if len(objects) > 0 { + authorisedObjects = handler.enforcer.EnforceInBatch(token, casbin.ResourceArgoApp, casbin.ActionGet, objects) + } + authorisedApps := make([]*bean.ArgoApplicationListDto, 0, len(resp)) + for _, app := range resp { + if object, ok := objectByApp[app]; ok && authorisedObjects[strings.ToLower(object)] { + authorisedApps = append(authorisedApps, app) + } + } + //RBAC enforcer Ends + common.WriteJsonResp(w, nil, authorisedApps, http.StatusOK) } func (handler *ArgoApplicationRestHandlerImpl) GetApplicationDetail(w http.ResponseWriter, r *http.Request) { - // handle super-admin RBAC token := r.Header.Get("token") - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { - common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) - return - } ctx := r.Context() ctx = context.WithValue(ctx, "token", token) @@ -108,6 +126,14 @@ func (handler *ArgoApplicationRestHandlerImpl) GetApplicationDetail(w http.Respo return } } + // RBAC enforcer applying + object := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(clusterId, namespace, resourceName) + if len(object) == 0 || !handler.enforcer.Enforce(token, casbin.ResourceArgoApp, casbin.ActionGet, object) { + common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) + return + } + //RBAC enforcer Ends + resp, err := handler.readService.GetAppDetailEA(ctx, resourceName, namespace, clusterId) if err != nil { handler.logger.Errorw("error in getting argo application app detail", "err", err, "resourceName", resourceName, "clusterId", clusterId) diff --git a/api/auth/user/UserRestHandler.go b/api/auth/user/UserRestHandler.go index 37188c14bd..8e617250b3 100644 --- a/api/auth/user/UserRestHandler.go +++ b/api/auth/user/UserRestHandler.go @@ -19,13 +19,14 @@ package user import ( "encoding/json" "errors" + "net/http" + "strconv" + "strings" + util2 "github.com/devtron-labs/devtron/api/auth/user/util" "github.com/devtron-labs/devtron/pkg/auth/user/helper" "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" "github.com/gorilla/schema" - "net/http" - "strconv" - "strings" "github.com/devtron-labs/devtron/api/restHandler/common" "github.com/devtron-labs/devtron/internal/util" @@ -38,6 +39,16 @@ import ( "gopkg.in/go-playground/validator.v9" ) +const ( + resultKeyArgoAppAccess = "hasArgoAppAccess" + resultKeyFluxAppAccess = "hasFluxAppAccess" +) + +var gitOpsAccessResultKeyByResource = map[string]string{ + casbin.ResourceArgoApp: resultKeyArgoAppAccess, + casbin.ResourceFluxApp: resultKeyFluxAppAccess, +} + type UserRestHandler interface { CreateUser(w http.ResponseWriter, r *http.Request) UpdateUser(w http.ResponseWriter, r *http.Request) @@ -795,9 +806,25 @@ func (handler UserRestHandlerImpl) CheckUserRoles(w http.ResponseWriter, r *http result := make(map[string]interface{}) result["roles"] = roles result["superAdmin"] = false + result[resultKeyArgoAppAccess] = false + result[resultKeyFluxAppAccess] = false for _, item := range roles { if item == bean2.SUPERADMIN { result["superAdmin"] = true + result[resultKeyArgoAppAccess] = true + result[resultKeyFluxAppAccess] = true + continue + } + + roleFragments := strings.Split(item, "_") + resourceActionFragment := strings.Split(roleFragments[0], ":") + + if len(resourceActionFragment) < 2 { + continue + } + + if resultKey, ok := gitOpsAccessResultKeyByResource[resourceActionFragment[0]]; ok { + result[resultKey] = true } } common.WriteJsonResp(w, err, result, http.StatusOK) diff --git a/api/fluxApplication/FluxApplicationRestHandler.go b/api/fluxApplication/FluxApplicationRestHandler.go index f1ef3d616c..bc1b9c3c02 100644 --- a/api/fluxApplication/FluxApplicationRestHandler.go +++ b/api/fluxApplication/FluxApplicationRestHandler.go @@ -5,6 +5,7 @@ import ( "github.com/devtron-labs/devtron/api/restHandler/common" "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" clientErrors "github.com/devtron-labs/devtron/pkg/errors" + "github.com/devtron-labs/devtron/util/rbac" "github.com/devtron-labs/devtron/pkg/fluxApplication" "github.com/gorilla/mux" "go.uber.org/zap" @@ -20,26 +21,34 @@ type FluxApplicationRestHandlerImpl struct { fluxApplicationService fluxApplication.FluxApplicationService logger *zap.SugaredLogger enforcer casbin.Enforcer + enforcerUtilGitOps rbac.EnforcerUtilGitOps } func NewFluxApplicationRestHandlerImpl(fluxApplicationService fluxApplication.FluxApplicationService, - logger *zap.SugaredLogger, enforcer casbin.Enforcer) *FluxApplicationRestHandlerImpl { + logger *zap.SugaredLogger, enforcer casbin.Enforcer, + enforcerUtilGitOps rbac.EnforcerUtilGitOps) *FluxApplicationRestHandlerImpl { return &FluxApplicationRestHandlerImpl{ fluxApplicationService: fluxApplicationService, logger: logger, enforcer: enforcer, + enforcerUtilGitOps: enforcerUtilGitOps, } } +// checkFluxAppAuth builds the RBAC object from the app identity and enforces on it. Passed into +// the service because the app list is streamed and cannot be filtered after the fact. +func (handler *FluxApplicationRestHandlerImpl) checkFluxAppAuth(token string, clusterName string, namespace string, appName string) bool { + object := handler.enforcerUtilGitOps.GetExternalGitOpsAppObjectByClusterName(clusterName, namespace, appName) + if len(object) == 0 { + return false + } + return handler.enforcer.Enforce(token, casbin.ResourceFluxApp, casbin.ActionGet, object) +} + func (handler *FluxApplicationRestHandlerImpl) ListFluxApplications(w http.ResponseWriter, r *http.Request) { - //handle super-admin RBAC token := r.Header.Get("token") - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { - common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) - return - } v := r.URL.Query() clusterIdString := v.Get("clusterIds") var clusterIds []int @@ -59,7 +68,7 @@ func (handler *FluxApplicationRestHandlerImpl) ListFluxApplications(w http.Respo return } handler.logger.Debugw("extracted ClusterIds successfully ", "clusterIds", clusterIds) - handler.fluxApplicationService.ListFluxApplications(r.Context(), clusterIds, noStream, w) + handler.fluxApplicationService.ListFluxApplications(r.Context(), clusterIds, noStream, w, token, handler.checkFluxAppAuth) } func (handler *FluxApplicationRestHandlerImpl) GetApplicationDetail(w http.ResponseWriter, r *http.Request) { @@ -76,12 +85,14 @@ func (handler *FluxApplicationRestHandlerImpl) GetApplicationDetail(w http.Respo return } - // handle super-admin RBAC + // RBAC enforcer applying token := r.Header.Get("token") - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { + object := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(appIdentifier.ClusterId, appIdentifier.Namespace, appIdentifier.Name) + if len(object) == 0 || !handler.enforcer.Enforce(token, casbin.ResourceFluxApp, casbin.ActionGet, object) { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) return } + //RBAC enforcer Ends res, err := handler.fluxApplicationService.GetFluxAppDetail(r.Context(), appIdentifier) if err != nil { diff --git a/api/helm-app/wire_helmApp.go b/api/helm-app/wire_helmApp.go index 86f45e672c..af668990bb 100644 --- a/api/helm-app/wire_helmApp.go +++ b/api/helm-app/wire_helmApp.go @@ -41,4 +41,7 @@ var HelmAppWireSet = wire.NewSet( gRPC.GetConfig, rbac.NewEnforcerUtilHelmImpl, wire.Bind(new(rbac.EnforcerUtilHelm), new(*rbac.EnforcerUtilHelmImpl)), + + rbac.NewEnforcerUtilGitOpsImpl, + wire.Bind(new(rbac.EnforcerUtilGitOps), new(*rbac.EnforcerUtilGitOpsImpl)), ) diff --git a/api/k8s/application/k8sApplicationRestHandler.go b/api/k8s/application/k8sApplicationRestHandler.go index a3d64a7ff5..8417225c8e 100644 --- a/api/k8s/application/k8sApplicationRestHandler.go +++ b/api/k8s/application/k8sApplicationRestHandler.go @@ -23,6 +23,13 @@ import ( "encoding/json" "errors" "fmt" + "io" + "net/http" + "regexp" + "strconv" + "strings" + "time" + "github.com/devtron-labs/common-lib/utils" util3 "github.com/devtron-labs/common-lib/utils/k8s" k8sCommonBean "github.com/devtron-labs/common-lib/utils/k8s/commonBean" @@ -52,14 +59,8 @@ import ( errors2 "github.com/juju/errors" "go.uber.org/zap" "gopkg.in/go-playground/validator.v9" - "io" errors3 "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "net/http" - "regexp" - "strconv" - "strings" - "time" ) type K8sApplicationRestHandler interface { @@ -91,6 +92,7 @@ type K8sApplicationRestHandlerImpl struct { validator *validator.Validate enforcerUtil rbac.EnforcerUtil enforcerUtilHelm rbac.EnforcerUtilHelm + enforcerUtilGitOps rbac.EnforcerUtilGitOps helmAppService client.HelmAppService userService user.UserService k8sCommonService k8s.K8sCommonService @@ -99,7 +101,7 @@ type K8sApplicationRestHandlerImpl struct { argoApplicationReadService read.ArgoApplicationReadService } -func NewK8sApplicationRestHandlerImpl(logger *zap.SugaredLogger, k8sApplicationService application2.K8sApplicationService, pump connector.Pump, terminalSessionHandler terminal.TerminalSessionHandler, enforcer casbin.Enforcer, enforcerUtilHelm rbac.EnforcerUtilHelm, enforcerUtil rbac.EnforcerUtil, helmAppService client.HelmAppService, userService user.UserService, k8sCommonService k8s.K8sCommonService, validator *validator.Validate, envVariables *util.EnvironmentVariables, fluxAppService fluxApplication.FluxApplicationService, argoApplicationReadService read.ArgoApplicationReadService, +func NewK8sApplicationRestHandlerImpl(logger *zap.SugaredLogger, k8sApplicationService application2.K8sApplicationService, pump connector.Pump, terminalSessionHandler terminal.TerminalSessionHandler, enforcer casbin.Enforcer, enforcerUtilHelm rbac.EnforcerUtilHelm, enforcerUtilGitOps rbac.EnforcerUtilGitOps, enforcerUtil rbac.EnforcerUtil, helmAppService client.HelmAppService, userService user.UserService, k8sCommonService k8s.K8sCommonService, validator *validator.Validate, envVariables *util.EnvironmentVariables, fluxAppService fluxApplication.FluxApplicationService, argoApplicationReadService read.ArgoApplicationReadService, ) *K8sApplicationRestHandlerImpl { return &K8sApplicationRestHandlerImpl{ logger: logger, @@ -109,6 +111,7 @@ func NewK8sApplicationRestHandlerImpl(logger *zap.SugaredLogger, k8sApplicationS enforcer: enforcer, validator: validator, enforcerUtilHelm: enforcerUtilHelm, + enforcerUtilGitOps: enforcerUtilGitOps, enforcerUtil: enforcerUtil, helmAppService: helmAppService, userService: userService, @@ -207,7 +210,20 @@ func (handler *K8sApplicationRestHandlerImpl) GetResource(w http.ResponseWriter, canUpdate := false // Obfuscate secret if user does not have edit access - if request.AppIdentifier == nil && request.DevtronAppIdentifier == nil && request.AppType != bean2.ArgoAppType && request.ClusterId > 0 { // if the appType is not argoAppType,then verify logic w.r.t resource browser, when rbac for argoApp is introduced, handle rbac accordingly + if request.AppType == bean2.ArgoAppType && request.ExternalArgoAppIdentifier != nil { + // External Argo app: edit access is decided by the app-level permission, not by the + // resource-browser cluster entity. Without this an Argo admin would never see Secret + // values, because canUpdate would stay false and the manifest would be masked below. + rbacObject := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(request.ExternalArgoAppIdentifier.ClusterId, + request.ExternalArgoAppIdentifier.Namespace, request.ExternalArgoAppIdentifier.AppName) + canUpdate = len(rbacObject) > 0 && + handler.enforcer.Enforce(token, casbin.ResourceArgoApp, casbin.ActionUpdate, rbacObject) + } else if request.AppType == bean2.FluxAppType && request.ExternalFluxAppIdentifier != nil { + rbacObject := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(request.ExternalFluxAppIdentifier.ClusterId, + request.ExternalFluxAppIdentifier.Namespace, request.ExternalFluxAppIdentifier.Name) + canUpdate = len(rbacObject) > 0 && + handler.enforcer.Enforce(token, casbin.ResourceFluxApp, casbin.ActionUpdate, rbacObject) + } else if request.AppIdentifier == nil && request.DevtronAppIdentifier == nil && request.AppType != bean2.ArgoAppType && request.ClusterId > 0 { // if the appType is not argoAppType,then verify logic w.r.t resource browser // Verify update access for Resource Browser canUpdate = handler.k8sApplicationService.ValidateClusterResourceBean(r.Context(), request.ClusterId, resource.ManifestResponse.Manifest, request.K8sRequest.ResourceIdentifier.GroupVersionKind, handler.getRbacCallbackForResource(token, casbin.ActionUpdate)) if !canUpdate { @@ -299,7 +315,8 @@ func (handler *K8sApplicationRestHandlerImpl) GetHostUrlsByBatch(w http.Response return } // RBAC enforcer applying - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { + rbacObject := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(appIdentifier.ClusterId, appIdentifier.Namespace, appIdentifier.AppName) + if len(rbacObject) == 0 || !handler.enforcer.Enforce(token, casbin.ResourceArgoApp, casbin.ActionGet, rbacObject) { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) return } @@ -326,7 +343,8 @@ func (handler *K8sApplicationRestHandlerImpl) GetHostUrlsByBatch(w http.Response return } // RBAC enforcer applying - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { + rbacObject := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(appIdentifier.ClusterId, appIdentifier.Namespace, appIdentifier.Name) + if len(rbacObject) == 0 || !handler.enforcer.Enforce(token, casbin.ResourceFluxApp, casbin.ActionGet, rbacObject) { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) return } @@ -718,7 +736,9 @@ func (handler *K8sApplicationRestHandlerImpl) requestValidationAndRBAC(w http.Re return } //RBAC enforcer starts here - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { + rbacObject := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(request.ExternalFluxAppIdentifier.ClusterId, + request.ExternalFluxAppIdentifier.Namespace, request.ExternalFluxAppIdentifier.Name) + if len(rbacObject) == 0 || !handler.enforcer.Enforce(token, casbin.ResourceFluxApp, casbin.ActionGet, rbacObject) { common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden) return } @@ -738,7 +758,8 @@ func (handler *K8sApplicationRestHandlerImpl) requestValidationAndRBAC(w http.Re } //RBAC enforcer starts here - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { + rbacObject := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(appIdentifier.ClusterId, appIdentifier.Namespace, appIdentifier.AppName) + if len(rbacObject) == 0 || !handler.enforcer.Enforce(token, casbin.ResourceArgoApp, casbin.ActionGet, rbacObject) { common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden) return } @@ -803,7 +824,9 @@ func (handler *K8sApplicationRestHandlerImpl) GetTerminalSession(w http.Response //RBAC enforcer Ends } else if resourceRequestBean.ExternalFluxAppIdentifier != nil { // RBAC enforcer applying For external flux app - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*"); !ok { + rbacObject := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(resourceRequestBean.ExternalFluxAppIdentifier.ClusterId, + resourceRequestBean.ExternalFluxAppIdentifier.Namespace, resourceRequestBean.ExternalFluxAppIdentifier.Name) + if len(rbacObject) == 0 || !handler.enforcer.Enforce(token, casbin.ResourceFluxApp, casbin.ActionUpdate, rbacObject) { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) return } @@ -811,7 +834,13 @@ func (handler *K8sApplicationRestHandlerImpl) GetTerminalSession(w http.Response } else if resourceRequestBean.ExternalArgoApplicationName != "" { // RBAC enforcer applying For external Argo app - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*"); !ok { + if resourceRequestBean.ExternalArgoAppIdentifier == nil { + common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) + return + } + rbacObject := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(resourceRequestBean.ExternalArgoAppIdentifier.ClusterId, + resourceRequestBean.ExternalArgoAppIdentifier.Namespace, resourceRequestBean.ExternalArgoAppIdentifier.AppName) + if len(rbacObject) == 0 || !handler.enforcer.Enforce(token, casbin.ResourceArgoApp, casbin.ActionUpdate, rbacObject) { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) return } @@ -1125,14 +1154,18 @@ func (handler *K8sApplicationRestHandlerImpl) handleEphemeralRBAC(podName, names //RBAC enforcer Ends } else if resourceRequestBean.ExternalFluxAppIdentifier != nil { //RBAC enforcer starts here - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { + rbacObject := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(resourceRequestBean.ExternalFluxAppIdentifier.ClusterId, + resourceRequestBean.ExternalFluxAppIdentifier.Namespace, resourceRequestBean.ExternalFluxAppIdentifier.Name) + if len(rbacObject) == 0 || !handler.enforcer.Enforce(token, casbin.ResourceFluxApp, casbin.ActionUpdate, rbacObject) { common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden) return resourceRequestBean } //RBAC enforcer ends here } else if resourceRequestBean.ExternalArgoApplicationName != "" { //RBAC enforcer starts here - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { + rbacObject := handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(resourceRequestBean.ExternalArgoAppIdentifier.ClusterId, + resourceRequestBean.ExternalArgoAppIdentifier.Namespace, resourceRequestBean.ExternalArgoAppIdentifier.AppName) + if len(rbacObject) == 0 || !handler.enforcer.Enforce(token, casbin.ResourceArgoApp, casbin.ActionUpdate, rbacObject) { common.WriteJsonResp(w, errors2.New("unauthorized"), nil, http.StatusForbidden) return resourceRequestBean } @@ -1179,7 +1212,11 @@ func (handler *K8sApplicationRestHandlerImpl) verifyRbacForAppRequests(token str return false, err } //RBAC enforcer starts here - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, actionType, "*"); !ok { + rbacObject = handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(argoAppIdentifier.ClusterId, argoAppIdentifier.Namespace, argoAppIdentifier.AppName) + if len(rbacObject) == 0 { + return false, nil + } + if ok := handler.enforcer.Enforce(token, casbin.ResourceArgoApp, actionType, rbacObject); !ok { return false, nil } return true, nil @@ -1247,7 +1284,11 @@ func (handler *K8sApplicationRestHandlerImpl) verifyRbacForAppRequests(token str return false, err } //RBAC enforcer starts here - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, actionType, "*"); !ok { + rbacObject = handler.enforcerUtilGitOps.GetExternalGitOpsAppObject(appIdentifier.ClusterId, appIdentifier.Namespace, appIdentifier.Name) + if len(rbacObject) == 0 { + return false, nil + } + if ok := handler.enforcer.Enforce(token, casbin.ResourceFluxApp, actionType, rbacObject); !ok { return false, nil } return true, nil diff --git a/api/userResource/bean/bean.go b/api/userResource/bean/bean.go index b6f5c9ea2f..375b6de21a 100644 --- a/api/userResource/bean/bean.go +++ b/api/userResource/bean/bean.go @@ -31,6 +31,8 @@ type AppAndJobReqDto struct { } type ClusterReqDto struct { *bean3.ResourceRequestBean + ClusterIds []int `json:"clusterIds,omitempty"` + EnvironmentIdentifiers []string `json:"environmentIdentifiers,omitempty"` } type JobWorkflowReqDto struct { *bean2.WorkflowNamesRequest diff --git a/cmd/external-app/wire_gen.go b/cmd/external-app/wire_gen.go index 2edf485c1b..658246eeaa 100644 --- a/cmd/external-app/wire_gen.go +++ b/cmd/external-app/wire_gen.go @@ -384,8 +384,9 @@ func InitializeApp() (*App, error) { environmentReadServiceImpl := read8.NewEnvironmentReadServiceImpl(sugaredLogger, environmentRepositoryImpl) environmentRestHandlerImpl := cluster2.NewEnvironmentRestHandlerImpl(environmentServiceImpl, environmentReadServiceImpl, sugaredLogger, userServiceImpl, validate, enforcerImpl, deleteServiceImpl, k8sServiceImpl, k8sCommonServiceImpl, commonEnforcementUtilImpl) environmentRouterImpl := cluster2.NewEnvironmentRouterImpl(environmentRestHandlerImpl) + enforcerUtilGitOpsImpl := rbac.NewEnforcerUtilGitOpsImpl(sugaredLogger, clusterReadServiceImpl) argoApplicationReadServiceImpl := read9.NewArgoApplicationReadServiceImpl(sugaredLogger, clusterRepositoryImpl, k8sServiceImpl, helmAppClientImpl, helmAppServiceImpl) - k8sApplicationRestHandlerImpl := application2.NewK8sApplicationRestHandlerImpl(sugaredLogger, k8sApplicationServiceImpl, pumpImpl, terminalSessionHandlerImpl, enforcerImpl, enforcerUtilHelmImpl, enforcerUtilImpl, helmAppServiceImpl, userServiceImpl, k8sCommonServiceImpl, validate, environmentVariables, fluxApplicationServiceImpl, argoApplicationReadServiceImpl) + k8sApplicationRestHandlerImpl := application2.NewK8sApplicationRestHandlerImpl(sugaredLogger, k8sApplicationServiceImpl, pumpImpl, terminalSessionHandlerImpl, enforcerImpl, enforcerUtilHelmImpl, enforcerUtilGitOpsImpl, enforcerUtilImpl, helmAppServiceImpl, userServiceImpl, k8sCommonServiceImpl, validate, environmentVariables, fluxApplicationServiceImpl, argoApplicationReadServiceImpl) k8sApplicationRouterImpl := application2.NewK8sApplicationRouterImpl(k8sApplicationRestHandlerImpl) chartRepositoryRestHandlerImpl := chartRepo2.NewChartRepositoryRestHandlerImpl(sugaredLogger, userServiceImpl, chartRepositoryServiceImpl, enforcerImpl, validate, deleteServiceImpl, attributesServiceImpl) chartRepositoryRouterImpl := chartRepo2.NewChartRepositoryRouterImpl(chartRepositoryRestHandlerImpl) @@ -505,11 +506,11 @@ func InitializeApp() (*App, error) { rbacRoleServiceImpl := user.NewRbacRoleServiceImpl(sugaredLogger, rbacRoleDataRepositoryImpl) rbacRoleRestHandlerImpl := user2.NewRbacRoleHandlerImpl(sugaredLogger, validate, rbacRoleServiceImpl, userServiceImpl, enforcerImpl, enforcerUtilImpl) rbacRoleRouterImpl := user2.NewRbacRoleRouterImpl(sugaredLogger, validate, rbacRoleRestHandlerImpl) - argoApplicationRestHandlerImpl := argoApplication2.NewArgoApplicationRestHandlerImpl(argoApplicationServiceImpl, argoApplicationReadServiceImpl, sugaredLogger, enforcerImpl) + argoApplicationRestHandlerImpl := argoApplication2.NewArgoApplicationRestHandlerImpl(argoApplicationServiceImpl, argoApplicationReadServiceImpl, sugaredLogger, enforcerImpl, enforcerUtilGitOpsImpl) argoApplicationRouterImpl := argoApplication2.NewArgoApplicationRouterImpl(argoApplicationRestHandlerImpl) - fluxApplicationRestHandlerImpl := fluxApplication2.NewFluxApplicationRestHandlerImpl(fluxApplicationServiceImpl, sugaredLogger, enforcerImpl) + fluxApplicationRestHandlerImpl := fluxApplication2.NewFluxApplicationRestHandlerImpl(fluxApplicationServiceImpl, sugaredLogger, enforcerImpl, enforcerUtilGitOpsImpl) fluxApplicationRouterImpl := fluxApplication2.NewFluxApplicationRouterImpl(fluxApplicationRestHandlerImpl) - userResourceServiceImpl := userResource.NewUserResourceServiceImpl(sugaredLogger, teamServiceImpl, environmentServiceImpl, clusterServiceImpl, k8sApplicationServiceImpl, enforcerUtilImpl, commonEnforcementUtilImpl, enforcerImpl, appCrudOperationServiceImpl) + userResourceServiceImpl := userResource.NewUserResourceServiceImpl(sugaredLogger, teamServiceImpl, environmentServiceImpl, clusterServiceImpl, k8sApplicationServiceImpl, enforcerUtilImpl, commonEnforcementUtilImpl, enforcerImpl, appCrudOperationServiceImpl, argoApplicationServiceImpl, fluxApplicationServiceImpl) restHandlerImpl := userResource2.NewUserResourceRestHandler(sugaredLogger, userServiceImpl, userResourceServiceImpl) routerImpl := userResource2.NewUserResourceRouterImpl(restHandlerImpl) clusterOverviewConfig, err := config4.GetClusterOverviewConfig() diff --git a/pkg/auth/authorisation/casbin/rbacpolicy.go b/pkg/auth/authorisation/casbin/rbacpolicy.go index 09423de63a..1bca4a6418 100644 --- a/pkg/auth/authorisation/casbin/rbacpolicy.go +++ b/pkg/auth/authorisation/casbin/rbacpolicy.go @@ -45,6 +45,12 @@ const ( ResourceAdmin = "admin" ResourceGlobal = "global-resource" ResourceHelmApp = "helm-app" + + // ResourceArgoApp, ResourceFluxApp are used for app-level RBAC on external + // Argo CD / Flux CD applications. Object shape is __/. + ResourceArgoApp = "argo-app" + ResourceFluxApp = "flux-app" + ActionGet = "get" ActionCreate = "create" ActionUpdate = "update" diff --git a/pkg/auth/user/UserCommonService.go b/pkg/auth/user/UserCommonService.go index 9f107d898b..f14cc1f3fb 100644 --- a/pkg/auth/user/UserCommonService.go +++ b/pkg/auth/user/UserCommonService.go @@ -18,13 +18,14 @@ package user import ( "fmt" + "math" + "strings" + "time" + bean3 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" "github.com/devtron-labs/devtron/pkg/auth/user/adapter" "github.com/devtron-labs/devtron/pkg/auth/user/repository/bean" "golang.org/x/exp/maps" - "math" - "strings" - "time" "github.com/caarlos0/env/v6" "github.com/devtron-labs/authenticator/middleware" @@ -372,6 +373,8 @@ func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPolicies(userInf if role, ok := roleIdVsRoleMap[userRoleModel.RoleId]; ok { isValidAuth := impl.checkRbacForARole(role, token, managerAuth) if !isValidAuth { + impl.logger.Warnw("not authorised to delete role, skipping", "roleId", role.Id, + "entity", role.Entity, "accessType", role.AccessType, "action", role.Action) continue } toBeDeletedUserRolesIds = append(toBeDeletedUserRolesIds, userRoleModel.Id) @@ -527,6 +530,8 @@ func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPoliciesForGroup if role, ok := roleIdVsRoleMap[model.RoleId]; ok { isValidAuth := impl.checkRbacForARole(role, token, managerAuth) if !isValidAuth { + impl.logger.Warnw("not authorised to delete role, skipping", "roleId", role.Id, + "entity", role.Entity, "accessType", role.AccessType, "action", role.Action) continue } toBeDeletedRoleGroupRoleMappingsIds = append(toBeDeletedRoleGroupRoleMappingsIds, model.Id) @@ -547,7 +552,9 @@ func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPoliciesForGroup func (impl UserCommonServiceImpl) checkRbacForARole(role *repository.RoleModel, token string, managerAuth func(resource string, token string, object string) bool) bool { isAuthorised := true switch { - case role.Action == bean2.SUPER_ADMIN || role.AccessType == bean2.APP_ACCESS_TYPE_HELM || role.Entity == bean2.EntityJobs: + case role.Action == bean2.SUPER_ADMIN || role.AccessType == bean2.APP_ACCESS_TYPE_HELM || + role.AccessType == bean2.APP_ACCESS_TYPE_ARGO || role.AccessType == bean2.APP_ACCESS_TYPE_FLUX || + role.Entity == bean2.EntityJobs: isValidAuth := managerAuth(casbin.ResourceGlobal, token, "*") if !isValidAuth { isAuthorised = false @@ -780,6 +787,18 @@ func (impl UserCommonServiceImpl) GetUniqueKeyForAllEntity(entityProcessor Entit default: key = fmt.Sprintf("%s_%s_%s_%s", entityProcessor.GetTeam(), entityProcessor.GetAction(), entityProcessor.GetAccessType(), entityProcessor.GetEntity()) } + } else if entityProcessor.GetEntity() == bean2.ENTITY_APPS { + switch baseToConsider { + case bean2.EnvironmentBasedKey: + key = fmt.Sprintf("%s_%s_%s_%s", entityProcessor.GetEntity(), entityProcessor.GetEnvironment(), + entityProcessor.GetAction(), entityProcessor.GetAccessType()) + case bean2.ApplicationBasedKey: + key = fmt.Sprintf("%s_%s_%s_%s", entityProcessor.GetEntity(), entityProcessor.GetEntityName(), + entityProcessor.GetAction(), entityProcessor.GetAccessType()) + default: + key = fmt.Sprintf("%s_%s_%s", entityProcessor.GetEntity(), entityProcessor.GetAction(), + entityProcessor.GetAccessType()) + } } else if len(entityProcessor.GetEntity()) > 0 { if entityProcessor.GetEntity() == bean2.CLUSTER_ENTITIY { key = fmt.Sprintf("%s_%s_%s_%s_%s", entityProcessor.GetEntity(), entityProcessor.GetAction(), entityProcessor.GetCluster(), diff --git a/pkg/auth/user/bean/bean.go b/pkg/auth/user/bean/bean.go index 2b03b0ad49..5447775239 100644 --- a/pkg/auth/user/bean/bean.go +++ b/pkg/auth/user/bean/bean.go @@ -66,6 +66,8 @@ const ( const ( DEVTRON_APP = "devtron-app" APP_ACCESS_TYPE_HELM = "helm-app" + APP_ACCESS_TYPE_ARGO = "argo-app" + APP_ACCESS_TYPE_FLUX = "flux-app" EmptyAccessType = "" ) diff --git a/pkg/auth/user/repository/UserAuthRepository.go b/pkg/auth/user/repository/UserAuthRepository.go index ce8fe2940f..7f37ab0b08 100644 --- a/pkg/auth/user/repository/UserAuthRepository.go +++ b/pkg/auth/user/repository/UserAuthRepository.go @@ -22,11 +22,12 @@ package repository import ( "encoding/json" "fmt" + "strings" + "time" + bean3 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" "github.com/devtron-labs/devtron/pkg/auth/user/adapter" bean4 "github.com/devtron-labs/devtron/pkg/auth/user/repository/bean" - "strings" - "time" "github.com/devtron-labs/devtron/api/bean" casbin2 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" @@ -73,6 +74,7 @@ type UserAuthRepository interface { GetRoleForClusterEntity(cluster, namespace, group, kind, resource, action string) (RoleModel, error) GetRoleForJobsEntity(entity, team, app, env, act string, workflow string) (RoleModel, error) GetRoleForOtherEntity(team, app, env, act, accessType string, oldValues bool) (RoleModel, error) + GetRoleForExternalGitOpsEntity(app, env, act, accessType string, oldValues bool) (RoleModel, error) GetRoleForChartGroupEntity(entity, app, act, accessType string) (RoleModel, error) } @@ -294,6 +296,13 @@ func (impl UserAuthRepositoryImpl) GetRoleByFilterForAllTypes(roleFieldDto *bean default: { team, app, env, accessType, oldValues := roleFieldDto.Team, roleFieldDto.App, roleFieldDto.Env, roleFieldDto.AccessType, roleFieldDto.OldValues + // External Argo/Flux applications share entity "apps" with devtron-app and + // helm-app but have no project, so they get their own project-free lookup + // instead of being clubbed into the team-keyed queries below. + switch accessType { + case bean2.APP_ACCESS_TYPE_ARGO, bean2.APP_ACCESS_TYPE_FLUX: + return impl.GetRoleForExternalGitOpsEntity(app, env, action, accessType, oldValues) + } return impl.GetRoleForOtherEntity(team, app, env, action, accessType, oldValues) } } @@ -1152,6 +1161,8 @@ func (impl UserAuthRepositoryImpl) GetRoleForOtherEntity(team, app, env, act, ac } else if team == "" && app == "" && env == "" && act == "" { return model, nil } else { + impl.Logger.Warnw("no query branch for the given role filter combination, returning empty role", + "team", team, "app", app, "env", env, "action", act, "accessType", accessType) return model, nil } if err != nil { @@ -1159,3 +1170,40 @@ func (impl UserAuthRepositoryImpl) GetRoleForOtherEntity(team, app, env, act, ac } return model, err } + +func (impl UserAuthRepositoryImpl) GetRoleForExternalGitOpsEntity(app, env, act, accessType string, oldValues bool) (RoleModel, error) { + var model RoleModel + if oldValues { + return model, nil + } + if len(act) == 0 || len(accessType) == 0 { + impl.Logger.Warnw("incomplete filter for external gitops role, returning empty role", + "app", app, "env", env, "action", act, "accessType", accessType) + return model, nil + } + query := "SELECT role.* FROM roles role WHERE coalesce(role.team,'') = ? AND role.action = ? AND role.access_type = ?" + queryParams := []interface{}{EMPTY_PLACEHOLDER_FOR_QUERY, act, accessType} + + if len(app) > 0 { + query += " AND role.entity_name = ?" + queryParams = append(queryParams, app) + } else { + query += " AND coalesce(role.entity_name,'') = ?" + queryParams = append(queryParams, EMPTY_PLACEHOLDER_FOR_QUERY) + } + + if len(env) > 0 { + query += " AND role.environment = ?" + queryParams = append(queryParams, env) + } else { + query += " AND coalesce(role.environment,'') = ?" + queryParams = append(queryParams, EMPTY_PLACEHOLDER_FOR_QUERY) + } + + _, err := impl.dbConnection.Query(&model, query, queryParams...) + if err != nil { + impl.Logger.Errorw("error in getting role for external gitops entity", "err", err, + "app", app, "env", env, "action", act, "accessType", accessType) + } + return model, err +} diff --git a/pkg/fluxApplication/FluxApplicationService.go b/pkg/fluxApplication/FluxApplicationService.go index 779b5b2c63..12a9ec121b 100644 --- a/pkg/fluxApplication/FluxApplicationService.go +++ b/pkg/fluxApplication/FluxApplicationService.go @@ -3,6 +3,9 @@ package fluxApplication import ( "context" "fmt" + "io" + "net/http" + "github.com/devtron-labs/common-lib/utils/k8s/commonBean" "github.com/devtron-labs/devtron/api/connector" "github.com/devtron-labs/devtron/api/helm-app/gRPC" @@ -19,15 +22,15 @@ import ( "github.com/gogo/protobuf/proto" "go.opentelemetry.io/otel" "go.uber.org/zap" - "io" - "net/http" ) type FluxApplicationService interface { - ListFluxApplications(ctx context.Context, clusterIds []int, noStream bool, w http.ResponseWriter) + ListFluxApplications(ctx context.Context, clusterIds []int, noStream bool, w http.ResponseWriter, + token string, fluxAuth func(token string, clusterName string, namespace string, appName string) bool) GetFluxAppDetail(ctx context.Context, app *bean.FluxAppIdentifier) (*bean.FluxApplicationDetailDto, error) HibernateFluxApplication(ctx context.Context, app *bean.FluxAppIdentifier, hibernateRequest *openapi.HibernateRequest) ([]*openapi.HibernateStatus, error) UnHibernateFluxApplication(ctx context.Context, app *bean.FluxAppIdentifier, hibernateRequest *openapi.HibernateRequest) ([]*openapi.HibernateStatus, error) + GetFluxApplicationList(ctx context.Context, clusterIds []int) ([]bean.FluxApplication, error) } type FluxApplicationServiceImpl struct { @@ -92,23 +95,55 @@ func (impl *FluxApplicationServiceImpl) UnHibernateFluxApplication(ctx context.C return response, nil } -func (impl *FluxApplicationServiceImpl) ListFluxApplications(ctx context.Context, clusterIds []int, noStream bool, w http.ResponseWriter) { +func (impl *FluxApplicationServiceImpl) GetFluxApplicationList(ctx context.Context, clusterIds []int) ([]bean.FluxApplication, error) { appStream, err := impl.listApplications(ctx, clusterIds) if err != nil { - impl.logger.Errorw("error in listing flux applications", "clusterIds", clusterIds, "err", err) - return + return nil, err + } + cdPipelineMap, installedAppMap, err := impl.getDevtronManagedMaps(clusterIds) + if err != nil { + return nil, err } + apps := make([]bean.FluxApplication, 0) + for { + appDetail, err := appStream.Recv() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + if appDetail.Errored { + impl.logger.Errorw("error in listing flux applications for cluster, skipping it", + "clusterId", appDetail.ClusterId, "errorMsg", appDetail.ErrorMsg) + continue + } + for _, d := range appDetail.FluxApplication { + key := fmt.Sprintf("%v-%s", d.EnvironmentDetail.ClusterId, d.EnvironmentDetail.Namespace) + if _, ok := cdPipelineMap[key][d.Name]; ok { + continue + } + if _, ok := installedAppMap[key][d.Name]; ok { + continue + } + apps = append(apps, toFluxApplication(d)) + } + } + return apps, nil +} + +func (impl *FluxApplicationServiceImpl) getDevtronManagedMaps(clusterIds []int) (map[string]map[string]bool, map[string]map[string]bool, error) { fluxCdPipelines, err := impl.pipelineRepository.GetAppAndEnvDetailsForDeploymentAppTypePipeline(util.PIPELINE_DEPLOYMENT_TYPE_FLUX, clusterIds) if err != nil { impl.logger.Errorw("error in fetching helm app list from DB created using cd_pipelines", "clusters", clusterIds, "err", err) - return + return nil, nil, err } installedHelmApps, err := impl.installedAppRepository.GetAppAndEnvDetailsForDeploymentAppTypeInstalledApps(util.PIPELINE_DEPLOYMENT_TYPE_FLUX, clusterIds) if err != nil { impl.logger.Errorw("error in fetching helm app list from DB created from app store", "clusters", clusterIds, "err", err) - return + return nil, nil, err } cdPipelineMap := make(map[string]map[string]bool) // map of clusterId-namespace, deploymentAppName @@ -129,52 +164,70 @@ func (impl *FluxApplicationServiceImpl) ListFluxApplications(ctx context.Context deploymentAppName := fmt.Sprintf("%s-%s", i.App.AppName, i.Environment.Namespace) installedAppMap[key][deploymentAppName] = true } + + return cdPipelineMap, installedAppMap, nil +} + +func toFluxApplication(app *gRPC.FluxApplication) bean.FluxApplication { + fluxApp := bean.FluxApplication{ + Name: app.Name, + HealthStatus: app.HealthStatus, + SyncStatus: app.SyncStatus, + ClusterId: int(app.EnvironmentDetail.ClusterId), + ClusterName: app.EnvironmentDetail.ClusterName, + Namespace: app.EnvironmentDetail.Namespace, + FluxAppDeploymentType: app.FluxAppDeploymentType, + } + + return fluxApp +} + +func (impl *FluxApplicationServiceImpl) ListFluxApplications(ctx context.Context, clusterIds []int, noStream bool, w http.ResponseWriter, + token string, fluxAuth func(token string, clusterName string, namespace string, appName string) bool) { + if !noStream { + appStream, err := impl.listApplications(ctx, clusterIds) + if err != nil { + impl.logger.Errorw("error in listing flux applications", "clusterIds", clusterIds, "err", err) + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + return + } + cdPipelineMap, installedAppMap, err := impl.getDevtronManagedMaps(clusterIds) + if err != nil { + impl.logger.Errorw("error in getting devtron managed flux apps", "clusterIds", clusterIds, "err", err) + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + return + } impl.pump.StartStreamWithTransformer(w, func() (proto.Message, error) { return appStream.Recv() }, err, func(message interface{}) interface{} { - return impl.appListRespProtoTransformer(message.(*gRPC.FluxApplicationList), cdPipelineMap, installedAppMap) + return impl.appListRespProtoTransformer(message.(*gRPC.FluxApplicationList), cdPipelineMap, installedAppMap, token, fluxAuth) }) } else { - fluxApps := make([]bean.FluxApplication, 0) - for { - appDetail, err := appStream.Recv() - if err == io.EOF { - break - } - if err != nil { - return + fluxApps, err := impl.GetFluxApplicationList(ctx, clusterIds) + if err != nil { + impl.logger.Errorw("error in getting flux application list", "clusterIds", clusterIds, "err", err) + errored := true + errMsg := err.Error() + appList := bean.FluxAppList{ + Errored: &errored, + ErrorMsg: &errMsg, } - if appDetail.Errored { - appList := bean.FluxAppList{ - Errored: &appDetail.Errored, - ErrorMsg: &appDetail.ErrorMsg, - } - common.WriteJsonResp(w, nil, appList, http.StatusOK) - return - } else { - for _, deployedApp := range appDetail.FluxApplication { - key := fmt.Sprintf("%v-%s", deployedApp.EnvironmentDetail.ClusterId, deployedApp.EnvironmentDetail.Namespace) - if _, ok := cdPipelineMap[key][deployedApp.Name]; ok { - continue - } - if _, ok := installedAppMap[key][deployedApp.Name]; ok { - continue - } - fluxApp := bean.FluxApplication{ - Name: deployedApp.Name, - HealthStatus: deployedApp.HealthStatus, - SyncStatus: deployedApp.SyncStatus, - ClusterId: int(deployedApp.EnvironmentDetail.ClusterId), - ClusterName: deployedApp.EnvironmentDetail.ClusterName, - Namespace: deployedApp.EnvironmentDetail.Namespace, - FluxAppDeploymentType: deployedApp.FluxAppDeploymentType, - } - fluxApps = append(fluxApps, fluxApp) + common.WriteJsonResp(w, nil, appList, http.StatusOK) + return + } + + if fluxAuth != nil { + authorised := make([]bean.FluxApplication, 0, len(fluxApps)) + for _, app := range fluxApps { + if fluxAuth(token, app.ClusterName, app.Namespace, app.Name) { + authorised = append(authorised, app) } } + fluxApps = authorised } + //RBAC enforcer Ends clusterIdsInt32 := sliceUtil.NewSliceFromFuncExec(clusterIds, func(clusterId int) int32 { return int32(clusterId) }) @@ -234,6 +287,11 @@ func (impl *FluxApplicationServiceImpl) listApplications(ctx context.Context, cl } for _, clusterDetail := range clusters { + if clusterDetail.IsVirtualCluster || len(clusterDetail.ErrorInConnecting) != 0 { + impl.logger.Debugw("skipping cluster for flux app listing", "clusterId", clusterDetail.Id, + "isVirtualCluster", clusterDetail.IsVirtualCluster, "errorInConnecting", clusterDetail.ErrorInConnecting) + continue + } config := &gRPC.ClusterConfig{ ApiServerUrl: clusterDetail.ServerUrl, Token: clusterDetail.Config[commonBean.BearerToken], @@ -252,7 +310,8 @@ func (impl *FluxApplicationServiceImpl) listApplications(ctx context.Context, cl return applicationStream, err } -func (impl *FluxApplicationServiceImpl) appListRespProtoTransformer(deployedApps *gRPC.FluxApplicationList, fluxCdPipelines map[string]map[string]bool, fluxInstalledApps map[string]map[string]bool) bean.FluxAppList { +func (impl *FluxApplicationServiceImpl) appListRespProtoTransformer(deployedApps *gRPC.FluxApplicationList, fluxCdPipelines map[string]map[string]bool, fluxInstalledApps map[string]map[string]bool, + token string, fluxAuth func(token string, clusterName string, namespace string, appName string) bool) bean.FluxAppList { appList := bean.FluxAppList{ClusterId: &[]int32{deployedApps.ClusterId}} if deployedApps.Errored { @@ -268,6 +327,11 @@ func (impl *FluxApplicationServiceImpl) appListRespProtoTransformer(deployedApps if _, ok := fluxInstalledApps[key][deployedApp.Name]; ok { continue } + if fluxAuth != nil && !fluxAuth(token, deployedApp.EnvironmentDetail.ClusterName, + deployedApp.EnvironmentDetail.Namespace, deployedApp.Name) { + continue + } + //RBAC enforcer Ends fluxApp := bean.FluxApplication{ Name: deployedApp.Name, HealthStatus: deployedApp.HealthStatus, diff --git a/pkg/k8s/application/k8sApplicationService.go b/pkg/k8s/application/k8sApplicationService.go index 550de045bb..8cbaeb88b9 100644 --- a/pkg/k8s/application/k8sApplicationService.go +++ b/pkg/k8s/application/k8sApplicationService.go @@ -21,6 +21,11 @@ import ( "encoding/json" "errors" "fmt" + "io" + "net/http" + "strconv" + "strings" + "github.com/devtron-labs/common-lib/utils" "github.com/devtron-labs/devtron/api/helm-app/gRPC" client "github.com/devtron-labs/devtron/api/helm-app/service" @@ -33,11 +38,7 @@ import ( "github.com/devtron-labs/devtron/pkg/fluxApplication" bean2 "github.com/devtron-labs/devtron/pkg/fluxApplication/bean" bean4 "github.com/devtron-labs/devtron/pkg/k8s/bean" - "io" v1 "k8s.io/client-go/kubernetes/typed/core/v1" - "net/http" - "strconv" - "strings" "github.com/caarlos0/env/v6" k8s2 "github.com/devtron-labs/common-lib/utils/k8s" @@ -369,6 +370,7 @@ func (impl *K8sApplicationServiceImpl) ValidateTerminalRequestQuery(r *http.Requ } resourceRequestBean.ExternalArgoApplicationName = appIdentifier.AppName resourceRequestBean.ClusterId = appIdentifier.ClusterId + resourceRequestBean.ExternalArgoAppIdentifier = appIdentifier request.ClusterId = appIdentifier.ClusterId request.ExternalArgoApplicationName = appIdentifier.AppName request.ExternalArgoApplicationNamespace = appIdentifier.Namespace diff --git a/pkg/userResource/UserResourceExtendedService.go b/pkg/userResource/UserResourceExtendedService.go index 358a5000e3..8d78cbb741 100644 --- a/pkg/userResource/UserResourceExtendedService.go +++ b/pkg/userResource/UserResourceExtendedService.go @@ -2,14 +2,18 @@ package userResource import ( "context" + "net/http" + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" "github.com/devtron-labs/devtron/internal/util" "github.com/devtron-labs/devtron/pkg/app" "github.com/devtron-labs/devtron/pkg/appStore/chartGroup" "github.com/devtron-labs/devtron/pkg/appWorkflow" + argoApplication2 "github.com/devtron-labs/devtron/pkg/argoApplication" "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" "github.com/devtron-labs/devtron/pkg/cluster" "github.com/devtron-labs/devtron/pkg/cluster/environment" + "github.com/devtron-labs/devtron/pkg/fluxApplication" application2 "github.com/devtron-labs/devtron/pkg/k8s/application" "github.com/devtron-labs/devtron/pkg/team" "github.com/devtron-labs/devtron/pkg/userResource/adapter" @@ -18,7 +22,6 @@ import ( "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" "github.com/devtron-labs/devtron/util/rbac" "go.uber.org/zap" - "net/http" ) type UserResourceExtendedServiceImpl struct { @@ -39,13 +42,15 @@ func NewUserResourceExtendedServiceImpl(logger *zap.SugaredLogger, teamService t clusterService cluster.ClusterService, rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil, enforcerUtil rbac.EnforcerUtil, - enforcer casbin.Enforcer) *UserResourceExtendedServiceImpl { + enforcer casbin.Enforcer, + argoService argoApplication2.ArgoApplicationService, + fluxService fluxApplication.FluxApplicationService) *UserResourceExtendedServiceImpl { return &UserResourceExtendedServiceImpl{ logger: logger, chartGroupService: chartGroupService, appListingService: appListingService, appWorkflowService: appWorkflowService, - UserResourceServiceImpl: NewUserResourceServiceImpl(logger, teamService, envService, clusterService, k8sApplicationService, enforcerUtil, rbacEnforcementUtil, enforcer, appService), + UserResourceServiceImpl: NewUserResourceServiceImpl(logger, teamService, envService, clusterService, k8sApplicationService, enforcerUtil, rbacEnforcementUtil, enforcer, appService, argoService, fluxService), } } diff --git a/pkg/userResource/UserResourceRbacExtendedService.go b/pkg/userResource/UserResourceRbacExtendedService.go index 0f6c30205d..eb146d5c3e 100644 --- a/pkg/userResource/UserResourceRbacExtendedService.go +++ b/pkg/userResource/UserResourceRbacExtendedService.go @@ -65,6 +65,24 @@ func (impl *UserResourceServiceImpl) enforceRbacForHelmAppsListing(token string, return adapter.BuildUserResourceResponseDto(resourceOptions.TeamAppResp), nil } +func (impl *UserResourceServiceImpl) enforceRbacForArgoAppsListing(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized enforceRbacForArgoAppsListing") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.ExternalGitOpsAppResp), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForFluxAppsListing(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized enforceRbacForFluxAppsListing") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.ExternalGitOpsAppResp), nil +} + func (impl *UserResourceServiceImpl) enforceRbacForJobs(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") if !isAuthorised { diff --git a/pkg/userResource/UserResourceRbacService.go b/pkg/userResource/UserResourceRbacService.go index 6e0e75e891..2ee5ef8ae8 100644 --- a/pkg/userResource/UserResourceRbacService.go +++ b/pkg/userResource/UserResourceRbacService.go @@ -16,10 +16,13 @@ func (impl *UserResourceServiceImpl) enforceRbacForTeamForHelmApp(token string, return adapter.BuildUserResourceResponseDto(resourceOptions.TeamsResp), nil } -func (impl *UserResourceServiceImpl) enforceRbacForEnvForHelmApp(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { +// enforceRbacForEnvOptions gates the environment options dropdown. It serves all three +// external application types — helm-app, argo-app and flux-app — because the options are +// cluster/namespace pairs regardless of who deployed the application. +func (impl *UserResourceServiceImpl) enforceRbacForEnvOptions(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") if !isAuthorised { - impl.logger.Errorw("user is unauthorized to enforceRbacForEnvForHelmApp") + impl.logger.Errorw("user is unauthorized to enforceRbacForEnvOptions") return adapter.BuildNullDataUserResourceResponseDto(), nil } return adapter.BuildUserResourceResponseDto(resourceOptions.HelmEnvResp), nil diff --git a/pkg/userResource/UserResourceService.go b/pkg/userResource/UserResourceService.go index 48274c5fe8..49d44e273d 100644 --- a/pkg/userResource/UserResourceService.go +++ b/pkg/userResource/UserResourceService.go @@ -2,23 +2,27 @@ package userResource import ( "context" + "net/http" + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" app2 "github.com/devtron-labs/devtron/internal/sql/repository/app" "github.com/devtron-labs/devtron/internal/util" "github.com/devtron-labs/devtron/pkg/app" + argoApplication2 "github.com/devtron-labs/devtron/pkg/argoApplication" "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/cluster" "github.com/devtron-labs/devtron/pkg/cluster/environment" + "github.com/devtron-labs/devtron/pkg/fluxApplication" application2 "github.com/devtron-labs/devtron/pkg/k8s/application" bean4 "github.com/devtron-labs/devtron/pkg/k8s/bean" "github.com/devtron-labs/devtron/pkg/team" + "github.com/devtron-labs/devtron/pkg/userResource/adapter" bean5 "github.com/devtron-labs/devtron/pkg/userResource/bean" "github.com/devtron-labs/devtron/pkg/userResource/helper" "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" "github.com/devtron-labs/devtron/util/rbac" "go.uber.org/zap" - "net/http" ) type UserResourceService interface { @@ -26,15 +30,17 @@ type UserResourceService interface { params *apiBean.PathParams) (*bean5.UserResourceResponseDto, error) } type UserResourceServiceImpl struct { - logger *zap.SugaredLogger - teamService team.TeamService - envService environment.EnvironmentService - clusterService cluster.ClusterService - k8sApplicationService application2.K8sApplicationService - enforcerUtil rbac.EnforcerUtil - rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil - enforcer casbin.Enforcer - appService app.AppCrudOperationService + logger *zap.SugaredLogger + teamService team.TeamService + envService environment.EnvironmentService + clusterService cluster.ClusterService + k8sApplicationService application2.K8sApplicationService + enforcerUtil rbac.EnforcerUtil + rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil + enforcer casbin.Enforcer + appService app.AppCrudOperationService + argoApplicationService argoApplication2.ArgoApplicationService + fluxApplicationService fluxApplication.FluxApplicationService } func NewUserResourceServiceImpl(logger *zap.SugaredLogger, @@ -45,17 +51,21 @@ func NewUserResourceServiceImpl(logger *zap.SugaredLogger, enforcerUtil rbac.EnforcerUtil, rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil, enforcer casbin.Enforcer, - appService app.AppCrudOperationService) *UserResourceServiceImpl { + appService app.AppCrudOperationService, + argoApplicationService argoApplication2.ArgoApplicationService, + fluxApplicationService fluxApplication.FluxApplicationService) *UserResourceServiceImpl { return &UserResourceServiceImpl{ - logger: logger, - teamService: teamService, - envService: envService, - clusterService: clusterService, - k8sApplicationService: k8sApplicationService, - enforcerUtil: enforcerUtil, - rbacEnforcementUtil: rbacEnforcementUtil, - enforcer: enforcer, - appService: appService, + logger: logger, + teamService: teamService, + envService: envService, + clusterService: clusterService, + k8sApplicationService: k8sApplicationService, + enforcerUtil: enforcerUtil, + rbacEnforcementUtil: rbacEnforcementUtil, + enforcer: enforcer, + appService: appService, + argoApplicationService: argoApplicationService, + fluxApplicationService: fluxApplicationService, } } @@ -121,7 +131,43 @@ func (impl *UserResourceServiceImpl) getHelmAppResourceOptions(context context.C return bean5.NewResourceOptionsDto().WithTeamAppResp(apps), nil } -func (impl *UserResourceServiceImpl) getHelmEnvResourceOptions(context context.Context, token string, +func (impl *UserResourceServiceImpl) getArgoAppResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + clusterIds, err := helper.GetValidatedClusterIds(reqBean) + if err != nil { + impl.logger.Errorw("error encountered in getArgoAppResourceOptions", "err", err) + return nil, err + } + apps, err := impl.argoApplicationService.ListApplications(clusterIds) + if err != nil { + impl.logger.Errorw("error encountered in getArgoAppResourceOptions", "err", err) + return nil, err + } + appDtos := helper.FilterExternalGitOpsAppsByEnvIdentifier( + adapter.ArgoAppToExternalGitOpsApp(apps), reqBean.EnvironmentIdentifiers) + + return bean5.NewResourceOptionsDto().WithExternalGitOpsAppResp(appDtos), nil +} + +func (impl *UserResourceServiceImpl) getFluxAppResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + clusterIds, err := helper.GetValidatedClusterIds(reqBean) + if err != nil { + impl.logger.Errorw("error encountered in getFluxAppResourceOptions", "err", err) + return nil, err + } + apps, err := impl.fluxApplicationService.GetFluxApplicationList(context, clusterIds) + if err != nil { + impl.logger.Errorw("error encountered in getFluxAppResourceOptions", "err", err) + return nil, err + } + appDtos := helper.FilterExternalGitOpsAppsByEnvIdentifier( + adapter.FluxAppToExternalGitOpsApp(apps), reqBean.EnvironmentIdentifiers) + + return bean5.NewResourceOptionsDto().WithExternalGitOpsAppResp(appDtos), nil +} + +func (impl *UserResourceServiceImpl) getCombinedEnvResourceOptions(context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { // get helm env resource options diff --git a/pkg/userResource/adapter/adapter.go b/pkg/userResource/adapter/adapter.go index 874b61b8f2..73b30b9a3c 100644 --- a/pkg/userResource/adapter/adapter.go +++ b/pkg/userResource/adapter/adapter.go @@ -4,6 +4,8 @@ import ( bean2 "github.com/devtron-labs/devtron/api/userResource/bean" "github.com/devtron-labs/devtron/internal/sql/repository/helper" "github.com/devtron-labs/devtron/pkg/app" + bean3 "github.com/devtron-labs/devtron/pkg/argoApplication/bean" + bean4 "github.com/devtron-labs/devtron/pkg/fluxApplication/bean" "github.com/devtron-labs/devtron/pkg/userResource/bean" ) @@ -26,3 +28,33 @@ func BuildFetchAppListingReqForJobFromDto(reqBean *bean2.ResourceOptionsReqDto) SortOrder: helper.Asc, // default values set } } + +func ArgoAppToExternalGitOpsApp(applications []*bean3.ArgoApplicationListDto) []*bean.ExternalGitOpsAppDto { + result := make([]*bean.ExternalGitOpsAppDto, 0, len(applications)) + for _, application := range applications { + appDto := bean.ExternalGitOpsAppDto{ + AppName: application.Name, + Namespace: application.Namespace, + ClusterId: application.ClusterId, + ClusterName: application.ClusterName, + } + result = append(result, &appDto) + } + + return result +} + +func FluxAppToExternalGitOpsApp(applications []bean4.FluxApplication) []*bean.ExternalGitOpsAppDto { + result := make([]*bean.ExternalGitOpsAppDto, 0, len(applications)) + for _, application := range applications { + appDto := bean.ExternalGitOpsAppDto{ + AppName: application.Name, + Namespace: application.Namespace, + ClusterId: application.ClusterId, + ClusterName: application.ClusterName, + } + result = append(result, &appDto) + } + + return result +} diff --git a/pkg/userResource/bean/bean.go b/pkg/userResource/bean/bean.go index 03f07bd071..7d085bb8dd 100644 --- a/pkg/userResource/bean/bean.go +++ b/pkg/userResource/bean/bean.go @@ -15,17 +15,25 @@ type UserResourceResponseDto struct { Data interface{} `json:"data"` } type ResourceOptionsDto struct { - TeamsResp []bean2.TeamRequest - HelmEnvResp []*bean.ClusterEnvDto - ClusterResp []bean3.ClusterBean - NameSpaces []string - ApiResourcesResp *k8s.GetAllApiResourcesResponse - ClusterResourcesResp *k8s.ClusterResourceListMap - TeamAppResp []*app.TeamAppBean - EnvResp []bean.EnvironmentBean - ChartGroupResp *chartGroup.ChartGroupList - JobsResp []*AppView.JobContainer - AppWfsResp *bean4.WorkflowNamesResponse + TeamsResp []bean2.TeamRequest + HelmEnvResp []*bean.ClusterEnvDto + ClusterResp []bean3.ClusterBean + NameSpaces []string + ApiResourcesResp *k8s.GetAllApiResourcesResponse + ClusterResourcesResp *k8s.ClusterResourceListMap + TeamAppResp []*app.TeamAppBean + EnvResp []bean.EnvironmentBean + ChartGroupResp *chartGroup.ChartGroupList + JobsResp []*AppView.JobContainer + AppWfsResp *bean4.WorkflowNamesResponse + ExternalGitOpsAppResp []*ExternalGitOpsAppDto +} + +type ExternalGitOpsAppDto struct { + AppName string `json:"appName"` + Namespace string `json:"namespace"` + ClusterId int `json:"clusterId"` + ClusterName string `json:"clusterName"` } func NewResourceOptionsDto() *ResourceOptionsDto { @@ -78,6 +86,11 @@ func (r *ResourceOptionsDto) WithAppWfsResp(appWfsResp *bean4.WorkflowNamesRespo return r } +func (r *ResourceOptionsDto) WithExternalGitOpsAppResp(externalGitOpsAppResp []*ExternalGitOpsAppDto) *ResourceOptionsDto { + r.ExternalGitOpsAppResp = externalGitOpsAppResp + return r +} + type Version string type UserResourceKind string @@ -95,6 +108,10 @@ const ( ClusterNamespaces UserResourceKind = "cluster/namespaces" ClusterApiResources UserResourceKind = "cluster/apiResources" ClusterResources UserResourceKind = "cluster/resources" + KindArgoEnvironment UserResourceKind = "environment/argo" + KindFluxEnvironment UserResourceKind = "environment/flux" + KindArgoApplication UserResourceKind = Application + "/argo" + KindFluxApplication UserResourceKind = Application + "/flux" ) const ( diff --git a/pkg/userResource/bean/messages.go b/pkg/userResource/bean/messages.go index b71c8c199e..27571ddf96 100644 --- a/pkg/userResource/bean/messages.go +++ b/pkg/userResource/bean/messages.go @@ -1,8 +1,9 @@ package bean const ( - InvalidPayloadMessage = "Invalid Payload" - InvalidEntityMessage = "Invalid Entity" + InvalidPayloadMessage = "Invalid Payload" + InvalidEntityMessage = "Invalid Entity" + InvalidClusterIdMessage = "Invalid clusterId" ) // messages diff --git a/pkg/userResource/helper/helper.go b/pkg/userResource/helper/helper.go index 58251e3c91..4fe89415b2 100644 --- a/pkg/userResource/helper/helper.go +++ b/pkg/userResource/helper/helper.go @@ -1,10 +1,13 @@ package helper import ( + "fmt" + "net/http" + "strings" + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" "github.com/devtron-labs/devtron/internal/util" bean5 "github.com/devtron-labs/devtron/pkg/userResource/bean" - "net/http" ) func ValidateResourceOptionReqBean(reqBean *apiBean.ResourceOptionsReqDto) error { @@ -16,3 +19,50 @@ func ValidateResourceOptionReqBean(reqBean *apiBean.ResourceOptionsReqDto) error } return nil } + +func GetValidatedClusterIds(reqBean *apiBean.ResourceOptionsReqDto) ([]int, error) { + invalid := util.GetApiErrorAdapter(http.StatusBadRequest, "400", + bean5.InvalidClusterIdMessage, bean5.InvalidClusterIdMessage) + if reqBean == nil { + return nil, invalid + } + seen := make(map[int]bool, len(reqBean.ClusterIds)+1) + clusterIds := make([]int, 0, len(reqBean.ClusterIds)+1) + appendIfValid := func(clusterId int) { + if clusterId > 0 && !seen[clusterId] { + seen[clusterId] = true + clusterIds = append(clusterIds, clusterId) + } + } + for _, clusterId := range reqBean.ClusterIds { + appendIfValid(clusterId) + } + if reqBean.ResourceRequestBean != nil { + appendIfValid(reqBean.ClusterId) + } + if len(clusterIds) == 0 { + return nil, invalid + } + return clusterIds, nil +} + +func FilterExternalGitOpsAppsByEnvIdentifier(apps []*bean5.ExternalGitOpsAppDto, envIdentifiers []string) []*bean5.ExternalGitOpsAppDto { + if len(envIdentifiers) == 0 { + return apps + } + selected := make(map[string]bool, len(envIdentifiers)) + for _, identifier := range envIdentifiers { + selected[strings.ToLower(identifier)] = true + } + filtered := make([]*bean5.ExternalGitOpsAppDto, 0, len(apps)) + for _, app := range apps { + if app == nil { + continue + } + identifier := fmt.Sprintf("%s__%s", app.ClusterName, app.Namespace) + if selected[strings.ToLower(identifier)] { + filtered = append(filtered, app) + } + } + return filtered +} diff --git a/pkg/userResource/logicRouteService.go b/pkg/userResource/logicRouteService.go index 9b9b41ab2c..5447b6db22 100644 --- a/pkg/userResource/logicRouteService.go +++ b/pkg/userResource/logicRouteService.go @@ -3,6 +3,7 @@ package userResource import ( "context" "fmt" + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" bean2 "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/userResource/bean" @@ -17,12 +18,16 @@ func getUserResourceKindWithVersionKey(kind bean.UserResourceKind, version bean. var mapOfUserResourceKindToAllResourceOptionsFunc = map[string]func(impl *UserResourceServiceImpl, context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean.ResourceOptionsDto, error){ getUserResourceKindWithVersionKey(bean.KindTeam, bean.Alpha1Version): (*UserResourceServiceImpl).getTeamResourceOptions, - getUserResourceKindWithVersionKey(bean.KindHelmEnvironment, bean.Alpha1Version): (*UserResourceServiceImpl).getHelmEnvResourceOptions, + getUserResourceKindWithVersionKey(bean.KindHelmEnvironment, bean.Alpha1Version): (*UserResourceServiceImpl).getCombinedEnvResourceOptions, getUserResourceKindWithVersionKey(bean.KindHelmApplication, bean.Alpha1Version): (*UserResourceServiceImpl).getHelmAppResourceOptions, getUserResourceKindWithVersionKey(bean.KindCluster, bean.Alpha1Version): (*UserResourceServiceImpl).getClusterResourceOptions, getUserResourceKindWithVersionKey(bean.ClusterApiResources, bean.Alpha1Version): (*UserResourceServiceImpl).getClusterApiResourceOptions, getUserResourceKindWithVersionKey(bean.ClusterNamespaces, bean.Alpha1Version): (*UserResourceServiceImpl).getClusterNamespacesResourceOptions, getUserResourceKindWithVersionKey(bean.ClusterResources, bean.Alpha1Version): (*UserResourceServiceImpl).getClusterResourceListOptions, + getUserResourceKindWithVersionKey(bean.KindArgoEnvironment, bean.Alpha1Version): (*UserResourceServiceImpl).getCombinedEnvResourceOptions, + getUserResourceKindWithVersionKey(bean.KindFluxEnvironment, bean.Alpha1Version): (*UserResourceServiceImpl).getCombinedEnvResourceOptions, + getUserResourceKindWithVersionKey(bean.KindArgoApplication, bean.Alpha1Version): (*UserResourceServiceImpl).getArgoAppResourceOptions, + getUserResourceKindWithVersionKey(bean.KindFluxApplication, bean.Alpha1Version): (*UserResourceServiceImpl).getFluxAppResourceOptions, } func getAllResourceOptionsFunc(kind bean.UserResourceKind, version bean.Version) func(impl *UserResourceServiceImpl, context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean.ResourceOptionsDto, error) { @@ -34,7 +39,7 @@ func getAllResourceOptionsFunc(kind bean.UserResourceKind, version bean.Version) var mapOfUserResourceKindToAllResourceOptionsExtendedFunc = map[string]func(impl *UserResourceExtendedServiceImpl, context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean.ResourceOptionsDto, error){ getUserResourceKindWithVersionKey(bean.KindTeam, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getTeamResourceOptions, - getUserResourceKindWithVersionKey(bean.KindHelmEnvironment, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getHelmEnvResourceOptions, + getUserResourceKindWithVersionKey(bean.KindHelmEnvironment, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getCombinedEnvResourceOptions, getUserResourceKindWithVersionKey(bean.KindHelmApplication, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getHelmAppResourceOptions, getUserResourceKindWithVersionKey(bean.KindCluster, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getClusterResourceOptions, getUserResourceKindWithVersionKey(bean.ClusterApiResources, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getClusterApiResourceOptions, @@ -45,6 +50,10 @@ var mapOfUserResourceKindToAllResourceOptionsExtendedFunc = map[string]func(impl getUserResourceKindWithVersionKey(bean.KindChartGroup, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getChartGroupResourceOptions, getUserResourceKindWithVersionKey(bean.KindJobs, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getJobsResourceOptions, getUserResourceKindWithVersionKey(bean.KindWorkflow, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getAppWfsResourceOptions, + getUserResourceKindWithVersionKey(bean.KindArgoEnvironment, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getCombinedEnvResourceOptions, + getUserResourceKindWithVersionKey(bean.KindFluxEnvironment, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getCombinedEnvResourceOptions, + getUserResourceKindWithVersionKey(bean.KindArgoApplication, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getArgoAppResourceOptions, + getUserResourceKindWithVersionKey(bean.KindFluxApplication, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getFluxAppResourceOptions, } func getAllResourceOptionsExtendedFunc(kind bean.UserResourceKind, version bean.Version) func(impl *UserResourceExtendedServiceImpl, context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean.ResourceOptionsDto, error) { @@ -57,11 +66,15 @@ func getAllResourceOptionsExtendedFunc(kind bean.UserResourceKind, version bean. var mapOfKindWithEntityAccessTypeKeyToResourceOptionRbacFunc = map[string]func(impl *UserResourceServiceImpl, token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error){ getUserResourceKindWithEntityAccessKey(bean.KindTeam, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceServiceImpl).enforceRbacForTeamForHelmApp, getUserResourceKindWithEntityAccessKey(bean.KindHelmApplication, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceServiceImpl).enforceRbacForHelmAppsListing, - getUserResourceKindWithEntityAccessKey(bean.KindHelmEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceServiceImpl).enforceRbacForEnvForHelmApp, + getUserResourceKindWithEntityAccessKey(bean.KindHelmEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceServiceImpl).enforceRbacForEnvOptions, getUserResourceKindWithEntityAccessKey(bean.KindCluster, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceServiceImpl).enforceRbacForClusterList, getUserResourceKindWithEntityAccessKey(bean.ClusterApiResources, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceServiceImpl).enforceRbacForClusterApiResource, getUserResourceKindWithEntityAccessKey(bean.ClusterNamespaces, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceServiceImpl).enforceRbacForClusterNamespaces, getUserResourceKindWithEntityAccessKey(bean.ClusterResources, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceServiceImpl).enforceRbacForClusterResourceList, + getUserResourceKindWithEntityAccessKey(bean.KindArgoEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_ARGO): (*UserResourceServiceImpl).enforceRbacForEnvOptions, + getUserResourceKindWithEntityAccessKey(bean.KindFluxEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_FLUX): (*UserResourceServiceImpl).enforceRbacForEnvOptions, + getUserResourceKindWithEntityAccessKey(bean.KindArgoApplication, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_ARGO): (*UserResourceServiceImpl).enforceRbacForArgoAppsListing, + getUserResourceKindWithEntityAccessKey(bean.KindFluxApplication, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_FLUX): (*UserResourceServiceImpl).enforceRbacForFluxAppsListing, } func getResourceOptionRbacFunc(kind bean.UserResourceKind, version bean.Version, entity string, accessType string) func(impl *UserResourceServiceImpl, token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { @@ -74,7 +87,7 @@ func getResourceOptionRbacFunc(kind bean.UserResourceKind, version bean.Version, var mapOfKindWithEntityAccessTypeKeyToResourceOptionRbacExtendedFunc = map[string]func(impl *UserResourceExtendedServiceImpl, token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error){ getUserResourceKindWithEntityAccessKey(bean.KindTeam, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceExtendedServiceImpl).enforceRbacForTeamForHelmApp, getUserResourceKindWithEntityAccessKey(bean.KindHelmApplication, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceExtendedServiceImpl).enforceRbacForHelmAppsListing, - getUserResourceKindWithEntityAccessKey(bean.KindHelmEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceExtendedServiceImpl).enforceRbacForEnvForHelmApp, + getUserResourceKindWithEntityAccessKey(bean.KindHelmEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceExtendedServiceImpl).enforceRbacForEnvOptions, getUserResourceKindWithEntityAccessKey(bean.KindCluster, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForClusterList, getUserResourceKindWithEntityAccessKey(bean.ClusterApiResources, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForClusterApiResource, getUserResourceKindWithEntityAccessKey(bean.ClusterNamespaces, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForClusterNamespaces, @@ -87,6 +100,10 @@ var mapOfKindWithEntityAccessTypeKeyToResourceOptionRbacExtendedFunc = map[strin getUserResourceKindWithEntityAccessKey(bean.KindChartGroup, bean.Alpha1Version, bean2.CHART_GROUP_ENTITY, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForChartGroup, getUserResourceKindWithEntityAccessKey(bean.KindJobs, bean.Alpha1Version, bean2.EntityJobs, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForJobs, getUserResourceKindWithEntityAccessKey(bean.KindWorkflow, bean.Alpha1Version, bean2.EntityJobs, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForJobsWfs, + getUserResourceKindWithEntityAccessKey(bean.KindArgoEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_ARGO): (*UserResourceExtendedServiceImpl).enforceRbacForEnvOptions, + getUserResourceKindWithEntityAccessKey(bean.KindFluxEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_FLUX): (*UserResourceExtendedServiceImpl).enforceRbacForEnvOptions, + getUserResourceKindWithEntityAccessKey(bean.KindArgoApplication, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_ARGO): (*UserResourceExtendedServiceImpl).enforceRbacForArgoAppsListing, + getUserResourceKindWithEntityAccessKey(bean.KindFluxApplication, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_FLUX): (*UserResourceExtendedServiceImpl).enforceRbacForFluxAppsListing, } func getResourceOptionRbacExtendedFunc(kind bean.UserResourceKind, version bean.Version, entity string, accessType string) func(impl *UserResourceExtendedServiceImpl, token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { diff --git a/scripts/casbin/12_argo_flux_rbac.down.sql b/scripts/casbin/12_argo_flux_rbac.down.sql new file mode 100644 index 0000000000..59b12b5f86 --- /dev/null +++ b/scripts/casbin/12_argo_flux_rbac.down.sql @@ -0,0 +1,2 @@ +DELETE FROM casbin_rule WHERE v0='role:super-admin___' AND v1='argo-app'; +DELETE FROM casbin_rule WHERE v0='role:super-admin___' AND v1='flux-app'; \ No newline at end of file diff --git a/scripts/casbin/12_argo_flux_rbac.up.sql b/scripts/casbin/12_argo_flux_rbac.up.sql new file mode 100644 index 0000000000..17e4b72057 --- /dev/null +++ b/scripts/casbin/12_argo_flux_rbac.up.sql @@ -0,0 +1,3 @@ +INSERT INTO "public"."casbin_rule" ("p_type","v0","v1","v2","v3","v4","v5") VALUES + ('p','role:super-admin___','argo-app','*','*','allow',''), + ('p','role:super-admin___','flux-app','*','*','allow',''); \ No newline at end of file diff --git a/scripts/sql/36604600_argo_flux_rbac.down.sql b/scripts/sql/36604600_argo_flux_rbac.down.sql new file mode 100644 index 0000000000..57d8b04829 --- /dev/null +++ b/scripts/sql/36604600_argo_flux_rbac.down.sql @@ -0,0 +1,2 @@ +DELETE FROM "public"."rbac_policy_data" WHERE entity='apps' AND access_type IN ('argo-app','flux-app'); +DELETE FROM "public"."rbac_role_data" WHERE entity='apps' AND access_type IN ('argo-app','flux-app'); \ No newline at end of file diff --git a/scripts/sql/36604600_argo_flux_rbac.up.sql b/scripts/sql/36604600_argo_flux_rbac.up.sql new file mode 100644 index 0000000000..c7ce5e8a0c --- /dev/null +++ b/scripts/sql/36604600_argo_flux_rbac.up.sql @@ -0,0 +1,111 @@ +INSERT INTO "public"."rbac_policy_data" +("entity","access_type","role","policy_data", + "created_on","created_by","updated_on","updated_by","is_preset_role","deleted") +VALUES + ('apps','argo-app','view','{ + "type": { "value": "p", "indexKeyMap": {} }, + "sub": { "value": "argo-app:view_%_%", + "indexKeyMap": { "14": "Env", "16": "App" } }, + "resActObjSet": [ + { "res": { "value": "argo-app", "indexKeyMap": {} }, + "act": { "value": "get", "indexKeyMap": {} }, + "obj": { "value": "%/%", + "indexKeyMap": { "0": "EnvObj", "2": "AppObj" } } }, + { "res": { "value": "global-environment", "indexKeyMap": {} }, + "act": { "value": "get", "indexKeyMap": {} }, + "obj": { "value": "%", "indexKeyMap": { "0": "EnvObj" } } } + ] +}','now()','1','now()','1',true,false), + + ('apps','argo-app','admin','{ + "type": { "value": "p", "indexKeyMap": {} }, + "sub": { "value": "argo-app:admin_%_%", + "indexKeyMap": { "15": "Env", "17": "App" } }, + "resActObjSet": [ + { "res": { "value": "argo-app", "indexKeyMap": {} }, + "act": { "value": "*", "indexKeyMap": {} }, + "obj": { "value": "%/%", + "indexKeyMap": { "0": "EnvObj", "2": "AppObj" } } }, + { "res": { "value": "global-environment", "indexKeyMap": {} }, + "act": { "value": "get", "indexKeyMap": {} }, + "obj": { "value": "%", "indexKeyMap": { "0": "EnvObj" } } } + ] +}','now()','1','now()','1',true,false), + + ('apps','flux-app','view','{ + "type": { "value": "p", "indexKeyMap": {} }, + "sub": { "value": "flux-app:view_%_%", + "indexKeyMap": { "14": "Env", "16": "App" } }, + "resActObjSet": [ + { "res": { "value": "flux-app", "indexKeyMap": {} }, + "act": { "value": "get", "indexKeyMap": {} }, + "obj": { "value": "%/%", + "indexKeyMap": { "0": "EnvObj", "2": "AppObj" } } }, + { "res": { "value": "global-environment", "indexKeyMap": {} }, + "act": { "value": "get", "indexKeyMap": {} }, + "obj": { "value": "%", "indexKeyMap": { "0": "EnvObj" } } } + ] +}','now()','1','now()','1',true,false), + + ('apps','flux-app','admin','{ + "type": { "value": "p", "indexKeyMap": {} }, + "sub": { "value": "flux-app:admin_%_%", + "indexKeyMap": { "15": "Env", "17": "App" } }, + "resActObjSet": [ + { "res": { "value": "flux-app", "indexKeyMap": {} }, + "act": { "value": "*", "indexKeyMap": {} }, + "obj": { "value": "%/%", + "indexKeyMap": { "0": "EnvObj", "2": "AppObj" } } }, + { "res": { "value": "global-environment", "indexKeyMap": {} }, + "act": { "value": "get", "indexKeyMap": {} }, + "obj": { "value": "%", "indexKeyMap": { "0": "EnvObj" } } } + ] +}','now()','1','now()','1',true,false); + +INSERT INTO "public"."rbac_role_data" +("entity","access_type","role","role_display_name","role_description","role_data", + "created_on","created_by","updated_on","updated_by","is_preset_role","deleted") +VALUES + ('apps','argo-app','view','View only', + 'Can view selected Argo CD application(s) and resource manifests of selected application(s)','{ + "role": { "value": "argo-app:view_%_%", + "indexKeyMap": { "14": "Env", "16": "App" } }, + "entityName": { "value": "%", "indexKeyMap": { "0": "App" } }, + "environment":{ "value": "%", "indexKeyMap": { "0": "Env" } }, + "action": { "value": "view", "indexKeyMap": {} }, + "entity": { "value": "%", "indexKeyMap": { "0": "Entity" } }, + "accessType": { "value": "argo-app", "indexKeyMap": {} } +}','now()','1','now()','1',true,false), + + ('apps','argo-app','admin','Admin', + 'Complete access on selected Argo CD application(s)','{ + "role": { "value": "argo-app:admin_%_%", + "indexKeyMap": { "15": "Env", "17": "App" } }, + "entityName": { "value": "%", "indexKeyMap": { "0": "App" } }, + "environment":{ "value": "%", "indexKeyMap": { "0": "Env" } }, + "action": { "value": "admin", "indexKeyMap": {} }, + "entity": { "value": "%", "indexKeyMap": { "0": "Entity" } }, + "accessType": { "value": "argo-app", "indexKeyMap": {} } +}','now()','1','now()','1',true,false), + + ('apps','flux-app','view','View only', + 'Can view selected Flux CD application(s) and resource manifests of selected application(s)','{ + "role": { "value": "flux-app:view_%_%", + "indexKeyMap": { "14": "Env", "16": "App" } }, + "entityName": { "value": "%", "indexKeyMap": { "0": "App" } }, + "environment":{ "value": "%", "indexKeyMap": { "0": "Env" } }, + "action": { "value": "view", "indexKeyMap": {} }, + "entity": { "value": "%", "indexKeyMap": { "0": "Entity" } }, + "accessType": { "value": "flux-app", "indexKeyMap": {} } +}','now()','1','now()','1',true,false), + + ('apps','flux-app','admin','Admin', + 'Complete access on selected Flux CD application(s)','{ + "role": { "value": "flux-app:admin_%_%", + "indexKeyMap": { "15": "Env", "17": "App" } }, + "entityName": { "value": "%", "indexKeyMap": { "0": "App" } }, + "environment":{ "value": "%", "indexKeyMap": { "0": "Env" } }, + "action": { "value": "admin", "indexKeyMap": {} }, + "entity": { "value": "%", "indexKeyMap": { "0": "Entity" } }, + "accessType": { "value": "flux-app", "indexKeyMap": {} } +}','now()','1','now()','1',true,false); \ No newline at end of file diff --git a/util/rbac/EnforcerUtilGitOps.go b/util/rbac/EnforcerUtilGitOps.go new file mode 100644 index 0000000000..546dcf2fab --- /dev/null +++ b/util/rbac/EnforcerUtilGitOps.go @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rbac + +import ( + "fmt" + + "github.com/devtron-labs/devtron/pkg/cluster/read" + "go.uber.org/zap" +) + +// EnforcerUtilGitOps builds RBAC objects for external Argo CD and Flux CD applications. +// +// The object is two segments — __/ — where the first segment +// is the environment_identifier convention used throughout Devtron. There is no project +// segment: external GitOps applications have no Devtron project, so unlike external Helm apps +// there is no team name and no "unassigned" placeholder to fill. +// +// Both segments are always populated, which matters because matchKeyByPart rejects an empty +// segment on either side unconditionally. +type EnforcerUtilGitOps interface { + // GetExternalGitOpsAppObject returns the RBAC object for an external Argo/Flux application. + // Returns an empty string if the cluster cannot be resolved, which callers must treat as + // a denial rather than as a wildcard. + GetExternalGitOpsAppObject(clusterId int, namespace string, appName string) string + // GetExternalGitOpsAppObjectByClusterName is the same, for callers that already hold the + // cluster name and can avoid the lookup. + GetExternalGitOpsAppObjectByClusterName(clusterName string, namespace string, appName string) string +} + +type EnforcerUtilGitOpsImpl struct { + logger *zap.SugaredLogger + clusterReadService read.ClusterReadService +} + +func NewEnforcerUtilGitOpsImpl(logger *zap.SugaredLogger, + clusterReadService read.ClusterReadService) *EnforcerUtilGitOpsImpl { + return &EnforcerUtilGitOpsImpl{ + logger: logger, + clusterReadService: clusterReadService, + } +} + +func (impl EnforcerUtilGitOpsImpl) GetExternalGitOpsAppObject(clusterId int, namespace string, appName string) string { + cluster, err := impl.clusterReadService.FindById(clusterId) + if err != nil { + impl.logger.Errorw("error on fetching cluster for rbac object", "err", err, "clusterId", clusterId) + return "" + } + return impl.GetExternalGitOpsAppObjectByClusterName(cluster.ClusterName, namespace, appName) +} + +func (impl EnforcerUtilGitOpsImpl) GetExternalGitOpsAppObjectByClusterName(clusterName string, namespace string, appName string) string { + if len(clusterName) == 0 || len(namespace) == 0 || len(appName) == 0 { + impl.logger.Errorw("incomplete identifier for rbac object, denying", + "clusterName", clusterName, "namespace", namespace, "appName", appName) + return "" + } + return fmt.Sprintf("%s__%s/%s", clusterName, namespace, appName) +} diff --git a/wire_gen.go b/wire_gen.go index e3518a51bb..12fafa8ce5 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -1009,7 +1009,8 @@ func InitializeApp() (*App, error) { coreAppRouterImpl := router.NewCoreAppRouterImpl(coreAppRestHandlerImpl) helmAppRestHandlerImpl := client3.NewHelmAppRestHandlerImpl(sugaredLogger, helmAppServiceImpl, enforcerImpl, clusterServiceImplExtended, enforcerUtilHelmImpl, appStoreDeploymentServiceImpl, installedAppDBServiceImpl, userServiceImpl, attributesServiceImpl, serverEnvConfigServerEnvConfig, fluxApplicationServiceImpl, argoApplicationServiceExtendedImpl) helmAppRouterImpl := client3.NewHelmAppRouterImpl(helmAppRestHandlerImpl) - k8sApplicationRestHandlerImpl := application3.NewK8sApplicationRestHandlerImpl(sugaredLogger, k8sApplicationServiceImpl, pumpImpl, terminalSessionHandlerImpl, enforcerImpl, enforcerUtilHelmImpl, enforcerUtilImpl, helmAppServiceImpl, userServiceImpl, k8sCommonServiceImpl, validate, environmentVariables, fluxApplicationServiceImpl, argoApplicationReadServiceImpl) + enforcerUtilGitOpsImpl := rbac.NewEnforcerUtilGitOpsImpl(sugaredLogger, clusterReadServiceImpl) + k8sApplicationRestHandlerImpl := application3.NewK8sApplicationRestHandlerImpl(sugaredLogger, k8sApplicationServiceImpl, pumpImpl, terminalSessionHandlerImpl, enforcerImpl, enforcerUtilHelmImpl, enforcerUtilGitOpsImpl, enforcerUtilImpl, helmAppServiceImpl, userServiceImpl, k8sCommonServiceImpl, validate, environmentVariables, fluxApplicationServiceImpl, argoApplicationReadServiceImpl) k8sApplicationRouterImpl := application3.NewK8sApplicationRouterImpl(k8sApplicationRestHandlerImpl) pProfRestHandlerImpl := restHandler.NewPProfRestHandler(userServiceImpl, enforcerImpl) pProfRouterImpl := router.NewPProfRouter(sugaredLogger, pProfRestHandlerImpl) @@ -1098,18 +1099,18 @@ func InitializeApp() (*App, error) { deploymentConfigurationRouterImpl := configDiff3.NewDeploymentConfigurationRouter(deploymentConfigurationRestHandlerImpl) infraConfigRestHandlerImpl := infraConfig.NewInfraConfigRestHandlerImpl(sugaredLogger, infraConfigServiceImpl, userServiceImpl, enforcerImpl, enforcerUtilImpl, validate) infraConfigRouterImpl := infraConfig.NewInfraProfileRouterImpl(infraConfigRestHandlerImpl) - argoApplicationRestHandlerImpl := argoApplication2.NewArgoApplicationRestHandlerImpl(argoApplicationServiceExtendedImpl, argoApplicationReadServiceImpl, sugaredLogger, enforcerImpl) + argoApplicationRestHandlerImpl := argoApplication2.NewArgoApplicationRestHandlerImpl(argoApplicationServiceExtendedImpl, argoApplicationReadServiceImpl, sugaredLogger, enforcerImpl, enforcerUtilGitOpsImpl) argoApplicationRouterImpl := argoApplication2.NewArgoApplicationRouterImpl(argoApplicationRestHandlerImpl) deploymentHistoryServiceImpl := cdPipeline.NewDeploymentHistoryServiceImpl(sugaredLogger, cdHandlerImpl, imageTaggingReadServiceImpl, imageTaggingServiceImpl, pipelineRepositoryImpl, deployedConfigurationHistoryServiceImpl) apiReqDecoderServiceImpl := devtronResource.NewAPIReqDecoderServiceImpl(sugaredLogger, pipelineRepositoryImpl) historyRestHandlerImpl := devtronResource2.NewHistoryRestHandlerImpl(sugaredLogger, enforcerImpl, deploymentHistoryServiceImpl, apiReqDecoderServiceImpl, enforcerUtilImpl) historyRouterImpl := devtronResource2.NewHistoryRouterImpl(historyRestHandlerImpl) devtronResourceRouterImpl := devtronResource2.NewDevtronResourceRouterImpl(historyRouterImpl) - fluxApplicationRestHandlerImpl := fluxApplication2.NewFluxApplicationRestHandlerImpl(fluxApplicationServiceImpl, sugaredLogger, enforcerImpl) + fluxApplicationRestHandlerImpl := fluxApplication2.NewFluxApplicationRestHandlerImpl(fluxApplicationServiceImpl, sugaredLogger, enforcerImpl, enforcerUtilGitOpsImpl) fluxApplicationRouterImpl := fluxApplication2.NewFluxApplicationRouterImpl(fluxApplicationRestHandlerImpl) scanningResultRestHandlerImpl := resourceScan.NewScanningResultRestHandlerImpl(sugaredLogger, userServiceImpl, imageScanServiceImpl, enforcerImpl, enforcerUtilImpl, validate) scanningResultRouterImpl := resourceScan.NewScanningResultRouterImpl(scanningResultRestHandlerImpl) - userResourceExtendedServiceImpl := userResource.NewUserResourceExtendedServiceImpl(sugaredLogger, teamServiceImpl, environmentServiceImpl, appCrudOperationServiceImpl, chartGroupServiceImpl, appListingServiceImpl, appWorkflowServiceImpl, k8sApplicationServiceImpl, clusterServiceImplExtended, commonEnforcementUtilImpl, enforcerUtilImpl, enforcerImpl) + userResourceExtendedServiceImpl := userResource.NewUserResourceExtendedServiceImpl(sugaredLogger, teamServiceImpl, environmentServiceImpl, appCrudOperationServiceImpl, chartGroupServiceImpl, appListingServiceImpl, appWorkflowServiceImpl, k8sApplicationServiceImpl, clusterServiceImplExtended, commonEnforcementUtilImpl, enforcerUtilImpl, enforcerImpl, argoApplicationServiceExtendedImpl, fluxApplicationServiceImpl) restHandlerImpl := userResource2.NewUserResourceRestHandler(sugaredLogger, userServiceImpl, userResourceExtendedServiceImpl) routerImpl := userResource2.NewUserResourceRouterImpl(restHandlerImpl) appManagementServiceImpl := overview.NewAppManagementServiceImpl(sugaredLogger, appRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, ciWorkflowRepositoryImpl, cdWorkflowRepositoryImpl, environmentRepositoryImpl, teamRepositoryImpl, workflowStageRepositoryImpl, repositoryImpl)