github.com/argoproj/argo-cd/v2@v2.10.5/docs/proposals/backend-support-appset.md (about) 1 --- 2 title: Adding backend support for ApplicationSets RBAC CLI/Web requests 3 authors: 4 - "@jgwest" # Authors' github accounts here. 5 sponsors: 6 - TBD # List all intereste parties here. 7 reviewers: 8 - "@alexmt" 9 - "@jannis" 10 approvers: 11 - "@alexmt" 12 - "@jannis" 13 14 creation-date: 2021-04-05 15 last-updated: 2021-04-05 16 --- 17 18 # Neat Enhancement Idea 19 20 This is a proposal to add support for creating ApplicationSets via the Argo CD Web/CLI, by adding support to the ApplicationSet and API Server backend that respects Argo CD RBAC. 21 22 ## Summary 23 24 Currently, users can only create ApplicationSets by applying configurations declaratively using `kubectl`. Introducing support for CLI/UI would improve the overall experience for managing large number of applications using ApplicationSet. 25 26 ## Motivation 27 28 As ApplicationSet Controller is now part of ArgoCD installation, we would like to allow users to be able to create/update/delete application sets via CLI/UI. 29 30 ### Goals 31 32 * **Expose endpoints in the API server to interact with ApplicationSets** 33 34 Users should be able to create/update/delete ApplicationSets using argocd CLI/UI. 35 36 * **Changes to CLI** 37 38 Add a new argocd command option `appset` with `create`, `delete`, `update` and `list` sub-commands. 39 40 ## Proposal 41 42 This is the high level overview of creation (update/deletion) of an ApplicationSet via Argo CD CLI/UI. 43 44  45 46 1. User issues `argocd appset create/update/delete`(or Web UI equivalent) command from CLI, into an Argo CD server on which they are logged-in. The command converts the command request into GRPC and sends it off to Argo CD API Server. 47 48 #### **Argo CD API Server:** 49 50 2. The API Server receives Create/Update/Delete request via GRPC and verifies that ApplicationSet controller is installed within the namespace (if not, return an error response back to user). 51 3. The API Server sends the GRPC request to the ApplicationSet controller via GRPC including authentication information from the user in the request. 52 53 #### **ApplicationSet controller:** 54 55 4. ApplicationSet controller receives Create/Update/Delete GRPC. (These next steps will be a create example, but update and delete are similar.) 56 57 5. ***Pre-generate check:*** Application controller will perform various checks before creating ApplicationSet: 58 59 * Ensure user has appropriate RBAC to run the generator: 60 * Verify that the user can access the Git repository (for Git generators) 61 * Verify that user has cluster access (to see the clusters, for Cluster generator) 62 * Verify that the user has permission to create/update/delete (depending on the request type) at least one Application within the RBAC policy. We want to prevent the generators being invoked by users that don't have permissions to create any Applications (since generators or templates might be exploited to DoS the ApplicationSet controller, using a malicious ApplicationSet) 63 64 6. Once the pre-checks have been confirmed, the controller will run the generator, and render the parameters into the template. Upon generating the template, the controller will need to perform some checks before creating the Applications. 65 66 7. ***Post-generate check:*** Look at the generated Applications (but don't apply them yet!), and verify that user has the required RBAC permissions to perform the required actions. This is a dynamic (or runtime) check, as it works on the dynamically generated applications; eg. it is not possible to predict the result of these checks without first running the generator, unlike the static checks. 67 68 8. Once all the checks have passed, apply the ApplicationSet and the Applications, to the namespace. 69 70 #### **Should we add a new 'applicationset' RBAC resource?** 71 72 * Do we need to add a new RBAC resource for applicationsets, alongside the existing ones? (clusters, projects, applications, repositories, certificates, accounts, gpgkeys) 73 Not mandatory for the design explained in this proposal. 74 75 * If we DID add a new RBAC resource, this would require application administrators to add a new ApplicationSet resource to the RBAC policy list, alongside their existing Application policies and to keep the ApplicationSet and Application RBAC policy lists in sync. Or, said another way: there would be very few (if any) cases where a user would need to be able to create an Application, but not be able to create an ApplicationSet (and vice versa). When it comes to security, the less moving parts that need to be synchronized, the better (with some minor loss in flexibility, eg the ability to specifically prevent access to ApplicationSet resource). 76 77 * If there is no RBAC resource for applicationsets, how do we control access to them? 78 Instead of an applicationset rbac resource, we instead examine the Applications that are owned by the applicationset. An ApplicationSet inherits permissions from its generated children. 79 Example: if you try to delete an ApplicationSet, we check if you can delete it’s child Applications. 80 81 #### **ApplicationSet Deletion Algorithm:** 82 83 A user is only able to delete an ApplicationSet if they have permissions to delete all of the Applications managed by the ApplicationSet. This check is performed in ApplicationSet controller, on receiving a delete request via GRPC from API server. 84 85 * For each application owned by the ApplicationSet that the user is attempting to delete: 86 * Check if the user has delete permission on the Application 87 * Check if the user has delete permission within the project (?) 88 * If the user does NOT have permission on least one of these, the operation should fail. 89 * On pass, ApplicationController server deletes (ie kubectl delete) the ApplicationSet resource. 90 91 #### **ApplicationSet Update Algorithm:** 92 93 A user can only update an ApplicationSet if the user has permission to create, update, delete all of the Applications currently owned by the ApplicationSet. 94 95 * When the user makes a change to an ApplicationSet, we assume that it's possible that the change might affect any or all of the Applications, and thus we require the user to have write access to all of those Applications. 96 * We likewise check that the resulting generated Applications are also compliant with the user's permissions. 97 98 Algorithm is, if the user attempts to update an ApplicationSet via Web UI/CLI: 99 100 * ApplicationSet controller receives a request to update an ApplicationSet from API server 101 * The ApplicationSetController looks at all the Applications owned by the ApplicationSet (via ownerref or annotation): 102 Verify that the user has permission to act on all of the Applications currently managed by the ApplicationSet 103 * If the above precondition is met, proceed to the next step, otherwise fail. 104 * The ApplicationSet is generated and rendered into a template 105 * Look at the generated Applications, and make sure the user has appropriate privileges 106 * All the same checks done by the Create workflow, described above, are done here (user can access repo, cluster, etc 107 * Finally, on success, the API server applies (kubectl apply) the requested change to the ApplicationSet (and the Applications). 108 109 #### **ApplicationSets and AppProjects:** 110 111 An important design constraint in this area: ApplicationSets do not belong to projects. They generate Applications that are a part of projects, but they themselves are not part of a project. 112 113 1. Why not just include a project field on ApplicationSet CR? 114 115 A single ApplicationSet has the power to produce Applications within multiple projects, so it does not necessarily make sense to include an ApplicationSet within a single project. 116 117 2. Why not just include a projects field (array of strings) on ApplicationSets, to allow it to belong to multiple projects? 118 119 This is getting closer to ideal, but still limits the expressive power of the ApplicationSet: it requires a user to specify, up front, what projects they expect to generate applications for. Eg you must statically define an ApplicationSet's projects. This excludes the scenario where the projects that a particular ApplicationSet will generate Applications for is truly dynamic, eg coming from a configuration file in Git, and thus not known at creation. 120 121 3. We still need a way to limit the scope of user actions against ApplicationSets. Even though ApplicationSets don't belong to a project, we still need to prevent users from modifying ApplicationSets that they don't have RBAC access to. By looking at the projects of child objects, we achieve the same goal. We don't want users to be able to delete ApplicationSets that they don't have RBAC access to. But, since ApplicationSets don't have a project, we need some way to tell what applications/projects an ApplicationSet manages. So we use the applications and projects within it. 122 123 #### **Command Design:** 124 125 ```shell 126 argocd appset create "(filename.yaml)" 127 argocd appset delete "(applicationset resource name)" 128 argocd appset apply "(filename.yaml)" 129 ``` 130 131 This proposal assumes that the ApplicationSet controller is still an optional, standalone install. Thus all argocd appset commands should fail if the ApplicationSet controller is not installed. (The Argo CD API server would check if the ApplicationServer is running by looking for a deployment with a specific 'applicationset controller' annotation, or similar mechanism.) 132 133 This command proposal differs significantly from how the argocd app create command is designed: notice the lack of parameters to appset create/apply besides the filename. Rather than creating an application(set) by adding support for a large number of parameters, eg: 134 135 ```shell 136 argocd app create guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --directory-recurse 137 ``` 138 139 Instead appset create and appset apply will just take as a parameter, a path to a YAML file, in the form of a standard ApplicationSet CR: 140 141 ```yaml 142 # cluster-addons.yaml: 143 apiVersion: argoproj.io/v1alpha1 144 kind: ApplicationSet 145 metadata: 146 name: cluster-addons 147 spec: 148 generators: 149 - git: 150 repoURL: https://github.com/argoproj-labs/applicationset.git 151 revision: HEAD 152 directories: 153 - path: examples/git-generator-directory/cluster-addons/* 154 template: 155 metadata: 156 name: '{{path.basename}}' 157 spec: 158 project: default 159 source: 160 repoURL: https://github.com/argoproj-labs/applicationset.git 161 targetRevision: HEAD 162 path: '{{path}}' 163 destination: 164 server: https://kubernetes.default.svc 165 namespace: '{{path.basename}}' 166 167 ``` 168 169 ```shell 170 # Create the above ApplicationSet 171 argocd appset create cluster-addons.yaml 172 ``` 173 174 In general, the reason to use YAML is that CLI parameters aren't a good fit for allowing the user to fully express what can be represented with an ApplicationSet resource. 175 176 #### **Why use YAML file, over CLI params:** 177 178 * Easier for users to specify a YAML file, than specifying a bunch of parameters 179 * By my count, if creating an ApplicationSet using parameters, it would take at least 8 parameters (16+ command arguments) 180 * For example, the above ApplicationSet would look like: 181 182 ```shell 183 appset create --name cluster-addons --gitGeneratorRepoURL "repo url" --gitGeneratorRevision "HEAD" --gitGeneratorDirectory "examples/git-generator-directory/cluster-addons/*" --templateMetadataName "{{path.basename}}" --templateProject "default" --templateSrcRepoURL "https://github.com/argoproj-labs/applicationset.git" --templateSrcRevision "HEAD" --templateSrcPath "{{path}}" --templateDestServer "https://kubernetes.default.svc" --templateDestNamespace "{{path.basename}}" 184 ``` 185 186 * Matrix generator is especially tough to represent with parameters, as it takes two generators as input, eg: 187 188 ```shell 189 spec: 190 generators: 191 - matrix: 192 generators: 193 - git: 194 name: cluster-deployments 195 repoURL: https://github.com/argoproj-labs/applicationset.git 196 revision: HEAD 197 directories: 198 - path: examples/proposal/matrix/cluster-addons/* 199 - clusters: 200 selector: 201 matchLabels: 202 argocd.argoproj.io/secret-type: cluster 203 ``` 204 205 * Likewise, tough to get full expressive power of YAML, due to support for arrays of generators: 206 207 ```yaml 208 spec: 209 generators: 210 - list: 211 # (...) 212 - list: 213 # (...) 214 - list: 215 # (...) 216 ``` 217 218 #### **Why use CLI params, instead of YAML:** 219 220 * Users might be more familiar with Argo CD CLI style commands 221 * Some folks are less literate in YAML, and thus don't grok YAML's hierarchy/parsing rules (which is totally fair, they are initially obtuse) 222 * CLI has the advantage of hiding the hierarchy (for better or worse) 223 224 225 #### A new component for Application Set 226 227 The Application Set will follow the same design as the rest of ArgoCD project by having separate components with different responsibilities. With that, Application Set Controller will not expose any API (internal or external) and be a simple Kubernetes Controller with a single responsibility of reconciling ApplicationSet resources. In order to expose the API endpoints for Application Set controller, we need to come up with a new additional server running Application Set controller + Application Set API. 228 229 Later, we would plan to move the code of this new server into existing controller package, but this would require a separate proposal on it's own. 230 231 ## Alternatives 232 233 Rather than using Argo CD's CLI, we could create a new AppSet CLI "appset" that would communicate directly with the ApplicationSet deployment, rather than going through the Argo CD API Server as an intermediary (though if we were adding web UI support, this would still be required regardless).