Energy MonitoringProgramming

Access Grafana v6 without a login screen

Unlike Grafana 5, we can’t use the ‘old’ method of a proxy login to get a cookie for semi-permanent login.

This post investigates options to achieve the goal of:

  • Provide a user a “secret” URL which allows them to login to grafana without typing into the login screen. Just “open and go!”
  • Preferably needs to login the user to grafana.

And off we go. Let’s check the general docs on auth in grafana.

Basic Auth

The docs show you can use basic http auth such as

http://admin:admin@localhost:3000/api/org

but this is problematic: most browsers pop up security warnings, and some plugs-in don’t work.

Not very promising. It’s useful for testing auth token though:

http://api_key:eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk@localhost:3000/api/org

Auth Token

Appears to be a Preferred Way. See docs on tokens. It “… applies when using Grafana’s built in user authentication, LDAP (without Auth proxy) or OAuth integration.”

Use the API to create a “token” such a “skadjfhkljh34sd”. Then pass this on EVERY CALL. This can be passed:

  • Via the basic_auth special user (see above)
  • Via a header:

Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk

  • Maybe? via the header from a proxy? The proxy could use a secret url (as in v5 with nginx) to grab the token, then pass it to grafana. However, this would have to be done on every call, so a session cookie would be required?
  • Somehow with OAuth?

Cookies

Website login to grafana uses a cookie with the grafana session id:

grafana_session=ee8bac4f67ffa7f1617011dcb6718976

Unclear yet, but maybe OAuth gets this session cookie too.

OAuth2

See the grafana docs on this. OAuth is a standard auth system. See an intro. Looks like we’d use “Resource Owner Password Credentials” method. A mock (i.e. one static user) example with OAuth worked ok.

Configure Nginx and PHP

# /etc/nginx/nginx.conf 
# in server {}:
location /oauth/ {
# pass through to this server (not the proxy)
}
location ~* .php$ {
# execute php files
fastcgi_pass unix:/run/php-fpm/www.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
}
# /etc/php-fpm.d/www.conf to match
listen = /run/php-fpm/www.sock
listen.owner = nginx # or nobody
listen.group = ngin # or nogroup

Configure Grafana

Configure grafana as per the example with OAuth

Add mock PHP OAuth Server

Copy php files to nginx webroot as per the example with OAuth

Test it

Now, we see a button to “login with oauth”. This is what happens

  • Client clicks button to “login with oauth”
  • Grafana requests http://monitor.phisaver.com/oauth/auth.php with:
    • access_type=online
    • client_id=phisaverorwhatever
    • redirect_uri=grafana:3000 oauth login
    • response_type=code
    • scope=user%3Aemail+read%3Aorg
    • state=some-long-cdoe
  • our auth.php code returns a code and redirects to:
  • http://monitor.phisaver.com/login/generic_oauth
    • state=same-long-string
    • code=******
    • cookies
  • Grafana reads the code and logins in the user
  • Cookies include (not sure what goes where)
    • grafana_remember,=asdfhalsjkdfhasdf
    • grafana_user=name
    • oauth_state=asdfasdfasdfasdf
    • (response) grafana_session=asdfasdf
    • thee three grafana_* are sent in future requests

Ok, that’s grand. Now let’s do this (from the example with OAuth):

“If you want users login automatically, you’ll need to place custom.js in /usr/share/grafana/public/build/index.html (Ubuntu / Debian) at the bottom of <body> tag”

Ok, that works: it ‘clicks’ the OAuth login and that runs requests authorisation. But, this don’t actaully help use to automatically login from a secret token. The oauth server would need to be given the token by Grafana, and that don’t seem possible.

How to provide a secret url and automatically login?

This wasn’t straightforward and I’m not 100% confident about the security. For my purposes (read-only, anonymous dashboards) that’s okay.

  1. Write a PHP command line script to generate a “code”. This code is a AES encrypted string containing the username and password. In addition, we append the Init Value (required for AES, not secret). This is all encrypted with a secret 256 bit key and base64 encoded. Phew!
  2. Write a PHP script to take the code as a parameter, decode it and find the username and password. This requires it knows the secret key.
  3. The same script makes a post call to /login in Grafana and receives a grafana_session cookie. It returns this cookie to the original client together with a redirect to /login
  4. /login is automatically send the cookie by the client’s browser, and so is logged in.

Setup Prerequistes

  • nginx (see above)
  • php ( see above)
  • lib-curl (for ubuntu, others might have it by default):
sudo apt-get install php-curl

Generate a ‘code’ to login

<?php
/*
make-me-a-code.php
Generate a code for auto-login
*/
$key = 'YOUR 16 CHAR KEY!';
$method = 'AES-256-CBC';
$user = $argv[1];
$password = $argv[2];
$pltxt = "$user:$password";
$length = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method));
$encrypted = openssl_encrypt($pltxt, $method, $key, OPENSSL_RAW_DATA, $iv);
$ctxt = base64_encode($iv . $encrypted) ;
$escaped= str_replace(['+','=','/'],['-','','_'],$ctxt);
// convert silly chars like encoder code does
print ($escaped . "\n")

Web-server script to process ‘code’ and login

<?php
/*
get-me-logged-in.php
eg : https://localhost/get-me-logged-in.php?code=asdfasdfasdfasdf
*/
define("SECRET_KEY","SAME KEY as in generator script!");
define("METHOD","AES-256-CBC");

function not_authorised_page() {
header("HTTP/1.1 403 Unauthorized");
print("Sorry, we couldn't not log you in automatically.");
}
// Get iv and cipher text from query string
//
$url = $_GET;
$url = $_SERVER['REQUEST_URI'];
parse_str( parse_url( $url, PHP_URL_QUERY), $query_array );
$code_b64 = $query_array['code'];
$code_b64 = str_replace(['-','','_'],['+','=','/'],$code_b64); // convert silly chars like encoder code does
$code = base64_decode($code_b64);
$iv = substr($code,0,16);
$data = substr($code,16);
// Get plaintext, using the ciphertext, the secret (key) and the provided iv
$pltxt = openssl_decrypt($data, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv); // OPENSLL_RAW_DATA => not b64 encoded

if (! $pltxt) {
not_authorised_page();
exit;
}
// Plaintext is in the form user:password
//
list($user,$password) = explode(':', $pltxt);
// Login by posting a form with the necessary user/pass
//
$prefix = $_SERVER['HTTPS'] ? 'https://' : 'http://';
$domain = $_SERVER['HTTP_HOST'];
$c = curl_init("$prefix$domain/login");
if ($debug) error_log("Redirect to $prefix$domain/login");
// Ignore previous session cookies
curl_setopt($c, CURLOPT_COOKIESESSION, 1);
// For writing to memory
curl_setopt($c,CURLOPT_COOKIEJAR,'');
curl_setopt($c,CURLOPT_COOKIEFILE,'');
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS, "user=$user&password=$password");
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1); // return as string
$page = curl_exec($c);
if ($debug) { error_log("Page:"); error_log($page); error_log("COOKIE LIST:"); }
// Check we logged in ok
//
$response_text = json_decode($page);
if ($debug) print_r($response_text);
if ($response_text->message != "Logged in") {
not_authorised_page();
exit;
}
// Extract the login cookie
//
$grafana_session_cookie = NULL;
foreach(curl_getinfo($c, CURLINFO_COOKIELIST) as $cookie_line) {
if ($debug) print_r($cookie_line,1);
$cookie_parsed = preg_split('/\t+/',$cookie_line);
if ($cookie_parsed[5]="grafana_session") {
$grafana_session_cookie = $cookie_parsed;
}
}
if (is_null($grafana_session_cookie)) {
not_authorised_page();
exit;
}
if ($debug) error_log(print_r($grafana_session_cookie,TRUE));
curl_close($c);
// Return the session cookie to client. They will use it on future calls and
// hence be logged in
// setcookie($grafana_session_cookie[5],$grafana_session_cookie[6],time() + 60*60*24*30,'/');
print("Logging you in as $user…\n");
header("Refresh: 3; URL=/"); // load home dashboard

Leave a Reply

Your email address will not be published. Required fields are marked *