Folders: let unified storage handle authorization (#110825)
This commit is contained in:
@@ -51,7 +51,7 @@ require (
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c // indirect
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
|
||||
github.com/grafana/otel-profiling-go v0.5.1 // indirect
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
|
||||
|
||||
@@ -95,8 +95,8 @@ github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25d
|
||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 h1:sdI2P18j78expDtLB6YbVI5dV+HguV719iiQNqx3wqw=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ replace github.com/grafana/grafana/pkg/aggregator => ../../pkg/aggregator
|
||||
replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604
|
||||
|
||||
require (
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c
|
||||
github.com/grafana/grafana v6.1.6+incompatible
|
||||
github.com/grafana/grafana-app-sdk v0.45.0
|
||||
github.com/grafana/grafana-app-sdk/logging v0.45.0
|
||||
|
||||
+2
-2
@@ -723,8 +723,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grafana/alerting v0.0.0-20250915130141-a8ee25091876 h1:BzoGpzARwRCNOHcqQdYPAFp2LS1pqnkLWhIuDdq1zho=
|
||||
github.com/grafana/alerting v0.0.0-20250915130141-a8ee25091876/go.mod h1:T5sitas9VhVj8/S9LeRLy6H75kTBdh/sCCqHo7gaQI8=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 h1:sdI2P18j78expDtLB6YbVI5dV+HguV719iiQNqx3wqw=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA=
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ require (
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c // indirect
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
|
||||
github.com/grafana/grafana-app-sdk/logging v0.45.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
|
||||
+2
-2
@@ -50,8 +50,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 h1:sdI2P18j78expDtLB6YbVI5dV+HguV719iiQNqx3wqw=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.24.6
|
||||
require (
|
||||
github.com/google/go-github/v70 v70.0.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c
|
||||
github.com/grafana/grafana-app-sdk/logging v0.45.0
|
||||
github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2
|
||||
|
||||
@@ -52,8 +52,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 h1:sdI2P18j78expDtLB6YbVI5dV+HguV719iiQNqx3wqw=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
|
||||
|
||||
@@ -33,7 +33,7 @@ require (
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c // indirect
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 // indirect
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
|
||||
@@ -48,8 +48,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 h1:sdI2P18j78expDtLB6YbVI5dV+HguV719iiQNqx3wqw=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
|
||||
|
||||
@@ -87,7 +87,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/alerting v0.0.0-20250915130141-a8ee25091876 // @grafana/alerting-backend
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 // @grafana/identity-access-team
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c // @grafana/identity-access-team
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 // @grafana/identity-access-team
|
||||
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
|
||||
github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics
|
||||
|
||||
@@ -1587,8 +1587,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grafana/alerting v0.0.0-20250915130141-a8ee25091876 h1:BzoGpzARwRCNOHcqQdYPAFp2LS1pqnkLWhIuDdq1zho=
|
||||
github.com/grafana/alerting v0.0.0-20250915130141-a8ee25091876/go.mod h1:T5sitas9VhVj8/S9LeRLy6H75kTBdh/sCCqHo7gaQI8=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 h1:sdI2P18j78expDtLB6YbVI5dV+HguV719iiQNqx3wqw=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA=
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery
|
||||
go 1.24.6
|
||||
|
||||
require (
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 // @grafana/identity-access-team
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c // @grafana/identity-access-team
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 // @grafana/identity-access-team
|
||||
github.com/stretchr/testify v1.11.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
@@ -30,8 +30,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 h1:sdI2P18j78expDtLB6YbVI5dV+HguV719iiQNqx3wqw=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
|
||||
|
||||
@@ -44,7 +44,7 @@ require (
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c // indirect
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect
|
||||
|
||||
@@ -63,8 +63,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781 h1:sdI2P18j78expDtLB6YbVI5dV+HguV719iiQNqx3wqw=
|
||||
github.com/grafana/authlib v0.0.0-20250917093142-83a502239781/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
|
||||
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
|
||||
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
package folders
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
|
||||
"github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
)
|
||||
|
||||
// newLegacyAuthorizer creates an authorizer using legacy access control, this is only usable for single tenant api.
|
||||
func newLegacyAuthorizer(ac accesscontrol.AccessControl) authorizer.Authorizer {
|
||||
return authorizer.AuthorizerFunc(func(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
in, err := authorizerFunc(ctx, attr)
|
||||
if err != nil {
|
||||
if errors.Is(err, errNoUser) {
|
||||
return authorizer.DecisionDeny, "", nil
|
||||
}
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
|
||||
ok, err := ac.Evaluate(ctx, in.user, in.evaluator)
|
||||
if ok {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
return authorizer.DecisionDeny, "folder", err
|
||||
})
|
||||
}
|
||||
|
||||
func authorizerFunc(ctx context.Context, attr authorizer.Attributes) (*authorizerParams, error) {
|
||||
allowedVerbs := []string{utils.VerbCreate, utils.VerbDelete, utils.VerbList}
|
||||
verb := attr.GetVerb()
|
||||
name := attr.GetName()
|
||||
if (!attr.IsResourceRequest()) || (name == "" && verb != utils.VerbCreate && slices.Contains(allowedVerbs, verb)) {
|
||||
return nil, errNoResource
|
||||
}
|
||||
|
||||
// require a user
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, errNoUser
|
||||
}
|
||||
|
||||
scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(name)
|
||||
var eval accesscontrol.Evaluator
|
||||
|
||||
// "get" is used for sub-resources with GET http (parents, access, count)
|
||||
switch verb {
|
||||
case utils.VerbCreate:
|
||||
eval = accesscontrol.EvalPermission(dashboards.ActionFoldersCreate)
|
||||
case utils.VerbPatch:
|
||||
fallthrough
|
||||
case utils.VerbUpdate:
|
||||
eval = accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, scope)
|
||||
case utils.VerbDeleteCollection:
|
||||
fallthrough
|
||||
case utils.VerbDelete:
|
||||
eval = accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, scope)
|
||||
case utils.VerbList:
|
||||
eval = accesscontrol.EvalPermission(dashboards.ActionFoldersRead)
|
||||
default:
|
||||
eval = accesscontrol.EvalPermission(dashboards.ActionFoldersRead, scope)
|
||||
}
|
||||
return &authorizerParams{evaluator: eval, user: user}, nil
|
||||
}
|
||||
|
||||
// newMultiTenantAuthorizer creates an authorizer suitable to multi-tenant setup.
|
||||
// For now it only allow authorization of access tokens.
|
||||
func newMultiTenantAuthorizer(ac types.AccessClient) authorizer.Authorizer {
|
||||
return authorizer.AuthorizerFunc(func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
info, ok := types.AuthInfoFrom(ctx)
|
||||
if !ok {
|
||||
return authorizer.DecisionDeny, "missing auth info", nil
|
||||
}
|
||||
|
||||
// For now we only allow access policy to authorize with multi-tenant setup
|
||||
if !types.IsIdentityType(info.GetIdentityType(), types.TypeAccessPolicy) {
|
||||
return authorizer.DecisionDeny, "permission denied", nil
|
||||
}
|
||||
|
||||
res, err := ac.Check(ctx, info, types.CheckRequest{
|
||||
Verb: a.GetVerb(),
|
||||
Group: a.GetAPIGroup(),
|
||||
Resource: a.GetResource(),
|
||||
Name: a.GetName(),
|
||||
Namespace: a.GetNamespace(),
|
||||
Subresource: a.GetSubresource(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, "faild to perform authorization", err
|
||||
}
|
||||
|
||||
if !res.Allowed {
|
||||
return authorizer.DecisionDeny, "permission denied", nil
|
||||
}
|
||||
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
})
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
package folders
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/go-jose/go-jose/v4/jwt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
|
||||
"github.com/grafana/authlib/authn"
|
||||
"github.com/grafana/authlib/authz"
|
||||
"github.com/grafana/authlib/types"
|
||||
|
||||
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
func TestLegacyAuthorizer(t *testing.T) {
|
||||
type input struct {
|
||||
user identity.Requester
|
||||
verb string
|
||||
}
|
||||
type expect struct {
|
||||
authorized authorizer.Decision
|
||||
err error
|
||||
}
|
||||
var orgID int64 = 1
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input input
|
||||
expect expect
|
||||
}{
|
||||
{
|
||||
name: "user with create permissions should be able to create a folder",
|
||||
input: input{
|
||||
user: &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: orgID,
|
||||
Name: "123",
|
||||
Permissions: map[int64]map[string][]string{
|
||||
orgID: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
|
||||
},
|
||||
},
|
||||
verb: string(utils.VerbCreate),
|
||||
},
|
||||
expect: expect{
|
||||
authorized: authorizer.DecisionAllow,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not possible to create a folder without a user",
|
||||
input: input{
|
||||
user: nil,
|
||||
verb: string(utils.VerbCreate),
|
||||
},
|
||||
expect: expect{authorized: authorizer.DecisionDeny},
|
||||
},
|
||||
{
|
||||
name: "user without permissions should not be able to create a folder",
|
||||
input: input{
|
||||
user: &user.SignedInUser{},
|
||||
verb: string(utils.VerbCreate),
|
||||
},
|
||||
expect: expect{authorized: authorizer.DecisionDeny},
|
||||
},
|
||||
{
|
||||
name: "user in another orgId should not be able to create a folder ",
|
||||
input: input{
|
||||
user: &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: 2,
|
||||
Name: "123",
|
||||
Permissions: map[int64]map[string][]string{
|
||||
orgID: {dashboards.ActionFoldersCreate: {}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
|
||||
},
|
||||
},
|
||||
verb: string(utils.VerbCreate),
|
||||
},
|
||||
expect: expect{authorized: authorizer.DecisionDeny},
|
||||
},
|
||||
{
|
||||
name: "user with read permissions should be able to list folders",
|
||||
input: input{
|
||||
user: &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: orgID,
|
||||
Name: "123",
|
||||
Permissions: map[int64]map[string][]string{
|
||||
orgID: {},
|
||||
},
|
||||
},
|
||||
verb: string(utils.VerbList),
|
||||
},
|
||||
expect: expect{authorized: authorizer.DecisionDeny},
|
||||
},
|
||||
{
|
||||
name: "user with delete permissions should be able to delete a folder",
|
||||
input: input{
|
||||
user: &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: orgID,
|
||||
Name: "123",
|
||||
Permissions: map[int64]map[string][]string{
|
||||
orgID: {dashboards.ActionFoldersDelete: {dashboards.ScopeFoldersAll}, dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
|
||||
},
|
||||
},
|
||||
verb: string(utils.VerbDelete),
|
||||
},
|
||||
expect: expect{authorized: authorizer.DecisionAllow},
|
||||
},
|
||||
{
|
||||
name: "user without delete permissions should NOT be able to delete a folder",
|
||||
input: input{
|
||||
user: &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: orgID,
|
||||
Name: "123",
|
||||
Permissions: map[int64]map[string][]string{
|
||||
orgID: {},
|
||||
},
|
||||
},
|
||||
verb: string(utils.VerbDelete),
|
||||
},
|
||||
expect: expect{authorized: authorizer.DecisionDeny},
|
||||
},
|
||||
{
|
||||
name: "user with write permissions should be able to update a folder",
|
||||
input: input{
|
||||
user: &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: orgID,
|
||||
Name: "123",
|
||||
Permissions: map[int64]map[string][]string{
|
||||
orgID: {dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll}},
|
||||
},
|
||||
},
|
||||
verb: string(utils.VerbUpdate),
|
||||
},
|
||||
expect: expect{authorized: authorizer.DecisionAllow},
|
||||
},
|
||||
{
|
||||
name: "user without write permissions should NOT be able to update a folder",
|
||||
input: input{
|
||||
user: &user.SignedInUser{
|
||||
UserID: 1,
|
||||
OrgID: orgID,
|
||||
Name: "123",
|
||||
Permissions: map[int64]map[string][]string{
|
||||
orgID: {},
|
||||
},
|
||||
},
|
||||
verb: string(utils.VerbUpdate),
|
||||
},
|
||||
expect: expect{authorized: authorizer.DecisionDeny},
|
||||
},
|
||||
}
|
||||
|
||||
authz := newLegacyAuthorizer(acimpl.ProvideAccessControl(featuremgmt.WithFeatures()))
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
authorized, _, err := authz.Authorize(
|
||||
identity.WithRequester(context.Background(), tt.input.user),
|
||||
authorizer.AttributesRecord{User: tt.input.user, Verb: tt.input.verb, Resource: "folders", ResourceRequest: true, Name: "123"},
|
||||
)
|
||||
if tt.expect.err != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, authorizer.DecisionDeny, authorized)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expect.authorized, authorized)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiTenantAuthorizer(t *testing.T) {
|
||||
type input struct {
|
||||
verb string
|
||||
info types.AuthInfo
|
||||
client types.AccessClient
|
||||
}
|
||||
|
||||
type expected struct {
|
||||
authorized authorizer.Decision
|
||||
err bool
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input input
|
||||
expeted expected
|
||||
}{
|
||||
{
|
||||
name: "non access policy idenity should not be able to authorize",
|
||||
input: input{
|
||||
verb: utils.VerbGet,
|
||||
info: &identity.StaticRequester{
|
||||
Type: types.TypeUser,
|
||||
UserID: 1,
|
||||
UserUID: "1",
|
||||
},
|
||||
},
|
||||
expeted: expected{
|
||||
authorized: authorizer.DecisionDeny,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "access policy identity with correct permissions should be able to authorize",
|
||||
input: input{
|
||||
verb: utils.VerbGet,
|
||||
info: authn.NewAccessTokenAuthInfo(authn.Claims[authn.AccessTokenClaims]{
|
||||
Claims: jwt.Claims{
|
||||
Subject: "access-policy:123",
|
||||
},
|
||||
Rest: authn.AccessTokenClaims{
|
||||
Namespace: "stacks-1",
|
||||
Permissions: []string{
|
||||
"folder.grafana.app/folders:get",
|
||||
},
|
||||
},
|
||||
}),
|
||||
client: authz.NewClient(nil),
|
||||
},
|
||||
expeted: expected{
|
||||
authorized: authorizer.DecisionAllow,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "access policy identity without correct permissions should not be able to authorize",
|
||||
input: input{
|
||||
verb: utils.VerbGet,
|
||||
info: authn.NewAccessTokenAuthInfo(authn.Claims[authn.AccessTokenClaims]{
|
||||
Claims: jwt.Claims{
|
||||
Subject: "access-policy:123",
|
||||
},
|
||||
Rest: authn.AccessTokenClaims{
|
||||
Namespace: "stacks-1",
|
||||
Permissions: []string{
|
||||
"folder.grafana.app/folders:create",
|
||||
},
|
||||
},
|
||||
}),
|
||||
client: authz.NewClient(nil),
|
||||
},
|
||||
expeted: expected{
|
||||
authorized: authorizer.DecisionDeny,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
authz := newMultiTenantAuthorizer(tt.input.client)
|
||||
authorized, _, err := authz.Authorize(
|
||||
types.WithAuthInfo(context.Background(), tt.input.info),
|
||||
authorizer.AttributesRecord{User: tt.input.info, Verb: tt.input.verb, APIGroup: folders.GROUP, Resource: "folders", ResourceRequest: true, Name: "123", Namespace: "stacks-1"},
|
||||
)
|
||||
|
||||
if tt.expeted.err {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, authorizer.DecisionDeny, authorized)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expeted.authorized, authorized)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package folders
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -20,12 +19,13 @@ import (
|
||||
|
||||
authlib "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
|
||||
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||
"github.com/grafana/grafana/apps/iam/pkg/reconcilers"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
grafanaauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||
@@ -42,16 +42,12 @@ var _ builder.APIGroupValidation = (*FolderAPIBuilder)(nil)
|
||||
|
||||
var resourceInfo = folders.FolderResourceInfo
|
||||
|
||||
var errNoUser = errors.New("valid user is required")
|
||||
var errNoResource = errors.New("resource name is required")
|
||||
|
||||
// This is used just so wire has something unique to return
|
||||
type FolderAPIBuilder struct {
|
||||
features featuremgmt.FeatureToggles
|
||||
namespacer request.NamespaceMapper
|
||||
storage grafanarest.Storage
|
||||
permissionStore reconcilers.PermissionStore
|
||||
authorizer authorizer.Authorizer
|
||||
accessClient authlib.AccessClient
|
||||
parents parentsGetter
|
||||
searcher resourcepb.ResourceIndexClient
|
||||
@@ -85,7 +81,6 @@ func RegisterAPIService(cfg *setting.Cfg,
|
||||
ac: accessControl,
|
||||
accessClient: accessClient,
|
||||
permissionsOnCreate: cfg.RBAC.PermissionsOnCreation("folder"),
|
||||
authorizer: newLegacyAuthorizer(accessControl),
|
||||
searcher: unified,
|
||||
permissionStore: reconcilers.NewZanzanaPermissionStore(zanzanaClient),
|
||||
}
|
||||
@@ -96,7 +91,6 @@ func RegisterAPIService(cfg *setting.Cfg,
|
||||
func NewAPIService(ac authlib.AccessClient, searcher resource.ResourceClient, features featuremgmt.FeatureToggles, zanzanaClient zanzana.Client) *FolderAPIBuilder {
|
||||
return &FolderAPIBuilder{
|
||||
features: features,
|
||||
authorizer: newMultiTenantAuthorizer(ac),
|
||||
searcher: searcher,
|
||||
permissionStore: reconcilers.NewZanzanaPermissionStore(zanzanaClient),
|
||||
}
|
||||
@@ -219,13 +213,9 @@ func (b *FolderAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAP
|
||||
return oas, nil
|
||||
}
|
||||
|
||||
type authorizerParams struct {
|
||||
user identity.Requester
|
||||
evaluator accesscontrol.Evaluator
|
||||
}
|
||||
|
||||
// The default authorizer is fine because authorization happens in storage where we know the parent folder
|
||||
func (b *FolderAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
||||
return b.authorizer
|
||||
return grafanaauthorizer.NewServiceAuthorizer()
|
||||
}
|
||||
|
||||
func (b *FolderAPIBuilder) Mutate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package authorizer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
authzlib "github.com/grafana/authlib/authz"
|
||||
"github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// Use this only when user authorization is not needed as it's performed downstream (e.g. in the UniStore).
|
||||
// This authorizer makes a final decision based on the service permissions only, it does not consider the user permissions.
|
||||
func NewServiceAuthorizer() authorizer.Authorizer {
|
||||
return authorizer.AuthorizerFunc(
|
||||
func(ctx context.Context, attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||
if !attr.IsResourceRequest() {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
|
||||
ident, ok := types.AuthInfoFrom(ctx)
|
||||
if !ok {
|
||||
return authorizer.DecisionDeny, "", errors.New("no identity found for request")
|
||||
}
|
||||
|
||||
res := authzlib.CheckServicePermissions(ident, attr.GetAPIGroup(), attr.GetResource(), attr.GetVerb())
|
||||
if !res.Allowed {
|
||||
log := logging.FromContext(ctx)
|
||||
log.Info("calling service lacks required permissions",
|
||||
"isServiceCall", res.ServiceCall,
|
||||
"apiGroup", attr.GetAPIGroup(),
|
||||
"resource", attr.GetResource(),
|
||||
"verb", attr.GetVerb(),
|
||||
"permissions", len(res.Permissions),
|
||||
)
|
||||
return authorizer.DecisionDeny, "calling service lacks required permissions", nil
|
||||
}
|
||||
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func TestIntegrationFolderTree(t *testing.T) {
|
||||
}
|
||||
|
||||
modes := []grafanarest.DualWriterMode{
|
||||
// grafanarest.Mode1, (nothing new tested in mode 0 or 1)
|
||||
grafanarest.Mode0, // legacy only
|
||||
grafanarest.Mode2, // write both, read legacy
|
||||
grafanarest.Mode3, // write both, read unified
|
||||
grafanarest.Mode4,
|
||||
|
||||
Reference in New Issue
Block a user