Linking external images securely

Hello,

I have a Grafana instance which requires the user to log in. I would like to show two images on in the dashboard (currently linked in the PictureIt Panel and the HTML-Panel but I am flexible on the panel choice). Because Grafana does not allow for secure image uploading, I am currently relying on a third-party service to host the images and then paste the link in the Grafana panel.

But because the images are confidential, I cannot ensure any kind of authentication. Everyone who finds the image URL would be able to see the image.

Is there a way I can pass some sort of Authentication Header along with the image request inside Grafana, so logged-in Grafana users can see the image, but nobody else can (even if they have the URL)? Again, I’m completely flexible on the authentication type (Apache BasicAuth, Blob Storage Token Auth etc.), it should just work with Grafana.

I’ve already looked into:

  • IP whitelisting for the image server (not practical, users login from different locations)
  • CORS (only blocks requests when the image is embedded on other sites, but not when accessed directly)
  • Using the username:password@www.example,com format (this is a deprecated feature in most modern browsers and does not work anymore)

The standard way of doing a thing like this would be to send an HTTP Auth Header, but I don’t believe Grafana (or any panels I found so far) support this.

Is there anything else I can try? Some input would be very much appreciated.

Cheers

Hi @d2za4j5 that’s an interesting question. We do something similar, but in a way that would probably be total overkill for you (more on that below).

I can think of two simple-ish solutions for your use case. Here I’m assuming that anyone who has access to this dashboard should be able to access the images. And that it’s also ok for them to see any (static) credentials that are used to access the image - which could in turn allow them to access the images in the future, even if they lose access to the dashboard.

  • Without any additional auth: Give the images a very long/random URL that would not really be guessable. A bit like the the secret link to a shared Google Doc. I don’t know whether that’s “security best practice”, but it may meet your requirements
  • With an auth header: Use the text panel is HTML mode, and in it put some JS that loads the image, including a manually set auth header in the request. I do think that should work, as whatever request you trigger will simply be executed by the browser. You’ll need to set GF_PANELS_DISABLE_SANITIZE_HTML=true in your Grafana config for the JS to work.

On the latter point, off the top of my head I can’t think of any other option that would make Grafana itself include additional auth/headers with a request like that. Those options might exist if you were loading the image via a datasource, but you’re not. By default, requests from the Grafana front-end will only include a cookie (grafana_session), which is what Grafana’s own back-end uses for auth.

===
In case it helps you (or anyone else) brainstorm, the following is our situation and approach. We have a GraphQL backend that’s separate from Grafana’s, but that is accessible by authenticated Grafana users. We use this to load some resources (metadata, images) from that backend into Grafana dashboards. In a nutshell, the auth and request flow is as follows:

  1. The backend has an auth endpoint. When the user loads Grafana their browser pings that endpoint, including the Grafana cookie in the request.
  2. The auth endpoint uses that cookie to ping the “who am I” endpoint on the Grafana API, validating the cookie and getting the user’s identity. Based on this identity, our auth endpoint generates a JWT for this user, which is passed back to the browser and stored in another cookie (a “JWT auth cookie”).
  3. Any further requests that the user’s browser makes to the GraphQL backend are authenticated using the JWT that they now have as a cookie.

There is of course some session management around that, but the overall idea is hopefully clear.

The nice thing about this approach is that the GraphQL backend is aware of the user’s actual identity, and can allow (or disallow) access to resources on that basis.

There is a cool potential enhancement to the above (somewhat clunky) “two cookie” approach, enabled by the Nginx “Authentication via Subrequest” proxying capability. Basically, it’s possible to eliminate the explicit auth/JWT cookie requests in steps 1 and 2, and instead have the proxy do auth “behind the scenes” based on a sub-request that includes the grafana_session cookie.

Though at that point it may simply be time to roll out OAuth2…

2 Likes

Sorry for the (very) late reply and thank you for sharing your extensive ideas. Could you elaborate a little more on your last paragraph? I actually have Azure AD OAuth2 setup. This is the main way to sign into my installation. I have already talked to an Azure Expert about using Azure Storage Accounts (Blob Storage) to serve images only to logged-in users. However Storage Accounts also requires the use of Auth Headers (theoretically getting them and using them to retrieve images from Blob Storage is easy, but Grafana does not allow to set Auth Headers). Are you maybe aware of another solution that ties into Azure AD without requiring the use of Auth Headers? I am even already running nginx as a Reverse Proxy infront of Grafana, so maybe there is a nginx plugin of sorts I could use?