Page d'authentification personnalisée pour Grafana avec NGINX

Publié le 2018-05-06, dernière mise à jour: 2018-11-01

Voici un moyen relativement simple d'avoir une page d'authentification personnalisée pour Grafana sans altérer l'application ce qui va nous permettre de pouvoir la mettre à jour sans problème. L'idée est d'utiliser NGINX comme reverse proxy pour intercepter les requêtes en direction de la page d'authentification de Grafana. NGINX renverra alors une page d'authentification HTML écrite par nos soins. Cette page contiendra un formulaire login/mot de passe dont l'envoie cible la page légitime de Grafana. Ainsi c'est le mécanisme d'authentification de Grafana qui est utilisé (LDAP ou interne). Selon le retours de la page légitime, notre page personnalisée redirigera ou non vers Grafana. En résumé nous utilisons le concept de "man in the middle".

Commençons par installer NGINX avec le gestionnaire adapté à la distribution en question (apt, yum, dnf).

apt-get install nginx
yum install nginx
dnf install nginx

Passons à la partie configuration /etc/nginx/nginx.conf. Nous utiliserons uniquement HTTPS ici (la procédure pour générer le certificat ne sera pas abordée).


# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

# Settings for a TLS enabled server.

    server {
        listen       443 ssl http2 default_server;
        listen       [::]:443 ssl http2 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        ssl_certificate "/etc/nginx/certs/cert.cert";
        ssl_certificate_key "/etc/nginx/certs/cert.key";
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

}

Jusque là, rien de particulier. Pour la suite nous considérerons Grafana installé sur la même machine et écoutant sur 127.0.0.1:3000 pour que seul NGINX puisse y avoir accès. Également Grafana doit être configuré avec la directive root_url=/grafana dans /etc/grafana/grafana.ini car nous hébergerons la page d'authentification à la racine dans NGINX.

Ajoutons un fichier de configuration pour Grafana dans /etc/nginx/default.d/grafana.conf :

location /grafana/ {
        proxy_pass http://127.0.0.1:3000/;
}

#Page personnalisée
location /grafana/login {
        proxy_pass https://127.0.0.1/login.html;
}

#Page légitime
location /grafana/auth {
        proxy_pass http://127.0.0.1:3000/login;
}

#Logo personnalisé
location /status/public/img/grafana_icon.svg {
        proxy_pass https://127.0.0.1/mon_logo.png;
}

#Favicon personnalisé
location /status/public/img/fav32.png {
        proxy_pass https://127.0.0.1/favicon.ico;
}

Maintenant ajoutons la page d'authentification personnalisée login.html dans /usr/share/nginx/html/. Voici une version minimaliste à étoffer selon vos besoins :

<!DOCTYPE html>
<html>
<head>
  <title>Grafana Authentification</title>
</head>
<body>
  <h1>Grafana Authentification</h1>
  <form onsubmit="return false;">
    <input type="text" id="user" placeholder="Identifiant" required><br>
    <input type="password" id="password" placeholder="Mot de passe" required><br>
    <input type="submit" value="Connexion" onClick="login()">
  </form>
</body>

<script>
function login(){
  var user = document.getElementById('user').value;
  var password = document.getElementById('password').value;
  if(user == '')
    return false;

  if(password == '')
    return false;

  var http = new XMLHttpRequest();
  var url = "/grafana/auth";
  var params = "user="+user+"&password="+password;
  http.open("POST", url, true);
  http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

  http.onreadystatechange = function() {
      if(http.readyState == 4 && http.status == 200) {
          //console.log(http.responseText);
          window.location.replace("/grafana");
      }
      //401 Unauthorized
      if(http.readyState == 4 && http.status == 401) {
          //console.log(http.responseText);
          alert("Login ou mot de passe incorrect.");
      }
  }
  http.send(params);
}
</script>
</html>

Si vous souhaitez personnaliser cette page en ajoutant des images, feuillet de style CSS ou autre, il faut placer les fichiers dans /usr/share/nginx/html/ et utiliser des URL dans le code.

Redémarrons NGINX:

systemctl restart nginx

Ouvrez votre navigateur et accédez au serveur en HTTPS pour tester: https://monserveur.local/grafana

Limites de la solution

NGINX se fie à l'URI pour fournir les pages demandée: la chaîne de caractères du bloc location. Si cette même chaîne de caractère est utilisée dans un lien par l'application nous aurons une erreur 404 en provenance de Grafana.

J'ai également pu observer l'affichage d'une erreur 404 de Grafana pendant une fraction de seconde après l'authentification sans en trouver l'origine.

Liens complémentaires