diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..468c218 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +# avoid issues with bracket spacing around e.g. {{ .Release.Namespace }} +# https://github.com/redhat-developer/vscode-yaml/issues/246 +charts/openshift-console-plugin/templates/*.yaml diff --git a/README.md b/README.md index a84490d..4db32f1 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Docker Repository on Quay](https://quay.io/repository/crowdstrike/falcon-openshift-console-plugin/status 'Docker Repository on Quay')](https://quay.io/repository/crowdstrike/falcon-openshift-console-plugin) This is a dynamic plugin for the Red Hat OpenShift console. The plugin provides additional visibility -to the Falcon operator and Falcon-protected virtual machines. +to the Falcon operator and Falcon-protected virtual machines and pods. ### Extension to the VirtualMachine page @@ -30,12 +30,9 @@ The Falcon OpenShift Console Plugin is available at [quay.io/crowdstrike/falcon- Install the chart using the name of the plugin as the Helm release name into a new namespace or an existing namespace as specified by the `plugin_console-plugin-template` parameter by using the following command: ```shell -helm upgrade -i my-plugin charts/openshift-console-plugin -n plugin__console-plugin-template --create-namespace --set plugin.image=quay.io/crowdstrike/falcon-openshift-console-plugin:latest +helm upgrade -i falcon-openshift-console-plugin charts/openshift-console-plugin -n falcon-openshift-console-plugin --create-namespace --set plugin.image=quay.io/crowdstrike/falcon-openshift-console-plugin:latest ``` -> [!NOTE] -> When deploying on OpenShift 4.10, it is recommended to add the parameter `--set plugin.securityContext.enabled=false` which will omit configurations related to Pod Security. - > [!NOTE] > When defining i18n namespace, adhere `plugin__` format. The name of the plugin should be extracted from the `consolePlugin` declaration within the [package.json](package.json) file. @@ -46,10 +43,12 @@ helm upgrade -i my-plugin charts/openshift-console-plugin -n plugin__console-pl - Alerts: Read - Hosts: Read - Vulnerabilities: Read + - Falcon Container Image: Read -2. In the same namespace as virtual machine workloads, create a secret named `crowdstrike-api` with +2. In the same namespace as virtual machine or pod workloads where you want security visibility, create a secret named `crowdstrike-api` with the following fields: + - `cloud` (e.g. `us-1`) - `client_id` - `client_secret` @@ -57,8 +56,7 @@ helm upgrade -i my-plugin charts/openshift-console-plugin -n plugin__console-pl > This configuration assumes any user with access to read secrets in the chosen namespace should > have access to the API client itself, as well as the related data from the Falcon platform. -If you have multiple namespaces with VM workloads, you will need to configure a `crowdstrike-api` secret -in each. +If you have multiple namespaces where you want to surface CrowdStrike security data, you will need to configure a `crowdstrike-api` secret in each. ## Development diff --git a/charts/openshift-console-plugin/templates/configmap.yaml b/charts/openshift-console-plugin/templates/configmap.yaml index 07c2170..8bb2c51 100644 --- a/charts/openshift-console-plugin/templates/configmap.yaml +++ b/charts/openshift-console-plugin/templates/configmap.yaml @@ -14,11 +14,39 @@ data: include /etc/nginx/mime.types; default_type application/octet-stream; keepalive_timeout 65; + + upstream crowdstrike_us1 { + server api.crowdstrike.com:443; + } + + upstream crowdstrike_us2 { + server api.us-2.crowdstrike.com:443; + } + + upstream crowdstrike_eu1 { + server api.eu-1.crowdstrike.com:443; + } + + # we receive the region in the header thanks to ProxiedFetch, so map that to the right upstream + map $http_x_cs_region $api_upstream { + default crowdstrike_us1; + us-1 crowdstrike_us1; + us-2 crowdstrike_us2; + eu-1 crowdstrike_eu1; + } + server { listen {{ .Values.plugin.port }} ssl; listen [::]:{{ .Values.plugin.port }} ssl; ssl_certificate /var/cert/tls.crt; ssl_certificate_key /var/cert/tls.key; root /usr/share/nginx/html; + + location /crwdapi/ { + # a rewrite is not usually needed with a location like /foo/ and with a trailing slash on + # proxy_pass, but variables mess with this behavior https://stackoverflow.com/a/71224059 + rewrite ^/crwdapi/(.*) /$1 break; + proxy_pass https://$api_upstream; + } } } diff --git a/charts/openshift-console-plugin/templates/consoleplugin.yaml b/charts/openshift-console-plugin/templates/consoleplugin.yaml index c70aa50..696aa35 100644 --- a/charts/openshift-console-plugin/templates/consoleplugin.yaml +++ b/charts/openshift-console-plugin/templates/consoleplugin.yaml @@ -7,7 +7,7 @@ metadata: {{- include "openshift-console-plugin.labels" . | nindent 4 }} spec: displayName: {{ default (printf "%s Plugin" (include "openshift-console-plugin.name" .)) .Values.plugin.description }} - i18n: + i18n: loadType: Preload backend: type: Service @@ -15,4 +15,16 @@ spec: name: {{ template "openshift-console-plugin.name" . }} namespace: {{ .Release.Namespace }} port: {{ .Values.plugin.port }} - basePath: {{ .Values.plugin.basePath }} \ No newline at end of file + basePath: {{ .Values.plugin.basePath }} + proxy: + # re-proxy the existing backend service, since the backend is normally exposed to only allow + # GET's, but our reverse proxy in the nginx config supports any method + # https://github.com/openshift/console/blob/master/pkg/plugins/handlers.go + - alias: reproxy + authorization: None + endpoint: + service: + name: {{ template "openshift-console-plugin.name" . }} + namespace: {{ .Release.Namespace }} + port: {{ .Values.plugin.port }} + type: Service diff --git a/src/components/pod/PodTab.tsx b/src/components/pod/PodTab.tsx index 32ba6c7..fe8cf46 100644 --- a/src/components/pod/PodTab.tsx +++ b/src/components/pod/PodTab.tsx @@ -10,11 +10,12 @@ import { PageSection, Spinner, } from '@patternfly/react-core'; -import { FalconClient } from 'crowdstrike-falcon'; +import { FalconClient, FalconCloud } from 'crowdstrike-falcon'; import { useK8sModel, k8sGet } from '@openshift-console/dynamic-plugin-sdk'; import ImageDetectionsCard from './ImageDetectionsCard'; import ImageVulnsCard from './ImageVulnsCard'; import RuntimeDetectionsCard from './RuntimeDetectionsCard'; +import proxiedFetchFactory from '../shared/ProxiedFetch'; export default function PodDetails({ obj }) { const [loading, setLoading] = React.useState(true); @@ -29,10 +30,11 @@ export default function PodDetails({ obj }) { React.useEffect(() => { k8sGet({ model: secretModel, name: 'crowdstrike-api', ns: obj.metadata.namespace }) .then((secret) => { + const cloud: FalconCloud = atob(secret['data'].cloud) as FalconCloud; setClient( new FalconClient({ - cloud: 'us-2', // TODO: cast cloud to FalconCloud type - // cloud: atob(secret['data'].cloud), + fetchApi: proxiedFetchFactory(cloud), + cloud: cloud, clientId: atob(secret['data'].client_id), clientSecret: atob(secret['data'].client_secret), }), diff --git a/src/components/shared/ProxiedFetch.ts b/src/components/shared/ProxiedFetch.ts new file mode 100644 index 0000000..24a56d2 --- /dev/null +++ b/src/components/shared/ProxiedFetch.ts @@ -0,0 +1,19 @@ +import { consoleFetch } from '@openshift-console/dynamic-plugin-sdk'; + +export default function proxiedFetchFactory(cloud) { + return (url, options) => { + // make requests to our reverse proxy instead of directly to the CrowdStrike API, since it doesn't + // support arbitrary CORS + const proxyBase = '/api/proxy/plugin/falcon-openshift-console-plugin/reproxy/crwdapi'; + const path = url.substr(url.indexOf('.com') + 4); + + // add region to request header so our reverse proxy can forward correctly + if (!options.headers) { + options.headers = {}; + } + options.headers['X-CS-REGION'] = cloud; + + // use consoleFetch to ensure CSRF headers are added + return consoleFetch(proxyBase + path, options); + }; +} diff --git a/src/components/virt/VirtualMachineTab.tsx b/src/components/virt/VirtualMachineTab.tsx index c7e0c7d..cd7ad59 100644 --- a/src/components/virt/VirtualMachineTab.tsx +++ b/src/components/virt/VirtualMachineTab.tsx @@ -16,6 +16,7 @@ import { DeviceapiDeviceSwagger, DomainBaseAPIVulnerabilityV2, FalconClient, + FalconCloud, } from 'crowdstrike-falcon'; import { k8sGet, useK8sModel } from '@openshift-console/dynamic-plugin-sdk'; import DetectionsTable from './DetectionsTable'; @@ -24,6 +25,7 @@ import VulnsTable from './VulnsTable'; import '../missing-pf-styles.css'; import './style.css'; import SecurityOverview from './SecurityOverview'; +import proxiedFetchFactory from '../shared/ProxiedFetch'; export default function VirtualMachineTab({ obj }) { const [loading, setLoading] = React.useState(true); @@ -45,10 +47,11 @@ export default function VirtualMachineTab({ obj }) { React.useEffect(() => { k8sGet({ model: secretModel, name: 'crowdstrike-api', ns: obj.metadata.namespace }) .then((secret) => { + const cloud: FalconCloud = atob(secret['data'].cloud) as FalconCloud; setClient( new FalconClient({ - cloud: 'us-2', // TODO: cast cloud to FalconCloud type - // cloud: atob(secret['data'].cloud), + fetchApi: proxiedFetchFactory(cloud), + cloud: cloud, clientId: atob(secret['data'].client_id), clientSecret: atob(secret['data'].client_secret), }),