-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: enable authn + authz in proxy extension (#11694)
* feat: enable authn + authz in proxy extension Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Better context key Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Enable authentication in proxy extensions Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Define headers for Authz Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * add tests to the ValidateHeader function Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Fix CSS bug Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * fix build Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Fix unit-test Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Run tests in parallel Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Implement rbac validation Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * fix CSS issue Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Fix CSS Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Add proxy extensions doc file Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * add title Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Add proxy config doc Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Document configuration and usage Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * fix configmap doc Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Address review comments Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * revert terminal changes Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Add rbac docs Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Fix merge Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * add more details in the rbac doc Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Add upgrading instructions for proxy extensions Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Address review comments Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Add more detail about headers validation Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Fix Host header Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * fix sanitize Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Address review comments Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Don't send error details in response Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Address comments Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * typo Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Fix codeql warning Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * fix codeql warning Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Implement better proxy correlation logic for multi backend setup Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Address security vulnerability Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Improve docs Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> * Fix docs Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com> --------- Signed-off-by: Leonardo Luz Almeida <leonardo_almeida@intuit.com>
- Loading branch information
Showing
16 changed files
with
1,386 additions
and
282 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
# Proxy Extensions | ||
*Current Status: [Alpha][1] (Since v2.7.0)* | ||
|
||
## Overview | ||
|
||
With UI extensions it is possible to enhance Argo CD web interface to | ||
provide valuable data to the user. However the data is restricted to | ||
the resources that belongs to the Application. With proxy extensions | ||
it is also possible to add additional functionality that have access | ||
to data provided by backend services. In this case Argo CD API server | ||
acts as a reverse-proxy authenticating and authorizing incoming | ||
requests before forwarding to the backend service. | ||
|
||
## Configuration | ||
|
||
As proxy extension is in [Alpha][1] phase, the feature is disabled by | ||
default. To enable it, it is necessary to configure the feature flag | ||
in Argo CD command parameters. The easiest way to to properly enable | ||
this feature flag is by adding the `server.enable.proxy.extension` key | ||
in the existing `argocd-cmd-params-cm`. For example: | ||
|
||
```yaml | ||
apiVersion: v1 | ||
kind: ConfigMap | ||
metadata: | ||
name: argocd-cmd-params-cm | ||
namespace: argocd | ||
data: | ||
server.enable.proxy.extension: "true" | ||
``` | ||
Once the proxy extension is enabled, it can be configured in the main | ||
Argo CD configmap ([argocd-cm][2]). | ||
The example below demonstrate all possible configurations available | ||
for proxy extensions: | ||
```yaml | ||
apiVersion: v1 | ||
kind: ConfigMap | ||
metadata: | ||
name: argocd-cm | ||
namespace: argocd | ||
data: | ||
extension.config: | | ||
extensions: | ||
- name: httpbin | ||
backend: | ||
connectionTimeout: 2s | ||
keepAlive: 15s | ||
idleConnectionTimeout: 60s | ||
maxIdleConnections: 30 | ||
services: | ||
- url: http://httpbin.org | ||
cluster: | ||
name: some-cluster | ||
server: https://some-cluster | ||
``` | ||
If a the configuration is changed, Argo CD Server will need to be | ||
restarted as the proxy handlers are only registered once during the | ||
initialization of the server. | ||
Every configuration entry is explained below: | ||
#### `extensions` (*list*) | ||
|
||
Defines configurations for all extensions enabled. | ||
|
||
#### `extensions.name` (*string*) | ||
(mandatory) | ||
|
||
Defines the endpoint that will be used to register the extension | ||
route. For example, if the value of the property is `extensions.name: | ||
my-extension` then the backend service will be exposed under the | ||
following url: | ||
|
||
<argocd-host>/extensions/my-extension | ||
|
||
#### `extensions.backend.connectionTimeout` (*duration string*) | ||
(optional. Default: 2s) | ||
|
||
Is the maximum amount of time a dial to the extension server will wait | ||
for a connect to complete. | ||
|
||
#### `extensions.backend.keepAlive` (*duration string*) | ||
(optional. Default: 15s) | ||
|
||
Specifies the interval between keep-alive probes for an active network | ||
connection between the API server and the extension server. | ||
|
||
#### `extensions.backend.idleConnectionTimeout` (*duration string*) | ||
(optional. Default: 60s) | ||
|
||
Is the maximum amount of time an idle (keep-alive) connection between | ||
the API server and the extension server will remain idle before | ||
closing itself. | ||
|
||
#### `extensions.backend.maxIdleConnections` (*int*) | ||
(optional. Default: 30) | ||
|
||
Controls the maximum number of idle (keep-alive) connections between | ||
the API server and the extension server. | ||
|
||
#### `extensions.backend.services` (*list*) | ||
|
||
Defines a list with backend url by cluster. | ||
|
||
#### `extensions.backend.services.url` (*string*) | ||
(mandatory) | ||
|
||
Is the address where the extension backend must be available. | ||
|
||
#### `extensions.backend.services.cluster` (*object*) | ||
(optional) | ||
|
||
If provided, and multiple services are configured, will have to match | ||
the application destination name or server to have requests properly | ||
forwarded to this service URL. If there are multiple backends for the | ||
same extension this field is required. In this case at least one of | ||
the two will be required: name or server. It is better to provide both | ||
values to avoid problems with applications unable to send requests to | ||
the proper backend service. If only one backend service is | ||
configured, this field is ignored, and all requests are forwarded to | ||
the configured one. | ||
|
||
#### `extensions.backend.services.cluster.name` (*string*) | ||
(optional) | ||
|
||
It will be matched with the value from | ||
`Application.Spec.Destination.Name` | ||
|
||
#### `extensions.backend.services.cluster.server` (*string*) | ||
(optional) | ||
|
||
It will be matched with the value from | ||
`Application.Spec.Destination.Server`. | ||
|
||
## Usage | ||
|
||
Once a proxy extension is configured it will be made available under | ||
the `/extensions/<extension-name>` endpoint exposed by Argo CD API | ||
server. The example above will proxy requests to | ||
`<apiserver-host>/extensions/httpbin/` to `http://httpbin.org`. | ||
|
||
The diagram below illustrates an interaction possible with this | ||
configuration: | ||
|
||
``` | ||
┌─────────────┐ | ||
│ Argo CD UI │ | ||
└────┬────────┘ | ||
│ ▲ | ||
GET <apiserver-host>/extensions/httpbin/anything │ │ 200 OK | ||
+ authn/authz headers │ │ | ||
▼ │ | ||
┌─────────┴────────┐ | ||
│Argo CD API Server│ | ||
└──────┬───────────┘ | ||
│ ▲ | ||
GET http://httpbin.org/anything │ │ 200 OK | ||
│ │ | ||
▼ │ | ||
┌────────┴────────┐ | ||
│ Backend Service │ | ||
└─────────────────┘ | ||
``` | ||
|
||
### Headers | ||
|
||
Note that Argo CD API Server requires additional HTTP headers to be | ||
sent in order to enforce if the incoming request is authenticated and | ||
authorized before being proxied to the backend service. The headers | ||
are documented below: | ||
|
||
#### `Cookie` (*mandatory*) | ||
|
||
Argo CD UI keeps the authentication token stored in a cookie | ||
(`argocd.token`). This value needs to be sent in the `Cookie` header | ||
so the API server can validate its authenticity. | ||
|
||
Example: | ||
|
||
Cookie: argocd.token=eyJhbGciOiJIUzI1Ni... | ||
|
||
The entire Argo CD cookie list can also be sent. The API server will | ||
only use the `argocd.token` attribute in this case. | ||
|
||
#### `Argocd-Application-Name` (mandatory) | ||
|
||
This is the name of the project for the application for which the | ||
extension is being invoked. The header value must follow the format: | ||
`"<namespace>:<app-name>"`. | ||
|
||
Example: | ||
|
||
Argocd-Application-Name: namespace:app-name | ||
|
||
#### `Argocd-Project-Name` (mandatory) | ||
|
||
The logged in user must have access to this project in order to be | ||
authorized. | ||
|
||
Example: | ||
|
||
Argocd-Project-Name: default | ||
|
||
Argo CD API Server will ensure that the logged in user has the | ||
permission to access the resources provided by the headers above. The | ||
validation is based on pre-configured [Argo CD RBAC rules][3]. The | ||
same headers are also sent to the backend service. The backend service | ||
must also validate if the validated headers are compatible with the | ||
rest of the incoming request. | ||
|
||
### Multi Backend Use-Case | ||
|
||
In some cases when Argo CD is configured to sync with multiple remote | ||
clusters, there might be a need to call a specific backend service in | ||
each of those clusters. The proxy-extension can be configured to | ||
address this use-case by defining multiple services for the same | ||
extension. Consider the following configuration as an example: | ||
|
||
```yaml | ||
extension.config: | | ||
extensions: | ||
- name: some-extension | ||
backend: | ||
services: | ||
- url: http://extension-name.com:8080 | ||
cluster | ||
name: kubernetes.local | ||
- url: https://extension-name.ppd.cluster.k8s.local:8080 | ||
cluster | ||
server: user@ppd.cluster.k8s.local | ||
``` | ||
|
||
In the example above, the API server will inspect the Application | ||
destination to verify which URL should be used to proxy the incoming | ||
request to. | ||
|
||
## Security | ||
|
||
When a request to `/extensions/*` reaches the API Server, it will | ||
first verify if it is authenticated with a valid token. It does so by | ||
inspecting if the `Cookie` header is properly sent from Argo CD UI | ||
extension. | ||
|
||
Once the request is authenticated it is then verified if the | ||
user has permission to invoke this extension. The permission is | ||
enforced by Argo CD RBAC configuration. The details about how to | ||
configure the RBAC for proxy-extensions can be found in the [RBAC | ||
documentation][3] page. | ||
|
||
Once the request is authenticated and authorized by the API server, it | ||
is then sanitized before being sent to the backend service. The | ||
request sanitization will remove sensitive information from the | ||
request like the `Cookie` and `Authorization` headers. | ||
|
||
[1]: https://github.com/argoproj/argoproj/blob/master/community/feature-status.md | ||
[2]: https://argo-cd.readthedocs.io/en/stable/operator-manual/argocd-cm.yaml | ||
[3]: ../../operator-manual/rbac.md#the-extensions-resource |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# UI Extensions | ||
|
||
Argo CD web user interface can be extended with additional UI elements. Extensions should be delivered as a javascript file | ||
in the `argocd-server` Pods that are placed in the `/tmp/extensions` directory and starts with `extension` prefix ( matches to `^extension(.*)\.js$` regex ). | ||
|
||
``` | ||
/tmp/extensions | ||
├── example1 | ||
│ └── extension-1.js | ||
└── example2 | ||
└── extension-2.js | ||
``` | ||
|
||
Extensions are loaded during initial page rendering and should register themselves using API exposed in the `extensionsAPI` global variable. (See | ||
corresponding extension type details for additional information). | ||
|
||
The extension should provide a React component that is responsible for rendering the UI element. Extension should not bundle the React library. | ||
Instead extension should use the `react` global variable. You can leverage `externals` setting if you are using webpack: | ||
|
||
```js | ||
externals: { | ||
react: "React"; | ||
} | ||
``` | ||
|
||
## Resource Tab Extensions | ||
|
||
Resource Tab extensions is an extension that provides an additional tab for the resource sliding panel at the Argo CD Application details page. | ||
|
||
The resource tab extension should be registered using the `extensionsAPI.registerResourceExtension` method: | ||
|
||
```typescript | ||
registerResourceExtension(component: ExtensionComponent, group: string, kind: string, tabTitle: string) | ||
``` | ||
|
||
- `component: ExtensionComponent` is a React component that receives the following properties: | ||
|
||
- application: Application - Argo CD Application resource; | ||
- resource: State - the kubernetes resource object; | ||
- tree: ApplicationTree - includes list of all resources that comprise the application; | ||
|
||
See properties interfaces in [models.ts](https://github.com/argoproj/argo-cd/blob/master/ui/src/app/shared/models.ts) | ||
|
||
- `group: string` - the glob expression that matches the group of the resource; note: use globstar (`**`) to match all groups including empty string; | ||
- `kind: string` - the glob expression that matches the kind of the resource; | ||
- `tabTitle: string` - the extension tab title. | ||
- `opts: Object` - additional options: | ||
- `icon: string` - the class name the represents the icon from the [https://fontawesome.com/](https://fontawesome.com/) library (e.g. 'fa-calendar-alt'); | ||
|
||
Below is an example of a resource tab extension: | ||
|
||
```javascript | ||
((window) => { | ||
const component = () => { | ||
return React.createElement("div", {}, "Hello World"); | ||
}; | ||
window.extensionsAPI.registerResourceExtension( | ||
component, | ||
"*", | ||
"*", | ||
"Nice extension" | ||
); | ||
})(window); | ||
``` | ||
|
||
## System Level Extensions | ||
|
||
Argo CD allows you to add new items to the sidebar that will be displayed as a new page with a custom component when clicked. The system level extension should be registered using the `extensionsAPI.registerSystemLevelExtension` method: | ||
|
||
```typescript | ||
registerSystemLevelExtension(component: ExtensionComponent, title: string, options: {icon?: string}) | ||
``` | ||
|
||
Below is an example of a simple system level extension: | ||
|
||
```typescript | ||
((window) => { | ||
const component = () => { | ||
return React.createElement( | ||
"div", | ||
{ style: { padding: "10px" } }, | ||
"Hello World" | ||
); | ||
}; | ||
window.extensionsAPI.registerSystemLevelExtension( | ||
component, | ||
"Test Ext", | ||
"/hello", | ||
"fa-flask" | ||
); | ||
})(window); | ||
``` | ||
|
||
## Application Tab Extensions | ||
|
||
Since the Argo CD Application is a Kubernetes resource, application tabs can be the same as any other resource tab. | ||
Make sure to use 'argoproj.io'/'Application' as group/kind and an extension will be used to render the application-level tab. |
Oops, something went wrong.