merge with main
This commit is contained in:
+2
-3
@@ -4776,10 +4776,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "3"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "4"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "5"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "8"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
|
||||
],
|
||||
"public/app/features/plugins/admin/components/InstallControls/index.tsx:5381": [
|
||||
[0, 0, 0, "Do not re-export imported variable (\`./InstallControlsButton\`)", "0"],
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
module swagger
|
||||
|
||||
go 1.24.1
|
||||
|
||||
tool github.com/go-swagger/go-swagger/cmd/swagger
|
||||
|
||||
require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.0 // indirect
|
||||
github.com/go-openapi/inflect v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/loads v0.22.0 // indirect
|
||||
github.com/go-openapi/runtime v0.28.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.23.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/jessevdk/go-flags v1.5.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/toqueteos/webbrowser v1.2.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
@@ -0,0 +1,181 @@
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
|
||||
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
|
||||
github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
|
||||
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
|
||||
github.com/go-openapi/inflect v0.21.0 h1:FoBjBTQEcbg2cJUWX6uwL9OyIW8eqc9k4KhN4lfbeYk=
|
||||
github.com/go-openapi/inflect v0.21.0/go.mod h1:INezMuUu7SJQc2AyR3WO0DqqYUJSj8Kb4hBd7WtjlAw=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
|
||||
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
|
||||
github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
|
||||
github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
|
||||
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
|
||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37 h1:KFcZmKdZmapAog2+eL1buervAYrYolBZk7fMecPPDmo=
|
||||
github.com/go-swagger/go-swagger v0.30.6-0.20240310114303-db51e79a0e37/go.mod h1:i1/E+d8iPNReSE7y04FaVu5OPKB3il5cn+T1Egogg3I=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
||||
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
|
||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -317,6 +317,7 @@
|
||||
/Makefile @grafana/grafana-developer-enablement-squad
|
||||
/scripts/build/ @grafana/grafana-developer-enablement-squad
|
||||
/scripts/list-release-artifacts.sh @grafana/grafana-developer-enablement-squad
|
||||
/scripts/releasefinder.sh @baldm0mma
|
||||
/.trivyignore @grafana/grafana-backend-services-squad
|
||||
|
||||
# OSS Plugin Partnerships backend code
|
||||
|
||||
@@ -67,6 +67,7 @@ COPY .citools/lefthook .citools/lefthook
|
||||
COPY .citools/jb .citools/jb
|
||||
COPY .citools/drone .citools/drone
|
||||
COPY .citools/golangci-lint .citools/golangci-lint
|
||||
COPY .citools/swagger ./citools/swagger
|
||||
|
||||
# Include vendored dependencies
|
||||
COPY pkg/util/xorm pkg/util/xorm
|
||||
|
||||
@@ -60,15 +60,15 @@ NGALERT_SPEC_TARGET = pkg/services/ngalert/api/tooling/api.json
|
||||
$(NGALERT_SPEC_TARGET):
|
||||
+$(MAKE) -C pkg/services/ngalert/api/tooling api.json
|
||||
|
||||
$(MERGED_SPEC_TARGET): swagger-oss-gen swagger-enterprise-gen $(NGALERT_SPEC_TARGET) $(SWAGGER) ## Merge generated and ngalert API specs
|
||||
$(MERGED_SPEC_TARGET): swagger-oss-gen swagger-enterprise-gen $(NGALERT_SPEC_TARGET) ## Merge generated and ngalert API specs
|
||||
# known conflicts DsPermissionType, AddApiKeyCommand, Json, Duration (identical models referenced by both specs)
|
||||
$(SWAGGER) mixin -q $(SPEC_TARGET) $(ENTERPRISE_SPEC_TARGET) $(NGALERT_SPEC_TARGET) --ignore-conflicts -o $(MERGED_SPEC_TARGET)
|
||||
GODEBUG=gotypesalias=0 $(GO) tool swagger mixin -q $(SPEC_TARGET) $(ENTERPRISE_SPEC_TARGET) $(NGALERT_SPEC_TARGET) --ignore-conflicts -o $(MERGED_SPEC_TARGET)
|
||||
|
||||
.PHONY: swagger-oss-gen
|
||||
swagger-oss-gen: $(SWAGGER) ## Generate API Swagger specification
|
||||
swagger-oss-gen: ## Generate API Swagger specification
|
||||
@echo "re-generating swagger for OSS"
|
||||
rm -f $(SPEC_TARGET)
|
||||
SWAGGER_GENERATE_EXTENSION=false $(SWAGGER) generate spec -q -m -w pkg/server -o $(SPEC_TARGET) \
|
||||
SWAGGER_GENERATE_EXTENSION=false GODEBUG=gotypesalias=0 $(GO) tool swagger generate spec -q -m -w pkg/server -o $(SPEC_TARGET) \
|
||||
-x "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" \
|
||||
-x "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" \
|
||||
-x "github.com/prometheus/alertmanager" \
|
||||
@@ -83,10 +83,10 @@ ifeq ("$(wildcard $(ENTERPRISE_EXT_FILE))","") ## if enterprise is not enabled
|
||||
swagger-enterprise-gen:
|
||||
@echo "skipping re-generating swagger for enterprise: not enabled"
|
||||
else
|
||||
swagger-enterprise-gen: $(SWAGGER) ## Generate API Swagger specification
|
||||
swagger-enterprise-gen: ## Generate API Swagger specification
|
||||
@echo "re-generating swagger for enterprise"
|
||||
rm -f $(ENTERPRISE_SPEC_TARGET)
|
||||
SWAGGER_GENERATE_EXTENSION=false $(SWAGGER) generate spec -q -m -w pkg/server -o $(ENTERPRISE_SPEC_TARGET) \
|
||||
SWAGGER_GENERATE_EXTENSION=false GODEBUG=gotypesalias=0 $(GO) tool swagger generate spec -q -m -w pkg/server -o $(ENTERPRISE_SPEC_TARGET) \
|
||||
-x "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" \
|
||||
-x "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" \
|
||||
-x "github.com/prometheus/alertmanager" \
|
||||
@@ -99,8 +99,8 @@ endif
|
||||
swagger-gen: gen-go $(MERGED_SPEC_TARGET) swagger-validate
|
||||
|
||||
.PHONY: swagger-validate
|
||||
swagger-validate: $(MERGED_SPEC_TARGET) $(SWAGGER) ## Validate API spec
|
||||
$(SWAGGER) validate --skip-warnings $(<)
|
||||
swagger-validate: $(MERGED_SPEC_TARGET) # Validate API spec
|
||||
GODEBUG=gotypesalias=0 $(GO) tool swagger validate --skip-warnings $(<)
|
||||
|
||||
.PHONY: swagger-clean
|
||||
swagger-clean:
|
||||
@@ -441,7 +441,6 @@ protobuf: ## Compile protobuf definitions
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.4.0
|
||||
buf generate pkg/plugins/backendplugin/pluginextensionv2 --template pkg/plugins/backendplugin/pluginextensionv2/buf.gen.yaml
|
||||
buf generate pkg/plugins/backendplugin/secretsmanagerplugin --template pkg/plugins/backendplugin/secretsmanagerplugin/buf.gen.yaml
|
||||
buf generate pkg/apis/secret/v0alpha1/decrypt --template pkg/apis/secret/v0alpha1/decrypt/buf.gen.yaml
|
||||
buf generate pkg/storage/unified/resource --template pkg/storage/unified/resource/buf.gen.yaml
|
||||
buf generate pkg/services/authz/proto/v1 --template pkg/services/authz/proto/v1/buf.gen.yaml
|
||||
|
||||
@@ -119,7 +119,7 @@ For example:
|
||||
|
||||
Today it's only possible to provision data sources and dashboards, but we want to support it throughout Grafana in the future.
|
||||
|
||||
### 109 - Use `context.Context`
|
||||
## 109 - Use `context.Context`
|
||||
|
||||
**State:** Completed.
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ This feature addresses two common challenges faced by Grafana users:
|
||||
LBAC for data sources lets Grafana Teams use the same dashboard with different access control rules.
|
||||
|
||||
To set up LBAC for data sources for a Loki data source, refer to [Configure LBAC for Loki Data Source](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/teamlbac/configure-teamlbac-for-loki/).
|
||||
To set up LBAC for data sources for a Loki data source, refer to [Configure LBAC for Prometheus Data Source](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/teamlbac/configure-teamlbac-for-prometheus/).
|
||||
To set up LBAC for data sources for a Prometheus data source, refer to [Configure LBAC for Prometheus Data Source](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/teamlbac/configure-teamlbac-for-prometheus/).
|
||||
|
||||
## Logs and Metrics Filtering with LBAC
|
||||
|
||||
|
||||
@@ -63,6 +63,8 @@ refs:
|
||||
|
||||
You can use a service account to run automated workloads in Grafana, such as dashboard provisioning, configuration, or report generation. Create service accounts and tokens to authenticate applications, such as Terraform, with the Grafana API.
|
||||
|
||||
A service account is not the same as a Grafana [user](/docs/grafana/latest/administration/user-management/). The role of a service account is primarily to automate work using the Grafana API.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Service accounts replace [API keys](ref:migrate-api-keys) as the primary way to authenticate applications that interact with Grafana.
|
||||
{{< /admonition >}}
|
||||
|
||||
@@ -41,7 +41,7 @@ refs:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/share-dashboards-panels/shared-dashboards/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/grafana-cloud/visualizations/dashboards/share-dashboards-panels/shared-dashboards/
|
||||
destination: /docs/grafana-cloud/visualizations/dashboards/share-dashboards-panels/shared-dashboards/
|
||||
configure-report:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/dashboards/create-reports/#create-a-report
|
||||
|
||||
@@ -55,6 +55,42 @@ Only users with the organization administrator role can add data sources.
|
||||
|
||||
Once you've added the Google Cloud Monitoring data source, you can [configure it](#configure-the-data-source) so that your Grafana instance's users can create queries in its [query editor](query-editor/) and apply [annotations](#annotations) when they [build dashboards](ref:build-dashboards) and use [Explore](ref:explore).
|
||||
|
||||
## Before you begin
|
||||
|
||||
Complete the following before you configure the data source.
|
||||
|
||||
### Configure Google authentication
|
||||
|
||||
Before you can request data from Google Cloud Monitoring, you must configure authentication.
|
||||
All requests to Google APIs are performed on the server-side by the Grafana backend.
|
||||
|
||||
For authentication options and configuration details, refer to [Google authentication](google-authentication/).
|
||||
|
||||
When configuring Google authentication, keep in mind the following additional steps related to Google Cloud Monitoring.
|
||||
|
||||
#### Configure a GCP Service Account
|
||||
|
||||
When you [create a Google Cloud Platform (GCP) Service Account and key file](google-authentication/#create-a-gcp-service-account-and-key-file), the Service Account must have the **Monitoring Viewer** role (**Role > Select a role > Monitoring > Monitoring Viewer**):
|
||||
|
||||
{{< figure src="/static/img/docs/v71/cloudmonitoring_service_account_choose_role.png" max-width="600px" class="docs-image--no-shadow" caption="Choose role" >}}
|
||||
|
||||
#### Grant the GCE Default Service Account scope
|
||||
|
||||
If Grafana is running on a Google Compute Engine (GCE) virtual machine, when you [Configure a GCE Default Service Account](google-authentication/#configure-a-gce-default-service-account), you must also grant that Service Account access to the "Cloud Monitoring API" scope.
|
||||
|
||||
### Enable necessary Google Cloud Platform APIs
|
||||
|
||||
Before you can request data from Google Cloud Monitoring, you must first enable necessary APIs on the Google end.
|
||||
|
||||
1. Open the Monitoring and Cloud Resource Manager API pages:
|
||||
|
||||
- [Monitoring API](https://console.cloud.google.com/apis/library/monitoring.googleapis.com)
|
||||
- [Cloud Resource Manager API](https://console.cloud.google.com/apis/library/cloudresourcemanager.googleapis.com)
|
||||
|
||||
1. On each page, click the `Enable` button.
|
||||
|
||||
{{< figure src="/static/img/docs/v71/cloudmonitoring_enable_api.png" max-width="450px" class="docs-image--no-shadow" caption="Enable GCP APIs" >}}
|
||||
|
||||
## Configure the data source
|
||||
|
||||
To configure basic settings for the data source, complete the following steps:
|
||||
@@ -73,41 +109,9 @@ To configure basic settings for the data source, complete the following steps:
|
||||
| **Name** | Sets the name you use to refer to the data source in panels and queries. |
|
||||
| **Default** | Sets whether the data source is pre-selected for new panels. |
|
||||
|
||||
### Configure Google authentication
|
||||
|
||||
Before you can request data from Google Cloud Monitoring, you must configure authentication.
|
||||
All requests to Google APIs are performed on the server-side by the Grafana backend.
|
||||
|
||||
For authentication options and configuration details, refer to [Google authentication](google-authentication/).
|
||||
|
||||
When configuring Google authentication, note these additional Google Cloud Monitoring-specific steps:
|
||||
|
||||
#### Configure a GCP Service Account
|
||||
|
||||
When you [create a Google Cloud Platform (GCP) Service Account and key file](google-authentication/#create-a-gcp-service-account-and-key-file), the Service Account must have the **Monitoring Viewer** role (**Role > Select a role > Monitoring > Monitoring Viewer**):
|
||||
|
||||
{{< figure src="/static/img/docs/v71/cloudmonitoring_service_account_choose_role.png" max-width="600px" class="docs-image--no-shadow" caption="Choose role" >}}
|
||||
|
||||
#### Grant the GCE Default Service Account scope
|
||||
|
||||
If Grafana is running on a Google Compute Engine (GCE) virtual machine, then when you [Configure a GCE Default Service Account](google-authentication/#configure-a-gce-default-service-account), you must also grant that Service Account access to the "Cloud Monitoring API" scope.
|
||||
|
||||
### Enable necessary Google Cloud Platform APIs
|
||||
|
||||
Before you can request data from Google Cloud Monitoring, you must first enable necessary APIs on the Google end.
|
||||
|
||||
1. Open the Monitoring and Cloud Resource Manager API pages:
|
||||
|
||||
- [Monitoring API](https://console.cloud.google.com/apis/library/monitoring.googleapis.com)
|
||||
- [Cloud Resource Manager API](https://console.cloud.google.com/apis/library/cloudresourcemanager.googleapis.com)
|
||||
|
||||
1. On each page, click the `Enable` button.
|
||||
|
||||
{{< figure src="/static/img/docs/v71/cloudmonitoring_enable_api.png" max-width="450px" class="docs-image--no-shadow" caption="Enable GCP APIs" >}}
|
||||
|
||||
### Provision the data source
|
||||
|
||||
You can define and configure the data source in YAML files as part of Grafana's provisioning system.
|
||||
You can define and configure the data source in YAML files as part of the Grafana provisioning system.
|
||||
For more information about provisioning, and for available configuration options, refer to [Provisioning Grafana](ref:provisioning-data-sources).
|
||||
|
||||
#### Provisioning examples
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Unique name of the plugin. If the plugin is published on grafana.com, then the plugin `id` has to follow the naming conventions.",
|
||||
"pattern": "^[0-9a-z]+\\-([0-9a-z]+\\-)?(app|panel|datasource|secretsmanager)$"
|
||||
"pattern": "^[0-9a-z]+\\-([0-9a-z]+\\-)?(app|panel|datasource)$"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Plugin type.",
|
||||
"enum": ["app", "datasource", "panel", "renderer", "secretsmanager"]
|
||||
"enum": ["app", "datasource", "panel", "renderer"]
|
||||
},
|
||||
"info": {
|
||||
"type": "object",
|
||||
@@ -183,11 +183,11 @@
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9a-z]+\\-([0-9a-z]+\\-)?(app|panel|datasource|secretsmanager)$"
|
||||
"pattern": "^[0-9a-z]+\\-([0-9a-z]+\\-)?(app|panel|datasource)$"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["app", "datasource", "panel", "secretsmanager"]
|
||||
"enum": ["app", "datasource", "panel"]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -280,7 +280,7 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["dashboard", "page", "panel", "datasource", "secretsmanager"]
|
||||
"enum": ["dashboard", "page", "panel", "datasource"]
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
@@ -647,7 +647,7 @@
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "A unique identifier for your exposed component. This is used to reference the component in other plugins. It must be in the following format: '{PLUGIN_ID}/name-of-component/v1'.",
|
||||
"pattern": "^[0-9a-z]+-([0-9a-z]+-)?(app|panel|datasource|secretsmanager)\\/[a-zA-Z0-9_-]+\\/v[0-9_.-]+$"
|
||||
"pattern": "^[0-9a-z]+-([0-9a-z]+-)?(app|panel|datasource)\\/[a-zA-Z0-9_-]+\\/v[0-9_.-]+$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
@@ -670,7 +670,7 @@
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "A unique identifier for your extension point. This is used to reference the extension point in other plugins. It must be in the following format: '{PLUGIN_ID}/name-of-my-extension-point/v1'.",
|
||||
"pattern": "^[0-9a-z]+-([0-9a-z]+-)?(app|panel|datasource|secretsmanager)\\/[a-zA-Z0-9_-]+\\/v[0-9_.-]+$"
|
||||
"pattern": "^[0-9a-z]+-([0-9a-z]+-)?(app|panel|datasource)\\/[a-zA-Z0-9_-]+\\/v[0-9_.-]+$"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
|
||||
@@ -73,6 +73,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
|
||||
| `newFiltersUI` | Enables new combobox style UI for the Ad hoc filters variable in scenes architecture | Yes |
|
||||
| `alertingQueryAndExpressionsStepMode` | Enables step mode for alerting queries and expressions | Yes |
|
||||
| `useSessionStorageForRedirection` | Use session storage for handling the redirection after login | Yes |
|
||||
| `pluginsSriChecks` | Enables SRI checks for plugin assets | |
|
||||
| `userStorageAPI` | Enables the user storage API | Yes |
|
||||
| `azureMonitorDisableLogLimit` | Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default. | |
|
||||
| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins | Yes |
|
||||
@@ -147,7 +148,6 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `alertStateHistoryLokiOnly` | Disable Grafana alerts from emitting annotations when a remote Loki instance is available. |
|
||||
| `extraThemes` | Enables extra themes |
|
||||
| `lokiPredefinedOperations` | Adds predefined query operations to Loki query editor |
|
||||
| `frontendSandboxMonitorOnly` | Enables monitor only in the plugin frontend sandbox (if enabled) |
|
||||
| `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers |
|
||||
| `mlExpressions` | Enable support for Machine Learning in server-side expressions |
|
||||
| `metricsSummary` | Enables metrics summary queries in the Tempo data source |
|
||||
@@ -203,7 +203,6 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `homeSetupGuide` | Used in Home for users who want to return to the onboarding flow or quickly find popular config pages |
|
||||
| `appSidecar` | Enable the app sidecar feature that allows rendering 2 apps at the same time |
|
||||
| `rolePickerDrawer` | Enables the new role picker drawer design |
|
||||
| `pluginsSriChecks` | Enables SRI checks for plugin assets |
|
||||
| `unifiedStorageBigObjectsSupport` | Enables to save big objects in blob storage |
|
||||
| `timeRangeProvider` | Enables time pickers sync |
|
||||
| `prometheusUsesCombobox` | Use new combobox component for Prometheus query editor |
|
||||
|
||||
@@ -118,6 +118,18 @@ Example:
|
||||
hide_version = true
|
||||
```
|
||||
|
||||
### Enable auth for metrics
|
||||
|
||||
By default, metrics from Grafana itself can be accessed without authentication. This can lead to inadvertent information leakage.
|
||||
|
||||
To enable basic authentication for the metrics endpoint:
|
||||
|
||||
```toml
|
||||
# If both are set, basic auth will be required for the metrics endpoints
|
||||
basic_auth_username =
|
||||
basic_auth_password =
|
||||
```
|
||||
|
||||
### Enforce domain verification
|
||||
|
||||
If set to `true`, the Grafana server redirects requests that have a Host-header value that is mismatched to the actual domain. This might help to mitigate some DNS rebinding attacks.
|
||||
|
||||
@@ -33,8 +33,8 @@ require (
|
||||
github.com/beevik/etree v1.4.1 // @grafana/grafana-backend-group
|
||||
github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect; @grafana/grafana-developer-enablement-squad
|
||||
github.com/blevesearch/bleve/v2 v2.4.3 // @grafana/grafana-search-and-storage
|
||||
github.com/blevesearch/bleve_index_api v1.1.12 // @grafana/grafana-search-and-storage
|
||||
github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205 // @grafana/grafana-search-and-storage
|
||||
github.com/blevesearch/bleve_index_api v1.2.3 // @grafana/grafana-search-and-storage
|
||||
github.com/blugelabs/bluge v0.2.2 // @grafana/grafana-backend-group
|
||||
github.com/blugelabs/bluge_segment_api v0.2.0 // @grafana/grafana-backend-group
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // @grafana/grafana-backend-group
|
||||
@@ -124,7 +124,7 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c //@grafana/identity-access-team
|
||||
github.com/mocktools/go-smtp-mock/v2 v2.3.1 // @grafana/grafana-backend-group
|
||||
github.com/modern-go/reflect2 v1.0.2 // @grafana/alerting-backend
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // @grafana/alerting-backend
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect; @grafana/alerting-backend
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // @grafana/grafana-operator-experience-squad
|
||||
github.com/olekukonko/tablewriter v0.0.5 // @grafana/grafana-backend-group
|
||||
github.com/openfga/api/proto v0.0.0-20250127102726-f9709139a369 // @grafana/identity-access-team
|
||||
@@ -288,21 +288,21 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.12.0 // indirect
|
||||
github.com/blevesearch/geo v0.1.20 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.23 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.24 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.2.16 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.5 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
github.com/blevesearch/vellum v1.0.10 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.3.10 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.3.16 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.1.8 // indirect
|
||||
github.com/blevesearch/vellum v1.1.0 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.4.1 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.4.1 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.4.1 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.1 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.1 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.2-0.20250305220028-89edb0ef9aa9 // indirect
|
||||
github.com/blugelabs/ice v1.0.0 // indirect
|
||||
github.com/blugelabs/ice/v2 v2.0.1 // indirect
|
||||
github.com/bufbuild/protocompile v0.4.0 // indirect
|
||||
@@ -561,6 +561,7 @@ require (
|
||||
require (
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
|
||||
github.com/bluele/gcache v0.0.2 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.3 // indirect
|
||||
github.com/open-feature/go-sdk-contrib/providers/ofrep v0.1.5 // indirect
|
||||
|
||||
@@ -765,6 +765,8 @@ github.com/RoaringBitmap/roaring v0.9.1/go.mod h1:h1B7iIUOmnAeb5ytYMvnHJwxMc6LUr
|
||||
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
||||
github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM=
|
||||
github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5 h1:uGrrMreGjvAtTBobc0g5IrW1D5ldxDQYe2JW2gggRdg=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.4.5/go.mod h1:FiJcsfkGje/nZBZgCu0ZxCPOKD/hVXDS2dXi7/eUFE0=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f h1:HR5nRmUQgXrwqZOwZ2DAc/aCi3Bu3xENpspW935vxu0=
|
||||
@@ -898,14 +900,14 @@ github.com/bits-and-blooms/bitset v1.12.0 h1:U/q1fAF7xXRhFCrhROzIfffYnu+dlS38vCZ
|
||||
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/blevesearch/bleve/v2 v2.4.3 h1:XDYj+1prgX84L2Cf+V3ojrOPqXxy0qxyd2uLMmeuD+4=
|
||||
github.com/blevesearch/bleve/v2 v2.4.3/go.mod h1:hEPDPrbYw3vyrm5VOa36GyS4bHWuIf4Fflp7460QQXY=
|
||||
github.com/blevesearch/bleve_index_api v1.1.12 h1:P4bw9/G/5rulOF7SJ9l4FsDoo7UFJ+5kexNy1RXfegY=
|
||||
github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8=
|
||||
github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205 h1:u6DQJ1k4FKwRNtsrVhIRQenNdtz31way7/LgWCluFzA=
|
||||
github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205/go.mod h1:nSmFOQ7M264rKoM3jf63Gl2G+ylCgZGovPgL6ZEQYzU=
|
||||
github.com/blevesearch/bleve_index_api v1.2.3 h1:aJJPhTb30zc68qg2HbvCZLAF1Eh5Hhm296mDAUFaUeg=
|
||||
github.com/blevesearch/bleve_index_api v1.2.3/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM=
|
||||
github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w=
|
||||
github.com/blevesearch/go-faiss v1.0.23 h1:Wmc5AFwDLKGl2L6mjLX1Da3vCL0EKa2uHHSorcIS1Uc=
|
||||
github.com/blevesearch/go-faiss v1.0.23/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-faiss v1.0.24 h1:K79IvKjoKHdi7FdiXEsAhxpMuns0x4fM0BO93bW5jLI=
|
||||
github.com/blevesearch/go-faiss v1.0.24/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
@@ -914,8 +916,8 @@ github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+
|
||||
github.com/blevesearch/mmap-go v1.0.3/go.mod h1:pYvKl/grLQrBxuaRYgoTssa4rVujYYeenDp++2E+yvs=
|
||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.2.16 h1:uGvKVvG7zvSxCwcm4/ehBa9cCEuZVE+/zvrSl57QUVY=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.2.16/go.mod h1:VF5oHVbIFTu+znY1v30GjSpT5+9YFs9dV2hjvuh34F0=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.5 h1:t6NVTdS8xp1xHMJoe5WjxdZWLKhsIIb6kB0U49rhAno=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.5/go.mod h1:7gG1vsL3lu2L+7RuEtZ7PJhxGYE+AV9zjxTSHuXFMdM=
|
||||
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||
@@ -925,20 +927,20 @@ github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMG
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||
github.com/blevesearch/vellum v1.0.5/go.mod h1:atE0EH3fvk43zzS7t1YNdNC7DbmcC3uz+eMD5xZ2OyQ=
|
||||
github.com/blevesearch/vellum v1.0.7/go.mod h1:doBZpmRhwTsASB4QdUZANlJvqVAUdUyX0ZK7QJCTeBE=
|
||||
github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI=
|
||||
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns=
|
||||
github.com/blevesearch/zapx/v15 v15.3.16 h1:Ct3rv7FUJPfPk99TI/OofdC+Kpb4IdyfdMH48sb+FmE=
|
||||
github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
|
||||
github.com/blevesearch/zapx/v16 v16.1.8 h1:Bxzpw6YQpFs7UjoCV1+RvDw6fmAT2GZxldwX8b3wVBM=
|
||||
github.com/blevesearch/zapx/v16 v16.1.8/go.mod h1:JqQlOqlRVaYDkpLIl3JnKql8u4zKTNlVEa3nLsi0Gn8=
|
||||
github.com/blevesearch/vellum v1.1.0 h1:CinkGyIsgVlYf8Y2LUQHvdelgXr6PYuvoDIajq6yR9w=
|
||||
github.com/blevesearch/vellum v1.1.0/go.mod h1:QgwWryE8ThtNPxtgWJof5ndPfx0/YMBh+W2weHKPw8Y=
|
||||
github.com/blevesearch/zapx/v11 v11.4.1 h1:qFCPlFbsEdwbbckJkysptSQOsHn4s6ZOHL5GMAIAVHA=
|
||||
github.com/blevesearch/zapx/v11 v11.4.1/go.mod h1:qNOGxIqdPC1MXauJCD9HBG487PxviTUUbmChFOAosGs=
|
||||
github.com/blevesearch/zapx/v12 v12.4.1 h1:K77bhypII60a4v8mwvav7r4IxWA8qxhNjgF9xGdb9eQ=
|
||||
github.com/blevesearch/zapx/v12 v12.4.1/go.mod h1:QRPrlPOzAxBNMI0MkgdD+xsTqx65zbuPr3Ko4Re49II=
|
||||
github.com/blevesearch/zapx/v13 v13.4.1 h1:EnkEMZFUK0lsW/jOJJF2xOcp+W8TjEsyeN5BeAZEYYE=
|
||||
github.com/blevesearch/zapx/v13 v13.4.1/go.mod h1:e6duBMlCvgbH9rkzNMnUa9hRI9F7ri2BRcHfphcmGn8=
|
||||
github.com/blevesearch/zapx/v14 v14.4.1 h1:G47kGCshknBZzZAtjcnIAMn3oNx8XBLxp8DMq18ogyE=
|
||||
github.com/blevesearch/zapx/v14 v14.4.1/go.mod h1:O7sDxiaL2r2PnCXbhh1Bvm7b4sP+jp4unE9DDPWGoms=
|
||||
github.com/blevesearch/zapx/v15 v15.4.1 h1:B5IoTMUCEzFdc9FSQbhVOxAY+BO17c05866fNruiI7g=
|
||||
github.com/blevesearch/zapx/v15 v15.4.1/go.mod h1:b/MreHjYeQoLjyY2+UaM0hGZZUajEbE0xhnr1A2/Q6Y=
|
||||
github.com/blevesearch/zapx/v16 v16.2.2-0.20250305220028-89edb0ef9aa9 h1:a5GLOQkJqCnpD/lTfNiEVQnJ1e2CVOR8JFO6u1D7bLE=
|
||||
github.com/blevesearch/zapx/v16 v16.2.2-0.20250305220028-89edb0ef9aa9/go.mod h1:v8B9VjBtELr2qRO0f3RoRG8RAZg3Jml/5SYuBT0hvGw=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||
github.com/blugelabs/bluge v0.2.2 h1:gat8CqE6P6tOgeX30XGLOVNTC26cpM2RWVcreXWtYcM=
|
||||
@@ -3429,6 +3431,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
|
||||
@@ -12,6 +12,7 @@ use (
|
||||
./.citools/golangci-lint
|
||||
./.citools/jb
|
||||
./.citools/lefthook
|
||||
./.citools/swagger
|
||||
./apps/advisor
|
||||
./apps/alerting/notifications
|
||||
./apps/dashboard
|
||||
@@ -41,3 +42,7 @@ replace github.com/getkin/kin-openapi => github.com/getkin/kin-openapi v0.126.0
|
||||
replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20240930132144-b5e64e81e8d3
|
||||
|
||||
replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20240917091248-ae3bbdad8a56
|
||||
|
||||
// Can be removed once Bleve releases v2.5.0.
|
||||
// We've pinned this for: https://github.com/blevesearch/bleve/commit/af9e3111dadfedf9d30f0448506b4a57fecc8550
|
||||
replace github.com/blevesearch/bleve/v2 => github.com/blevesearch/bleve/v2 v2.4.4-0.20250319135056-b82baf10b205
|
||||
|
||||
+16
-7
@@ -535,7 +535,6 @@ github.com/DataDog/sketches-go v1.4.6 h1:acd5fb+QdUzGrosfNLwrIhqyrbMORpvBy7mE+vH
|
||||
github.com/DataDog/sketches-go v1.4.6/go.mod h1:7Y8GN8Jf66DLyDhc94zuWA3uHEt/7ttt8jHOBWWrSOg=
|
||||
github.com/DmitriyVTitov/size v1.5.0 h1:/PzqxYrOyOUX1BXj6J9OuVRVGe+66VL4D9FlUaW515g=
|
||||
github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0=
|
||||
github.com/FZambia/eagle v0.2.0/go.mod h1:LKMYBwGYhao5sJI0TppvQ4SvvldFj9gITxrl8NvGwG0=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0 h1:kAtNAWwvTt5+iew6baV0kbOrtjYTXPtWNSyOFlcxkBU=
|
||||
github.com/GoogleCloudPlatform/cloudsql-proxy v1.36.0/go.mod h1:VRKXU8C7Y/aUKjRBTGfw0Ndv4YqNxlB8zAPJJDxbASE=
|
||||
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0=
|
||||
@@ -633,14 +632,26 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ
|
||||
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
|
||||
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blevesearch/bleve_index_api v1.1.12/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8=
|
||||
github.com/blevesearch/bleve_index_api v1.2.1/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/go-faiss v1.0.23/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:kDy+zgJFJJoJYBvdfBSiZYBbdsUL0XcjHYWezpQBGPA=
|
||||
github.com/blevesearch/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:9eJDeqxJ3E7WnLebQUlPD7ZjSce7AnDb9vjGmMCbD0A=
|
||||
github.com/blevesearch/goleveldb v1.0.1 h1:iAtV2Cu5s0GD1lwUiekkFHe2gTMCCNVj2foPclDLIFI=
|
||||
github.com/blevesearch/goleveldb v1.0.1/go.mod h1:WrU8ltZbIp0wAoig/MHbrPCXSOLpe79nz5lv5nqfYrQ=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.2.16/go.mod h1:VF5oHVbIFTu+znY1v30GjSpT5+9YFs9dV2hjvuh34F0=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.3/go.mod h1:LXidEjeenMdbcLKP/UdZi1HJOny61FbhslAh5SgN5Ik=
|
||||
github.com/blevesearch/snowball v0.6.1 h1:cDYjn/NCH+wwt2UdehaLpr2e4BwLIjN4V/TdLsL+B5A=
|
||||
github.com/blevesearch/snowball v0.6.1/go.mod h1:ZF0IBg5vgpeoUhnMza2v0A/z8m1cWPlwhke08LpNusg=
|
||||
github.com/blevesearch/stempel v0.2.0 h1:CYzVPaScODMvgE9o+kf6D4RJ/VRomyi9uHF+PtB+Afc=
|
||||
github.com/blevesearch/stempel v0.2.0/go.mod h1:wjeTHqQv+nQdbPuJ/YcvOjTInA2EIc6Ks1FoSUzSLvc=
|
||||
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns=
|
||||
github.com/blevesearch/zapx/v15 v15.3.16/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
|
||||
github.com/blevesearch/zapx/v16 v16.1.8/go.mod h1:JqQlOqlRVaYDkpLIl3JnKql8u4zKTNlVEa3nLsi0Gn8=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs=
|
||||
@@ -660,8 +671,6 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
|
||||
github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0=
|
||||
github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY=
|
||||
github.com/centrifugal/centrifuge v0.34.4/go.mod h1:VliWwN01/Iom9tLFJyk4E5FdKcn3cbJhnlt0XTnlolo=
|
||||
github.com/centrifugal/protocol v0.16.0/go.mod h1:7V5vI30VcoxJe4UD87xi7bOsvI0bmEhvbQuMjrFM2L4=
|
||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0 h1:WW2B2uxx9KWF6bGlHqhm8Okiafwwx7Y2kcpn8lCpjgo=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0 h1:TW8f/UvntYoVDMN1K2HlT82qH1rb0sOjpGw3m6Ym+i4=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
@@ -745,6 +754,7 @@ github.com/couchbase/moss v0.2.0 h1:VCYrMzFwEryyhRSeI+/b3tRBSeTpi/8gn5Kf6dxqn+o=
|
||||
github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
|
||||
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||
github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo=
|
||||
@@ -1052,6 +1062,7 @@ github.com/jon-whit/go-grpc-prometheus v1.4.0 h1:/wmpGDJcLXuEjXryWhVYEGt9YBRhtLw
|
||||
github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg5ax6YQEe1I0f6vtBuao=
|
||||
github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a h1:sfe532Ipn7GX0V6mHdynBk393rDmqgI0QmjLK7ct7TU=
|
||||
github.com/joncrlsn/dque v0.0.0-20211108142734-c2ef48c5192a/go.mod h1:dNKs71rs2VJGBAmttu7fouEsRQlRjxy0p1Sx+T5wbpY=
|
||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
||||
github.com/jsternberg/zap-logfmt v1.2.0 h1:1v+PK4/B48cy8cfQbxL4FmmNZrjnIMr2BsnyEmXqv2o=
|
||||
github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0=
|
||||
@@ -1120,7 +1131,6 @@ github.com/matryer/moq v0.3.3/go.mod h1:RJ75ZZZD71hejp39j4crZLsEDszGk6iH4v4YsWFK
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/maypok86/otter v1.2.4/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4=
|
||||
github.com/mfridman/xflag v0.1.0 h1:TWZrZwG1QklFX5S4j1vxfF1sZbZeZSGofMwPMLAF29M=
|
||||
github.com/mfridman/xflag v0.1.0/go.mod h1:/483ywM5ZO5SuMVjrIGquYNE5CzLrj5Ux/LxWWnjRaE=
|
||||
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0=
|
||||
@@ -1271,7 +1281,6 @@ github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc
|
||||
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/rueidis v1.0.53/go.mod h1:by+34b0cFXndxtYmPAHpoTHO5NkosDlBvhexoTURIxM=
|
||||
github.com/relvacode/iso8601 v1.4.0 h1:GsInVSEJfkYuirYFxa80nMLbH2aydgZpIf52gYZXUJs=
|
||||
github.com/relvacode/iso8601 v1.4.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I=
|
||||
github.com/richardartoul/molecule v1.0.0 h1:+LFA9cT7fn8KF39zy4dhOnwcOwRoqKiBkPqKqya+8+U=
|
||||
@@ -1293,10 +1302,8 @@ github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtm
|
||||
github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXIB2aspiZs=
|
||||
github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo=
|
||||
github.com/segmentio/encoding v0.4.1/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI=
|
||||
github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
|
||||
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
|
||||
github.com/shadowspore/fossil-delta v0.0.0-20241213113458-1d797d70cbe3/go.mod h1:aJIMhRsunltJR926EB2MUg8qHemFQDreSB33pyto2Ps=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
@@ -1311,6 +1318,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I
|
||||
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
|
||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 h1:lIOOHPEbXzO3vnmx2gok1Tfs31Q8GQqKLc8vVqyQq/I=
|
||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
||||
@@ -1409,6 +1417,7 @@ github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxt
|
||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs=
|
||||
go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw=
|
||||
go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489 h1:1JFLBqwIgdyHN1ZtgjTBwO+blA6gVOmZurpiMEsETKo=
|
||||
go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4=
|
||||
|
||||
+2
-2
@@ -277,8 +277,8 @@
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/saga-icons": "workspace:*",
|
||||
"@grafana/scenes": "6.3.1",
|
||||
"@grafana/scenes-react": "6.3.1",
|
||||
"@grafana/scenes": "6.5.2",
|
||||
"@grafana/scenes-react": "6.5.2",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
||||
@@ -215,7 +215,6 @@ export interface GrafanaConfig {
|
||||
unifiedAlerting: UnifiedAlertingConfig;
|
||||
angularSupportEnabled: boolean;
|
||||
feedbackLinksEnabled: boolean;
|
||||
secretsManagerPluginEnabled: boolean;
|
||||
supportBundlesEnabled: boolean;
|
||||
secureSocksDSProxyEnabled: boolean;
|
||||
googleAnalyticsId: string | undefined;
|
||||
|
||||
@@ -826,6 +826,7 @@ export interface FeatureToggles {
|
||||
managedDualWriter?: boolean;
|
||||
/**
|
||||
* Enables SRI checks for plugin assets
|
||||
* @default false
|
||||
*/
|
||||
pluginsSriChecks?: boolean;
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,6 @@ export enum PluginType {
|
||||
datasource = 'datasource',
|
||||
app = 'app',
|
||||
renderer = 'renderer',
|
||||
secretsmanager = 'secretsmanager',
|
||||
}
|
||||
|
||||
/** Describes status of {@link https://grafana.com/docs/grafana/latest/plugins/plugin-signatures/ | plugin signature} */
|
||||
|
||||
@@ -185,6 +185,40 @@ describe('Format value with value mappings', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Range mapping with null From or null To', () => {
|
||||
expect(
|
||||
getValueMappingResult(
|
||||
[
|
||||
{
|
||||
type: MappingType.RangeToText,
|
||||
options: {
|
||||
from: 0,
|
||||
to: null,
|
||||
result: { text: 'pos' },
|
||||
},
|
||||
},
|
||||
],
|
||||
100
|
||||
)
|
||||
).toEqual({ text: 'pos' });
|
||||
|
||||
expect(
|
||||
getValueMappingResult(
|
||||
[
|
||||
{
|
||||
type: MappingType.RangeToText,
|
||||
options: {
|
||||
from: null,
|
||||
to: 0,
|
||||
result: { text: 'neg' },
|
||||
},
|
||||
},
|
||||
],
|
||||
-100
|
||||
)
|
||||
).toEqual({ text: 'neg' });
|
||||
});
|
||||
|
||||
describe('Format value with regex mappings', () => {
|
||||
it('should return correct regular expression result', () => {
|
||||
const value = 'www.foo.com';
|
||||
|
||||
@@ -35,13 +35,17 @@ export function getValueMappingResult(valueMappings: ValueMapping[], value: any)
|
||||
continue;
|
||||
}
|
||||
|
||||
const isNumFrom = !isNaN(vm.options.from!);
|
||||
if (isNumFrom && valueAsNumber < vm.options.from!) {
|
||||
const from = vm.options.from ?? -Infinity;
|
||||
|
||||
const isNumFrom = !isNaN(from);
|
||||
if (isNumFrom && valueAsNumber < from) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isNumTo = !isNaN(vm.options.to!);
|
||||
if (isNumTo && valueAsNumber > vm.options.to!) {
|
||||
const to = vm.options.to ?? Infinity;
|
||||
|
||||
const isNumTo = !isNaN(to);
|
||||
if (isNumTo && valueAsNumber > to) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
rendererDefaultImageWidth = 1000;
|
||||
rendererDefaultImageHeight = 500;
|
||||
rendererDefaultImageScale = 1;
|
||||
secretsManagerPluginEnabled = false;
|
||||
supportBundlesEnabled = false;
|
||||
http2Enabled = false;
|
||||
dateFormats?: SystemDateFormatSettings;
|
||||
|
||||
@@ -126,5 +126,7 @@ const collapsableSectionStyles = (theme: GrafanaTheme2) => ({
|
||||
}),
|
||||
label: css({
|
||||
display: 'flex',
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
color: theme.colors.text.maxContrast,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
skv "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
)
|
||||
|
||||
func (hs *HTTPServer) AdminRotateDataEncryptionKeys(c *contextmodel.ReqContext) response.Response {
|
||||
@@ -50,51 +49,3 @@ func (hs *HTTPServer) AdminRollbackSecrets(c *contextmodel.ReqContext) response.
|
||||
|
||||
return response.Respond(http.StatusOK, "Secrets rolled back successfully")
|
||||
}
|
||||
|
||||
// To migrate to the plugin, it must be installed and configured
|
||||
// so as not to lose access to migrated secrets
|
||||
func (hs *HTTPServer) AdminMigrateSecretsToPlugin(c *contextmodel.ReqContext) response.Response {
|
||||
if skv.EvaluateRemoteSecretsPlugin(c.Req.Context(), hs.secretsPluginManager, hs.Cfg) != nil {
|
||||
hs.log.Warn("Received secrets plugin migration request while plugin is not available")
|
||||
return response.Respond(http.StatusBadRequest, "Secrets plugin is not available")
|
||||
}
|
||||
err := hs.secretsPluginMigrator.TriggerPluginMigration(c.Req.Context(), true)
|
||||
if err != nil {
|
||||
hs.log.Error("Failed to trigger secret migration to plugin", "error", err.Error())
|
||||
return response.Respond(http.StatusInternalServerError, "Secret migration to plugin failed")
|
||||
}
|
||||
return response.Respond(http.StatusOK, "Secret migration to plugin triggered successfully")
|
||||
}
|
||||
|
||||
// To migrate from the plugin, it must be installed only
|
||||
// as it is possible the user disabled it and then wants to migrate
|
||||
func (hs *HTTPServer) AdminMigrateSecretsFromPlugin(c *contextmodel.ReqContext) response.Response {
|
||||
if hs.secretsPluginManager.SecretsManager(c.Req.Context()) == nil {
|
||||
hs.log.Warn("Received secrets plugin migration request while plugin is not installed")
|
||||
return response.Respond(http.StatusBadRequest, "Secrets plugin is not installed")
|
||||
}
|
||||
err := hs.secretsPluginMigrator.TriggerPluginMigration(c.Req.Context(), false)
|
||||
if err != nil {
|
||||
hs.log.Error("Failed to trigger secret migration from plugin", "error", err.Error())
|
||||
return response.Respond(http.StatusInternalServerError, "Secret migration from plugin failed")
|
||||
}
|
||||
return response.Respond(http.StatusOK, "Secret migration from plugin triggered successfully")
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) AdminDeleteAllSecretsManagerPluginSecrets(c *contextmodel.ReqContext) response.Response {
|
||||
if hs.secretsPluginManager.SecretsManager(c.Req.Context()) == nil {
|
||||
hs.log.Warn("Received secrets plugin deletion request while plugin is not installed")
|
||||
return response.Respond(http.StatusBadRequest, "Secrets plugin is not installed")
|
||||
}
|
||||
items, err := hs.secretsStore.GetAll(c.Req.Context())
|
||||
if err != nil {
|
||||
return response.Respond(http.StatusInternalServerError, "an error occurred while retrieving secrets")
|
||||
}
|
||||
for _, item := range items {
|
||||
err := hs.secretsStore.Del(c.Req.Context(), *item.OrgId, *item.Namespace, *item.Type)
|
||||
if err != nil {
|
||||
return response.Respond(http.StatusInternalServerError, fmt.Sprintf("error deleting key with org=%v namespace=%v type=%v. error=%v", *item.OrgId, *item.Namespace, *item.Type, err.Error()))
|
||||
}
|
||||
}
|
||||
return response.Respond(http.StatusOK, fmt.Sprintf("All %d Secrets Manager plugin secrets deleted", len(items)))
|
||||
}
|
||||
|
||||
@@ -573,9 +573,6 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
adminRoute.Post("/encryption/reencrypt-data-keys", reqGrafanaAdmin, routing.Wrap(hs.AdminReEncryptEncryptionKeys))
|
||||
adminRoute.Post("/encryption/reencrypt-secrets", reqGrafanaAdmin, routing.Wrap(hs.AdminReEncryptSecrets))
|
||||
adminRoute.Post("/encryption/rollback-secrets", reqGrafanaAdmin, routing.Wrap(hs.AdminRollbackSecrets))
|
||||
adminRoute.Post("/encryption/migrate-secrets/to-plugin", reqGrafanaAdmin, routing.Wrap(hs.AdminMigrateSecretsToPlugin))
|
||||
adminRoute.Post("/encryption/migrate-secrets/from-plugin", reqGrafanaAdmin, routing.Wrap(hs.AdminMigrateSecretsFromPlugin))
|
||||
adminRoute.Post("/encryption/delete-secretsmanagerplugin-secrets", reqGrafanaAdmin, routing.Wrap(hs.AdminDeleteAllSecretsManagerPluginSecrets))
|
||||
|
||||
adminRoute.Post("/provisioning/dashboards/reload", authorize(ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersDashboards)), routing.Wrap(hs.AdminProvisioningReloadDashboards))
|
||||
adminRoute.Post("/provisioning/plugins/reload", authorize(ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersPlugins)), routing.Wrap(hs.AdminProvisioningReloadPlugins))
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
)
|
||||
|
||||
var datasourcesLogger = log.New("datasources")
|
||||
var secretsPluginError datasources.ErrDatasourceSecretsPluginUserFriendly
|
||||
|
||||
// swagger:route GET /datasources datasources getDataSources
|
||||
//
|
||||
@@ -178,9 +177,6 @@ func (hs *HTTPServer) DeleteDataSourceById(c *contextmodel.ReqContext) response.
|
||||
|
||||
err = hs.DataSourcesService.DeleteDataSource(c.Req.Context(), cmd)
|
||||
if err != nil {
|
||||
if errors.As(err, &secretsPluginError) {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to delete datasource: "+err.Error(), err)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "Failed to delete datasource", err)
|
||||
}
|
||||
|
||||
@@ -257,9 +253,6 @@ func (hs *HTTPServer) DeleteDataSourceByUID(c *contextmodel.ReqContext) response
|
||||
|
||||
err = hs.DataSourcesService.DeleteDataSource(c.Req.Context(), cmd)
|
||||
if err != nil {
|
||||
if errors.As(err, &secretsPluginError) {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to delete datasource: "+err.Error(), err)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "Failed to delete datasource", err)
|
||||
}
|
||||
|
||||
@@ -307,9 +300,6 @@ func (hs *HTTPServer) DeleteDataSourceByName(c *contextmodel.ReqContext) respons
|
||||
cmd := &datasources.DeleteDataSourceCommand{Name: name, OrgID: c.SignedInUser.GetOrgID()}
|
||||
err = hs.DataSourcesService.DeleteDataSource(c.Req.Context(), cmd)
|
||||
if err != nil {
|
||||
if errors.As(err, &secretsPluginError) {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to delete datasource: "+err.Error(), err)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "Failed to delete datasource", err)
|
||||
}
|
||||
|
||||
@@ -405,10 +395,6 @@ func (hs *HTTPServer) AddDataSource(c *contextmodel.ReqContext) response.Respons
|
||||
return response.Error(http.StatusConflict, err.Error(), err)
|
||||
}
|
||||
|
||||
if errors.As(err, &secretsPluginError) {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to add datasource: "+err.Error(), err)
|
||||
}
|
||||
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to add datasource", err)
|
||||
}
|
||||
|
||||
@@ -532,10 +518,6 @@ func (hs *HTTPServer) updateDataSourceByID(c *contextmodel.ReqContext, ds *datas
|
||||
return response.Error(http.StatusConflict, "Datasource has already been updated by someone else. Please reload and try again", err)
|
||||
}
|
||||
|
||||
if errors.As(err, &secretsPluginError) {
|
||||
return response.Error(http.StatusInternalServerError, "Failed to update datasource: "+err.Error(), err)
|
||||
}
|
||||
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Failed to update datasource", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,6 @@ func (hs *HTTPServer) handleQueryMetricsError(err error) *response.NormalRespons
|
||||
return response.Error(http.StatusNotFound, "Data source not found", err)
|
||||
}
|
||||
|
||||
var secretsPlugin datasources.ErrDatasourceSecretsPluginUserFriendly
|
||||
if errors.As(err, &secretsPlugin) {
|
||||
return response.Error(http.StatusInternalServerError, fmt.Sprint("Secrets Plugin error: ", err.Error()), err)
|
||||
}
|
||||
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Query data error", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -25,7 +24,6 @@ import (
|
||||
fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/query"
|
||||
@@ -40,11 +38,6 @@ type fakeDataSourceRequestValidator struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type secretsErrorResponseBody struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (rv *fakeDataSourceRequestValidator) Validate(ds *datasources.DataSource, req *http.Request) error {
|
||||
return rv.err
|
||||
}
|
||||
@@ -93,64 +86,6 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIEndpoint_Metrics_PluginDecryptionFailure(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
ds := &fakeDatasources.FakeDataSourceService{SimulatePluginFailure: true}
|
||||
db := &dbtest.FakeDB{ExpectedError: pluginsettings.ErrPluginSettingNotFound}
|
||||
pcp := plugincontext.ProvideService(cfg, localcache.ProvideService(),
|
||||
&pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{
|
||||
{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "grafana",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
&fakeDatasources.FakeCacheService{},
|
||||
ds, pluginSettings.ProvideService(db, secretstest.NewFakeSecretsService()), pluginconfig.NewFakePluginRequestConfigProvider(),
|
||||
)
|
||||
qds := query.ProvideService(
|
||||
cfg,
|
||||
nil,
|
||||
nil,
|
||||
&fakeDataSourceRequestValidator{},
|
||||
&fakePluginClient{
|
||||
QueryDataHandlerFunc: func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
resp := backend.Responses{
|
||||
"A": backend.DataResponse{
|
||||
Error: errors.New("query failed"),
|
||||
},
|
||||
}
|
||||
return &backend.QueryDataResponse{Responses: resp}, nil
|
||||
},
|
||||
},
|
||||
pcp,
|
||||
)
|
||||
httpServer := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||
hs.queryDataService = qds
|
||||
hs.QuotaService = quotatest.New(false, nil)
|
||||
hs.pluginContextProvider = pcp
|
||||
})
|
||||
|
||||
t.Run("Status code is 500 and a secrets plugin error is returned if there is a problem getting secrets from the remote plugin", func(t *testing.T) {
|
||||
req := httpServer.NewPostRequest("/api/ds/query", strings.NewReader(reqValid))
|
||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {datasources.ActionQuery: []string{datasources.ScopeAll}}}})
|
||||
resp, err := httpServer.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, resp.Body.Close())
|
||||
var resObj secretsErrorResponseBody
|
||||
err = json.Unmarshal(buf.Bytes(), &resObj)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", resObj.Error)
|
||||
require.Contains(t, resObj.Message, "Secrets Plugin error:")
|
||||
})
|
||||
}
|
||||
|
||||
var reqValid = `{
|
||||
"from": "",
|
||||
"to": "",
|
||||
|
||||
@@ -220,30 +220,29 @@ type FrontendSettingsDTO struct {
|
||||
|
||||
LicenseInfo FrontendSettingsLicenseInfoDTO `json:"licenseInfo"`
|
||||
|
||||
FeatureToggles map[string]bool `json:"featureToggles"`
|
||||
AnonymousEnabled bool `json:"anonymousEnabled"`
|
||||
AnonymousDeviceLimit int64 `json:"anonymousDeviceLimit"`
|
||||
RendererAvailable bool `json:"rendererAvailable"`
|
||||
RendererVersion string `json:"rendererVersion"`
|
||||
RendererDefaultImageWidth int `json:"rendererDefaultImageWidth"`
|
||||
RendererDefaultImageHeight int `json:"rendererDefaultImageHeight"`
|
||||
RendererDefaultImageScale float64 `json:"rendererDefaultImageScale"`
|
||||
SecretsManagerPluginEnabled bool `json:"secretsManagerPluginEnabled"`
|
||||
Http2Enabled bool `json:"http2Enabled"`
|
||||
GrafanaJavascriptAgent setting.GrafanaJavascriptAgent `json:"grafanaJavascriptAgent"`
|
||||
PluginCatalogURL string `json:"pluginCatalogURL"`
|
||||
PluginAdminEnabled bool `json:"pluginAdminEnabled"`
|
||||
PluginAdminExternalManageEnabled bool `json:"pluginAdminExternalManageEnabled"`
|
||||
PluginCatalogHiddenPlugins []string `json:"pluginCatalogHiddenPlugins"`
|
||||
PluginCatalogManagedPlugins []string `json:"pluginCatalogManagedPlugins"`
|
||||
PluginCatalogPreinstalledPlugins []setting.InstallPlugin `json:"pluginCatalogPreinstalledPlugins"`
|
||||
ExpressionsEnabled bool `json:"expressionsEnabled"`
|
||||
AwsAllowedAuthProviders []string `json:"awsAllowedAuthProviders"`
|
||||
AwsAssumeRoleEnabled bool `json:"awsAssumeRoleEnabled"`
|
||||
SupportBundlesEnabled bool `json:"supportBundlesEnabled"`
|
||||
SnapshotEnabled bool `json:"snapshotEnabled"`
|
||||
SecureSocksDSProxyEnabled bool `json:"secureSocksDSProxyEnabled"`
|
||||
ReportingStaticContext map[string]string `json:"reportingStaticContext"`
|
||||
FeatureToggles map[string]bool `json:"featureToggles"`
|
||||
AnonymousEnabled bool `json:"anonymousEnabled"`
|
||||
AnonymousDeviceLimit int64 `json:"anonymousDeviceLimit"`
|
||||
RendererAvailable bool `json:"rendererAvailable"`
|
||||
RendererVersion string `json:"rendererVersion"`
|
||||
RendererDefaultImageWidth int `json:"rendererDefaultImageWidth"`
|
||||
RendererDefaultImageHeight int `json:"rendererDefaultImageHeight"`
|
||||
RendererDefaultImageScale float64 `json:"rendererDefaultImageScale"`
|
||||
Http2Enabled bool `json:"http2Enabled"`
|
||||
GrafanaJavascriptAgent setting.GrafanaJavascriptAgent `json:"grafanaJavascriptAgent"`
|
||||
PluginCatalogURL string `json:"pluginCatalogURL"`
|
||||
PluginAdminEnabled bool `json:"pluginAdminEnabled"`
|
||||
PluginAdminExternalManageEnabled bool `json:"pluginAdminExternalManageEnabled"`
|
||||
PluginCatalogHiddenPlugins []string `json:"pluginCatalogHiddenPlugins"`
|
||||
PluginCatalogManagedPlugins []string `json:"pluginCatalogManagedPlugins"`
|
||||
PluginCatalogPreinstalledPlugins []setting.InstallPlugin `json:"pluginCatalogPreinstalledPlugins"`
|
||||
ExpressionsEnabled bool `json:"expressionsEnabled"`
|
||||
AwsAllowedAuthProviders []string `json:"awsAllowedAuthProviders"`
|
||||
AwsAssumeRoleEnabled bool `json:"awsAssumeRoleEnabled"`
|
||||
SupportBundlesEnabled bool `json:"supportBundlesEnabled"`
|
||||
SnapshotEnabled bool `json:"snapshotEnabled"`
|
||||
SecureSocksDSProxyEnabled bool `json:"secureSocksDSProxyEnabled"`
|
||||
ReportingStaticContext map[string]string `json:"reportingStaticContext"`
|
||||
PluginDependencies map[string][]plugins.Dependency `json:"pluginDependants"`
|
||||
|
||||
Azure FrontendSettingsAzureDTO `json:"azure"`
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@@ -174,7 +173,6 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||
}
|
||||
|
||||
hasAccess := accesscontrol.HasAccess(hs.AccessControl, c)
|
||||
secretsManagerPluginEnabled := kvstore.EvaluateRemoteSecretsPlugin(c.Req.Context(), hs.secretsPluginManager, hs.Cfg) == nil
|
||||
trustedTypesDefaultPolicyEnabled := (hs.Cfg.CSPEnabled && strings.Contains(hs.Cfg.CSPTemplate, "require-trusted-types-for")) || (hs.Cfg.CSPReportOnlyEnabled && strings.Contains(hs.Cfg.CSPReportOnlyTemplate, "require-trusted-types-for"))
|
||||
isCloudMigrationTarget := hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagOnPremToCloudMigrations) && hs.Cfg.CloudMigration.IsTarget
|
||||
featureToggles := hs.Features.GetEnabled(c.Req.Context())
|
||||
@@ -280,7 +278,6 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||
RendererDefaultImageWidth: hs.Cfg.RendererDefaultImageWidth,
|
||||
RendererDefaultImageHeight: hs.Cfg.RendererDefaultImageHeight,
|
||||
RendererDefaultImageScale: hs.Cfg.RendererDefaultImageScale,
|
||||
SecretsManagerPluginEnabled: secretsManagerPluginEnabled,
|
||||
Http2Enabled: hs.Cfg.Protocol == setting.HTTP2Scheme,
|
||||
GrafanaJavascriptAgent: hs.Cfg.GrafanaJavascriptAgent,
|
||||
PluginCatalogURL: hs.Cfg.PluginCatalogURL,
|
||||
|
||||
@@ -169,10 +169,9 @@ type HTTPServer struct {
|
||||
Listener net.Listener
|
||||
EncryptionService encryption.Internal
|
||||
SecretsService secrets.Service
|
||||
secretsPluginManager plugins.SecretsPluginManager
|
||||
secretsStore secretsKV.SecretsKVStore
|
||||
secretsMigrator secrets.Migrator
|
||||
secretsPluginMigrator spm.SecretMigrationProvider
|
||||
secretMigrationProvider spm.SecretMigrationProvider
|
||||
DataSourcesService datasources.DataSourceService
|
||||
cleanUpService *cleanup.CleanUpService
|
||||
tracer tracing.Tracer
|
||||
@@ -264,8 +263,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service,
|
||||
starService star.Service, csrfService csrf.Service, managedPlugins managedplugins.Manager,
|
||||
playlistService playlist.Service, apiKeyService apikey.Service, kvStore kvstore.KVStore,
|
||||
secretsMigrator secrets.Migrator, secretsPluginManager plugins.SecretsPluginManager, secretsService secrets.Service,
|
||||
secretsPluginMigrator spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore,
|
||||
secretsMigrator secrets.Migrator, secretsService secrets.Service,
|
||||
secretMigrationProvider spm.SecretMigrationProvider, secretsStore secretsKV.SecretsKVStore,
|
||||
publicDashboardsApi *publicdashboardsApi.Api, userService user.Service, tempUserService tempUser.Service,
|
||||
loginAttemptService loginAttempt.Service, orgService org.Service, orgDeletionService org.DeletionService, teamService team.Service,
|
||||
accesscontrolService accesscontrol.Service, navTreeService navtree.Service,
|
||||
@@ -329,9 +328,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
SocialService: socialService,
|
||||
EncryptionService: encryptionService,
|
||||
SecretsService: secretsService,
|
||||
secretsPluginManager: secretsPluginManager,
|
||||
secretsMigrator: secretsMigrator,
|
||||
secretsPluginMigrator: secretsPluginMigrator,
|
||||
secretMigrationProvider: secretMigrationProvider,
|
||||
secretsStore: secretsStore,
|
||||
DataSourcesService: dataSourcesService,
|
||||
searchUsersService: searchUsersService,
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
)
|
||||
|
||||
@@ -31,14 +30,13 @@ var handshake = goplugin.HandshakeConfig{
|
||||
// pluginSet is list of plugins supported on v2.
|
||||
var pluginSet = map[int]goplugin.PluginSet{
|
||||
grpcplugin.ProtocolVersion: {
|
||||
"diagnostics": &grpcplugin.DiagnosticsGRPCPlugin{},
|
||||
"resource": &grpcplugin.ResourceGRPCPlugin{},
|
||||
"data": &grpcplugin.DataGRPCPlugin{},
|
||||
"stream": &grpcplugin.StreamGRPCPlugin{},
|
||||
"admission": &grpcplugin.AdmissionGRPCPlugin{},
|
||||
"conversion": &grpcplugin.ConversionGRPCPlugin{},
|
||||
"renderer": &pluginextensionv2.RendererGRPCPlugin{},
|
||||
"secretsmanager": &secretsmanagerplugin.SecretsManagerGRPCPlugin{},
|
||||
"diagnostics": &grpcplugin.DiagnosticsGRPCPlugin{},
|
||||
"resource": &grpcplugin.ResourceGRPCPlugin{},
|
||||
"data": &grpcplugin.DataGRPCPlugin{},
|
||||
"stream": &grpcplugin.StreamGRPCPlugin{},
|
||||
"admission": &grpcplugin.AdmissionGRPCPlugin{},
|
||||
"conversion": &grpcplugin.ConversionGRPCPlugin{},
|
||||
"renderer": &pluginextensionv2.RendererGRPCPlugin{},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -84,19 +82,15 @@ func newClientConfig(executablePath string, args []string, env []string, skipHos
|
||||
// StartRendererFunc callback function called when a renderer plugin is started.
|
||||
type StartRendererFunc func(pluginID string, renderer pluginextensionv2.RendererPlugin, logger log.Logger) error
|
||||
|
||||
// StartSecretsManagerFunc callback function called when a secrets manager plugin is started.
|
||||
type StartSecretsManagerFunc func(pluginID string, secretsmanager secretsmanagerplugin.SecretsManagerPlugin, logger log.Logger) error
|
||||
|
||||
// PluginDescriptor is a descriptor used for registering backend plugins.
|
||||
type PluginDescriptor struct {
|
||||
pluginID string
|
||||
executablePath string
|
||||
executableArgs []string
|
||||
skipHostEnvVars bool
|
||||
managed bool
|
||||
versionedPlugins map[int]goplugin.PluginSet
|
||||
startRendererFn StartRendererFunc
|
||||
startSecretsManagerFn StartSecretsManagerFunc
|
||||
pluginID string
|
||||
executablePath string
|
||||
executableArgs []string
|
||||
skipHostEnvVars bool
|
||||
managed bool
|
||||
versionedPlugins map[int]goplugin.PluginSet
|
||||
startRendererFn StartRendererFunc
|
||||
}
|
||||
|
||||
// NewBackendPlugin creates a new backend plugin factory used for registering a backend plugin.
|
||||
@@ -131,14 +125,3 @@ func NewRendererPlugin(pluginID, executablePath string, startFn StartRendererFun
|
||||
startRendererFn: startFn,
|
||||
})
|
||||
}
|
||||
|
||||
// NewSecretsManagerPlugin creates a new secrets manager plugin factory used for registering a backend secrets manager plugin.
|
||||
func NewSecretsManagerPlugin(pluginID, executablePath string, startFn StartSecretsManagerFunc) backendplugin.PluginFactoryFunc {
|
||||
return newPlugin(PluginDescriptor{
|
||||
pluginID: pluginID,
|
||||
executablePath: executablePath,
|
||||
managed: false,
|
||||
versionedPlugins: pluginSet,
|
||||
startSecretsManagerFn: startFn,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
)
|
||||
|
||||
@@ -32,7 +31,6 @@ type ClientV2 struct {
|
||||
grpcplugin.AdmissionClient
|
||||
grpcplugin.ConversionClient
|
||||
pluginextensionv2.RendererPlugin
|
||||
secretsmanagerplugin.SecretsManagerPlugin
|
||||
}
|
||||
|
||||
func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugin.ClientProtocol) (*ClientV2, error) {
|
||||
@@ -71,11 +69,6 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawSecretsManager, err := rpcClient.Dispense("secretsmanager")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &ClientV2{}
|
||||
if rawDiagnostics != nil {
|
||||
if diagnosticsClient, ok := rawDiagnostics.(grpcplugin.DiagnosticsClient); ok {
|
||||
@@ -119,24 +112,12 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
|
||||
}
|
||||
}
|
||||
|
||||
if rawSecretsManager != nil {
|
||||
if secretsManagerPlugin, ok := rawSecretsManager.(secretsmanagerplugin.SecretsManagerPlugin); ok {
|
||||
c.SecretsManagerPlugin = secretsManagerPlugin
|
||||
}
|
||||
}
|
||||
|
||||
if descriptor.startRendererFn != nil {
|
||||
if err := descriptor.startRendererFn(descriptor.pluginID, c.RendererPlugin, logger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if descriptor.startSecretsManagerFn != nil {
|
||||
if err := descriptor.startSecretsManagerFn(descriptor.pluginID, c.SecretsManagerPlugin, logger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/grpcplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
)
|
||||
|
||||
@@ -21,7 +20,7 @@ type Service struct {
|
||||
|
||||
func New(providers ...PluginBackendProvider) *Service {
|
||||
if len(providers) == 0 {
|
||||
return New(SecretsManagerProvider, DefaultProvider)
|
||||
return New(DefaultProvider)
|
||||
}
|
||||
return &Service{
|
||||
providerChain: providers,
|
||||
@@ -29,7 +28,7 @@ func New(providers ...PluginBackendProvider) *Service {
|
||||
}
|
||||
|
||||
func ProvideService(coreRegistry *coreplugin.Registry) *Service {
|
||||
return New(coreRegistry.BackendFactoryProvider(), SecretsManagerProvider, DefaultProvider)
|
||||
return New(coreRegistry.BackendFactoryProvider(), DefaultProvider)
|
||||
}
|
||||
|
||||
func (s *Service) BackendFactory(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
|
||||
@@ -53,18 +52,6 @@ var RendererProvider PluginBackendProvider = func(_ context.Context, p *plugins.
|
||||
)
|
||||
}
|
||||
|
||||
var SecretsManagerProvider PluginBackendProvider = func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
|
||||
if !p.IsSecretsManager() {
|
||||
return nil
|
||||
}
|
||||
return grpcplugin.NewSecretsManagerPlugin(p.ID, p.ExecutablePath(),
|
||||
func(pluginID string, secretsmanager secretsmanagerplugin.SecretsManagerPlugin, logger log.Logger) error {
|
||||
p.SecretsManager = secretsmanager
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var DefaultProvider = PluginBackendProvider(func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
|
||||
return grpcplugin.NewBackendPlugin(p.ID, p.ExecutablePath(), p.SkipHostEnvVars)
|
||||
})
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
version: v1
|
||||
plugins:
|
||||
- plugin: go
|
||||
out: pkg/plugins/backendplugin/secretsmanagerplugin
|
||||
opt: paths=source_relative
|
||||
- plugin: go-grpc
|
||||
out: pkg/plugins/backendplugin/secretsmanagerplugin
|
||||
opt:
|
||||
- paths=source_relative
|
||||
@@ -1,7 +0,0 @@
|
||||
version: v1
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
lint:
|
||||
use:
|
||||
- DEFAULT
|
||||
@@ -1,921 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.5
|
||||
// protoc (unknown)
|
||||
// source: secretsmanager.proto
|
||||
|
||||
package secretsmanagerplugin
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Key struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
OrgId int64 `protobuf:"varint,1,opt,name=orgId,proto3" json:"orgId,omitempty"`
|
||||
Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"`
|
||||
Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Key) Reset() {
|
||||
*x = Key{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Key) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Key) ProtoMessage() {}
|
||||
|
||||
func (x *Key) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Key.ProtoReflect.Descriptor instead.
|
||||
func (*Key) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Key) GetOrgId() int64 {
|
||||
if x != nil {
|
||||
return x.OrgId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Key) GetNamespace() string {
|
||||
if x != nil {
|
||||
return x.Namespace
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Key) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Key *Key `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Item) Reset() {
|
||||
*x = Item{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Item) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Item) ProtoMessage() {}
|
||||
|
||||
func (x *Item) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Item.ProtoReflect.Descriptor instead.
|
||||
func (*Item) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *Item) GetKey() *Key {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Item) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type GetSecretRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
KeyDescriptor *Key `protobuf:"bytes,1,opt,name=keyDescriptor,proto3" json:"keyDescriptor,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetSecretRequest) Reset() {
|
||||
*x = GetSecretRequest{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetSecretRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetSecretRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetSecretRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetSecretRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetSecretRequest) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *GetSecretRequest) GetKeyDescriptor() *Key {
|
||||
if x != nil {
|
||||
return x.KeyDescriptor
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetSecretResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"`
|
||||
DecryptedValue string `protobuf:"bytes,2,opt,name=decryptedValue,proto3" json:"decryptedValue,omitempty"`
|
||||
Exists bool `protobuf:"varint,3,opt,name=exists,proto3" json:"exists,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetSecretResponse) Reset() {
|
||||
*x = GetSecretResponse{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetSecretResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetSecretResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetSecretResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetSecretResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetSecretResponse) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *GetSecretResponse) GetUserFriendlyError() string {
|
||||
if x != nil {
|
||||
return x.UserFriendlyError
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetSecretResponse) GetDecryptedValue() string {
|
||||
if x != nil {
|
||||
return x.DecryptedValue
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetSecretResponse) GetExists() bool {
|
||||
if x != nil {
|
||||
return x.Exists
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type SetSecretRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
KeyDescriptor *Key `protobuf:"bytes,1,opt,name=keyDescriptor,proto3" json:"keyDescriptor,omitempty"`
|
||||
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SetSecretRequest) Reset() {
|
||||
*x = SetSecretRequest{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SetSecretRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SetSecretRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SetSecretRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SetSecretRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SetSecretRequest) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *SetSecretRequest) GetKeyDescriptor() *Key {
|
||||
if x != nil {
|
||||
return x.KeyDescriptor
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SetSecretRequest) GetValue() string {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type SetSecretResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SetSecretResponse) Reset() {
|
||||
*x = SetSecretResponse{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SetSecretResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SetSecretResponse) ProtoMessage() {}
|
||||
|
||||
func (x *SetSecretResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SetSecretResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SetSecretResponse) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *SetSecretResponse) GetUserFriendlyError() string {
|
||||
if x != nil {
|
||||
return x.UserFriendlyError
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type DeleteSecretRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
KeyDescriptor *Key `protobuf:"bytes,1,opt,name=keyDescriptor,proto3" json:"keyDescriptor,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DeleteSecretRequest) Reset() {
|
||||
*x = DeleteSecretRequest{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DeleteSecretRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DeleteSecretRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DeleteSecretRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DeleteSecretRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DeleteSecretRequest) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *DeleteSecretRequest) GetKeyDescriptor() *Key {
|
||||
if x != nil {
|
||||
return x.KeyDescriptor
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type DeleteSecretResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DeleteSecretResponse) Reset() {
|
||||
*x = DeleteSecretResponse{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DeleteSecretResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DeleteSecretResponse) ProtoMessage() {}
|
||||
|
||||
func (x *DeleteSecretResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DeleteSecretResponse.ProtoReflect.Descriptor instead.
|
||||
func (*DeleteSecretResponse) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *DeleteSecretResponse) GetUserFriendlyError() string {
|
||||
if x != nil {
|
||||
return x.UserFriendlyError
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListSecretsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
KeyDescriptor *Key `protobuf:"bytes,1,opt,name=keyDescriptor,proto3" json:"keyDescriptor,omitempty"`
|
||||
AllOrganizations bool `protobuf:"varint,2,opt,name=allOrganizations,proto3" json:"allOrganizations,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListSecretsRequest) Reset() {
|
||||
*x = ListSecretsRequest{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListSecretsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListSecretsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ListSecretsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListSecretsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ListSecretsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *ListSecretsRequest) GetKeyDescriptor() *Key {
|
||||
if x != nil {
|
||||
return x.KeyDescriptor
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListSecretsRequest) GetAllOrganizations() bool {
|
||||
if x != nil {
|
||||
return x.AllOrganizations
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ListSecretsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"`
|
||||
Keys []*Key `protobuf:"bytes,2,rep,name=keys,proto3" json:"keys,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ListSecretsResponse) Reset() {
|
||||
*x = ListSecretsResponse{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ListSecretsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ListSecretsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ListSecretsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[9]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ListSecretsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ListSecretsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *ListSecretsResponse) GetUserFriendlyError() string {
|
||||
if x != nil {
|
||||
return x.UserFriendlyError
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListSecretsResponse) GetKeys() []*Key {
|
||||
if x != nil {
|
||||
return x.Keys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetAllSecretsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetAllSecretsRequest) Reset() {
|
||||
*x = GetAllSecretsRequest{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetAllSecretsRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetAllSecretsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetAllSecretsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetAllSecretsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetAllSecretsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
type GetAllSecretsResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"`
|
||||
Items []*Item `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetAllSecretsResponse) Reset() {
|
||||
*x = GetAllSecretsResponse{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetAllSecretsResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetAllSecretsResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetAllSecretsResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[11]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetAllSecretsResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetAllSecretsResponse) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
func (x *GetAllSecretsResponse) GetUserFriendlyError() string {
|
||||
if x != nil {
|
||||
return x.UserFriendlyError
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetAllSecretsResponse) GetItems() []*Item {
|
||||
if x != nil {
|
||||
return x.Items
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RenameSecretRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
KeyDescriptor *Key `protobuf:"bytes,1,opt,name=keyDescriptor,proto3" json:"keyDescriptor,omitempty"`
|
||||
NewNamespace string `protobuf:"bytes,2,opt,name=newNamespace,proto3" json:"newNamespace,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RenameSecretRequest) Reset() {
|
||||
*x = RenameSecretRequest{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RenameSecretRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RenameSecretRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RenameSecretRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[12]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RenameSecretRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RenameSecretRequest) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
func (x *RenameSecretRequest) GetKeyDescriptor() *Key {
|
||||
if x != nil {
|
||||
return x.KeyDescriptor
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RenameSecretRequest) GetNewNamespace() string {
|
||||
if x != nil {
|
||||
return x.NewNamespace
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type RenameSecretResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
UserFriendlyError string `protobuf:"bytes,1,opt,name=userFriendlyError,proto3" json:"userFriendlyError,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RenameSecretResponse) Reset() {
|
||||
*x = RenameSecretResponse{}
|
||||
mi := &file_secretsmanager_proto_msgTypes[13]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *RenameSecretResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*RenameSecretResponse) ProtoMessage() {}
|
||||
|
||||
func (x *RenameSecretResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_secretsmanager_proto_msgTypes[13]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use RenameSecretResponse.ProtoReflect.Descriptor instead.
|
||||
func (*RenameSecretResponse) Descriptor() ([]byte, []int) {
|
||||
return file_secretsmanager_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
func (x *RenameSecretResponse) GetUserFriendlyError() string {
|
||||
if x != nil {
|
||||
return x.UserFriendlyError
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_secretsmanager_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_secretsmanager_proto_rawDesc = string([]byte{
|
||||
0x0a, 0x14, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x22, 0x4d, 0x0a, 0x03,
|
||||
0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x49, 0x0a, 0x04, 0x49,
|
||||
0x74, 0x65, 0x6d, 0x12, 0x2b, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x53, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63,
|
||||
0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x6b, 0x65,
|
||||
0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x6b, 0x65,
|
||||
0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x22, 0x81, 0x01, 0x0a, 0x11,
|
||||
0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x2c, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c,
|
||||
0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73,
|
||||
0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12,
|
||||
0x26, 0x0a, 0x0e, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74,
|
||||
0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78, 0x69, 0x73, 0x74,
|
||||
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x22,
|
||||
0x69, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63,
|
||||
0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69,
|
||||
0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69,
|
||||
0x70, 0x74, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x41, 0x0a, 0x11, 0x53, 0x65,
|
||||
0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x2c, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45,
|
||||
0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72,
|
||||
0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x56, 0x0a,
|
||||
0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65,
|
||||
0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67,
|
||||
0x69, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x6f, 0x72, 0x22, 0x44, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53,
|
||||
0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a,
|
||||
0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72,
|
||||
0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72,
|
||||
0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x81, 0x01, 0x0a, 0x12,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
|
||||
0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, 0x72,
|
||||
0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x2e, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x6b, 0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
|
||||
0x74, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x61, 0x6c, 0x6c, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69,
|
||||
0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x61,
|
||||
0x6c, 0x6c, 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22,
|
||||
0x72, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72,
|
||||
0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45,
|
||||
0x72, 0x72, 0x6f, 0x72, 0x12, 0x2d, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03,
|
||||
0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x04, 0x6b,
|
||||
0x65, 0x79, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x63,
|
||||
0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x77, 0x0a, 0x15, 0x47,
|
||||
0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65,
|
||||
0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72,
|
||||
0x6f, 0x72, 0x12, 0x30, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x1a, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67,
|
||||
0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69,
|
||||
0x74, 0x65, 0x6d, 0x73, 0x22, 0x7a, 0x0a, 0x13, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65,
|
||||
0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x6b,
|
||||
0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61,
|
||||
0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x0d, 0x6b,
|
||||
0x65, 0x79, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x22, 0x0a, 0x0c,
|
||||
0x6e, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65,
|
||||
0x22, 0x44, 0x0a, 0x14, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72,
|
||||
0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x72, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x6c,
|
||||
0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x32, 0xe8, 0x04, 0x0a, 0x0e, 0x53, 0x65, 0x63, 0x72, 0x65,
|
||||
0x74, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, 0x5c, 0x0a, 0x09, 0x47, 0x65, 0x74,
|
||||
0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x26, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73,
|
||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x47, 0x65,
|
||||
0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27,
|
||||
0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70,
|
||||
0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x53, 0x65,
|
||||
0x63, 0x72, 0x65, 0x74, 0x12, 0x26, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61,
|
||||
0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x53,
|
||||
0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x73,
|
||||
0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75,
|
||||
0x67, 0x69, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53,
|
||||
0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x29, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x44, 0x65, 0x6c,
|
||||
0x65, 0x74, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x2a, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65,
|
||||
0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0b,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x28, 0x2e, 0x73, 0x65,
|
||||
0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67,
|
||||
0x69, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x65, 0x0a, 0x0c, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74,
|
||||
0x12, 0x29, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
|
||||
0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65,
|
||||
0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x73, 0x65,
|
||||
0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67,
|
||||
0x69, 0x6e, 0x2e, 0x52, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c,
|
||||
0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x12, 0x2a, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65,
|
||||
0x74, 0x73, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e,
|
||||
0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d, 0x61,
|
||||
0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x41,
|
||||
0x6c, 0x6c, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x42, 0x19, 0x5a, 0x17, 0x2e, 0x2f, 0x3b, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x6d,
|
||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
|
||||
var (
|
||||
file_secretsmanager_proto_rawDescOnce sync.Once
|
||||
file_secretsmanager_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_secretsmanager_proto_rawDescGZIP() []byte {
|
||||
file_secretsmanager_proto_rawDescOnce.Do(func() {
|
||||
file_secretsmanager_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_secretsmanager_proto_rawDesc), len(file_secretsmanager_proto_rawDesc)))
|
||||
})
|
||||
return file_secretsmanager_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_secretsmanager_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
|
||||
var file_secretsmanager_proto_goTypes = []any{
|
||||
(*Key)(nil), // 0: secretsmanagerplugin.Key
|
||||
(*Item)(nil), // 1: secretsmanagerplugin.Item
|
||||
(*GetSecretRequest)(nil), // 2: secretsmanagerplugin.GetSecretRequest
|
||||
(*GetSecretResponse)(nil), // 3: secretsmanagerplugin.GetSecretResponse
|
||||
(*SetSecretRequest)(nil), // 4: secretsmanagerplugin.SetSecretRequest
|
||||
(*SetSecretResponse)(nil), // 5: secretsmanagerplugin.SetSecretResponse
|
||||
(*DeleteSecretRequest)(nil), // 6: secretsmanagerplugin.DeleteSecretRequest
|
||||
(*DeleteSecretResponse)(nil), // 7: secretsmanagerplugin.DeleteSecretResponse
|
||||
(*ListSecretsRequest)(nil), // 8: secretsmanagerplugin.ListSecretsRequest
|
||||
(*ListSecretsResponse)(nil), // 9: secretsmanagerplugin.ListSecretsResponse
|
||||
(*GetAllSecretsRequest)(nil), // 10: secretsmanagerplugin.GetAllSecretsRequest
|
||||
(*GetAllSecretsResponse)(nil), // 11: secretsmanagerplugin.GetAllSecretsResponse
|
||||
(*RenameSecretRequest)(nil), // 12: secretsmanagerplugin.RenameSecretRequest
|
||||
(*RenameSecretResponse)(nil), // 13: secretsmanagerplugin.RenameSecretResponse
|
||||
}
|
||||
var file_secretsmanager_proto_depIdxs = []int32{
|
||||
0, // 0: secretsmanagerplugin.Item.key:type_name -> secretsmanagerplugin.Key
|
||||
0, // 1: secretsmanagerplugin.GetSecretRequest.keyDescriptor:type_name -> secretsmanagerplugin.Key
|
||||
0, // 2: secretsmanagerplugin.SetSecretRequest.keyDescriptor:type_name -> secretsmanagerplugin.Key
|
||||
0, // 3: secretsmanagerplugin.DeleteSecretRequest.keyDescriptor:type_name -> secretsmanagerplugin.Key
|
||||
0, // 4: secretsmanagerplugin.ListSecretsRequest.keyDescriptor:type_name -> secretsmanagerplugin.Key
|
||||
0, // 5: secretsmanagerplugin.ListSecretsResponse.keys:type_name -> secretsmanagerplugin.Key
|
||||
1, // 6: secretsmanagerplugin.GetAllSecretsResponse.items:type_name -> secretsmanagerplugin.Item
|
||||
0, // 7: secretsmanagerplugin.RenameSecretRequest.keyDescriptor:type_name -> secretsmanagerplugin.Key
|
||||
2, // 8: secretsmanagerplugin.SecretsManager.GetSecret:input_type -> secretsmanagerplugin.GetSecretRequest
|
||||
4, // 9: secretsmanagerplugin.SecretsManager.SetSecret:input_type -> secretsmanagerplugin.SetSecretRequest
|
||||
6, // 10: secretsmanagerplugin.SecretsManager.DeleteSecret:input_type -> secretsmanagerplugin.DeleteSecretRequest
|
||||
8, // 11: secretsmanagerplugin.SecretsManager.ListSecrets:input_type -> secretsmanagerplugin.ListSecretsRequest
|
||||
12, // 12: secretsmanagerplugin.SecretsManager.RenameSecret:input_type -> secretsmanagerplugin.RenameSecretRequest
|
||||
10, // 13: secretsmanagerplugin.SecretsManager.GetAllSecrets:input_type -> secretsmanagerplugin.GetAllSecretsRequest
|
||||
3, // 14: secretsmanagerplugin.SecretsManager.GetSecret:output_type -> secretsmanagerplugin.GetSecretResponse
|
||||
5, // 15: secretsmanagerplugin.SecretsManager.SetSecret:output_type -> secretsmanagerplugin.SetSecretResponse
|
||||
7, // 16: secretsmanagerplugin.SecretsManager.DeleteSecret:output_type -> secretsmanagerplugin.DeleteSecretResponse
|
||||
9, // 17: secretsmanagerplugin.SecretsManager.ListSecrets:output_type -> secretsmanagerplugin.ListSecretsResponse
|
||||
13, // 18: secretsmanagerplugin.SecretsManager.RenameSecret:output_type -> secretsmanagerplugin.RenameSecretResponse
|
||||
11, // 19: secretsmanagerplugin.SecretsManager.GetAllSecrets:output_type -> secretsmanagerplugin.GetAllSecretsResponse
|
||||
14, // [14:20] is the sub-list for method output_type
|
||||
8, // [8:14] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_secretsmanager_proto_init() }
|
||||
func file_secretsmanager_proto_init() {
|
||||
if File_secretsmanager_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_secretsmanager_proto_rawDesc), len(file_secretsmanager_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 14,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_secretsmanager_proto_goTypes,
|
||||
DependencyIndexes: file_secretsmanager_proto_depIdxs,
|
||||
MessageInfos: file_secretsmanager_proto_msgTypes,
|
||||
}.Build()
|
||||
File_secretsmanager_proto = out.File
|
||||
file_secretsmanager_proto_goTypes = nil
|
||||
file_secretsmanager_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
syntax = "proto3";
|
||||
package secretsmanagerplugin;
|
||||
|
||||
option go_package = "./;secretsmanagerplugin";
|
||||
|
||||
message Key {
|
||||
int64 orgId = 1;
|
||||
string namespace = 2;
|
||||
string type = 3;
|
||||
}
|
||||
|
||||
message Item {
|
||||
Key key = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message GetSecretRequest {
|
||||
Key keyDescriptor = 1;
|
||||
}
|
||||
|
||||
message GetSecretResponse {
|
||||
string userFriendlyError = 1;
|
||||
string decryptedValue = 2;
|
||||
bool exists = 3;
|
||||
}
|
||||
|
||||
message SetSecretRequest {
|
||||
Key keyDescriptor = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message SetSecretResponse {
|
||||
string userFriendlyError = 1;
|
||||
}
|
||||
|
||||
message DeleteSecretRequest {
|
||||
Key keyDescriptor = 1;
|
||||
}
|
||||
|
||||
message DeleteSecretResponse {
|
||||
string userFriendlyError = 1;
|
||||
}
|
||||
|
||||
message ListSecretsRequest {
|
||||
Key keyDescriptor = 1;
|
||||
bool allOrganizations = 2;
|
||||
}
|
||||
|
||||
message ListSecretsResponse {
|
||||
string userFriendlyError = 1;
|
||||
repeated Key keys = 2;
|
||||
}
|
||||
|
||||
message GetAllSecretsRequest {
|
||||
}
|
||||
|
||||
message GetAllSecretsResponse {
|
||||
string userFriendlyError = 1;
|
||||
repeated Item items = 2;
|
||||
}
|
||||
|
||||
message RenameSecretRequest {
|
||||
Key keyDescriptor = 1;
|
||||
string newNamespace = 2;
|
||||
}
|
||||
|
||||
message RenameSecretResponse {
|
||||
string userFriendlyError = 1;
|
||||
}
|
||||
|
||||
service SecretsManager {
|
||||
rpc GetSecret(GetSecretRequest) returns (GetSecretResponse);
|
||||
rpc SetSecret(SetSecretRequest) returns (SetSecretResponse);
|
||||
rpc DeleteSecret(DeleteSecretRequest) returns (DeleteSecretResponse);
|
||||
rpc ListSecrets(ListSecretsRequest) returns (ListSecretsResponse);
|
||||
rpc RenameSecret(RenameSecretRequest) returns (RenameSecretResponse);
|
||||
rpc GetAllSecrets(GetAllSecretsRequest) returns (GetAllSecretsResponse);
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package secretsmanagerplugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type SecretsManagerPlugin interface {
|
||||
SecretsManagerClient
|
||||
}
|
||||
|
||||
type SecretsManagerGRPCPlugin struct {
|
||||
plugin.NetRPCUnsupportedPlugin
|
||||
}
|
||||
|
||||
func (p *SecretsManagerGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SecretsManagerGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (any, error) {
|
||||
return &SecretsManagerGRPCClient{NewSecretsManagerClient(c)}, nil
|
||||
}
|
||||
|
||||
type SecretsManagerGRPCClient struct {
|
||||
SecretsManagerClient
|
||||
}
|
||||
|
||||
// Get an item from the store
|
||||
func (sm *SecretsManagerGRPCClient) GetSecret(ctx context.Context, req *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error) {
|
||||
return sm.SecretsManagerClient.GetSecret(ctx, req)
|
||||
}
|
||||
|
||||
// Set an item in the store
|
||||
func (sm *SecretsManagerGRPCClient) SetSecret(ctx context.Context, req *SetSecretRequest, opts ...grpc.CallOption) (*SetSecretResponse, error) {
|
||||
return sm.SecretsManagerClient.SetSecret(ctx, req)
|
||||
}
|
||||
|
||||
// Del deletes an item from the store.
|
||||
func (sm *SecretsManagerGRPCClient) DeleteSecret(ctx context.Context, req *DeleteSecretRequest, opts ...grpc.CallOption) (*DeleteSecretResponse, error) {
|
||||
return sm.SecretsManagerClient.DeleteSecret(ctx, req)
|
||||
}
|
||||
|
||||
// Keys get all keys for a given namespace.
|
||||
func (sm *SecretsManagerGRPCClient) ListSecrets(ctx context.Context, req *ListSecretsRequest, opts ...grpc.CallOption) (*ListSecretsResponse, error) {
|
||||
return sm.SecretsManagerClient.ListSecrets(ctx, req)
|
||||
}
|
||||
|
||||
// Rename an item in the store
|
||||
func (sm *SecretsManagerGRPCClient) RenameSecret(ctx context.Context, req *RenameSecretRequest, opts ...grpc.CallOption) (*RenameSecretResponse, error) {
|
||||
return sm.SecretsManagerClient.RenameSecret(ctx, req)
|
||||
}
|
||||
|
||||
// Get all items from the store
|
||||
func (sm *SecretsManagerGRPCClient) GetAllSecrets(ctx context.Context, req *GetAllSecretsRequest, opts ...grpc.CallOption) (*GetAllSecretsResponse, error) {
|
||||
return sm.SecretsManagerClient.GetAllSecrets(ctx, req)
|
||||
}
|
||||
|
||||
var _ SecretsManagerClient = &SecretsManagerGRPCClient{}
|
||||
var _ plugin.GRPCPlugin = &SecretsManagerGRPCPlugin{}
|
||||
@@ -1,300 +0,0 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.4.0
|
||||
// - protoc (unknown)
|
||||
// source: secretsmanager.proto
|
||||
|
||||
package secretsmanagerplugin
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.62.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion8
|
||||
|
||||
const (
|
||||
SecretsManager_GetSecret_FullMethodName = "/secretsmanagerplugin.SecretsManager/GetSecret"
|
||||
SecretsManager_SetSecret_FullMethodName = "/secretsmanagerplugin.SecretsManager/SetSecret"
|
||||
SecretsManager_DeleteSecret_FullMethodName = "/secretsmanagerplugin.SecretsManager/DeleteSecret"
|
||||
SecretsManager_ListSecrets_FullMethodName = "/secretsmanagerplugin.SecretsManager/ListSecrets"
|
||||
SecretsManager_RenameSecret_FullMethodName = "/secretsmanagerplugin.SecretsManager/RenameSecret"
|
||||
SecretsManager_GetAllSecrets_FullMethodName = "/secretsmanagerplugin.SecretsManager/GetAllSecrets"
|
||||
)
|
||||
|
||||
// SecretsManagerClient is the client API for SecretsManager service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type SecretsManagerClient interface {
|
||||
GetSecret(ctx context.Context, in *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error)
|
||||
SetSecret(ctx context.Context, in *SetSecretRequest, opts ...grpc.CallOption) (*SetSecretResponse, error)
|
||||
DeleteSecret(ctx context.Context, in *DeleteSecretRequest, opts ...grpc.CallOption) (*DeleteSecretResponse, error)
|
||||
ListSecrets(ctx context.Context, in *ListSecretsRequest, opts ...grpc.CallOption) (*ListSecretsResponse, error)
|
||||
RenameSecret(ctx context.Context, in *RenameSecretRequest, opts ...grpc.CallOption) (*RenameSecretResponse, error)
|
||||
GetAllSecrets(ctx context.Context, in *GetAllSecretsRequest, opts ...grpc.CallOption) (*GetAllSecretsResponse, error)
|
||||
}
|
||||
|
||||
type secretsManagerClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewSecretsManagerClient(cc grpc.ClientConnInterface) SecretsManagerClient {
|
||||
return &secretsManagerClient{cc}
|
||||
}
|
||||
|
||||
func (c *secretsManagerClient) GetSecret(ctx context.Context, in *GetSecretRequest, opts ...grpc.CallOption) (*GetSecretResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetSecretResponse)
|
||||
err := c.cc.Invoke(ctx, SecretsManager_GetSecret_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *secretsManagerClient) SetSecret(ctx context.Context, in *SetSecretRequest, opts ...grpc.CallOption) (*SetSecretResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(SetSecretResponse)
|
||||
err := c.cc.Invoke(ctx, SecretsManager_SetSecret_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *secretsManagerClient) DeleteSecret(ctx context.Context, in *DeleteSecretRequest, opts ...grpc.CallOption) (*DeleteSecretResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(DeleteSecretResponse)
|
||||
err := c.cc.Invoke(ctx, SecretsManager_DeleteSecret_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *secretsManagerClient) ListSecrets(ctx context.Context, in *ListSecretsRequest, opts ...grpc.CallOption) (*ListSecretsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ListSecretsResponse)
|
||||
err := c.cc.Invoke(ctx, SecretsManager_ListSecrets_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *secretsManagerClient) RenameSecret(ctx context.Context, in *RenameSecretRequest, opts ...grpc.CallOption) (*RenameSecretResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(RenameSecretResponse)
|
||||
err := c.cc.Invoke(ctx, SecretsManager_RenameSecret_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *secretsManagerClient) GetAllSecrets(ctx context.Context, in *GetAllSecretsRequest, opts ...grpc.CallOption) (*GetAllSecretsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(GetAllSecretsResponse)
|
||||
err := c.cc.Invoke(ctx, SecretsManager_GetAllSecrets_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SecretsManagerServer is the server API for SecretsManager service.
|
||||
// All implementations must embed UnimplementedSecretsManagerServer
|
||||
// for forward compatibility
|
||||
type SecretsManagerServer interface {
|
||||
GetSecret(context.Context, *GetSecretRequest) (*GetSecretResponse, error)
|
||||
SetSecret(context.Context, *SetSecretRequest) (*SetSecretResponse, error)
|
||||
DeleteSecret(context.Context, *DeleteSecretRequest) (*DeleteSecretResponse, error)
|
||||
ListSecrets(context.Context, *ListSecretsRequest) (*ListSecretsResponse, error)
|
||||
RenameSecret(context.Context, *RenameSecretRequest) (*RenameSecretResponse, error)
|
||||
GetAllSecrets(context.Context, *GetAllSecretsRequest) (*GetAllSecretsResponse, error)
|
||||
mustEmbedUnimplementedSecretsManagerServer()
|
||||
}
|
||||
|
||||
// UnimplementedSecretsManagerServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedSecretsManagerServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedSecretsManagerServer) GetSecret(context.Context, *GetSecretRequest) (*GetSecretResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetSecret not implemented")
|
||||
}
|
||||
func (UnimplementedSecretsManagerServer) SetSecret(context.Context, *SetSecretRequest) (*SetSecretResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SetSecret not implemented")
|
||||
}
|
||||
func (UnimplementedSecretsManagerServer) DeleteSecret(context.Context, *DeleteSecretRequest) (*DeleteSecretResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteSecret not implemented")
|
||||
}
|
||||
func (UnimplementedSecretsManagerServer) ListSecrets(context.Context, *ListSecretsRequest) (*ListSecretsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListSecrets not implemented")
|
||||
}
|
||||
func (UnimplementedSecretsManagerServer) RenameSecret(context.Context, *RenameSecretRequest) (*RenameSecretResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RenameSecret not implemented")
|
||||
}
|
||||
func (UnimplementedSecretsManagerServer) GetAllSecrets(context.Context, *GetAllSecretsRequest) (*GetAllSecretsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAllSecrets not implemented")
|
||||
}
|
||||
func (UnimplementedSecretsManagerServer) mustEmbedUnimplementedSecretsManagerServer() {}
|
||||
|
||||
// UnsafeSecretsManagerServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to SecretsManagerServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeSecretsManagerServer interface {
|
||||
mustEmbedUnimplementedSecretsManagerServer()
|
||||
}
|
||||
|
||||
func RegisterSecretsManagerServer(s grpc.ServiceRegistrar, srv SecretsManagerServer) {
|
||||
s.RegisterService(&SecretsManager_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _SecretsManager_GetSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetSecretRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SecretsManagerServer).GetSecret(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: SecretsManager_GetSecret_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SecretsManagerServer).GetSecret(ctx, req.(*GetSecretRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SecretsManager_SetSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SetSecretRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SecretsManagerServer).SetSecret(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: SecretsManager_SetSecret_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SecretsManagerServer).SetSecret(ctx, req.(*SetSecretRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SecretsManager_DeleteSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteSecretRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SecretsManagerServer).DeleteSecret(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: SecretsManager_DeleteSecret_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SecretsManagerServer).DeleteSecret(ctx, req.(*DeleteSecretRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SecretsManager_ListSecrets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListSecretsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SecretsManagerServer).ListSecrets(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: SecretsManager_ListSecrets_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SecretsManagerServer).ListSecrets(ctx, req.(*ListSecretsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SecretsManager_RenameSecret_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RenameSecretRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SecretsManagerServer).RenameSecret(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: SecretsManager_RenameSecret_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SecretsManagerServer).RenameSecret(ctx, req.(*RenameSecretRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SecretsManager_GetAllSecrets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetAllSecretsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SecretsManagerServer).GetAllSecrets(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: SecretsManager_GetAllSecrets_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SecretsManagerServer).GetAllSecrets(ctx, req.(*GetAllSecretsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// SecretsManager_ServiceDesc is the grpc.ServiceDesc for SecretsManager service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var SecretsManager_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "secretsmanagerplugin.SecretsManager",
|
||||
HandlerType: (*SecretsManagerServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "GetSecret",
|
||||
Handler: _SecretsManager_GetSecret_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SetSecret",
|
||||
Handler: _SecretsManager_SetSecret_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteSecret",
|
||||
Handler: _SecretsManager_DeleteSecret_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListSecrets",
|
||||
Handler: _SecretsManager_ListSecrets_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "RenameSecret",
|
||||
Handler: _SecretsManager_RenameSecret_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetAllSecrets",
|
||||
Handler: _SecretsManager_GetAllSecrets_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "secretsmanager.proto",
|
||||
}
|
||||
@@ -98,11 +98,6 @@ type BackendFactoryProvider interface {
|
||||
BackendFactory(ctx context.Context, p *Plugin) backendplugin.PluginFactoryFunc
|
||||
}
|
||||
|
||||
type SecretsPluginManager interface {
|
||||
// SecretsManager returns a secretsmanager plugin
|
||||
SecretsManager(ctx context.Context) *Plugin
|
||||
}
|
||||
|
||||
type StaticRouteResolver interface {
|
||||
Routes(ctx context.Context) []*StaticRoute
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
Links: []plugins.InfoLink{
|
||||
{Name: "Raise issue", URL: "https://github.com/grafana/grafana/issues/new"},
|
||||
{Name: "Documentation", URL: "https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/"},
|
||||
},
|
||||
},
|
||||
Includes: []*plugins.Includes{
|
||||
|
||||
@@ -64,30 +64,6 @@ func TestInitializer_Initialize(t *testing.T) {
|
||||
require.NotNil(t, c)
|
||||
})
|
||||
|
||||
t.Run("secretsmanager", func(t *testing.T) {
|
||||
p := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test",
|
||||
Type: plugins.TypeSecretsManager,
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaVersion: ">=8.x",
|
||||
},
|
||||
Backend: true,
|
||||
},
|
||||
Class: plugins.ClassExternal,
|
||||
}
|
||||
|
||||
stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest())
|
||||
|
||||
var err error
|
||||
p, err = stepFunc(context.Background(), p)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, exists := p.Client()
|
||||
require.True(t, exists)
|
||||
require.NotNil(t, c)
|
||||
})
|
||||
|
||||
t.Run("non backend plugin app", func(t *testing.T) {
|
||||
p := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
|
||||
+9
-21
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/auth"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/log"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@@ -58,10 +57,9 @@ type Plugin struct {
|
||||
|
||||
ExternalService *auth.ExternalService
|
||||
|
||||
Renderer pluginextensionv2.RendererPlugin
|
||||
SecretsManager secretsmanagerplugin.SecretsManagerPlugin
|
||||
client backendplugin.Plugin
|
||||
log log.Logger
|
||||
Renderer pluginextensionv2.RendererPlugin
|
||||
client backendplugin.Plugin
|
||||
log log.Logger
|
||||
|
||||
SkipHostEnvVars bool
|
||||
|
||||
@@ -126,7 +124,7 @@ type JSONData struct {
|
||||
SDK bool `json:"sdk,omitempty"`
|
||||
MultiValueFilterOperators bool `json:"multiValueFilterOperators,omitempty"`
|
||||
|
||||
// Backend (Datasource + Renderer + SecretsManager)
|
||||
// Backend (Datasource + Renderer)
|
||||
Executable string `json:"executable,omitempty"`
|
||||
|
||||
// App Service Auth Registration
|
||||
@@ -442,10 +440,6 @@ func (p *Plugin) ExecutablePath() string {
|
||||
return p.executablePath("plugin_start")
|
||||
}
|
||||
|
||||
if p.IsSecretsManager() {
|
||||
return p.executablePath("secrets_plugin_start")
|
||||
}
|
||||
|
||||
return p.executablePath(p.Executable)
|
||||
}
|
||||
|
||||
@@ -486,10 +480,6 @@ func (p *Plugin) IsRenderer() bool {
|
||||
return p.Type == TypeRenderer
|
||||
}
|
||||
|
||||
func (p *Plugin) IsSecretsManager() bool {
|
||||
return p.Type == TypeSecretsManager
|
||||
}
|
||||
|
||||
func (p *Plugin) IsApp() bool {
|
||||
return p.Type == TypeApp
|
||||
}
|
||||
@@ -519,22 +509,20 @@ var PluginTypes = []Type{
|
||||
TypePanel,
|
||||
TypeApp,
|
||||
TypeRenderer,
|
||||
TypeSecretsManager,
|
||||
}
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
TypeDataSource Type = "datasource"
|
||||
TypePanel Type = "panel"
|
||||
TypeApp Type = "app"
|
||||
TypeRenderer Type = "renderer"
|
||||
TypeSecretsManager Type = "secretsmanager"
|
||||
TypeDataSource Type = "datasource"
|
||||
TypePanel Type = "panel"
|
||||
TypeApp Type = "app"
|
||||
TypeRenderer Type = "renderer"
|
||||
)
|
||||
|
||||
func (pt Type) IsValid() bool {
|
||||
switch pt {
|
||||
case TypeDataSource, TypePanel, TypeApp, TypeRenderer, TypeSecretsManager:
|
||||
case TypeDataSource, TypePanel, TypeApp, TypeRenderer:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -2,7 +2,6 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
@@ -13,9 +12,11 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/query/clientapi"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
type pluginClient struct {
|
||||
pluginClient plugins.Client
|
||||
pCtxProvider *plugincontext.Provider
|
||||
ac accesscontrol.AccessControl
|
||||
}
|
||||
|
||||
type pluginRegistry struct {
|
||||
@@ -43,10 +45,11 @@ var _ clientapi.QueryDataClient = (*pluginClient)(nil)
|
||||
var _ query.DataSourceApiServerRegistry = (*pluginRegistry)(nil)
|
||||
|
||||
// NewQueryClientForPluginClient creates a client that delegates to the internal plugins.Client stack
|
||||
func NewQueryClientForPluginClient(p plugins.Client, ctx *plugincontext.Provider) clientapi.QueryDataClient {
|
||||
func NewQueryClientForPluginClient(p plugins.Client, ctx *plugincontext.Provider, accessControl accesscontrol.AccessControl) clientapi.QueryDataClient {
|
||||
return &pluginClient{
|
||||
pluginClient: p,
|
||||
pCtxProvider: ctx,
|
||||
ac: accessControl,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +62,17 @@ func NewDataSourceRegistryFromStore(pluginStore pluginstore.Store,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *pluginClient) CanQueryDataSource(ctx context.Context, uid string) (bool, error) {
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
requiredScope := "datasources:uid:" + uid
|
||||
evaluate := accesscontrol.EvalPermission(datasources.ActionQuery, requiredScope)
|
||||
return d.ac.Evaluate(ctx, user, evaluate)
|
||||
}
|
||||
|
||||
// ExecuteQueryData implements QueryHelper.
|
||||
func (d *pluginClient) QueryData(ctx context.Context, req data.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
queries, dsRef, err := data.ToDataSourceQueries(req)
|
||||
@@ -69,17 +83,23 @@ func (d *pluginClient) QueryData(ctx context.Context, req data.QueryDataRequest)
|
||||
return nil, fmt.Errorf("expected single datasource request")
|
||||
}
|
||||
|
||||
canQuery, err := d.CanQueryDataSource(ctx, dsRef.UID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !canQuery {
|
||||
status := metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusForbidden,
|
||||
Message: "Access denied to the data source",
|
||||
}
|
||||
return nil, &apierrors.StatusError{ErrStatus: status}
|
||||
}
|
||||
|
||||
// NOTE: this depends on uid unique across datasources
|
||||
settings, err := d.pCtxProvider.GetDataSourceInstanceSettings(ctx, dsRef.UID)
|
||||
if err != nil {
|
||||
if errors.Is(err, datasources.ErrDataSourceNotFound) {
|
||||
status := metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotFound,
|
||||
Message: "datasource not found",
|
||||
}
|
||||
return nil, &apierrors.StatusError{ErrStatus: status}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ func RegisterAPIService(features featuremgmt.FeatureToggles,
|
||||
builder, err := NewQueryAPIBuilder(
|
||||
features,
|
||||
&CommonDataSourceClientSupplier{
|
||||
Client: client.NewQueryClientForPluginClient(pluginClient, pCtxProvider),
|
||||
Client: client.NewQueryClientForPluginClient(pluginClient, pCtxProvider, accessControl),
|
||||
},
|
||||
ar,
|
||||
client.NewDataSourceRegistryFromStore(pluginStore, dataSourcesService),
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package secret
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
)
|
||||
|
||||
const (
|
||||
// SecureValues
|
||||
ActionSecretSecureValuesCreate = "secret.securevalues:create" // CREATE.
|
||||
ActionSecretSecureValuesWrite = "secret.securevalues:write" // UPDATE.
|
||||
ActionSecretSecureValuesRead = "secret.securevalues:read" // GET + LIST.
|
||||
ActionSecretSecureValuesDelete = "secret.securevalues:delete" // DELETE.
|
||||
|
||||
// Keepers
|
||||
ActionSecretKeepersCreate = "secret.keepers:create" // CREATE.
|
||||
ActionSecretKeepersWrite = "secret.keepers:write" // UPDATE.
|
||||
ActionSecretKeepersRead = "secret.keepers:read" // GET + LIST.
|
||||
ActionSecretKeepersDelete = "secret.keepers:delete" // DELETE.
|
||||
)
|
||||
|
||||
var (
|
||||
ScopeProviderSecretSecureValues = accesscontrol.NewScopeProvider("secret.securevalues")
|
||||
ScopeProviderSecretKeepers = accesscontrol.NewScopeProvider("secret.keepers")
|
||||
|
||||
ScopeAllSecureValues = ScopeProviderSecretSecureValues.GetResourceAllScope()
|
||||
ScopeAllKeepers = ScopeProviderSecretKeepers.GetResourceAllScope()
|
||||
)
|
||||
|
||||
func RegisterAccessControlRoles(service accesscontrol.Service) error {
|
||||
// SecureValues
|
||||
secureValuesReader := accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: "fixed:secret.securevalues:reader",
|
||||
DisplayName: "Secrets Manager secure values reader",
|
||||
Description: "Read and list secure values.",
|
||||
Group: "Secrets Manager",
|
||||
Permissions: []accesscontrol.Permission{
|
||||
{
|
||||
Action: ActionSecretSecureValuesRead,
|
||||
Scope: ScopeAllSecureValues,
|
||||
},
|
||||
},
|
||||
},
|
||||
Grants: []string{string(org.RoleAdmin)},
|
||||
}
|
||||
|
||||
secureValuesWriter := accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: "fixed:secret.securevalues:writer",
|
||||
DisplayName: "Secrets Manager secure values writer",
|
||||
Description: "Create, update and delete secure values.",
|
||||
Group: "Secrets Manager",
|
||||
Permissions: []accesscontrol.Permission{
|
||||
{
|
||||
Action: ActionSecretSecureValuesCreate,
|
||||
Scope: ScopeAllSecureValues,
|
||||
},
|
||||
{
|
||||
Action: ActionSecretSecureValuesRead,
|
||||
Scope: ScopeAllSecureValues,
|
||||
},
|
||||
{
|
||||
Action: ActionSecretSecureValuesWrite,
|
||||
Scope: ScopeAllSecureValues,
|
||||
},
|
||||
{
|
||||
Action: ActionSecretSecureValuesDelete,
|
||||
Scope: ScopeAllSecureValues,
|
||||
},
|
||||
},
|
||||
},
|
||||
Grants: []string{string(org.RoleAdmin)},
|
||||
}
|
||||
|
||||
// Keepers
|
||||
keepersReader := accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: "fixed:secret.keepers:reader",
|
||||
DisplayName: "Secrets Manager keepers reader",
|
||||
Description: "Read and list keepers.",
|
||||
Group: "Secrets Manager",
|
||||
Permissions: []accesscontrol.Permission{
|
||||
{
|
||||
Action: ActionSecretKeepersRead,
|
||||
Scope: ScopeAllKeepers,
|
||||
},
|
||||
},
|
||||
},
|
||||
Grants: []string{string(org.RoleAdmin)},
|
||||
}
|
||||
|
||||
keepersWriter := accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: "fixed:secret.keepers:writer",
|
||||
DisplayName: "Secrets Manager keepers writer",
|
||||
Description: "Create, update and delete keepers.",
|
||||
Group: "Secrets Manager",
|
||||
Permissions: []accesscontrol.Permission{
|
||||
{
|
||||
Action: ActionSecretKeepersCreate,
|
||||
Scope: ScopeAllKeepers,
|
||||
},
|
||||
{
|
||||
Action: ActionSecretKeepersRead,
|
||||
Scope: ScopeAllKeepers,
|
||||
},
|
||||
{
|
||||
Action: ActionSecretKeepersWrite,
|
||||
Scope: ScopeAllKeepers,
|
||||
},
|
||||
{
|
||||
Action: ActionSecretKeepersDelete,
|
||||
Scope: ScopeAllKeepers,
|
||||
},
|
||||
},
|
||||
},
|
||||
Grants: []string{string(org.RoleAdmin)},
|
||||
}
|
||||
|
||||
return service.DeclareFixedRoles(
|
||||
secureValuesReader, secureValuesWriter,
|
||||
keepersReader, keepersWriter,
|
||||
)
|
||||
}
|
||||
@@ -2,12 +2,18 @@ package contracts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeeperNotFound = errors.New("keeper not found")
|
||||
)
|
||||
|
||||
// KeeperMetadataStorage is the interface for wiring and dependency injection.
|
||||
type KeeperMetadataStorage interface {
|
||||
Create(ctx context.Context, keeper *secretv0alpha1.Keeper) (*secretv0alpha1.Keeper, error)
|
||||
Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.Keeper, error)
|
||||
|
||||
@@ -2,12 +2,18 @@ package contracts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSecureValueNotFound = errors.New("secure value not found")
|
||||
)
|
||||
|
||||
// SecureValueMetadataStorage is the interface for wiring and dependency injection.
|
||||
type SecureValueMetadataStorage interface {
|
||||
Create(ctx context.Context, sv *secretv0alpha1.SecureValue) (*secretv0alpha1.SecureValue, error)
|
||||
Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.SecureValue, error)
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/reststorage"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
authsvc "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -68,6 +69,10 @@ func RegisterAPIService(
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := RegisterAccessControlRoles(accessControlService); err != nil {
|
||||
return nil, fmt.Errorf("register secret access control roles: %w", err)
|
||||
}
|
||||
|
||||
builder := NewSecretAPIBuilder(
|
||||
tracer,
|
||||
secureValueMetadataStorage,
|
||||
@@ -148,7 +153,7 @@ func (b *SecretAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions
|
||||
// For Secrets, this is not the case, but if we want to make it so, we need to update this ResourceAuthorizer to check the containing folder.
|
||||
// If we ever want to do that, get guidance from IAM first as well.
|
||||
func (b *SecretAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
||||
return nil
|
||||
return authsvc.NewResourceAuthorizer(b.accessClient)
|
||||
}
|
||||
|
||||
// Register additional routes with the server.
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
package reststorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
)
|
||||
|
||||
var (
|
||||
_ rest.Scoper = (*KeeperRest)(nil)
|
||||
_ rest.SingularNameProvider = (*KeeperRest)(nil)
|
||||
_ rest.Getter = (*KeeperRest)(nil)
|
||||
_ rest.Lister = (*KeeperRest)(nil)
|
||||
_ rest.Storage = (*KeeperRest)(nil)
|
||||
_ rest.Creater = (*KeeperRest)(nil)
|
||||
_ rest.Updater = (*KeeperRest)(nil)
|
||||
_ rest.GracefulDeleter = (*KeeperRest)(nil)
|
||||
)
|
||||
|
||||
// KeeperRest is an ddimplementation of CRUDL operations on a `keeper` backed by TODO.
|
||||
type KeeperRest struct {
|
||||
storage contracts.KeeperMetadataStorage
|
||||
resource utils.ResourceInfo
|
||||
tableConverter rest.TableConvertor
|
||||
}
|
||||
|
||||
// NewKeeperRest is a returns a constructed `*KeeperRest`.
|
||||
func NewKeeperRest(storage contracts.KeeperMetadataStorage, resource utils.ResourceInfo) *KeeperRest {
|
||||
return &KeeperRest{storage, resource, resource.TableConverter()}
|
||||
}
|
||||
|
||||
// New returns an empty `*Keeper` that is used by the `Create` method.
|
||||
func (s *KeeperRest) New() runtime.Object {
|
||||
return s.resource.NewFunc()
|
||||
}
|
||||
|
||||
// Destroy is called when? [TODO]
|
||||
func (s *KeeperRest) Destroy() {}
|
||||
|
||||
// NamespaceScoped returns `true` because the storage is namespaced (== org).
|
||||
func (s *KeeperRest) NamespaceScoped() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetSingularName is used by `kubectl` discovery to have singular name representation of resources.
|
||||
func (s *KeeperRest) GetSingularName() string {
|
||||
return s.resource.GetSingularName()
|
||||
}
|
||||
|
||||
// NewList returns an empty `*KeeperList` that is used by the `List` method.
|
||||
func (s *KeeperRest) NewList() runtime.Object {
|
||||
return s.resource.NewListFunc()
|
||||
}
|
||||
|
||||
// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`.
|
||||
func (s *KeeperRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
// List calls the inner `store` (persistence) and returns a list of `Keepers` within a `namespace` filtered by the `options`.
|
||||
func (s *KeeperRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
_, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
return &secretv0alpha1.KeeperList{Items: make([]secretv0alpha1.Keeper, 0)}, nil
|
||||
}
|
||||
|
||||
// Get calls the inner `store` (persistence) and returns a `Keeper` by `name`.
|
||||
func (s *KeeperRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
_, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
return nil, s.resource.NewNotFound(name)
|
||||
}
|
||||
|
||||
// Create a new `Keeper`. Does some validation and allows empty `name` (generated).
|
||||
func (s *KeeperRest) Create(
|
||||
ctx context.Context,
|
||||
obj runtime.Object,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
options *metav1.CreateOptions,
|
||||
) (runtime.Object, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Update a `Keeper`'s `value`. The second return parameter indicates whether the resource was newly created.
|
||||
func (s *KeeperRest) Update(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
objInfo rest.UpdatedObjectInfo,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
updateValidation rest.ValidateObjectUpdateFunc,
|
||||
forceAllowCreate bool,
|
||||
options *metav1.UpdateOptions,
|
||||
) (runtime.Object, bool, error) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Delete calls the inner `store` (persistence) in order to delete the `Keeper`.
|
||||
// The second return parameter `bool` indicates whether the delete was intant or not. It always is for `Keepers`.
|
||||
func (s *KeeperRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
_, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
// ValidateKeeper does basic spec validation of a keeper.
|
||||
func ValidateKeeper(keeper *secretv0alpha1.Keeper, operation admission.Operation) field.ErrorList {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
package reststorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
)
|
||||
|
||||
var (
|
||||
_ rest.Scoper = (*KeeperRest)(nil)
|
||||
_ rest.SingularNameProvider = (*KeeperRest)(nil)
|
||||
_ rest.Getter = (*KeeperRest)(nil)
|
||||
_ rest.Lister = (*KeeperRest)(nil)
|
||||
_ rest.Storage = (*KeeperRest)(nil)
|
||||
_ rest.Creater = (*KeeperRest)(nil)
|
||||
_ rest.Updater = (*KeeperRest)(nil)
|
||||
_ rest.GracefulDeleter = (*KeeperRest)(nil)
|
||||
)
|
||||
|
||||
// KeeperRest is an implementation of CRUDL operations on a `keeper` backed by TODO.
|
||||
type KeeperRest struct {
|
||||
storage contracts.KeeperMetadataStorage
|
||||
resource utils.ResourceInfo
|
||||
tableConverter rest.TableConvertor
|
||||
}
|
||||
|
||||
// NewKeeperRest is a returns a constructed `*KeeperRest`.
|
||||
func NewKeeperRest(storage contracts.KeeperMetadataStorage, resource utils.ResourceInfo) *KeeperRest {
|
||||
return &KeeperRest{storage, resource, resource.TableConverter()}
|
||||
}
|
||||
|
||||
// New returns an empty `*Keeper` that is used by the `Create` method.
|
||||
func (s *KeeperRest) New() runtime.Object {
|
||||
return s.resource.NewFunc()
|
||||
}
|
||||
|
||||
// Destroy is called when? [TODO]
|
||||
func (s *KeeperRest) Destroy() {}
|
||||
|
||||
// NamespaceScoped returns `true` because the storage is namespaced (== org).
|
||||
func (s *KeeperRest) NamespaceScoped() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetSingularName is used by `kubectl` discovery to have singular name representation of resources.
|
||||
func (s *KeeperRest) GetSingularName() string {
|
||||
return s.resource.GetSingularName()
|
||||
}
|
||||
|
||||
// NewList returns an empty `*KeeperList` that is used by the `List` method.
|
||||
func (s *KeeperRest) NewList() runtime.Object {
|
||||
return s.resource.NewListFunc()
|
||||
}
|
||||
|
||||
// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`.
|
||||
func (s *KeeperRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
// List calls the inner `store` (persistence) and returns a list of `Keepers` within a `namespace` filtered by the `options`.
|
||||
func (s *KeeperRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
namespace, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
keepersList, err := s.storage.List(ctx, xkube.Namespace(namespace), options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list keepers: %w", err)
|
||||
}
|
||||
|
||||
return keepersList, nil
|
||||
}
|
||||
|
||||
// Get calls the inner `store` (persistence) and returns a `Keeper` by `name`.
|
||||
func (s *KeeperRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
namespace, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
kp, err := s.storage.Read(ctx, xkube.Namespace(namespace), name)
|
||||
if err != nil {
|
||||
if errors.Is(err, contracts.ErrKeeperNotFound) {
|
||||
return nil, s.resource.NewNotFound(name)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read keeper: %w", err)
|
||||
}
|
||||
|
||||
return kp, nil
|
||||
}
|
||||
|
||||
// Create a new `Keeper`. Does some validation and allows empty `name` (generated).
|
||||
func (s *KeeperRest) Create(
|
||||
ctx context.Context,
|
||||
obj runtime.Object,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
options *metav1.CreateOptions,
|
||||
) (runtime.Object, error) {
|
||||
kp, ok := obj.(*secretv0alpha1.Keeper)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected Keeper for create")
|
||||
}
|
||||
|
||||
if err := createValidation(ctx, obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createdKeeper, err := s.storage.Create(ctx, kp)
|
||||
if err != nil {
|
||||
var kErr xkube.ErrorLister
|
||||
if errors.As(err, &kErr) {
|
||||
return nil, apierrors.NewInvalid(kp.GroupVersionKind().GroupKind(), kp.Name, kErr.ErrorList())
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to create keeper: %w", err)
|
||||
}
|
||||
|
||||
return createdKeeper, nil
|
||||
}
|
||||
|
||||
// Update a `Keeper`'s `value`. The second return parameter indicates whether the resource was newly created.
|
||||
func (s *KeeperRest) Update(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
objInfo rest.UpdatedObjectInfo,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
updateValidation rest.ValidateObjectUpdateFunc,
|
||||
forceAllowCreate bool,
|
||||
options *metav1.UpdateOptions,
|
||||
) (runtime.Object, bool, error) {
|
||||
oldObj, err := s.Get(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Makes sure the UID and ResourceVersion are OK.
|
||||
// TODO: this also makes it so the labels and annotations are additive, unless we check and remove manually.
|
||||
newObj, err := objInfo.UpdatedObject(ctx, oldObj)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("k8s updated object: %w", err)
|
||||
}
|
||||
|
||||
// The current supported behavior for `Update` is to replace the entire `spec` with the new one.
|
||||
// Each provider-specific setting of a keeper lives at the top-level, so it makes it possible to change a provider
|
||||
// during an update. Otherwise both old and new providers would be merged in the `newObj` which is not allowed.
|
||||
if err := updateValidation(ctx, newObj, oldObj); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
newKeeper, ok := newObj.(*secretv0alpha1.Keeper)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("expected Keeper for update")
|
||||
}
|
||||
|
||||
// TODO: do we need to do this here again? Probably not, but double-check!
|
||||
newKeeper.Annotations = xkube.CleanAnnotations(newKeeper.Annotations)
|
||||
|
||||
// Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that?
|
||||
updatedKeeper, err := s.storage.Update(ctx, newKeeper)
|
||||
if err != nil {
|
||||
var kErr xkube.ErrorLister
|
||||
if errors.As(err, &kErr) {
|
||||
return nil, false, apierrors.NewInvalid(newKeeper.GroupVersionKind().GroupKind(), newKeeper.Name, kErr.ErrorList())
|
||||
}
|
||||
|
||||
return nil, false, fmt.Errorf("failed to update keeper: %w", err)
|
||||
}
|
||||
|
||||
return updatedKeeper, false, nil
|
||||
}
|
||||
|
||||
// Delete calls the inner `store` (persistence) in order to delete the `Keeper`.
|
||||
// The second return parameter `bool` indicates whether the delete was intant or not. It always is for `Keepers`.
|
||||
func (s *KeeperRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
namespace, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
if err := s.storage.Delete(ctx, xkube.Namespace(namespace), name); err != nil {
|
||||
return nil, false, fmt.Errorf("failed to delete keeper: %w", err)
|
||||
}
|
||||
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
// ValidateKeeper does basic spec validation of a keeper.
|
||||
func ValidateKeeper(keeper *secretv0alpha1.Keeper, operation admission.Operation) field.ErrorList {
|
||||
// Only validate Create and Update for now.
|
||||
if operation != admission.Create && operation != admission.Update {
|
||||
return nil
|
||||
}
|
||||
|
||||
errs := make(field.ErrorList, 0)
|
||||
|
||||
if keeper.Spec.Title == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "title"), "a `title` is required"))
|
||||
}
|
||||
|
||||
// Only one keeper type can be configured. Return early and don't validate the specific keeper fields.
|
||||
if err := validateKeepers(keeper); err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// TODO: Improve SQL keeper validation.
|
||||
// SQL keeper is not allowed to use `secureValueName` in credentials fields to avoid depending on another keeper.
|
||||
if keeper.IsSqlKeeper() {
|
||||
if keeper.Spec.SQL.Encryption.AWS != nil {
|
||||
if keeper.Spec.SQL.Encryption.AWS.AccessKeyID.SecureValueName != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec", "aws", "accessKeyId"), "secureValueName cannot be used with SQL keeper"))
|
||||
}
|
||||
|
||||
if keeper.Spec.SQL.Encryption.AWS.SecretAccessKey.SecureValueName != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec", "aws", "secretAccessKey"), "secureValueName cannot be used with SQL keeper"))
|
||||
}
|
||||
}
|
||||
|
||||
if keeper.Spec.SQL.Encryption.Azure != nil && keeper.Spec.SQL.Encryption.Azure.ClientSecret.SecureValueName != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec", "azure", "clientSecret"), "secureValueName cannot be used with SQL keeper"))
|
||||
}
|
||||
|
||||
if keeper.Spec.SQL.Encryption.HashiCorp != nil && keeper.Spec.SQL.Encryption.HashiCorp.Token.SecureValueName != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec", "hashicorp", "token"), "secureValueName cannot be used with SQL keeper"))
|
||||
}
|
||||
}
|
||||
|
||||
if keeper.Spec.AWS != nil {
|
||||
if err := validateCredentialValue(field.NewPath("spec", "aws", "accessKeyId"), keeper.Spec.AWS.AccessKeyID); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if err := validateCredentialValue(field.NewPath("spec", "aws", "secretAccessKey"), keeper.Spec.AWS.SecretAccessKey); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if keeper.Spec.Azure != nil {
|
||||
if keeper.Spec.Azure.KeyVaultName == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "azure", "keyVaultName"), "a `keyVaultName` is required"))
|
||||
}
|
||||
|
||||
if keeper.Spec.Azure.TenantID == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "azure", "tenantId"), "a `tenantId` is required"))
|
||||
}
|
||||
|
||||
if keeper.Spec.Azure.ClientID == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "azure", "clientId"), "a `clientId` is required"))
|
||||
}
|
||||
|
||||
if err := validateCredentialValue(field.NewPath("spec", "azure", "clientSecret"), keeper.Spec.Azure.ClientSecret); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if keeper.Spec.GCP != nil {
|
||||
if keeper.Spec.GCP.ProjectID == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "gcp", "projectId"), "a `projectId` is required"))
|
||||
}
|
||||
|
||||
if keeper.Spec.GCP.CredentialsFile == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "gcp", "credentialsFile"), "a `credentialsFile` is required"))
|
||||
}
|
||||
}
|
||||
|
||||
if keeper.Spec.HashiCorp != nil {
|
||||
if keeper.Spec.HashiCorp.Address == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "hashicorp", "address"), "a `address` is required"))
|
||||
}
|
||||
|
||||
if err := validateCredentialValue(field.NewPath("spec", "hashicorp", "token"), keeper.Spec.HashiCorp.Token); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func validateKeepers(keeper *secretv0alpha1.Keeper) *field.Error {
|
||||
availableKeepers := map[string]bool{
|
||||
"sql": keeper.Spec.SQL != nil,
|
||||
"aws": keeper.Spec.AWS != nil,
|
||||
"azure": keeper.Spec.Azure != nil,
|
||||
"gcp": keeper.Spec.GCP != nil,
|
||||
"hashicorp": keeper.Spec.HashiCorp != nil,
|
||||
}
|
||||
|
||||
configuredKeepers := make([]string, 0)
|
||||
|
||||
for keeperKind, notNil := range availableKeepers {
|
||||
if notNil {
|
||||
configuredKeepers = append(configuredKeepers, keeperKind)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configuredKeepers) == 0 {
|
||||
return field.Required(field.NewPath("spec"), "at least one `keeper` must be present")
|
||||
}
|
||||
|
||||
if len(configuredKeepers) > 1 {
|
||||
return field.Invalid(
|
||||
field.NewPath("spec"),
|
||||
strings.Join(configuredKeepers, " & "),
|
||||
"only one `keeper` can be present at a time but found more",
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCredentialValue(path *field.Path, credentials secretv0alpha1.CredentialValue) *field.Error {
|
||||
availableOptions := map[string]bool{
|
||||
"secureValueName": credentials.SecureValueName != "",
|
||||
"valueFromEnv": credentials.ValueFromEnv != "",
|
||||
"valueFromConfig": credentials.ValueFromConfig != "",
|
||||
}
|
||||
|
||||
configuredCredentials := make([]string, 0)
|
||||
|
||||
for credentialKind, notEmpty := range availableOptions {
|
||||
if notEmpty {
|
||||
configuredCredentials = append(configuredCredentials, credentialKind)
|
||||
}
|
||||
}
|
||||
|
||||
if len(configuredCredentials) == 0 {
|
||||
return field.Required(path, "one of `secureValueName`, `valueFromEnv` or `valueFromConfig` must be present")
|
||||
}
|
||||
|
||||
if len(configuredCredentials) > 1 {
|
||||
return field.Invalid(
|
||||
path,
|
||||
strings.Join(configuredCredentials, " & "),
|
||||
"only one of `secureValueName`, `valueFromEnv` or `valueFromConfig` must be present at a time but found more",
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
package reststorage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
func TestValidateKeeper(t *testing.T) {
|
||||
t.Run("when creating a new keeper", func(t *testing.T) {
|
||||
t.Run("the `title` must be present", func(t *testing.T) {
|
||||
keeper := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
SQL: &secretv0alpha1.SQLKeeperConfig{},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.title", errs[0].Field)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("only one `keeper` must be present", func(t *testing.T) {
|
||||
keeper := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
SQL: &secretv0alpha1.SQLKeeperConfig{},
|
||||
AWS: &secretv0alpha1.AWSKeeperConfig{},
|
||||
Azure: &secretv0alpha1.AzureKeeperConfig{},
|
||||
GCP: &secretv0alpha1.GCPKeeperConfig{},
|
||||
HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("at least one `keeper` must be present", func(t *testing.T) {
|
||||
keeper := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("aws keeper validation", func(t *testing.T) {
|
||||
validKeeperAWS := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
AWS: &secretv0alpha1.AWSKeeperConfig{
|
||||
AWSCredentials: secretv0alpha1.AWSCredentials{
|
||||
AccessKeyID: secretv0alpha1.CredentialValue{
|
||||
ValueFromEnv: "some-value",
|
||||
},
|
||||
SecretAccessKey: secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "some-value",
|
||||
},
|
||||
KMSKeyID: "optional",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("`accessKeyId` must be present", func(t *testing.T) {
|
||||
t.Run("at least one of the credential value must be present", func(t *testing.T) {
|
||||
keeper := validKeeperAWS.DeepCopy()
|
||||
keeper.Spec.AWS.AccessKeyID = secretv0alpha1.CredentialValue{}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.aws.accessKeyId", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("at most one of the credential value must be present", func(t *testing.T) {
|
||||
keeper := validKeeperAWS.DeepCopy()
|
||||
keeper.Spec.AWS.AccessKeyID = secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "a",
|
||||
ValueFromEnv: "b",
|
||||
ValueFromConfig: "c",
|
||||
}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.aws.accessKeyId", errs[0].Field)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("`secretAccessKey` must be present", func(t *testing.T) {
|
||||
t.Run("at least one of the credential value must be present", func(t *testing.T) {
|
||||
keeper := validKeeperAWS.DeepCopy()
|
||||
keeper.Spec.AWS.SecretAccessKey = secretv0alpha1.CredentialValue{}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("at most one of the credential value must be present", func(t *testing.T) {
|
||||
keeper := validKeeperAWS.DeepCopy()
|
||||
keeper.Spec.AWS.SecretAccessKey = secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "a",
|
||||
ValueFromEnv: "b",
|
||||
ValueFromConfig: "c",
|
||||
}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.aws.secretAccessKey", errs[0].Field)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("azure keeper validation", func(t *testing.T) {
|
||||
validKeeperAzure := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
Azure: &secretv0alpha1.AzureKeeperConfig{
|
||||
AzureCredentials: secretv0alpha1.AzureCredentials{
|
||||
KeyVaultName: "kv-name",
|
||||
TenantID: "tenant-id",
|
||||
ClientID: "client-id",
|
||||
ClientSecret: secretv0alpha1.CredentialValue{
|
||||
ValueFromConfig: "config.path.value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("`keyVaultName` must be present", func(t *testing.T) {
|
||||
keeper := validKeeperAzure.DeepCopy()
|
||||
keeper.Spec.Azure.KeyVaultName = ""
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.azure.keyVaultName", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("`tenantId` must be present", func(t *testing.T) {
|
||||
keeper := validKeeperAzure.DeepCopy()
|
||||
keeper.Spec.Azure.TenantID = ""
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.azure.tenantId", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("`clientId` must be present", func(t *testing.T) {
|
||||
keeper := validKeeperAzure.DeepCopy()
|
||||
keeper.Spec.Azure.ClientID = ""
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.azure.clientId", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("`clientSecret` must be present", func(t *testing.T) {
|
||||
t.Run("at least one of the credential value must be present", func(t *testing.T) {
|
||||
keeper := validKeeperAzure.DeepCopy()
|
||||
keeper.Spec.Azure.ClientSecret = secretv0alpha1.CredentialValue{}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.azure.clientSecret", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("at most one of the credential value must be present", func(t *testing.T) {
|
||||
keeper := validKeeperAzure.DeepCopy()
|
||||
keeper.Spec.Azure.ClientSecret = secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "a",
|
||||
ValueFromEnv: "b",
|
||||
ValueFromConfig: "c",
|
||||
}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.azure.clientSecret", errs[0].Field)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("gcp keeper validation", func(t *testing.T) {
|
||||
validKeeperGCP := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
GCP: &secretv0alpha1.GCPKeeperConfig{
|
||||
GCPCredentials: secretv0alpha1.GCPCredentials{
|
||||
ProjectID: "project-id",
|
||||
CredentialsFile: "/path/to/credentials/file.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("`projectId` must be present", func(t *testing.T) {
|
||||
keeper := validKeeperGCP.DeepCopy()
|
||||
keeper.Spec.GCP.ProjectID = ""
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.gcp.projectId", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("`credentialsFile` must be present", func(t *testing.T) {
|
||||
keeper := validKeeperGCP.DeepCopy()
|
||||
keeper.Spec.GCP.CredentialsFile = ""
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.gcp.credentialsFile", errs[0].Field)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("hashicorp keeper validation", func(t *testing.T) {
|
||||
validKeeperHashiCorp := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
HashiCorp: &secretv0alpha1.HashiCorpKeeperConfig{
|
||||
HashiCorpCredentials: secretv0alpha1.HashiCorpCredentials{
|
||||
Address: "http://address",
|
||||
Token: secretv0alpha1.CredentialValue{
|
||||
ValueFromConfig: "config.path.value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("`address` must be present", func(t *testing.T) {
|
||||
keeper := validKeeperHashiCorp.DeepCopy()
|
||||
keeper.Spec.HashiCorp.Address = ""
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.hashicorp.address", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("`token` must be present", func(t *testing.T) {
|
||||
t.Run("at least one of the credential value must be present", func(t *testing.T) {
|
||||
keeper := validKeeperHashiCorp.DeepCopy()
|
||||
keeper.Spec.HashiCorp.Token = secretv0alpha1.CredentialValue{}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.hashicorp.token", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("at most one of the credential value must be present", func(t *testing.T) {
|
||||
keeper := validKeeperHashiCorp.DeepCopy()
|
||||
keeper.Spec.HashiCorp.Token = secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "a",
|
||||
ValueFromEnv: "b",
|
||||
ValueFromConfig: "c",
|
||||
}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.hashicorp.token", errs[0].Field)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("sql keeper validation", func(t *testing.T) {
|
||||
t.Run("does not allow usage of `secureValueName` in credentials", func(t *testing.T) {
|
||||
providers := []struct {
|
||||
name string
|
||||
enc secretv0alpha1.Encryption
|
||||
expectedErrors int
|
||||
}{
|
||||
{
|
||||
name: "aws",
|
||||
enc: secretv0alpha1.Encryption{
|
||||
AWS: &secretv0alpha1.AWSCredentials{
|
||||
AccessKeyID: secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "not-empty",
|
||||
},
|
||||
SecretAccessKey: secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "not-empty",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 2,
|
||||
},
|
||||
{
|
||||
name: "azure",
|
||||
enc: secretv0alpha1.Encryption{
|
||||
Azure: &secretv0alpha1.AzureCredentials{
|
||||
ClientSecret: secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "not-empty",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 1,
|
||||
},
|
||||
{
|
||||
name: "hashicorp",
|
||||
enc: secretv0alpha1.Encryption{
|
||||
HashiCorp: &secretv0alpha1.HashiCorpCredentials{
|
||||
Token: secretv0alpha1.CredentialValue{
|
||||
SecureValueName: "not-empty",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range providers {
|
||||
t.Run("when using credentials for "+tc.name, func(t *testing.T) {
|
||||
keeper := &secretv0alpha1.Keeper{
|
||||
Spec: secretv0alpha1.KeeperSpec{
|
||||
Title: "title",
|
||||
SQL: &secretv0alpha1.SQLKeeperConfig{Encryption: &tc.enc},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateKeeper(keeper, admission.Create)
|
||||
require.Len(t, errs, tc.expectedErrors)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
package reststorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
)
|
||||
|
||||
var (
|
||||
_ rest.Scoper = (*SecureValueRest)(nil)
|
||||
_ rest.SingularNameProvider = (*SecureValueRest)(nil)
|
||||
_ rest.Getter = (*SecureValueRest)(nil)
|
||||
_ rest.Lister = (*SecureValueRest)(nil)
|
||||
_ rest.Storage = (*SecureValueRest)(nil)
|
||||
_ rest.Creater = (*SecureValueRest)(nil)
|
||||
_ rest.Updater = (*SecureValueRest)(nil)
|
||||
_ rest.GracefulDeleter = (*SecureValueRest)(nil)
|
||||
)
|
||||
|
||||
// SecureValueRest is an implementation of CRUDL operations on a `securevalue` backed by a persistence layer `store`.
|
||||
type SecureValueRest struct {
|
||||
storage contracts.SecureValueMetadataStorage
|
||||
resource utils.ResourceInfo
|
||||
tableConverter rest.TableConvertor
|
||||
}
|
||||
|
||||
// NewSecureValueRest is a returns a constructed `*SecureValueRest`.
|
||||
func NewSecureValueRest(storage contracts.SecureValueMetadataStorage, resource utils.ResourceInfo) *SecureValueRest {
|
||||
return &SecureValueRest{storage, resource, resource.TableConverter()}
|
||||
}
|
||||
|
||||
// New returns an empty `*SecureValue` that is used by the `Create` method.
|
||||
func (s *SecureValueRest) New() runtime.Object {
|
||||
return s.resource.NewFunc()
|
||||
}
|
||||
|
||||
// Destroy is called when? [TODO]
|
||||
func (s *SecureValueRest) Destroy() {}
|
||||
|
||||
// NamespaceScoped returns `true` because the storage is namespaced (== org).
|
||||
func (s *SecureValueRest) NamespaceScoped() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetSingularName is used by `kubectl` discovery to have singular name representation of resources.
|
||||
func (s *SecureValueRest) GetSingularName() string {
|
||||
return s.resource.GetSingularName()
|
||||
}
|
||||
|
||||
// NewList returns an empty `*SecureValueList` that is used by the `List` method.
|
||||
func (s *SecureValueRest) NewList() runtime.Object {
|
||||
return s.resource.NewListFunc()
|
||||
}
|
||||
|
||||
// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`.
|
||||
func (s *SecureValueRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
// List calls the inner `store` (persistence) and returns a list of `securevalues` within a `namespace` filtered by the `options`.
|
||||
func (s *SecureValueRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
_, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
return &secretv0alpha1.SecureValueList{Items: make([]secretv0alpha1.SecureValue, 0)}, nil
|
||||
}
|
||||
|
||||
// Get calls the inner `store` (persistence) and returns a `securevalue` by `name`. It will NOT return the decrypted `value`.
|
||||
func (s *SecureValueRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
_, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
return nil, s.resource.NewNotFound(name)
|
||||
}
|
||||
|
||||
// Create a new `securevalue`. Does some validation and allows empty `name` (generated).
|
||||
func (s *SecureValueRest) Create(
|
||||
ctx context.Context,
|
||||
obj runtime.Object,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
options *metav1.CreateOptions,
|
||||
) (runtime.Object, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Update a `securevalue`'s `value`. The second return parameter indicates whether the resource was newly created.
|
||||
// Currently does not support "create on update" functionality. If the securevalue does not yet exist, it returns an error.
|
||||
func (s *SecureValueRest) Update(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
objInfo rest.UpdatedObjectInfo,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
updateValidation rest.ValidateObjectUpdateFunc,
|
||||
forceAllowCreate bool,
|
||||
options *metav1.UpdateOptions,
|
||||
) (runtime.Object, bool, error) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Delete calls the inner `store` (persistence) in order to delete the `securevalue`.
|
||||
// The second return parameter `bool` indicates whether the delete was instant or not. It always is for `securevalues`.
|
||||
func (s *SecureValueRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
_, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
// ValidateSecureValue does basic spec validation of a securevalue.
|
||||
func ValidateSecureValue(sv, oldSv *secretv0alpha1.SecureValue, operation admission.Operation, decryptersAllowList map[string]struct{}) field.ErrorList {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
package reststorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
)
|
||||
|
||||
var (
|
||||
_ rest.Scoper = (*SecureValueRest)(nil)
|
||||
_ rest.SingularNameProvider = (*SecureValueRest)(nil)
|
||||
_ rest.Getter = (*SecureValueRest)(nil)
|
||||
_ rest.Lister = (*SecureValueRest)(nil)
|
||||
_ rest.Storage = (*SecureValueRest)(nil)
|
||||
_ rest.Creater = (*SecureValueRest)(nil)
|
||||
_ rest.Updater = (*SecureValueRest)(nil)
|
||||
_ rest.GracefulDeleter = (*SecureValueRest)(nil)
|
||||
)
|
||||
|
||||
// SecureValueRest is an implementation of CRUDL operations on a `securevalue` backed by a persistence layer `store`.
|
||||
type SecureValueRest struct {
|
||||
storage contracts.SecureValueMetadataStorage
|
||||
resource utils.ResourceInfo
|
||||
tableConverter rest.TableConvertor
|
||||
}
|
||||
|
||||
// NewSecureValueRest is a returns a constructed `*SecureValueRest`.
|
||||
func NewSecureValueRest(storage contracts.SecureValueMetadataStorage, resource utils.ResourceInfo) *SecureValueRest {
|
||||
return &SecureValueRest{storage, resource, resource.TableConverter()}
|
||||
}
|
||||
|
||||
// New returns an empty `*SecureValue` that is used by the `Create` method.
|
||||
func (s *SecureValueRest) New() runtime.Object {
|
||||
return s.resource.NewFunc()
|
||||
}
|
||||
|
||||
// Destroy is called when? [TODO]
|
||||
func (s *SecureValueRest) Destroy() {}
|
||||
|
||||
// NamespaceScoped returns `true` because the storage is namespaced (== org).
|
||||
func (s *SecureValueRest) NamespaceScoped() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetSingularName is used by `kubectl` discovery to have singular name representation of resources.
|
||||
func (s *SecureValueRest) GetSingularName() string {
|
||||
return s.resource.GetSingularName()
|
||||
}
|
||||
|
||||
// NewList returns an empty `*SecureValueList` that is used by the `List` method.
|
||||
func (s *SecureValueRest) NewList() runtime.Object {
|
||||
return s.resource.NewListFunc()
|
||||
}
|
||||
|
||||
// ConvertToTable is used by Kubernetes and converts objects to `metav1.Table`.
|
||||
func (s *SecureValueRest) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
// List calls the inner `store` (persistence) and returns a list of `securevalues` within a `namespace` filtered by the `options`.
|
||||
func (s *SecureValueRest) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
namespace, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
secureValueList, err := s.storage.List(ctx, xkube.Namespace(namespace), options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list secure values: %w", err)
|
||||
}
|
||||
|
||||
return secureValueList, nil
|
||||
}
|
||||
|
||||
// Get calls the inner `store` (persistence) and returns a `securevalue` by `name`. It will NOT return the decrypted `value`.
|
||||
func (s *SecureValueRest) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
namespace, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
sv, err := s.storage.Read(ctx, xkube.Namespace(namespace), name)
|
||||
if err != nil {
|
||||
if errors.Is(err, contracts.ErrSecureValueNotFound) {
|
||||
return nil, s.resource.NewNotFound(name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to read secure value: %w", err)
|
||||
}
|
||||
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
// Create a new `securevalue`. Does some validation and allows empty `name` (generated).
|
||||
func (s *SecureValueRest) Create(
|
||||
ctx context.Context,
|
||||
obj runtime.Object,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
options *metav1.CreateOptions,
|
||||
) (runtime.Object, error) {
|
||||
sv, ok := obj.(*secretv0alpha1.SecureValue)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected SecureValue for create")
|
||||
}
|
||||
|
||||
if err := createValidation(ctx, obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createdSecureValue, err := s.storage.Create(ctx, sv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create secure value: %w", err)
|
||||
}
|
||||
|
||||
return createdSecureValue, nil
|
||||
}
|
||||
|
||||
// Update a `securevalue`'s `value`. The second return parameter indicates whether the resource was newly created.
|
||||
// Currently does not support "create on update" functionality. If the securevalue does not yet exist, it returns an error.
|
||||
func (s *SecureValueRest) Update(
|
||||
ctx context.Context,
|
||||
name string,
|
||||
objInfo rest.UpdatedObjectInfo,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
updateValidation rest.ValidateObjectUpdateFunc,
|
||||
forceAllowCreate bool,
|
||||
options *metav1.UpdateOptions,
|
||||
) (runtime.Object, bool, error) {
|
||||
oldObj, err := s.Get(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Makes sure the UID and ResourceVersion are OK.
|
||||
// TODO: this also makes it so the labels and annotations are additive, unless we check and remove manually.
|
||||
newObj, err := objInfo.UpdatedObject(ctx, oldObj)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("k8s updated object: %w", err)
|
||||
}
|
||||
|
||||
if err := updateValidation(ctx, newObj, oldObj); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
newSecureValue, ok := newObj.(*secretv0alpha1.SecureValue)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("expected SecureValue for update")
|
||||
}
|
||||
|
||||
// TODO: do we need to do this here again? Probably not, but double-check!
|
||||
newSecureValue.Annotations = xkube.CleanAnnotations(newSecureValue.Annotations)
|
||||
|
||||
// Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that?
|
||||
updatedSecureValue, err := s.storage.Update(ctx, newSecureValue)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("failed to update secure value: %w", err)
|
||||
}
|
||||
|
||||
return updatedSecureValue, false, nil
|
||||
}
|
||||
|
||||
// Delete calls the inner `store` (persistence) in order to delete the `securevalue`.
|
||||
// The second return parameter `bool` indicates whether the delete was instant or not. It always is for `securevalues`.
|
||||
func (s *SecureValueRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
namespace, ok := request.NamespaceFrom(ctx)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("missing namespace")
|
||||
}
|
||||
|
||||
if err := s.storage.Delete(ctx, xkube.Namespace(namespace), name); err != nil {
|
||||
return nil, false, fmt.Errorf("delete secure value: %w", err)
|
||||
}
|
||||
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
// ValidateSecureValue does basic spec validation of a securevalue.
|
||||
func ValidateSecureValue(sv, oldSv *secretv0alpha1.SecureValue, operation admission.Operation, decryptersAllowList map[string]struct{}) field.ErrorList {
|
||||
errs := make(field.ErrorList, 0)
|
||||
|
||||
// Operation-specific field validation.
|
||||
switch operation {
|
||||
case admission.Create:
|
||||
errs = validateSecureValueCreate(sv)
|
||||
|
||||
// If we plan to support PATCH-style updates, we shouldn't be requiring fields to be set.
|
||||
case admission.Update:
|
||||
errs = validateSecureValueUpdate(sv, oldSv)
|
||||
|
||||
case admission.Delete:
|
||||
case admission.Connect:
|
||||
}
|
||||
|
||||
// General validations.
|
||||
if errs := validateDecrypters(sv.Spec.Decrypters, decryptersAllowList); len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// validateSecureValueCreate does basic spec validation of a securevalue for the Create operation.
|
||||
func validateSecureValueCreate(sv *secretv0alpha1.SecureValue) field.ErrorList {
|
||||
errs := make(field.ErrorList, 0)
|
||||
|
||||
if sv.Spec.Title == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "title"), "a `title` is required"))
|
||||
}
|
||||
|
||||
if sv.Spec.Keeper == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec", "keeper"), "a `keeper` is required"))
|
||||
}
|
||||
|
||||
if sv.Spec.Value == "" && sv.Spec.Ref == "" {
|
||||
errs = append(errs, field.Required(field.NewPath("spec"), "either a `value` or `ref` is required"))
|
||||
}
|
||||
|
||||
if sv.Spec.Value != "" && sv.Spec.Ref != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec"), "only one of `value` or `ref` can be set"))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// validateSecureValueUpdate does basic spec validation of a securevalue for the Update operation.
|
||||
func validateSecureValueUpdate(sv, oldSv *secretv0alpha1.SecureValue) field.ErrorList {
|
||||
errs := make(field.ErrorList, 0)
|
||||
|
||||
// For updates, an `old` object is required.
|
||||
if oldSv == nil {
|
||||
errs = append(errs, field.InternalError(field.NewPath("spec"), errors.New("old object is nil")))
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// Only validate if one of the fields is being changed/set.
|
||||
if sv.Spec.Value != "" || sv.Spec.Ref != "" {
|
||||
if oldSv.Spec.Ref != "" && sv.Spec.Value != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec"), "cannot set `value` when `ref` was already previously set"))
|
||||
}
|
||||
|
||||
if oldSv.Spec.Ref == "" && sv.Spec.Ref != "" {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec"), "cannot set `ref` when `value` was already previously set"))
|
||||
}
|
||||
}
|
||||
|
||||
// Keeper cannot be changed.
|
||||
if sv.Spec.Keeper != oldSv.Spec.Keeper {
|
||||
errs = append(errs, field.Forbidden(field.NewPath("spec"), "the `keeper` cannot be changed"))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// validateDecrypters validates that (if populated) the `decrypters` must match "actor_{name}" and must be unique.
|
||||
func validateDecrypters(decrypters []string, decryptersAllowList map[string]struct{}) field.ErrorList {
|
||||
errs := make(field.ErrorList, 0)
|
||||
|
||||
decrypterNames := make(map[string]struct{}, 0)
|
||||
|
||||
for i, decrypter := range decrypters {
|
||||
// Allow List: decrypters must match exactly and be in the allowed list to be able to decrypt.
|
||||
// This means an allow list item should have the format "actor_{name}" and not just "{name}".
|
||||
if len(decryptersAllowList) > 0 {
|
||||
if _, exists := decryptersAllowList[decrypter]; !exists {
|
||||
errs = append(
|
||||
errs,
|
||||
field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, fmt.Sprintf("allowed values: %v", decryptersAllowList)),
|
||||
)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
actor, name, found := strings.Cut(strings.TrimSpace(decrypter), "_")
|
||||
if !found || actor != "actor" || name == "" {
|
||||
errs = append(
|
||||
errs,
|
||||
field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "a decrypter must have the format `actor_{name}`"),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := decrypterNames[name]; exists {
|
||||
errs = append(
|
||||
errs,
|
||||
field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "decrypters must be unique"),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
decrypterNames[name] = struct{}{}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package reststorage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
|
||||
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
|
||||
)
|
||||
|
||||
func TestValidateSecureValue(t *testing.T) {
|
||||
t.Run("when creating a new securevalue", func(t *testing.T) {
|
||||
validSecureValue := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title",
|
||||
Value: "value",
|
||||
Keeper: "keeper",
|
||||
Decrypters: []string{"actor_app1", "actor_app2"},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("the `title` must be present", func(t *testing.T) {
|
||||
sv := validSecureValue.DeepCopy()
|
||||
sv.Spec.Title = ""
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.title", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("the `keeper` must be present", func(t *testing.T) {
|
||||
sv := validSecureValue.DeepCopy()
|
||||
sv.Spec.Keeper = ""
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.keeper", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("either a `value` or `ref` must be present but not both", func(t *testing.T) {
|
||||
sv := validSecureValue.DeepCopy()
|
||||
sv.Spec.Value = ""
|
||||
sv.Spec.Ref = ""
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
|
||||
sv.Spec.Value = "value"
|
||||
sv.Spec.Ref = "value"
|
||||
|
||||
errs = ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when updating a securevalue", func(t *testing.T) {
|
||||
t.Run("when trying to switch from a `value` (old) to a `ref` (new), it returns an error", func(t *testing.T) {
|
||||
oldSv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Ref: "", // empty `ref` means a `value` was present.
|
||||
},
|
||||
}
|
||||
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Ref: "ref",
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, oldSv, admission.Update, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("when trying to switch from a `ref` (old) to a `value` (new), it returns an error", func(t *testing.T) {
|
||||
oldSv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Ref: "non-empty",
|
||||
},
|
||||
}
|
||||
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Value: "value",
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, oldSv, admission.Update, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("when both `value` and `ref` are set, it returns an error", func(t *testing.T) {
|
||||
oldSv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Ref: "non-empty",
|
||||
},
|
||||
}
|
||||
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Value: "value",
|
||||
Ref: "ref",
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, oldSv, admission.Update, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
|
||||
oldSv = &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Value: "non-empty",
|
||||
},
|
||||
}
|
||||
|
||||
errs = ValidateSecureValue(sv, oldSv, admission.Update, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("when no changes are made, it returns no errors", func(t *testing.T) {
|
||||
oldSv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "old-title",
|
||||
},
|
||||
}
|
||||
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "new-title",
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, oldSv, admission.Update, nil)
|
||||
require.Empty(t, errs)
|
||||
})
|
||||
|
||||
t.Run("when the old object is `nil` it returns an error", func(t *testing.T) {
|
||||
sv := &secretv0alpha1.SecureValue{}
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Update, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("when trying to change the `keeper`, it returns an error", func(t *testing.T) {
|
||||
oldSv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Keeper: "a-keeper",
|
||||
},
|
||||
}
|
||||
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Keeper: "another-keeper",
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, oldSv, admission.Update, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec", errs[0].Field)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("`decrypters` must have unique items", func(t *testing.T) {
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
|
||||
Decrypters: []string{
|
||||
"actor_app1",
|
||||
"actor_app1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, 1)
|
||||
require.Equal(t, "spec.decrypters.[1]", errs[0].Field)
|
||||
})
|
||||
|
||||
t.Run("`decrypters` must match the expected format", func(t *testing.T) {
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
|
||||
Decrypters: []string{
|
||||
"app1",
|
||||
"_app1",
|
||||
"actr_app1",
|
||||
"actor_ ",
|
||||
"actor_",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, nil)
|
||||
require.Len(t, errs, len(sv.Spec.Decrypters))
|
||||
|
||||
for i, err := range errs {
|
||||
require.Equal(t, fmt.Sprintf("spec.decrypters.[%d]", i), err.Field)
|
||||
require.Contains(t, err.Error(), "a decrypter must have the format `actor_{name}`")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("when set, the `decrypters` must be one of the allowed in the allow list", func(t *testing.T) {
|
||||
allowList := map[string]struct{}{"actor_app1": {}, "actor_app2": {}}
|
||||
decrypters := slices.Collect(maps.Keys(allowList))
|
||||
|
||||
t.Run("no matches, returns an error", func(t *testing.T) {
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
|
||||
Decrypters: []string{"actor_app3"},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, allowList)
|
||||
require.Len(t, errs, 1)
|
||||
})
|
||||
|
||||
t.Run("no decrypters, returns no error", func(t *testing.T) {
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
|
||||
Decrypters: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, allowList)
|
||||
require.Empty(t, errs)
|
||||
})
|
||||
|
||||
t.Run("one match, returns no errors", func(t *testing.T) {
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
|
||||
Decrypters: []string{decrypters[0]},
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, allowList)
|
||||
require.Empty(t, errs)
|
||||
})
|
||||
|
||||
t.Run("all matches, returns no errors", func(t *testing.T) {
|
||||
sv := &secretv0alpha1.SecureValue{
|
||||
Spec: secretv0alpha1.SecureValueSpec{
|
||||
Title: "title", Keeper: "keeper", Ref: "ref",
|
||||
|
||||
Decrypters: decrypters,
|
||||
},
|
||||
}
|
||||
|
||||
errs := ValidateSecureValue(sv, nil, admission.Create, allowList)
|
||||
require.Empty(t, errs)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package xkube
|
||||
|
||||
import "github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
|
||||
var (
|
||||
// Exclude these annotations
|
||||
skipAnnotations = map[string]bool{
|
||||
"kubectl.kubernetes.io/last-applied-configuration": true, // force server side apply
|
||||
utils.AnnoKeyCreatedBy: true,
|
||||
utils.AnnoKeyUpdatedBy: true,
|
||||
utils.AnnoKeyUpdatedTimestamp: true,
|
||||
}
|
||||
)
|
||||
|
||||
func CleanAnnotations(anno map[string]string) map[string]string {
|
||||
copy := make(map[string]string)
|
||||
for k, v := range anno {
|
||||
if skipAnnotations[k] {
|
||||
continue
|
||||
}
|
||||
copy[k] = v
|
||||
}
|
||||
return copy
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package xkube
|
||||
|
||||
import "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
// ErrorLister is an interface compatible with errors that also returns a list of Kubernetes field errors.
|
||||
type ErrorLister interface {
|
||||
error
|
||||
ErrorList() field.ErrorList
|
||||
}
|
||||
+19
-21
@@ -4,25 +4,6 @@ package semconv
|
||||
|
||||
import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
// Describes Grafana service attributes.
|
||||
const (
|
||||
// GrafanaServiceNameKey is the attribute Key conforming to the
|
||||
// "grafana.service.name" semantic conventions. It represents the service
|
||||
// name.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Optional
|
||||
// Stability: stable
|
||||
// Examples: 'grafana-apiserver'
|
||||
grafanaServiceNameKey = attribute.Key("grafana.service.name")
|
||||
)
|
||||
|
||||
// GrafanaServiceName returns an attribute KeyValue conforming to the
|
||||
// "grafana.service.name" semantic conventions. It represents the service name.
|
||||
func GrafanaServiceName(val string) attribute.KeyValue {
|
||||
return grafanaServiceNameKey.String(val)
|
||||
}
|
||||
|
||||
// Describes Grafana datasource attributes.
|
||||
const (
|
||||
// GrafanaDatasourceTypeKey is the attribute Key conforming to the
|
||||
@@ -131,8 +112,6 @@ var (
|
||||
GrafanaPluginTypeApp = grafanaPluginTypeKey.String("app")
|
||||
// Renderer Plugin
|
||||
GrafanaPluginTypeRenderer = grafanaPluginTypeKey.String("renderer")
|
||||
// Secret Manager Plugin
|
||||
GrafanaPluginTypeSecretmanager = grafanaPluginTypeKey.String("secretmanager")
|
||||
)
|
||||
|
||||
// GrafanaPluginId returns an attribute KeyValue conforming to the
|
||||
@@ -140,3 +119,22 @@ var (
|
||||
func GrafanaPluginId(val string) attribute.KeyValue {
|
||||
return grafanaPluginIdKey.String(val)
|
||||
}
|
||||
|
||||
// Describes Grafana service attributes.
|
||||
const (
|
||||
// GrafanaServiceNameKey is the attribute Key conforming to the
|
||||
// "grafana.service.name" semantic conventions. It represents the service
|
||||
// name.
|
||||
//
|
||||
// Type: string
|
||||
// RequirementLevel: Optional
|
||||
// Stability: stable
|
||||
// Examples: 'grafana-apiserver'
|
||||
grafanaServiceNameKey = attribute.Key("grafana.service.name")
|
||||
)
|
||||
|
||||
// GrafanaServiceName returns an attribute KeyValue conforming to the
|
||||
// "grafana.service.name" semantic conventions. It represents the service name.
|
||||
func GrafanaServiceName(val string) attribute.KeyValue {
|
||||
return grafanaServiceNameKey.String(val)
|
||||
}
|
||||
|
||||
@@ -31,10 +31,6 @@ groups:
|
||||
value: "renderer"
|
||||
brief: 'Renderer Plugin'
|
||||
stability: stable
|
||||
- id: secretmanager
|
||||
value: "secretmanager"
|
||||
brief: 'Secret Manager Plugin'
|
||||
stability: stable
|
||||
brief: The plugin type.
|
||||
examples: datasource
|
||||
stability: stable
|
||||
stability: stable
|
||||
|
||||
@@ -363,8 +363,6 @@ var wireBasicSet = wire.NewSet(
|
||||
loginattemptimpl.ProvideService,
|
||||
wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)),
|
||||
secretsMigrations.ProvideDataSourceMigrationService,
|
||||
secretsMigrations.ProvideMigrateToPluginService,
|
||||
secretsMigrations.ProvideMigrateFromPluginService,
|
||||
secretsMigrations.ProvideSecretMigrationProvider,
|
||||
wire.Bind(new(secretsMigrations.SecretMigrationProvider), new(*secretsMigrations.SecretMigrationProviderImpl)),
|
||||
resourcepermissions.NewActionSetService,
|
||||
|
||||
@@ -76,24 +76,26 @@ func ProvidePermissionRegistry() PermissionRegistry {
|
||||
func newPermissionRegistry() *permissionRegistry {
|
||||
// defaultKindScopes maps the most specific accepted scope prefix for a given kind (folders, dashboards, etc)
|
||||
defaultKindScopes := map[string]string{
|
||||
"teams": "teams:id:",
|
||||
"users": "users:id:",
|
||||
"datasources": "datasources:uid:",
|
||||
"dashboards": "dashboards:uid:",
|
||||
"folders": "folders:uid:",
|
||||
"annotations": "annotations:type:",
|
||||
"apikeys": "apikeys:id:",
|
||||
"orgs": "orgs:id:",
|
||||
"plugins": "plugins:id:",
|
||||
"provisioners": "provisioners:",
|
||||
"reports": "reports:id:",
|
||||
"permissions": "permissions:type:",
|
||||
"serviceaccounts": "serviceaccounts:id:",
|
||||
"settings": "settings:",
|
||||
"global.users": "global.users:id:",
|
||||
"roles": "roles:uid:",
|
||||
"services": "services:",
|
||||
"receivers": "receivers:uid:",
|
||||
"teams": "teams:id:",
|
||||
"users": "users:id:",
|
||||
"datasources": "datasources:uid:",
|
||||
"dashboards": "dashboards:uid:",
|
||||
"folders": "folders:uid:",
|
||||
"annotations": "annotations:type:",
|
||||
"apikeys": "apikeys:id:",
|
||||
"orgs": "orgs:id:",
|
||||
"plugins": "plugins:id:",
|
||||
"provisioners": "provisioners:",
|
||||
"reports": "reports:id:",
|
||||
"permissions": "permissions:type:",
|
||||
"serviceaccounts": "serviceaccounts:id:",
|
||||
"settings": "settings:",
|
||||
"global.users": "global.users:id:",
|
||||
"roles": "roles:uid:",
|
||||
"services": "services:",
|
||||
"receivers": "receivers:uid:",
|
||||
"secret.securevalues": "secret.securevalues:uid:",
|
||||
"secret.keepers": "secret.keepers:uid:",
|
||||
}
|
||||
return &permissionRegistry{
|
||||
actionScopePrefixes: make(map[string]PrefixSet, 200),
|
||||
|
||||
@@ -63,6 +63,10 @@ func newMapper() mapper {
|
||||
"iam.grafana.app": {
|
||||
"teams": newResourceTranslation("teams", "id", false),
|
||||
},
|
||||
"secret.grafana.app": {
|
||||
"securevalues": newResourceTranslation("secret.securevalues", "uid", false),
|
||||
"keepers": newResourceTranslation("secret.keepers", "uid", false),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM {{ .Ident .PermissionTable }} as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM {{ .Ident .BuiltinRoleTable }} as br WHERE (br.role = {{ .Arg .Query.Role }} AND (br.org_id = {{ .Arg .Query.OrgID }} OR br.org_id = 0))
|
||||
{{ if .Query.IsServerAdmin }}
|
||||
OR (br.role = 'Grafana Admin')
|
||||
{{ end }}
|
||||
{{ if .Query.UserID }}
|
||||
UNION ALL
|
||||
SELECT role_id FROM {{ .Ident .UserRoleTable }} as ur WHERE ur.user_id = {{ .Arg .Query.UserID }} AND (ur.org_id = {{ .Arg .Query.OrgID }} OR ur.org_id = 0)
|
||||
{{ end }}
|
||||
{{ if .Query.TeamIDs }}
|
||||
UNION ALL
|
||||
SELECT role_id FROM {{ .Ident .TeamRoleTable }} as tr WHERE tr.team_id IN ({{ .ArgList .Query.TeamIDs }}) AND tr.org_id = {{ .Arg .Query.OrgID }}
|
||||
{{ end }}
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
{{ if .Query.ActionSets }}
|
||||
p.action IN ({{ .ArgList .Query.ActionSets }}, {{ .Arg .Query.Action }})
|
||||
{{ else }}
|
||||
p.action = {{ .Arg .Query.Action }}
|
||||
{{ end }}
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM {{ .Ident .BuiltinRoleTable }} as br WHERE (br.role = {{ .Arg .Query.Role }} AND (br.org_id = {{ .Arg .Query.OrgID }} OR br.org_id = 0))
|
||||
{{ if .Query.IsServerAdmin }}
|
||||
OR (br.role = 'Grafana Admin')
|
||||
{{ end }}
|
||||
{{ if .Query.UserID }}
|
||||
UNION
|
||||
SELECT role_id FROM {{ .Ident .UserRoleTable }} as ur WHERE ur.user_id = {{ .Arg .Query.UserID }} AND (ur.org_id = {{ .Arg .Query.OrgID }} OR ur.org_id = 0)
|
||||
{{ end }}
|
||||
{{ if .Query.TeamIDs }}
|
||||
UNION
|
||||
SELECT role_id FROM {{ .Ident .TeamRoleTable }} as tr WHERE tr.team_id IN ({{ .ArgList .Query.TeamIDs }}) AND tr.org_id = {{ .Arg .Query.OrgID }}
|
||||
{{ end }}
|
||||
)
|
||||
{{ end }}
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION ALL
|
||||
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action IN ('folders:edit', 'folders:admin', 'folders:create')
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION
|
||||
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
)
|
||||
|
||||
+6
-6
@@ -1,9 +1,9 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
OR (br.role = 'Grafana Admin')
|
||||
UNION ALL
|
||||
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
OR (br.role = 'Grafana Admin')
|
||||
UNION
|
||||
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
)
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
)
|
||||
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION ALL
|
||||
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
UNION ALL
|
||||
SELECT role_id FROM `grafana`.`team_role` as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION
|
||||
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
UNION
|
||||
SELECT role_id FROM `grafana`.`team_role` as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1
|
||||
)
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM `grafana`.`permission` as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION ALL
|
||||
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM `grafana`.`builtin_role` as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION
|
||||
SELECT role_id FROM `grafana`.`user_role` as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
)
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION ALL
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action IN ('folders:edit', 'folders:admin', 'folders:create')
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
)
|
||||
|
||||
+6
-6
@@ -1,9 +1,9 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
OR (br.role = 'Grafana Admin')
|
||||
UNION ALL
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
OR (br.role = 'Grafana Admin')
|
||||
UNION
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
)
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
)
|
||||
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION ALL
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
UNION ALL
|
||||
SELECT role_id FROM "grafana"."team_role" as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
UNION
|
||||
SELECT role_id FROM "grafana"."team_role" as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1
|
||||
)
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION ALL
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
)
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION ALL
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action IN ('folders:edit', 'folders:admin', 'folders:create')
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
)
|
||||
|
||||
+6
-6
@@ -1,9 +1,9 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
OR (br.role = 'Grafana Admin')
|
||||
UNION ALL
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Admin' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
OR (br.role = 'Grafana Admin')
|
||||
UNION
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
)
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
)
|
||||
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION ALL
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
UNION ALL
|
||||
SELECT role_id FROM "grafana"."team_role" as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'None' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
UNION
|
||||
SELECT role_id FROM "grafana"."team_role" as tr WHERE tr.team_id IN (1, 2) AND tr.org_id = 1
|
||||
)
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
SELECT p.kind, p.attribute, p.identifier, p.scope FROM "grafana"."permission" as p
|
||||
INNER JOIN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION ALL
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
) as roles ON p.role_id = roles.role_id
|
||||
WHERE
|
||||
p.action = 'folders:read'
|
||||
AND p.role_id IN (
|
||||
SELECT role_id FROM "grafana"."builtin_role" as br WHERE (br.role = 'Viewer' AND (br.org_id = 1 OR br.org_id = 0))
|
||||
UNION
|
||||
SELECT role_id FROM "grafana"."user_role" as ur WHERE ur.user_id = 1 AND (ur.org_id = 1 OR ur.org_id = 0)
|
||||
)
|
||||
|
||||
@@ -122,11 +122,7 @@ func (s *FakeDataSourceService) GetHTTPTransport(ctx context.Context, ds *dataso
|
||||
}
|
||||
|
||||
func (s *FakeDataSourceService) DecryptedValues(ctx context.Context, ds *datasources.DataSource) (map[string]string, error) {
|
||||
if s.SimulatePluginFailure {
|
||||
return nil, datasources.ErrDatasourceSecretsPluginUserFriendly{Err: "unknown error"}
|
||||
}
|
||||
values := make(map[string]string)
|
||||
return values, nil
|
||||
return make(map[string]string), nil
|
||||
}
|
||||
|
||||
func (s *FakeDataSourceService) DecryptedValue(ctx context.Context, ds *datasources.DataSource, key string) (string, bool, error) {
|
||||
|
||||
@@ -145,15 +145,6 @@ func (ds DataSource) AllowedCookies() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Specific error type for grpc secrets management so that we can show more detailed plugin errors to users
|
||||
type ErrDatasourceSecretsPluginUserFriendly struct {
|
||||
Err string
|
||||
}
|
||||
|
||||
func (e ErrDatasourceSecretsPluginUserFriendly) Error() string {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// COMMANDS
|
||||
|
||||
|
||||
@@ -376,7 +376,7 @@ var (
|
||||
{
|
||||
Name: "frontendSandboxMonitorOnly",
|
||||
Description: "Enables monitor only in the plugin frontend sandbox (if enabled)",
|
||||
Stage: FeatureStageExperimental,
|
||||
Stage: FeatureStagePrivatePreview,
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaPluginsPlatformSquad,
|
||||
},
|
||||
@@ -1421,8 +1421,9 @@ var (
|
||||
{
|
||||
Name: "pluginsSriChecks",
|
||||
Description: "Enables SRI checks for plugin assets",
|
||||
Stage: FeatureStageExperimental,
|
||||
Stage: FeatureStageGeneralAvailability,
|
||||
Owner: grafanaPluginsPlatformSquad,
|
||||
Expression: "false", // disabled by default
|
||||
},
|
||||
{
|
||||
Name: "unifiedStorageBigObjectsSupport",
|
||||
|
||||
@@ -49,7 +49,7 @@ enableDatagridEditing,preview,@grafana/dataviz-squad,false,false,true
|
||||
extraThemes,experimental,@grafana/grafana-frontend-platform,false,false,true
|
||||
lokiPredefinedOperations,experimental,@grafana/observability-logs,false,false,true
|
||||
pluginsFrontendSandbox,privatePreview,@grafana/plugins-platform-backend,false,false,false
|
||||
frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,false,false,true
|
||||
frontendSandboxMonitorOnly,privatePreview,@grafana/plugins-platform-backend,false,false,true
|
||||
pluginsDetailsRightPanel,privatePreview,@grafana/plugins-platform-backend,false,false,true
|
||||
sqlDatasourceDatabaseSelection,preview,@grafana/dataviz-squad,false,false,true
|
||||
recordedQueriesMulti,GA,@grafana/observability-metrics,false,false,false
|
||||
@@ -186,7 +186,7 @@ unifiedStorageSearch,experimental,@grafana/search-and-storage,false,false,false
|
||||
unifiedStorageSearchSprinkles,experimental,@grafana/search-and-storage,false,false,false
|
||||
unifiedStorageSearchPermissionFiltering,experimental,@grafana/search-and-storage,false,false,false
|
||||
managedDualWriter,experimental,@grafana/search-and-storage,false,false,false
|
||||
pluginsSriChecks,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||
pluginsSriChecks,GA,@grafana/plugins-platform-backend,false,false,false
|
||||
unifiedStorageBigObjectsSupport,experimental,@grafana/search-and-storage,false,false,false
|
||||
timeRangeProvider,experimental,@grafana/grafana-frontend-platform,false,false,false
|
||||
prometheusUsesCombobox,experimental,@grafana/oss-big-tent,false,false,false
|
||||
|
||||
|
@@ -1913,12 +1913,15 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "frontendSandboxMonitorOnly",
|
||||
"resourceVersion": "1718727528075",
|
||||
"creationTimestamp": "2023-07-05T11:48:25Z"
|
||||
"resourceVersion": "1741965568775",
|
||||
"creationTimestamp": "2023-07-05T11:48:25Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-03-14 15:19:28.77575 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enables monitor only in the plugin frontend sandbox (if enabled)",
|
||||
"stage": "experimental",
|
||||
"stage": "privatePreview",
|
||||
"codeowner": "@grafana/plugins-platform-backend",
|
||||
"frontend": true
|
||||
}
|
||||
@@ -3281,13 +3284,17 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "pluginsSriChecks",
|
||||
"resourceVersion": "1727785264632",
|
||||
"creationTimestamp": "2024-10-04T12:55:09Z"
|
||||
"resourceVersion": "1742405194835",
|
||||
"creationTimestamp": "2024-10-04T12:55:09Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-03-19 17:26:34.83562 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enables SRI checks for plugin assets",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/plugins-platform-backend"
|
||||
"stage": "GA",
|
||||
"codeowner": "@grafana/plugins-platform-backend",
|
||||
"expression": "false"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/star"
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
)
|
||||
|
||||
type ServiceImpl struct {
|
||||
@@ -34,6 +36,7 @@ type ServiceImpl struct {
|
||||
pluginSettings pluginsettings.Service
|
||||
starService star.Service
|
||||
features featuremgmt.FeatureToggles
|
||||
openFeature *featuremgmt.OpenFeatureService
|
||||
dashboardService dashboards.DashboardService
|
||||
accesscontrolService ac.Service
|
||||
kvStore kvstore.KVStore
|
||||
@@ -54,7 +57,7 @@ type NavigationAppConfig struct {
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStore pluginstore.Store, pluginSettings pluginsettings.Service, starService star.Service,
|
||||
features featuremgmt.FeatureToggles, dashboardService dashboards.DashboardService, accesscontrolService ac.Service, kvStore kvstore.KVStore, apiKeyService apikey.Service,
|
||||
license licensing.Licensing, authnService authn.Service) navtree.Service {
|
||||
license licensing.Licensing, authnService authn.Service, openFeature *featuremgmt.OpenFeatureService) navtree.Service {
|
||||
service := &ServiceImpl{
|
||||
cfg: cfg,
|
||||
log: log.New("navtree service"),
|
||||
@@ -64,6 +67,7 @@ func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStor
|
||||
pluginSettings: pluginSettings,
|
||||
starService: starService,
|
||||
features: features,
|
||||
openFeature: openFeature,
|
||||
dashboardService: dashboardService,
|
||||
accesscontrolService: accesscontrolService,
|
||||
kvStore: kvStore,
|
||||
@@ -80,6 +84,7 @@ func ProvideService(cfg *setting.Cfg, accessControl ac.AccessControl, pluginStor
|
||||
func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Preference) (*navtree.NavTreeRoot, error) {
|
||||
hasAccess := ac.HasAccess(s.accessControl, c)
|
||||
treeRoot := &navtree.NavTreeRoot{}
|
||||
ctx := c.Req.Context()
|
||||
|
||||
treeRoot.AddSection(s.getHomeNode(c, prefs))
|
||||
|
||||
@@ -184,7 +189,8 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere
|
||||
treeRoot.RemoveSectionByID(navtree.NavIDCfg)
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagPinNavItems) && c.IsSignedIn {
|
||||
enabled := s.openFeature.Client.Boolean(ctx, featuremgmt.FlagPinNavItems, true, openfeature.TransactionContext(ctx))
|
||||
if enabled && c.IsSignedIn {
|
||||
treeRoot.AddSection(&navtree.NavLink{
|
||||
Text: "Bookmarks",
|
||||
Id: navtree.NavIDBookmarks,
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/mohae/deepcopy"
|
||||
amv2 "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/common/model"
|
||||
@@ -698,26 +697,6 @@ func (c *PostableUserConfig) validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decrypt returns a copy of the configuration struct with decrypted secure settings in receivers.
|
||||
func (c *PostableUserConfig) Decrypt(decryptFn func(payload []byte) ([]byte, error)) (PostableUserConfig, error) {
|
||||
newCfg, ok := deepcopy.Copy(c).(*PostableUserConfig)
|
||||
if !ok {
|
||||
return PostableUserConfig{}, fmt.Errorf("failed to copy config")
|
||||
}
|
||||
|
||||
// Iterate through receivers and decrypt secure settings.
|
||||
for _, rcv := range newCfg.AlertmanagerConfig.Receivers {
|
||||
for _, gmr := range rcv.PostableGrafanaReceivers.GrafanaManagedReceivers {
|
||||
decrypted, err := gmr.DecryptSecureSettings(decryptFn)
|
||||
if err != nil {
|
||||
return PostableUserConfig{}, err
|
||||
}
|
||||
gmr.SecureSettings = decrypted
|
||||
}
|
||||
}
|
||||
return *newCfg, nil
|
||||
}
|
||||
|
||||
// GetGrafanaReceiverMap returns a map that associates UUIDs to grafana receivers
|
||||
func (c *PostableUserConfig) GetGrafanaReceiverMap() map[string]*PostableGrafanaReceiver {
|
||||
UIDs := make(map[string]*PostableGrafanaReceiver)
|
||||
|
||||
@@ -255,17 +255,22 @@ func (am *Alertmanager) CompareAndSendConfiguration(ctx context.Context, config
|
||||
if err := am.autogenFn(ctx, am.log, am.orgID, &c.AlertmanagerConfig, true); err != nil {
|
||||
return err
|
||||
}
|
||||
decrypted, err := am.decryptConfiguration(ctx, c)
|
||||
rawDecrypted, configHash, err := am.decryptConfiguration(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send the configuration only if we need to.
|
||||
if !am.shouldSendConfig(ctx, decrypted) {
|
||||
if !am.shouldSendConfig(ctx, configHash) {
|
||||
return nil
|
||||
}
|
||||
|
||||
isDefault, err := am.isDefaultConfiguration(decrypted)
|
||||
isDefault, err := am.isDefaultConfiguration(configHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decrypted, err := notifier.Load(rawDecrypted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -273,27 +278,37 @@ func (am *Alertmanager) CompareAndSendConfiguration(ctx context.Context, config
|
||||
return am.sendConfiguration(ctx, decrypted, config.ConfigurationHash, config.CreatedAt, isDefault)
|
||||
}
|
||||
|
||||
func (am *Alertmanager) isDefaultConfiguration(cfg *apimodels.PostableUserConfig) (bool, error) {
|
||||
rawCfg, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
configHash := fmt.Sprintf("%x", md5.Sum(rawCfg))
|
||||
|
||||
return configHash == am.defaultConfigHash, nil
|
||||
func (am *Alertmanager) isDefaultConfiguration(configHash [16]byte) (bool, error) {
|
||||
return fmt.Sprintf("%x", configHash) == am.defaultConfigHash, nil
|
||||
}
|
||||
|
||||
func (am *Alertmanager) decryptConfiguration(ctx context.Context, cfg *apimodels.PostableUserConfig) (*apimodels.PostableUserConfig, error) {
|
||||
// decryptConfiguration decrypts the configuration in-place and returns the decrypted configuration alongside its hash.
|
||||
// Should not be used outside of this package and the specific use case of decrypting the configuration before sending
|
||||
// it to the remote Alertmanager.
|
||||
func (am *Alertmanager) decryptConfiguration(ctx context.Context, cfg *apimodels.PostableUserConfig) ([]byte, [16]byte, error) {
|
||||
fn := func(payload []byte) ([]byte, error) {
|
||||
return am.decrypt(ctx, payload)
|
||||
}
|
||||
decrypted, err := cfg.Decrypt(fn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decrypt the configuration: %w", err)
|
||||
|
||||
// Iterate through receivers and decrypt secure settings.
|
||||
// It's not necessary to be careful about not modifying the original, as it's used only in a specific context where
|
||||
// the config is read from json and then immediately sent to the remote Alertmanager.
|
||||
for _, rcv := range cfg.AlertmanagerConfig.Receivers {
|
||||
for _, gmr := range rcv.PostableGrafanaReceivers.GrafanaManagedReceivers {
|
||||
decrypted, err := gmr.DecryptSecureSettings(fn)
|
||||
if err != nil {
|
||||
return nil, [16]byte{}, fmt.Errorf("unable to decrypt settings on receiver %q (uid: %q): %w", gmr.Name, gmr.UID, err)
|
||||
}
|
||||
gmr.SecureSettings = decrypted
|
||||
}
|
||||
}
|
||||
|
||||
return &decrypted, nil
|
||||
rawDecrypted, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return nil, [16]byte{}, fmt.Errorf("unable to marshal decrypted configuration: %w", err)
|
||||
}
|
||||
|
||||
return rawDecrypted, md5.Sum(rawDecrypted), nil
|
||||
}
|
||||
|
||||
func (am *Alertmanager) sendConfiguration(ctx context.Context, decrypted *apimodels.PostableUserConfig, hash string, createdAt int64, isDefault bool) error {
|
||||
@@ -345,7 +360,12 @@ func (am *Alertmanager) SaveAndApplyConfig(ctx context.Context, cfg *apimodels.P
|
||||
if err := am.autogenFn(ctx, am.log, am.orgID, &cfg.AlertmanagerConfig, false); err != nil {
|
||||
return err
|
||||
}
|
||||
decrypted, err := am.decryptConfiguration(ctx, cfg)
|
||||
rawDecrypted, _, err := am.decryptConfiguration(ctx, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decrypted, err := notifier.Load(rawDecrypted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -364,7 +384,12 @@ func (am *Alertmanager) SaveAndApplyDefaultConfig(ctx context.Context) error {
|
||||
if err := am.autogenFn(ctx, am.log, am.orgID, &c.AlertmanagerConfig, true); err != nil {
|
||||
return err
|
||||
}
|
||||
decrypted, err := am.decryptConfiguration(ctx, c)
|
||||
rawDecrypted, _, err := am.decryptConfiguration(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
decrypted, err := notifier.Load(rawDecrypted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -634,7 +659,7 @@ func (am *Alertmanager) getFullState(ctx context.Context) (string, error) {
|
||||
|
||||
// shouldSendConfig compares the remote Alertmanager configuration with our local one.
|
||||
// It returns true if the configurations are different.
|
||||
func (am *Alertmanager) shouldSendConfig(ctx context.Context, config *apimodels.PostableUserConfig) bool {
|
||||
func (am *Alertmanager) shouldSendConfig(ctx context.Context, hash [16]byte) bool {
|
||||
rc, err := am.mimirClient.GetGrafanaAlertmanagerConfig(ctx)
|
||||
if err != nil {
|
||||
// Log the error and return true so we try to upload our config anyway.
|
||||
@@ -651,12 +676,7 @@ func (am *Alertmanager) shouldSendConfig(ctx context.Context, config *apimodels.
|
||||
am.log.Error("Unable to marshal the remote Alertmanager configuration for comparison", "err", err)
|
||||
return true
|
||||
}
|
||||
rawInternal, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
am.log.Error("Unable to marshal the internal Alertmanager configuration for comparison", "err", err)
|
||||
return true
|
||||
}
|
||||
return md5.Sum(rawRemote) != md5.Sum(rawInternal)
|
||||
return md5.Sum(rawRemote) != hash
|
||||
}
|
||||
|
||||
// shouldSendState compares the remote Alertmanager state with our local one.
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"github.com/grafana/alerting/definition"
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
"github.com/grafana/alerting/notify"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
@@ -39,13 +41,12 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tests/testsuite"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
// Valid Grafana Alertmanager configurations.
|
||||
testGrafanaConfig = `{"template_files":{},"alertmanager_config":{"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"","name":"some other name","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"}}]}]}}`
|
||||
testGrafanaConfigWithSecret = `{"template_files":{},"alertmanager_config":{"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"dde6ntuob69dtf","name":"WH","type":"webhook","disableResolveMessage":false,"settings":{"url":"http://localhost:8080","username":"test"},"secureSettings":{"password":"test"}}]}]}}`
|
||||
testGrafanaConfig = `{"template_files":{},"alertmanager_config":{"time_intervals":[{"name":"weekends","time_intervals":[{"weekdays":["saturday","sunday"],"location":"Africa/Accra"}]}],"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"","name":"some other name","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"}}]}]}}`
|
||||
testGrafanaConfigWithSecret = `{"template_files":{},"alertmanager_config":{"time_intervals":[{"name":"weekends","time_intervals":[{"weekdays":["saturday","sunday"],"location":"Africa/Accra"}]}],"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"dde6ntuob69dtf","name":"WH","type":"webhook","disableResolveMessage":false,"settings":{"url":"http://localhost:8080","username":"test"},"secureSettings":{"password":"test"}}]}]}}`
|
||||
testGrafanaDefaultConfigWithDifferentFieldOrder = `{"alertmanager_config":{"route":{"group_by":["alertname","grafana_folder"],"receiver":"grafana-default-email"},"receivers":[{"grafana_managed_receiver_configs":[{"uid":"","name":"email receiver","type":"email","settings":{"addresses":"<example@email.com>"}}],"name":"grafana-default-email"}]}}`
|
||||
|
||||
// Valid Alertmanager state base64 encoded.
|
||||
@@ -287,14 +288,14 @@ func TestCompareAndSendConfiguration(t *testing.T) {
|
||||
strings.Replace(testGrafanaConfigWithSecret, `"password":"test"`, `"password":"!"`, 1),
|
||||
NoopAutogenFn,
|
||||
nil,
|
||||
"unable to decrypt the configuration: failed to decode value for key 'password': illegal base64 data at input byte 0",
|
||||
`unable to decrypt settings on receiver "WH" (uid: "dde6ntuob69dtf"): failed to decode value for key 'password': illegal base64 data at input byte 0`,
|
||||
},
|
||||
{
|
||||
"decrypt error",
|
||||
testGrafanaConfigWithSecret,
|
||||
NoopAutogenFn,
|
||||
nil,
|
||||
fmt.Sprintf("unable to decrypt the configuration: failed to decrypt value for key 'password': %s", errTest.Error()),
|
||||
fmt.Sprintf(`unable to decrypt settings on receiver "WH" (uid: "dde6ntuob69dtf"): failed to decrypt value for key 'password': %s`, errTest.Error()),
|
||||
},
|
||||
{
|
||||
"error from autogen function",
|
||||
@@ -443,7 +444,9 @@ func Test_isDefaultConfiguration(t *testing.T) {
|
||||
defaultConfig: string(rawDefaultCfg),
|
||||
defaultConfigHash: fmt.Sprintf("%x", md5.Sum(rawDefaultCfg)),
|
||||
}
|
||||
isDefault, _ := am.isDefaultConfiguration(test.config)
|
||||
raw, err := json.Marshal(test.config)
|
||||
require.NoError(tt, err)
|
||||
isDefault, _ := am.isDefaultConfiguration(md5.Sum(raw))
|
||||
require.Equal(tt, test.expected, isDefault)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
Links: []plugins.InfoLink{
|
||||
{Name: "Raise issue", URL: "https://github.com/grafana/grafana/issues/new"},
|
||||
{Name: "Documentation", URL: "https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/"},
|
||||
},
|
||||
},
|
||||
Includes: []*plugins.Includes{
|
||||
|
||||
@@ -27,7 +27,7 @@ func ProvideDiscoveryStage(cfg *config.PluginManagementCfg, pf finder.Finder, pr
|
||||
FindFunc: pf.Find,
|
||||
FindFilterFuncs: []discovery.FindFilterFunc{
|
||||
discovery.NewPermittedPluginTypesFilterStep([]plugins.Type{
|
||||
plugins.TypeDataSource, plugins.TypeApp, plugins.TypePanel, plugins.TypeSecretsManager,
|
||||
plugins.TypeDataSource, plugins.TypeApp, plugins.TypePanel,
|
||||
}),
|
||||
func(ctx context.Context, _ plugins.Class, b []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) {
|
||||
return NewDuplicatePluginIDFilterStep(pr).Filter(ctx, b)
|
||||
|
||||
@@ -70,7 +70,6 @@ var WireSet = wire.NewSet(
|
||||
wire.Bind(new(pluginconfig.PluginRequestConfigProvider), new(*pluginconfig.RequestConfigProvider)),
|
||||
pluginstore.ProvideService,
|
||||
wire.Bind(new(pluginstore.Store), new(*pluginstore.Service)),
|
||||
wire.Bind(new(plugins.SecretsPluginManager), new(*pluginstore.Service)),
|
||||
wire.Bind(new(plugins.StaticRouteResolver), new(*pluginstore.Service)),
|
||||
process.ProvideService,
|
||||
wire.Bind(new(process.Manager), new(*process.Service)),
|
||||
|
||||
@@ -94,15 +94,6 @@ func (s *Service) Plugins(ctx context.Context, pluginTypes ...plugins.Type) []Pl
|
||||
return pluginsList
|
||||
}
|
||||
|
||||
func (s *Service) SecretsManager(ctx context.Context) *plugins.Plugin {
|
||||
for _, p := range s.availablePlugins(ctx) {
|
||||
if p.IsSecretsManager() {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// plugin finds a plugin with `pluginID` from the registry that is not decommissioned
|
||||
func (s *Service) plugin(ctx context.Context, pluginID string) (*plugins.Plugin, bool) {
|
||||
p, exists := s.pluginRegistry.Plugin(ctx, pluginID, "") // version is not required since Grafana only supports single versions of a plugin
|
||||
|
||||
@@ -120,7 +120,6 @@ func TestStore_Routes(t *testing.T) {
|
||||
t.Run("Routes returns all static routes for non-decommissioned plugins", func(t *testing.T) {
|
||||
p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "a-test-renderer", Type: plugins.TypeRenderer}, FS: fakes.NewFakePluginFS("/some/dir")}
|
||||
p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "b-test-panel", Type: plugins.TypePanel}, FS: fakes.NewFakePluginFS("/grafana/")}
|
||||
p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "c-test-secrets", Type: plugins.TypeSecretsManager}, FS: fakes.NewFakePluginFS("./secrets"), Class: plugins.ClassCore}
|
||||
p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "d-test-datasource", Type: plugins.TypeDataSource}, FS: fakes.NewFakePluginFS("../test")}
|
||||
p5 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "e-test-app", Type: plugins.TypeApp}, FS: fakes.NewFakePluginFS("any/path")}
|
||||
p6 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "f-test-app", Type: plugins.TypeApp}}
|
||||
@@ -130,7 +129,6 @@ func TestStore_Routes(t *testing.T) {
|
||||
Store: map[string]*plugins.Plugin{
|
||||
p1.ID: p1,
|
||||
p2.ID: p2,
|
||||
p3.ID: p3,
|
||||
p4.ID: p4,
|
||||
p5.ID: p5,
|
||||
p6.ID: p6,
|
||||
@@ -146,27 +144,6 @@ func TestStore_Routes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStore_SecretsManager(t *testing.T) {
|
||||
t.Run("Renderer returns a single (non-decommissioned) secrets manager plugin", func(t *testing.T) {
|
||||
p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-renderer", Type: plugins.TypeRenderer}}
|
||||
p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-panel", Type: plugins.TypePanel}}
|
||||
p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-secrets", Type: plugins.TypeSecretsManager}}
|
||||
p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-datasource", Type: plugins.TypeDataSource}}
|
||||
|
||||
ps := New(&fakes.FakePluginRegistry{
|
||||
Store: map[string]*plugins.Plugin{
|
||||
p1.ID: p1,
|
||||
p2.ID: p2,
|
||||
p3.ID: p3,
|
||||
p4.ID: p4,
|
||||
},
|
||||
}, &fakes.FakeLoader{})
|
||||
|
||||
r := ps.SecretsManager(context.Background())
|
||||
require.Equal(t, p3, r)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProcessManager_shutdown(t *testing.T) {
|
||||
p := &plugins.Plugin{JSONData: plugins.JSONData{ID: "test-datasource", Type: plugins.TypeDataSource}} // Backend: true
|
||||
backend := &fakes.FakeBackendPlugin{}
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestRenderer(t *testing.T) {
|
||||
|
||||
r, exists := m.Renderer(context.Background())
|
||||
require.False(t, exists)
|
||||
require.Equal(t, 4, numLoaded)
|
||||
require.Equal(t, 3, numLoaded)
|
||||
require.Equal(t, 0, numUnloaded)
|
||||
require.Nil(t, r)
|
||||
})
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"type": "secretsmanager"
|
||||
}
|
||||
@@ -5,13 +5,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/kvstore"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -22,45 +17,8 @@ const (
|
||||
func ProvideService(
|
||||
sqlStore db.DB,
|
||||
secretsService secrets.Service,
|
||||
pluginsManager plugins.SecretsPluginManager,
|
||||
kvstore kvstore.KVStore,
|
||||
features featuremgmt.FeatureToggles,
|
||||
cfg *setting.Cfg,
|
||||
) (SecretsKVStore, error) {
|
||||
var logger = log.New("secrets.kvstore")
|
||||
var store SecretsKVStore
|
||||
ctx := context.Background()
|
||||
store = NewSQLSecretsKVStore(sqlStore, secretsService, logger)
|
||||
err := EvaluateRemoteSecretsPlugin(ctx, pluginsManager, cfg)
|
||||
if err != nil {
|
||||
logger.Debug("secrets manager evaluator returned false", "reason", err.Error())
|
||||
} else {
|
||||
// Attempt to start the plugin
|
||||
var secretsPlugin secretsmanagerplugin.SecretsManagerPlugin
|
||||
secretsPlugin, err = StartAndReturnPlugin(pluginsManager, ctx)
|
||||
namespacedKVStore := GetNamespacedKVStore(kvstore)
|
||||
if err != nil || secretsPlugin == nil {
|
||||
logger.Error("failed to start remote secrets management plugin")
|
||||
if isFatal, readErr := IsPluginStartupErrorFatal(ctx, namespacedKVStore); isFatal || readErr != nil {
|
||||
// plugin error was fatal or there was an error determining if the error was fatal
|
||||
logger.Error("secrets management plugin is required to start -- exiting app")
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// as the plugin is installed, SecretsKVStoreSQL is now replaced with
|
||||
// an instance of SecretsKVStorePlugin with the sql store as a fallback
|
||||
// (used for migration and in case a secret is not found).
|
||||
store = NewPluginSecretsKVStore(secretsPlugin, secretsService, namespacedKVStore, features, WithCache(store, 5*time.Second, 5*time.Minute), logger)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Debug("secrets kvstore is using the default (SQL) implementation for secrets management")
|
||||
}
|
||||
|
||||
store := NewSQLSecretsKVStore(sqlStore, secretsService, log.New("secrets.kvstore"))
|
||||
return WithCache(store, 5*time.Second, 5*time.Minute), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/kvstore"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
secretskvs "github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var errPluginUnavailable = errors.New("remote secret management plugin is unavailable")
|
||||
|
||||
// MigrateFromPluginService This migrator will handle migration of the configured plugin secrets back to Grafana unified secrets
|
||||
type MigrateFromPluginService struct {
|
||||
cfg *setting.Cfg
|
||||
sqlStore db.DB
|
||||
secretsService secrets.Service
|
||||
manager plugins.SecretsPluginManager
|
||||
kvstore kvstore.KVStore
|
||||
}
|
||||
|
||||
func ProvideMigrateFromPluginService(
|
||||
cfg *setting.Cfg,
|
||||
sqlStore db.DB,
|
||||
secretsService secrets.Service,
|
||||
manager plugins.SecretsPluginManager,
|
||||
kvstore kvstore.KVStore,
|
||||
|
||||
) *MigrateFromPluginService {
|
||||
return &MigrateFromPluginService{
|
||||
cfg: cfg,
|
||||
sqlStore: sqlStore,
|
||||
secretsService: secretsService,
|
||||
manager: manager,
|
||||
kvstore: kvstore,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MigrateFromPluginService) Migrate(ctx context.Context) error {
|
||||
logger.Debug("starting migration of plugin secrets to unified secrets")
|
||||
// access the plugin directly
|
||||
plugin, err := secretskvs.StartAndReturnPlugin(s.manager, context.Background())
|
||||
if err != nil {
|
||||
return errPluginUnavailable
|
||||
}
|
||||
// Get full list of secrets from the plugin
|
||||
res, err := plugin.GetAllSecrets(ctx, &secretsmanagerplugin.GetAllSecretsRequest{})
|
||||
if err != nil {
|
||||
logger.Error("Failed to retrieve all secrets from plugin")
|
||||
return err
|
||||
}
|
||||
totalSecrets := len(res.Items)
|
||||
logger.Debug("retrieved all secrets from plugin", "num secrets", totalSecrets)
|
||||
// create a secret sql store manually
|
||||
secretsSql := secretskvs.NewSQLSecretsKVStore(s.sqlStore, s.secretsService, logger)
|
||||
for i, item := range res.Items {
|
||||
logger.Debug(fmt.Sprintf("Migrating secret %d of %d", i+1, totalSecrets), "current", i+1, "secretCount", totalSecrets)
|
||||
// Add to sql store
|
||||
err = secretsSql.Set(ctx, item.Key.OrgId, item.Key.Namespace, item.Key.Type, item.Value)
|
||||
if err != nil {
|
||||
logger.Error("Error adding secret to unified secrets", "orgId", item.Key.OrgId,
|
||||
"namespace", item.Key.Namespace, "type", item.Key.Type)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, item := range res.Items {
|
||||
logger.Debug(fmt.Sprintf("Cleaning secret %d of %d", i+1, totalSecrets), "current", i+1, "secretCount", totalSecrets)
|
||||
// Delete from the plugin
|
||||
_, err := plugin.DeleteSecret(ctx, &secretsmanagerplugin.DeleteSecretRequest{
|
||||
KeyDescriptor: &secretsmanagerplugin.Key{
|
||||
OrgId: item.Key.OrgId,
|
||||
Namespace: item.Key.Namespace,
|
||||
Type: item.Key.Type,
|
||||
}})
|
||||
if err != nil {
|
||||
logger.Error("Error deleting secret from plugin after migration", "orgId", item.Key.OrgId,
|
||||
"namespace", item.Key.Namespace, "type", item.Key.Type)
|
||||
continue
|
||||
}
|
||||
}
|
||||
logger.Debug("Completed migration of secrets from plugin")
|
||||
|
||||
// The plugin is no longer needed at the moment
|
||||
err = secretskvs.SetPluginStartupErrorFatal(ctx, secretskvs.GetNamespacedKVStore(s.kvstore), false)
|
||||
if err != nil {
|
||||
logger.Error("Failed to remove plugin error fatal flag", "error", err.Error())
|
||||
}
|
||||
// Reset the fatal flag setter in case another secret is created on the plugin
|
||||
secretskvs.ResetPlugin()
|
||||
|
||||
logger.Debug("Shutting down secrets plugin now that migration is complete")
|
||||
// if `use_plugin` wasn't set, stop the plugin after migration
|
||||
if !s.cfg.SectionWithEnvOverrides("secrets").Key("use_plugin").MustBool(false) {
|
||||
err := s.manager.SecretsManager(ctx).Stop(ctx)
|
||||
if err != nil {
|
||||
// Log a warning but don't throw an error
|
||||
logger.Error("Error stopping secrets plugin after migration", "error", err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user