From 43f6841f6522a44dc675c27e517aafc55fd8db75 Mon Sep 17 00:00:00 2001 From: ricoberger Date: Mon, 29 Nov 2021 21:14:04 +0100 Subject: [PATCH] [azure] Add permissions for Azure plugin It is now possible to restrict the permissions to resources, resource groups and action in the Azure plugin. For that we extended the Teams CRD with a custom permissions field, which can be used to define the permissions for an Azure instance. The permission schema for the Azure plugin and an example can be found in the documentation of the Azure plugin. --- CHANGELOG.md | 1 + deploy/helm/kobs/Chart.yaml | 2 +- .../helm/kobs/crds/kobs.io_applications.yaml | 10 +-- deploy/helm/kobs/crds/kobs.io_dashboards.yaml | 10 +-- deploy/helm/kobs/crds/kobs.io_teams.yaml | 22 ++++-- deploy/helm/kobs/crds/kobs.io_users.yaml | 10 +-- .../kustomize/crds/kobs.io_applications.yaml | 10 +-- deploy/kustomize/crds/kobs.io_dashboards.yaml | 10 +-- deploy/kustomize/crds/kobs.io_teams.yaml | 22 ++++-- deploy/kustomize/crds/kobs.io_users.yaml | 10 +-- deploy/kustomize/crds/kustomization.yaml | 1 + docs/plugins/azure.md | 77 +++++++++++++++++++ docs/resources/teams.md | 10 +++ pkg/api/apis/team/v1beta1/types.go | 7 ++ .../team/v1beta1/zz_generated.deepcopy.go | 24 ++++++ pkg/api/middleware/auth/auth.go | 1 + pkg/api/middleware/auth/context/context.go | 18 +++++ plugins/azure/azure.go | 18 +++-- plugins/azure/containerinstances.go | 41 ++++++++-- plugins/azure/pkg/instance/instance.go | 9 ++- plugins/azure/pkg/instance/permissions.go | 71 +++++++++++++++++ .../containerinstances/ContainerGroups.tsx | 2 +- .../DetailsContainerGroup.tsx | 2 +- .../DetailsContainerGroupActions.tsx | 4 +- .../containerinstances/DetailsLogs.tsx | 2 +- .../containerinstances/DetailsMetric.tsx | 2 +- plugins/azure/src/components/page/Page.tsx | 2 +- plugins/harbor/pkg/instance/instance.go | 1 - 28 files changed, 316 insertions(+), 83 deletions(-) create mode 100644 plugins/azure/pkg/instance/permissions.go diff --git a/CHANGELOG.md b/CHANGELOG.md index c546bcae2..6c55d9ca6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan - [#213](https://github.com/kobsio/kobs/pull/213): [techdocs] Add TechDocs plugin, to access the documentation for your services within kobs. - [#215](https://github.com/kobsio/kobs/pull/215): [azure] Add Azure plugin, to monitor your Azure resources. +- [#219](https://github.com/kobsio/kobs/pull/219): [azure] Add permissions for Azure plugin, so that access to resources and actions can be restricted based on resource groups. ### Fixed diff --git a/deploy/helm/kobs/Chart.yaml b/deploy/helm/kobs/Chart.yaml index 5e892ea97..13540ea92 100644 --- a/deploy/helm/kobs/Chart.yaml +++ b/deploy/helm/kobs/Chart.yaml @@ -4,5 +4,5 @@ description: Kubernetes Observability Platform type: application home: https://kobs.io icon: https://kobs.io/assets/images/logo.svg -version: 0.8.1 +version: 0.8.2 appVersion: v0.7.0 diff --git a/deploy/helm/kobs/crds/kobs.io_applications.yaml b/deploy/helm/kobs/crds/kobs.io_applications.yaml index 3c7563496..1eaa3ee4f 100644 --- a/deploy/helm/kobs/crds/kobs.io_applications.yaml +++ b/deploy/helm/kobs/crds/kobs.io_applications.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.3.0 + controller-gen.kubebuilder.io/version: v0.5.0 creationTimestamp: null name: applications.kobs.io spec: @@ -22,14 +22,10 @@ spec: description: Application is the Application CRD. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object diff --git a/deploy/helm/kobs/crds/kobs.io_dashboards.yaml b/deploy/helm/kobs/crds/kobs.io_dashboards.yaml index 4127a4d92..2c0720b45 100644 --- a/deploy/helm/kobs/crds/kobs.io_dashboards.yaml +++ b/deploy/helm/kobs/crds/kobs.io_dashboards.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.3.0 + controller-gen.kubebuilder.io/version: v0.5.0 creationTimestamp: null name: dashboards.kobs.io spec: @@ -22,14 +22,10 @@ spec: description: Dashboard is the Dashboard CRD. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object diff --git a/deploy/helm/kobs/crds/kobs.io_teams.yaml b/deploy/helm/kobs/crds/kobs.io_teams.yaml index 85e184e70..2437a7ba0 100644 --- a/deploy/helm/kobs/crds/kobs.io_teams.yaml +++ b/deploy/helm/kobs/crds/kobs.io_teams.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.3.0 + controller-gen.kubebuilder.io/version: v0.5.0 creationTimestamp: null name: teams.kobs.io spec: @@ -22,14 +22,10 @@ spec: description: Team is the Team CRD. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -149,6 +145,18 @@ spec: type: string permissions: properties: + custom: + items: + properties: + name: + type: string + permissions: + x-kubernetes-preserve-unknown-fields: true + required: + - name + - permissions + type: object + type: array plugins: items: type: string diff --git a/deploy/helm/kobs/crds/kobs.io_users.yaml b/deploy/helm/kobs/crds/kobs.io_users.yaml index e40478fef..5f4773d53 100644 --- a/deploy/helm/kobs/crds/kobs.io_users.yaml +++ b/deploy/helm/kobs/crds/kobs.io_users.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.3.0 + controller-gen.kubebuilder.io/version: v0.5.0 creationTimestamp: null name: users.kobs.io spec: @@ -22,14 +22,10 @@ spec: description: User is the User CRD. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object diff --git a/deploy/kustomize/crds/kobs.io_applications.yaml b/deploy/kustomize/crds/kobs.io_applications.yaml index 3c7563496..1eaa3ee4f 100644 --- a/deploy/kustomize/crds/kobs.io_applications.yaml +++ b/deploy/kustomize/crds/kobs.io_applications.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.3.0 + controller-gen.kubebuilder.io/version: v0.5.0 creationTimestamp: null name: applications.kobs.io spec: @@ -22,14 +22,10 @@ spec: description: Application is the Application CRD. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object diff --git a/deploy/kustomize/crds/kobs.io_dashboards.yaml b/deploy/kustomize/crds/kobs.io_dashboards.yaml index 4127a4d92..2c0720b45 100644 --- a/deploy/kustomize/crds/kobs.io_dashboards.yaml +++ b/deploy/kustomize/crds/kobs.io_dashboards.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.3.0 + controller-gen.kubebuilder.io/version: v0.5.0 creationTimestamp: null name: dashboards.kobs.io spec: @@ -22,14 +22,10 @@ spec: description: Dashboard is the Dashboard CRD. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object diff --git a/deploy/kustomize/crds/kobs.io_teams.yaml b/deploy/kustomize/crds/kobs.io_teams.yaml index 85e184e70..2437a7ba0 100644 --- a/deploy/kustomize/crds/kobs.io_teams.yaml +++ b/deploy/kustomize/crds/kobs.io_teams.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.3.0 + controller-gen.kubebuilder.io/version: v0.5.0 creationTimestamp: null name: teams.kobs.io spec: @@ -22,14 +22,10 @@ spec: description: Team is the Team CRD. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object @@ -149,6 +145,18 @@ spec: type: string permissions: properties: + custom: + items: + properties: + name: + type: string + permissions: + x-kubernetes-preserve-unknown-fields: true + required: + - name + - permissions + type: object + type: array plugins: items: type: string diff --git a/deploy/kustomize/crds/kobs.io_users.yaml b/deploy/kustomize/crds/kobs.io_users.yaml index e40478fef..5f4773d53 100644 --- a/deploy/kustomize/crds/kobs.io_users.yaml +++ b/deploy/kustomize/crds/kobs.io_users.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.3.0 + controller-gen.kubebuilder.io/version: v0.5.0 creationTimestamp: null name: users.kobs.io spec: @@ -22,14 +22,10 @@ spec: description: User is the User CRD. properties: apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object diff --git a/deploy/kustomize/crds/kustomization.yaml b/deploy/kustomize/crds/kustomization.yaml index 26f3cd0e8..939eaa058 100644 --- a/deploy/kustomize/crds/kustomization.yaml +++ b/deploy/kustomize/crds/kustomization.yaml @@ -5,3 +5,4 @@ resources: - kobs.io_applications.yaml - kobs.io_dashboards.yaml - kobs.io_teams.yaml + - kobs.io_users.yaml diff --git a/docs/plugins/azure.md b/docs/plugins/azure.md index 0d7b1d3dc..4f2d7712e 100644 --- a/docs/plugins/azure.md +++ b/docs/plugins/azure.md @@ -23,6 +23,7 @@ plugins: | name | string | Name of the Azure instance. | Yes | | displayName | string | Name of the Azure instance as it is shown in the UI. | Yes | | descriptions | string | Description of the Azure instance. | No | +| permissionsEnabled | boolean | Enable the permission handling. The permissions can be defined via the [PermissionsCustom](../resources/teams.md#permissionscustom) in a team. An example of the permission format can be found in the [usage](#usage) section of this page. | No | To authenticate against the Azure API you have to set the following environment variables: @@ -50,6 +51,82 @@ The following options can be used for a panel with the Azure plugin: | containers | string[] | A list of container names. This is only required if the type is `logs`. | No | | metric | string | The name of the metric for which the data should be displayed. Supported values are `CPUUsage`, `MemoryUsage`, `NetworkBytesReceivedPerSecond` and `NetworkBytesTransmittedPerSecond`. This is only required if the type is `metrics`. | No | +## Usage + +### Permissions + +You can define fine grained permissions to access your Azure resources via kobs. The permissions are defined via the `permissions.cusomt` field of a [Team](../resources/teams.md). Each user which is member of this team, will then get the defined permissions. + +In the following example each member of `team1` will get access to all Azure resource, while members of `team2` can only access container instances in the `development` resource group: + +??? note "team1" + + ```yaml + --- + apiVersion: kobs.io/v1beta1 + kind: Team + metadata: + name: team1 + spec: + permissions: + plugins: + - "*" + resources: + - clusters: + - "*" + namespaces: + - "*" + resources: + - "*" + custom: + - name: azure + permissions: + - resources: + - "*" + resourceGroups: + - "*" + verbs: + - "*" + ``` + +??? note "team2" + + ```yaml + --- + apiVersion: kobs.io/v1beta1 + kind: Team + metadata: + name: team2 + spec: + permissions: + plugins: + - "*" + resources: + - clusters: + - "*" + namespaces: + - "*" + resources: + - "*" + custom: + - name: azure + permissions: + - resources: + - "containerinstances" + resourceGroups: + - "development" + verbs: + - "*" + ``` + +The `*` value is a special value, which allows access to all resources, resource groups and action. The following values can also be used for resources and verbs: + +- `resources`: `containerinstances` +- `verbs`: `get`, `put`, `post` and `delete` + +!!! note + You have to set the `permissionsEnabled` property in the configuration to `true` and you must enable [authentication](../configuration/authentication.md) for kobs to use this feature. + ## Examples ### Container Instances Dashboard diff --git a/docs/resources/teams.md b/docs/resources/teams.md index 1aa6bb415..4ff948e9c 100644 --- a/docs/resources/teams.md +++ b/docs/resources/teams.md @@ -31,6 +31,7 @@ In the following you can found the specification for the Team CRD. | ----- | ---- | ----------- | -------- | | plugins | []string | A list of plugins, which can be accessed by the members of the team. The special list entry `*` allows access to all plugins. | Yes | | resources | [[]PermissionResources](#permissionresources) | A list of resources, which can be accessed by the members of the team. | Yes | +| custom | [[]PermissionsCustom](#permissionscustom) | A list of custom permissions. | Yes | ### PermissionResources @@ -40,6 +41,15 @@ In the following you can found the specification for the Team CRD. | namespaces | []string | A list of namespaces to allow access to. The special list entry `*` allows access to all namespaces. | Yes | | resources | []string | A list of resources to allow access to. The special list entry `*` allows access to all resources. | Yes | +### PermissionsCustom + +Custom permissions can be used by plugin to have a fine grained permission model. + +| Field | Type | Description | Required | +| ----- | ---- | ----------- | -------- | +| name | string | The name of the plugin instance as it is defined in the configuration. | Yes | +| permissions | any | The permissions, which should be grant to a user. The format of this property is different for each plugin. You can find an example for each plugin on the corresponding plugin page in the documentation. | Yes | + ### Dashboard Define the dashboards, which should be used for the team. diff --git a/pkg/api/apis/team/v1beta1/types.go b/pkg/api/apis/team/v1beta1/types.go index 2d4af17d5..c2379ff3c 100644 --- a/pkg/api/apis/team/v1beta1/types.go +++ b/pkg/api/apis/team/v1beta1/types.go @@ -1,6 +1,7 @@ package v1beta1 import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" dashboard "github.com/kobsio/kobs/pkg/api/apis/dashboard/v1beta1" @@ -53,6 +54,12 @@ type Reference struct { type Permissions struct { Plugins []string `json:"plugins"` Resources []PermissionsResources `json:"resources"` + Custom []PermissionsCustom `json:"custom,omitempty"` +} + +type PermissionsCustom struct { + Name string `json:"name"` + Permissions apiextensionsv1.JSON `json:"permissions"` } type PermissionsResources struct { diff --git a/pkg/api/apis/team/v1beta1/zz_generated.deepcopy.go b/pkg/api/apis/team/v1beta1/zz_generated.deepcopy.go index 0db9e2ac4..fd44d0a89 100644 --- a/pkg/api/apis/team/v1beta1/zz_generated.deepcopy.go +++ b/pkg/api/apis/team/v1beta1/zz_generated.deepcopy.go @@ -57,6 +57,13 @@ func (in *Permissions) DeepCopyInto(out *Permissions) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Custom != nil { + in, out := &in.Custom, &out.Custom + *out = make([]PermissionsCustom, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -70,6 +77,23 @@ func (in *Permissions) DeepCopy() *Permissions { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PermissionsCustom) DeepCopyInto(out *PermissionsCustom) { + *out = *in + in.Permissions.DeepCopyInto(&out.Permissions) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PermissionsCustom. +func (in *PermissionsCustom) DeepCopy() *PermissionsCustom { + if in == nil { + return nil + } + out := new(PermissionsCustom) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PermissionsResources) DeepCopyInto(out *PermissionsResources) { *out = *in diff --git a/pkg/api/middleware/auth/auth.go b/pkg/api/middleware/auth/auth.go index 30ca98ec5..af8c73031 100644 --- a/pkg/api/middleware/auth/auth.go +++ b/pkg/api/middleware/auth/auth.go @@ -188,6 +188,7 @@ func getUserPermissions(user user.UserSpec, teams []team.TeamSpec) authContext.U if c == team.Cluster && n == team.Namespace && userTeam.Name == team.Name { u.Permissions.Plugins = append(u.Permissions.Plugins, team.Permissions.Plugins...) u.Permissions.Resources = append(u.Permissions.Resources, team.Permissions.Resources...) + u.Permissions.Custom = append(u.Permissions.Custom, team.Permissions.Custom...) } } } diff --git a/pkg/api/middleware/auth/context/context.go b/pkg/api/middleware/auth/context/context.go index 0944c3039..323390f21 100644 --- a/pkg/api/middleware/auth/context/context.go +++ b/pkg/api/middleware/auth/context/context.go @@ -85,6 +85,24 @@ func (u *User) HasResourceAccess(cluster, namespace, name string) bool { return false } +// GetPluginPermissions returns the custom plugin permissions for a user. For that the name of the plugin must be +// provided. +func (u *User) GetPluginPermissions(name string) ([][]byte, error) { + if u.Permissions.Custom == nil { + return nil, fmt.Errorf("custom permissions are empty for the user") + } + + var allCustomPermissions [][]byte + + for _, plugin := range u.Permissions.Custom { + if plugin.Name == name { + allCustomPermissions = append(allCustomPermissions, plugin.Permissions.Raw) + } + } + + return allCustomPermissions, nil +} + // GetUser returns a user from the given context if one is present. Returns the empty string if a user can not be found. func GetUser(ctx context.Context) (*User, error) { if ctx == nil { diff --git a/plugins/azure/azure.go b/plugins/azure/azure.go index 5a02a28e8..26823d6a5 100644 --- a/plugins/azure/azure.go +++ b/plugins/azure/azure.go @@ -64,14 +64,16 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi instances, } - router.Get("/resourcegroups/{name}", router.getResourceGroups) - - router.Route("/containerinstances", func(r chi.Router) { - r.Get("/containergroups/{name}", router.getContainerGroups) - r.Get("/containergroup/details/{name}", router.getContainerGroup) - r.Get("/containergroup/metrics/{name}", router.getContainerMetrics) - r.Get("/containergroup/logs/{name}", router.getContainerLogs) - r.Get("/containergroup/restart/{name}", router.restartContainerGroup) + router.Route("/{name}", func(r chi.Router) { + r.Get("/resourcegroups", router.getResourceGroups) + + r.Route("/containerinstances", func(containerInstancesRouter chi.Router) { + containerInstancesRouter.Get("/containergroups", router.getContainerGroups) + containerInstancesRouter.Get("/containergroup/details", router.getContainerGroup) + containerInstancesRouter.Get("/containergroup/metrics", router.getContainerMetrics) + containerInstancesRouter.Get("/containergroup/logs", router.getContainerLogs) + containerInstancesRouter.Put("/containergroup/restart", router.restartContainerGroup) + }) }) return router diff --git a/plugins/azure/containerinstances.go b/plugins/azure/containerinstances.go index d4ff311b3..c6784fe4e 100644 --- a/plugins/azure/containerinstances.go +++ b/plugins/azure/containerinstances.go @@ -26,13 +26,16 @@ func (router *Router) getContainerGroups(w http.ResponseWriter, r *http.Request) var containerGroups []map[string]interface{} for _, resourceGroup := range resourceGroups { - cgs, err := i.ContainerInstances.ListContainerGroups(r.Context(), resourceGroup) - if err != nil { - errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not list container instances") - return + err := i.CheckPermissions(r, "containerinstances", resourceGroup) + if err == nil { + cgs, err := i.ContainerInstances.ListContainerGroups(r.Context(), resourceGroup) + if err != nil { + errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not list container groups") + return + } + + containerGroups = append(containerGroups, cgs...) } - - containerGroups = append(containerGroups, cgs...) } render.JSON(w, r, containerGroups) @@ -51,6 +54,12 @@ func (router *Router) getContainerGroup(w http.ResponseWriter, r *http.Request) return } + err := i.CheckPermissions(r, "containerinstances", resourceGroup) + if err != nil { + errresponse.Render(w, r, err, http.StatusForbidden, "You are not allowed to get the container instance") + return + } + cg, err := i.ContainerInstances.GetContainerGroup(r.Context(), resourceGroup, containerGroup) if err != nil { errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get container instances") @@ -76,6 +85,12 @@ func (router *Router) getContainerMetrics(w http.ResponseWriter, r *http.Request return } + err := i.CheckPermissions(r, "containerinstances", resourceGroup) + if err != nil { + errresponse.Render(w, r, err, http.StatusForbidden, "You are not allowed to get the metrics of the container instance") + return + } + parsedTimeStart, err := strconv.ParseInt(timeStart, 10, 64) if err != nil { errresponse.Render(w, r, err, http.StatusBadRequest, "Could not parse start time") @@ -110,7 +125,13 @@ func (router *Router) restartContainerGroup(w http.ResponseWriter, r *http.Reque return } - err := i.ContainerInstances.RestartContainerGroup(r.Context(), resourceGroup, containerGroup) + err := i.CheckPermissions(r, "containerinstances", resourceGroup) + if err != nil { + errresponse.Render(w, r, err, http.StatusForbidden, "You are not allowed to restart the container instance") + return + } + + err = i.ContainerInstances.RestartContainerGroup(r.Context(), resourceGroup, containerGroup) if err != nil { errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get restart container group") return @@ -133,6 +154,12 @@ func (router *Router) getContainerLogs(w http.ResponseWriter, r *http.Request) { return } + err := i.CheckPermissions(r, "containerinstances", resourceGroup) + if err != nil { + errresponse.Render(w, r, err, http.StatusForbidden, "You are not allowed to get the logs of the container instance") + return + } + tail := int32(10000) timestamps := false diff --git a/plugins/azure/pkg/instance/instance.go b/plugins/azure/pkg/instance/instance.go index 4e06577a4..a468acb5b 100644 --- a/plugins/azure/pkg/instance/instance.go +++ b/plugins/azure/pkg/instance/instance.go @@ -17,14 +17,16 @@ var ( // Config is the structure of the configuration for a single Azure instance. type Config struct { - Name string `json:"name"` - DisplayName string `json:"displayName"` - Description string `json:"description"` + Name string `json:"name"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + PermissionsEnabled bool `json:"permissionsEnabled"` } // Instance represents a single Azure instance, which can be added via the configuration file. type Instance struct { Name string + PermissionsEnabled bool ResourceGroups *resourcegroups.Client ContainerInstances *containerinstances.Client } @@ -48,6 +50,7 @@ func New(config Config) (*Instance, error) { return &Instance{ Name: config.Name, + PermissionsEnabled: config.PermissionsEnabled, ResourceGroups: resourceGroups, ContainerInstances: containerInstances, }, nil diff --git a/plugins/azure/pkg/instance/permissions.go b/plugins/azure/pkg/instance/permissions.go new file mode 100644 index 000000000..b4795ad48 --- /dev/null +++ b/plugins/azure/pkg/instance/permissions.go @@ -0,0 +1,71 @@ +package instance + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + authContext "github.com/kobsio/kobs/pkg/api/middleware/auth/context" + + "github.com/go-chi/chi/v5" +) + +// Permissions is the structure of the custom permissions field for the Azure instance. +type Permissions struct { + Resources []string `json:"resources` + ResourceGroups []string `json:"resourceGroups` + Verbs []string `json:"resourceGroups` +} + +// CheckPermissions can be used to check if a user has the permissions to access a resource. The permissions of the user +// are determined from the passed in request context. +func (i *Instance) CheckPermissions(r *http.Request, resource, resourceGroup string) error { + if !i.PermissionsEnabled { + return nil + } + + user, err := authContext.GetUser(r.Context()) + if err != nil { + return err + } + + permissions, err := user.GetPluginPermissions(chi.URLParam(r, "name")) + if err != nil { + return err + } + + for _, permission := range permissions { + var p []Permissions + err := json.Unmarshal(permission, &p) + if err != nil { + return fmt.Errorf("invalid permission format: %w", err) + } + + if hasAccess(resource, resourceGroup, strings.ToLower(r.Method), p) { + return nil + } + } + + return fmt.Errorf("access forbidden") +} + +func hasAccess(resource, resourceGroup, verb string, permissions []Permissions) bool { + for _, p := range permissions { + for _, r := range p.Resources { + if r == resource || r == "*" { + for _, rg := range p.ResourceGroups { + if rg == resourceGroup || rg == "*" { + for _, v := range p.Verbs { + if v == verb || v == "*" { + return true + } + } + } + } + } + } + } + + return false +} diff --git a/plugins/azure/src/components/containerinstances/ContainerGroups.tsx b/plugins/azure/src/components/containerinstances/ContainerGroups.tsx index c7ea3765f..288a58037 100644 --- a/plugins/azure/src/components/containerinstances/ContainerGroups.tsx +++ b/plugins/azure/src/components/containerinstances/ContainerGroups.tsx @@ -23,7 +23,7 @@ const ContainerGroups: React.FunctionComponent = ({ const resourceGroupsParams = resourceGroups.map((resourceGroup) => `resourceGroup=${resourceGroup}`).join('&'); const response = await fetch( - `/api/plugins/azure/containerinstances/containergroups/${name}?${resourceGroupsParams}`, + `/api/plugins/azure/${name}/containerinstances/containergroups?${resourceGroupsParams}`, { method: 'get', }, diff --git a/plugins/azure/src/components/containerinstances/DetailsContainerGroup.tsx b/plugins/azure/src/components/containerinstances/DetailsContainerGroup.tsx index a54f63320..6d29a8523 100644 --- a/plugins/azure/src/components/containerinstances/DetailsContainerGroup.tsx +++ b/plugins/azure/src/components/containerinstances/DetailsContainerGroup.tsx @@ -34,7 +34,7 @@ const DetailsContainerGroup: React.FunctionComponent { try { const response = await fetch( - `/api/plugins/azure/containerinstances/containergroup/details/${name}?resourceGroup=${resourceGroup}&containerGroup=${containerGroup}`, + `/api/plugins/azure/${name}/containerinstances/containergroup/details?resourceGroup=${resourceGroup}&containerGroup=${containerGroup}`, { method: 'get', }, diff --git a/plugins/azure/src/components/containerinstances/DetailsContainerGroupActions.tsx b/plugins/azure/src/components/containerinstances/DetailsContainerGroupActions.tsx index 251d37e37..bc4aa0ae1 100644 --- a/plugins/azure/src/components/containerinstances/DetailsContainerGroupActions.tsx +++ b/plugins/azure/src/components/containerinstances/DetailsContainerGroupActions.tsx @@ -29,9 +29,9 @@ const DetailsContainerGroupActions: React.FunctionComponent => { try { const response = await fetch( - `/api/plugins/azure/containerinstances/containergroup/restart/${name}?resourceGroup=${resourceGroup}&containerGroup=${containerGroup}`, + `/api/plugins/azure/${name}/containerinstances/containergroup/restart?resourceGroup=${resourceGroup}&containerGroup=${containerGroup}`, { - method: 'get', + method: 'put', }, ); const json = await response.json(); diff --git a/plugins/azure/src/components/containerinstances/DetailsLogs.tsx b/plugins/azure/src/components/containerinstances/DetailsLogs.tsx index 735df20fe..ee3acf598 100644 --- a/plugins/azure/src/components/containerinstances/DetailsLogs.tsx +++ b/plugins/azure/src/components/containerinstances/DetailsLogs.tsx @@ -38,7 +38,7 @@ const DetailsLogs: React.FunctionComponent = ({ try { if (container !== '') { const response = await fetch( - `/api/plugins/azure/containerinstances/containergroup/logs/${name}?resourceGroup=${resourceGroup}&containerGroup=${containerGroup}&container=${container}`, + `/api/plugins/azure/${name}/containerinstances/containergroup/logs?resourceGroup=${resourceGroup}&containerGroup=${containerGroup}&container=${container}`, { method: 'get', }, diff --git a/plugins/azure/src/components/containerinstances/DetailsMetric.tsx b/plugins/azure/src/components/containerinstances/DetailsMetric.tsx index 22aea66aa..aa1387820 100644 --- a/plugins/azure/src/components/containerinstances/DetailsMetric.tsx +++ b/plugins/azure/src/components/containerinstances/DetailsMetric.tsx @@ -28,7 +28,7 @@ const DetailsMetric: React.FunctionComponent = ({ async () => { try { const response = await fetch( - `/api/plugins/azure/containerinstances/containergroup/metrics/${name}?resourceGroup=${resourceGroup}&containerGroup=${containerGroup}&metricName=${metricName}&timeStart=${times.timeStart}&timeEnd=${times.timeEnd}`, + `/api/plugins/azure/${name}/containerinstances/containergroup/metrics?resourceGroup=${resourceGroup}&containerGroup=${containerGroup}&metricName=${metricName}&timeStart=${times.timeStart}&timeEnd=${times.timeEnd}`, { method: 'get', }, diff --git a/plugins/azure/src/components/page/Page.tsx b/plugins/azure/src/components/page/Page.tsx index f5e2106ff..54949d534 100644 --- a/plugins/azure/src/components/page/Page.tsx +++ b/plugins/azure/src/components/page/Page.tsx @@ -22,7 +22,7 @@ const Page: React.FunctionComponent = ({ name, displayName, de ['azure/resourcegroups', name], async () => { try { - const response = await fetch(`/api/plugins/azure/resourcegroups/${name}`, { method: 'get' }); + const response = await fetch(`/api/plugins/azure/${name}/resourcegroups`, { method: 'get' }); const json = await response.json(); if (response.status >= 200 && response.status < 300) { diff --git a/plugins/harbor/pkg/instance/instance.go b/plugins/harbor/pkg/instance/instance.go index 4e3c51b10..e24689368 100644 --- a/plugins/harbor/pkg/instance/instance.go +++ b/plugins/harbor/pkg/instance/instance.go @@ -37,7 +37,6 @@ type Instance struct { } func (i *Instance) doRequest(ctx context.Context, url string) ([]byte, int64, error) { - fmt.Println(url) req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/v2.0/%s", i.address, url), nil) if err != nil { return nil, 0, err