From f0dd120500c4780ac37de17d6f23cb6118c5e58f Mon Sep 17 00:00:00 2001 From: Jason Parraga Date: Sat, 19 Oct 2024 17:10:30 -0700 Subject: [PATCH] Flyte Admin RBAC RFC draft Signed-off-by: Jason Parraga --- rfc/images/rbac-high-level-black.png | Bin 0 -> 39618 bytes rfc/system/0000-flyteadmin-rbac.md | 201 +++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 rfc/images/rbac-high-level-black.png create mode 100644 rfc/system/0000-flyteadmin-rbac.md diff --git a/rfc/images/rbac-high-level-black.png b/rfc/images/rbac-high-level-black.png new file mode 100644 index 0000000000000000000000000000000000000000..973fcdde3027035ea2b7e925cff2fa64590d8abf GIT binary patch literal 39618 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 literal 0 HcmV?d00001 diff --git a/rfc/system/0000-flyteadmin-rbac.md b/rfc/system/0000-flyteadmin-rbac.md new file mode 100644 index 0000000000..5e1a3cc775 --- /dev/null +++ b/rfc/system/0000-flyteadmin-rbac.md @@ -0,0 +1,201 @@ +# [RFC] FlyteAdmin RBAC + +**Authors:** + +- @sovietaced + +## 1 Executive Summary + +We propose support for role based access control in Flyte Admin with support for project and domain level isolation/filtering. + +## 2 Motivation + +Support for authorization in Flyte Admin is minimal and may be unsuitable for production deployments. Flyte Admin only +supports blanket authorization which does not align with information security best practices like the +[principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege). Flyte's project and domain +concepts seem like strong constructs for supporting multi-tenancy but there are no mechanisms in place to prevent users +of one tenant from accidentally or maliciously maniuplating another tenant's executions, tasks, launch plans, etc. + +At Stack AV we have solved this problem in our fork and are looking to contribute our work back after several requests +from the community. + +## 3 Proposed Implementation + +### High Level Design + +![High Level Design](/rfc/images/rbac-high-level-black.png) + +This proposal introduces an authorization interceptor that will be executed after the authentication interceptor. The +authorization interceptor will consult the auth or authorization config to understand how it should resolve roles from +the identity context along with what authorization policies are tied to the user's roles. If the user has an authorization +policy that permits the incoming request the project and domain level scope will be extracted from the authorization +policy and injected into the authorization context for use in the service layer. The request will hit the service layer which +will make requests to the repository layer. The repository layer will leverage some utility functions to 1) authorize +whether resources can be created in a project/domain and 2) generate some SQL filters to be injected into queries that +read/update/delete resources. + +Ultimately the authorization interceptor will provide RPC level authorization and the changes to the repository layer +will provide resource level authorization within the target RPC. + +#### Authorization Config +The authorization config will be used to configure the behavior of RBAC. The first thing it will need is a way to +resolve roles from the user's identity context. Ideally this should be flexible enough to resolve a role from different +elements of a token to support various use cases. + +```yaml +authorization: + roleResolutionStrategies: # one or more can be configured + - scopes # attempts to use token scopes as roles + - claims # attempts to use token claim values as roles + - userID # attempts to use the user ID as the role + + claimRoleResolver: # claim based role resolution requires additional configuration + - key: groups # this is the key to look for in the token claims + type: list # this declares the structure of the value. parse value as comma separated string + - key: group # this is the key to look for in the token claims + type: string # this declares the structure of the value. parse value as single string +``` + +The second part of the authorization will include exceptions. There are some RPCs which should not be authorized. +```yaml +authorization: + methodBypassPatterns: # ideally these should be enabled by default + - "/grpc.health.v1.Health/.*" + - "/flyteidl.service.AuthMetadataService/.*" +``` + +The last part of the authorization will declare the authorization policies. The authorization policies will include the +name of the role and will include a list of permitting rules. Each rule includes a mandatory method pattern which is +regex statement that matches the target gRPC method. Each rules includes optional project and domain isolation/filtering +fields. If these fields are not included it is implied that the rule applies to all projects and domains. +```yaml +authorization: + policies: # policies is a list instead of a map since viper map keys are case insensitive + - name: admin + rules: # list of rules + - name: "allow all" # name / description of the rule + methodPattern: ".*" # regex pattern to match on gRPC method + - name: read-only + rules: + - name: "read everything" + methodPattern: "Get.*|List.*" + - name: mapping-team + rules: + - name: "r/w for the mapping project in dev only" + methodPattern: ".*" + project: mapping # access to only the mapping project + domain: development # access to only the development domain within the mapping project + - name: ci + rules: + - name: "r/w for every project in production" + methodPattern: ".*" + domain: production # you can wildcard project and declare domain level access + - name: 0oahjhk34aUxGnWcZ0h7 # the names can even include things like okta app IDs + rules: + - name: "flyte propeller" + methodPattern: ".*" +``` + +#### Authorization Interceptor + +The authorization interceptor logic will behave like so: +1. The interceptor checks to see if the target RPC method is configured to be bypassed. Call the next handler if bypassed. +2. The interceptor consults the role resolver to obtain a list of role names for the user. +3. The interceptor iterates over the list of role names and searches for any authorization policies tied to the role names. If no matching authorization policies are found return Permission Denied. +4. The interceptor iterates over the matching authorization policies to see if they permit the target RPC. Each rule that matches is added to a list. If no matching rules are found return Permission Denied. +5. The interceptor extracts the project and domain scope out of each of the permitting rules and sets authorized resource scope on the authorization context. +6. The interceptor calls the next handler. + +Below is a high level overview of what the authorization context would look like. + +```go +type ResourceScope struct { + Project string + Domain string +} + +type AuthorizationContext struct { + resourceScopes []ResourceScope +} + +// TargetResourceScope represents the scope of an individual resource. Sometimes only project level or domain level scope +// is applicable to a resource. In such cases, a user's resource scope may need to be truncated to matcht he depth +// of the target resource scope. +type TargetResourceScope = int + +const ( + ProjectTargetResourceScope = 0 + DomainTargetResourceScope = 1 +) +``` + +#### Authorization Utils + DB Layer + +The final piece of the puzzle is what performs resource level authorization and filtering. Historically, I have found +that the best (albeit challenging) way to do this is at the database layer for a few reasons: +* Filtering resources at the database works natively with any pagination +* Filtering resources at the database is the most performant since the database engine is fast and ends up returning a smaller data set over the network +* Filtering resources at the database plays nicely with logic to handle records that aren't found and does not leak data about which resources may or may not be present. + +My proposal is to have utility functions for two different workflows: +1. Creating Resources + * This is a dedicated utility function used in repository code to create resource since you cannot add a WHERE clause filter for records that don't exist yet :) + * ```go + func (r *ExecutionRepo) Create(ctx context.Context, input models.Execution, executionTagModel []*models.ExecutionTag) error { + if err := util.AuthorizeResourceCreation(ctx, input.Project, input.Domain); err != nil { + return err + } + + ... + } + ``` +2. Reading, Updating, Deleting resources with util + * This is a utility function used in repository code that translates project/domain level scope into gorm where clause operations that can be attached to existing gorm queries. + * ```go + var ( + executionColumnNames = util.ResourceColumns{Project: "executions.execution_project", Domain: "executions.execution_domain"} + ) + + func (r *ExecutionRepo) Get(ctx context.Context, input interfaces.Identifier) (models.Execution, error) { + authzFilter := util.GetAuthzFilter(ctx, DomainTargetResourceScope, executionColumnNames) + var execution models.Execution + tx := r.db.WithContext(ctx).Where(&models.Execution{....}) + + if authzFilter != nil { + cleanSession := tx.Session(&gorm.Session{NewDB: true}) + tx = tx.Where(cleanSession.Scopes(authzFilter.GetScopes()...)) + } + + tx = tx.Take(&execution) + + ... + } + + ``` + +## 4 Metrics & Dashboards + +Existing metrics should measure RPC response codes. + +## 5 Drawbacks + +*Are there any reasons why we should not do this? Here we aim to evaluate risk and check ourselves.* + +## 6 Alternatives + +*What are other ways of achieving the same outcome?* + +## 7 Potential Impact and Dependencies + +*Here, we aim to be mindful of our environment and generate empathy towards others who may be impacted by our decisions.* + +- *What other systems or teams are affected by this proposal?* +- *How could this be exploited by malicious attackers?* + +## 8 Unresolved questions + +*What parts of the proposal are still being defined or not covered by this proposal?* + +## 9 Conclusion + +*Here, we briefly outline why this is the right decision to make at this time and move forward!*