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