grafana-toolbox / grafana-client

Python client library for accessing the Grafana HTTP API.
MIT License
107 stars 30 forks source link

Feature: Organisation-awareness #126

Closed lilatomic closed 1 year ago

lilatomic commented 1 year ago

Grafana can support multiple organisations within a single instance. The organisation that requests are made to is stateful; that is, a request is made to change the user's context to an organisation, and all requests are then made against that organisation. This statefulness can lead to trouble if the org context is not changed at the beginning of the execution or if it is changed during execution.

Including the orgId query parameter will cause grafana to automatically change the user's context and returns a 302 Found for the same URL. This results in a retry of the request in the correct org.

Proposal: add an org_id parameter to GrafanaApi, which is passed to GrafanaClient, which adds it to self.s.params. If you'd like this, I can contribute the MR.

amotl commented 1 year ago

Dear Daniel,

thank you for suggesting that. The documentation at ^1 educates us about the X-Grafana-Org-Id Header:

X-Grafana-Org-Id is an optional property that specifies the organization to which the action is applied. If it is not set, the created key belongs to the current context org. Use this header in all requests except those regarding admin.

If that would be an appropriate alternative, I would prefer that variant, instead of using a query parameter. A corresponding patch will be much welcome.

With kind regards, Andreas.

amotl commented 1 year ago

When adding a corresponding optional parameter to GrafanaClient, like organization_id: int = None, it will be easy to populate it into an additional header value right away.

self.s.headers["X-Grafana-Org-Id"] = self.organization_id 

https://github.com/panodata/grafana-client/blob/d5b30d45a1fe585fcd38e4057fee01079354a087/grafana_client/client.py#L112-L113

lilatomic commented 1 year ago

Oh, wow, I didn't know about the X-Grafana-Org-Id header! That makes much more sense.

amotl commented 1 year ago

Oh.

The X-Grafana-Org-Id HTTP request header is an optional property that specifies the organization to which the action is applied. If it is not set, the created key belongs to the current context org.

I've re-read the Grafana documentation statement and stumbled upon the highlighted fragment. Is it possible that this header is only meant to designate the organization identifier when creating authentication tokens? Coming from that insight, is it possible that authentication tokens are only meant to work within the context of a single organization, i.e. they are implicitly bound to an organization, and can't be used cross-organization-wise?

I have to admit that I am not familiar with those details of the Grafana API, so I am humbly asking for education on this matter. Also, may I ask if you verified that your change GH-127 works as intended for you? Are you using it to successfully run requests to a real Grafana instance, impersonating different organizations?

Use this header in all requests except those regarding admin.

This is the other detail I've stumbled upon. Maybe it is inappropriate to submit this header on all requests? Would the code need to omit submitting it on admin-type requests?

lilatomic commented 1 year ago

Yeah, Grafana orgs have complexities with authentication and requests. I did some more reading and testing so hopefully what follows is correct and complete.


Grafana API Tokens (and later service accounts) are resources that exist within an organisation and so are only valid for that org. So if you create an API token in Org1 with "editor" permission, it can only edit dashboards in Org1. Session cookies can be acquired by regular login (through Basic Auth, Oauth, or however else Grafana is handling logins). Conceptually, session tokens are for normal users, who might belong to multiple organisations, and usually are using the Web UI. They can manually change which org they're viewing with a dropdown. This sets the "current context org". So when a user goes to the /dashboards endpoint, Grafana knows what org they were last in. Admin operations (eg adding a user) are for the Grafana instance itself, not any of the provisioned orgs. So, using API Tokens doesn't make sense, since no tokens have permissions over the instance. And, using the X-Grafana-Org-Id header doesn't make sense, since these are stats for the whole instance, not any org. That said, keeping the X-Grafana-Org-Id header doesn't break things, and even Grafana sends it with many admin requests.


That section on the X-Grafana-Org-Id header is correct in it's context. That is, if you're creating an api key, the X-Grafana-Org-Id will specify a specific org; without it, the request will be handled by the current context org. The section is also true of all other org-level requests, though. I'm a bit puzzled by why that documentation section is in the Authentication API and not in the API Tutorial at the top of that section...


Doing some reverse-engineering: The orgId query parameter is not included in the requests, but is added to the url (probably by the frontend). I think this is to make URL sharing work correctly. If a user copypastes the URL directly into their browser, Grafana can't set the headers yet; but the query params are part of the URL, so Grafana can change the context org and trigger a reload with the 302 Found.

amotl commented 1 year ago

That said, keeping the X-Grafana-Org-Id header doesn't break things, and even Grafana sends it with many admin requests.

Excellent. Thanks for clarifying.

That [documentation] section on the X-Grafana-Org-Id header is correct in it's context.

I see, thank you a again.

So, I guess GH-127 is good to go then?

amotl commented 1 year ago

Dear Daniel,

grafana-client 3.10.0 has been published, including your improvement. Thanks again for your contribution.

With kind regards, Andreas.

References