318 lines
10 KiB
Plaintext
318 lines
10 KiB
Plaintext
<page xmlns="http://projectmallard.org/1.0/"
|
||
type="topic" id="api-auth" xml:lang="fr">
|
||
|
||
<info>
|
||
<link type="guide" xref="index#api" />
|
||
<revision docversion="0.1" date="2013-01-04" status="draft"/>
|
||
<revision docversion="0.2" date="2015-12-18" status="draft"/>
|
||
<credit type="author">
|
||
<name>Frédéric Péters</name>
|
||
<email>fpeters@entrouvert.com</email>
|
||
</credit>
|
||
<desc>Accès aux API, identifiants et clé d’utilisation, utilisateurs, signature, etc.</desc>
|
||
|
||
</info>
|
||
|
||
<title>Authentification</title>
|
||
|
||
<section>
|
||
<title>Gestion des accès aux API</title>
|
||
|
||
<p>
|
||
La création d’accès aux API se fait dans l’espace de paramétrage, dans la
|
||
section « Sécurité », en suivant le lien « Accès aux API ». Le bouton « Nouvel
|
||
accès aux API » permet d’ajouter un accès, et il faut indiquer :
|
||
</p>
|
||
|
||
<list>
|
||
|
||
<item><p>Nom : le nom choisi pour l’accès, qui sera affiché dans la page des
|
||
accès ;</p></item>
|
||
|
||
<item><p>Description : pour se rappeler de l’usage prévu pour cet
|
||
accès ;</p></item>
|
||
|
||
<item><p>Identifiant d’accès : le nom de l’utilisateur à utiliser pour
|
||
l’authentification HTTP Basic, ou le paramètre <code>orig</code> pour
|
||
l’authentification par signature ;</p></item>
|
||
|
||
<item><p>Clé d’accès : le mot de passe pour l’authentification HTTP Basic de
|
||
cet utilisateur, ou la clé partagée à utiliser pour l’authentification par
|
||
signature ;</p></item>
|
||
|
||
<item><p>Rôles : liste des rôles automatiquement obtenus lorsque cet accès est
|
||
utilisé. Par exemple, s’il s’agit de permettre de lister des demandes d’une
|
||
certaine démarche, il faut indiquer un rôle qui permet de voir les demandes.
|
||
Ce rôle est à déterminer selon le paramétrage du formulaire de la démarche
|
||
et/ou de son workflow.</p></item>
|
||
|
||
</list>
|
||
|
||
<note><p>Il est conseillé de créer des «rôles techniques» dédiés aux API. Ces
|
||
rôles ne sont jamais donnés à des utilisateurs de la plateforme, ils sont
|
||
utilisés uniquement dans le paramétrage des accès. Typiquement une démarche est
|
||
paramétrée pour qu’un certain rôle technique ait accès à ses demandes, et ce
|
||
même rôle est indiqué dans l’accès aux API dédié.</p></note>
|
||
|
||
<section>
|
||
<title>Définitions de l’usager concerné</title>
|
||
|
||
<p>
|
||
Si l’authentification utilisée passe par un accès aux API qui ne donne pas de
|
||
rôle spécifique, alors il faut indiquer dans la query-string un usager qui aura
|
||
les rôles nécessaires.
|
||
</p>
|
||
|
||
<p>
|
||
C'est aussi nécessaire pour les appels concernant un usager particulier, tel
|
||
que la récupération de la liste de ses formulaires en cours.
|
||
</p>
|
||
|
||
<p>L’usager est précisé en ajoutant dans la query string :</p>
|
||
|
||
<list>
|
||
<item><p>soit un paramètre <code>email</code> pour trouver l’usager selon son
|
||
adresse électronique</p></item>
|
||
<item><p>soit un paramètre <code>NameID</code> pour trouver l’usager selon son
|
||
NameID SAML.</p></item>
|
||
</list>
|
||
|
||
</section>
|
||
</section>
|
||
|
||
<section>
|
||
<title>Authentification simple HTTP Basic</title>
|
||
|
||
<p>
|
||
Pour accéder aux API avec l’authentification HTTP Basic classique, il faut
|
||
utiliser le nom d’utilisateur (identifiant d’accès) et le mot de passe (clé
|
||
d’accès) de l’accès configuré ci-dessus.
|
||
</p>
|
||
|
||
</section>
|
||
|
||
|
||
<section>
|
||
<title>Authentification par signature de l’URL</title>
|
||
|
||
<p>
|
||
Un système d’authentification basé sur une signature ajoutée à la fin de l’URL
|
||
est également disponible. Il peut être jugé plus sécurisé que
|
||
l’authentification HTTP Basic, par ailleurs il assure une protection plus
|
||
explicite contre le rejeu. Ce système est spécifique à Publik/w.c.s.
|
||
</p>
|
||
|
||
<section id="req-security-shared-secret">
|
||
<title>Signature des requêtes</title>
|
||
|
||
<p>
|
||
La signature d’un appel aux API passe par une clé partagée à
|
||
configurer des deux cotés de la liaison, la signature est du type HMAC;
|
||
l’algorithme de hash à employer est passé en paramètre.
|
||
</p>
|
||
|
||
<note><p>En ce qui concerne l’algorithme de hash, il est préconisé d’utiliser
|
||
SHA-256 par respect du <link
|
||
href="http://references.modernisation.gouv.fr/securite">Référentiel Général
|
||
de Sécurité (RGS)</link>.</p></note>
|
||
|
||
<p>
|
||
La signature est à calculer sur la query string encodée complète, en
|
||
enlevant les paramètres terminaux <code>algo</code>, <code>timestamp</code>,
|
||
<code>nonce</code>, <code>orig</code> et <code>signature</code> s’ils sont
|
||
présents.</p>
|
||
|
||
<p>La formule de calcul de la signature est la suivante :</p>
|
||
|
||
<code>
|
||
BASE64(HMAC-HASH(query_string+'algo=HASH&timestamp=' + timestamp + '&nonce=' + nonce '&orig=" + orig, clé))
|
||
</code>
|
||
|
||
<list>
|
||
|
||
<item><p><code>timestamp</code> est la date dans la zone GMT au format ISO8601
|
||
en se limitant à la précision des secondes (ex : 2012-04-04T12:34:00Z),
|
||
</p></item>
|
||
|
||
<item><p><code>nonce</code> est un aléa, typiquement la réprésentation hexa
|
||
d’un nombre pseudo-aléatoire de 128 bits,</p></item>
|
||
|
||
<item><p><code>orig</code> est une chaîne précisant l’émetteur de la
|
||
requête,</p></item>
|
||
|
||
<item><p>algo est une chaîne représentant l’algorithme de hachage utilisé, sont
|
||
définis : sha1, sha256, sha512 pour les trois algorithmes correspondant.
|
||
L’utilisation d’une valeur différente n’est pas définie. L’algorithme sha256
|
||
est préconisé.</p></item>
|
||
|
||
</list>
|
||
|
||
<p>
|
||
La query string définitive est ainsi :
|
||
</p>
|
||
|
||
<code>
|
||
<var>qs_initial</var>&algo=<var>algo</var>&timestamp=<var>timestamp</var>&nonce=<var>nonce</var>&orig=<var>orig</var>&signature=<var>signature</var>
|
||
</code>
|
||
|
||
</section>
|
||
|
||
<section>
|
||
<title>Configuration des clés partagées</title>
|
||
|
||
<p>
|
||
La définition des clés se fait lors de la création des accès aux API (voir
|
||
plus haut). Lors de la création d'un nouvel accès, l’identifiant d'accès
|
||
correspond au «orig» et la clé d'accès est la clé de signature. Les rôles sont
|
||
ceux qui seront obtenus lors de l’usage de l’accès.
|
||
</p>
|
||
|
||
<p>
|
||
Les clés partagées peuvent aussi être définies dans le fichier
|
||
<code>site-options.cfg</code>, dans une section <code>[api-secrets]</code>, par
|
||
exemple :
|
||
</p>
|
||
|
||
<code>
|
||
[api-secrets]
|
||
intranet = 12345
|
||
</code>
|
||
|
||
</section>
|
||
|
||
<section>
|
||
<title>Exemples d'implémentation de l’algorithme de signature</title>
|
||
|
||
<p>
|
||
Voici des exemples de code pour créer des URLs signées selon l’algorithme
|
||
expliqué ci-dessus.
|
||
</p>
|
||
|
||
<listing>
|
||
<title>Python</title>
|
||
<code mime="text/x-python">
|
||
#! /usr/bin/env python3
|
||
|
||
import base64
|
||
import datetime
|
||
import hashlib
|
||
import hmac
|
||
import random
|
||
import urllib.parse
|
||
|
||
|
||
def sign_url(url, key, algo='sha256', orig=None, timestamp=None, nonce=None):
|
||
parsed = urllib.parse.urlparse(url)
|
||
new_query = sign_query(parsed.query, key, algo, orig, timestamp, nonce)
|
||
return urllib.parse.urlunparse(parsed[:4] + (new_query,) + parsed[5:])
|
||
|
||
|
||
def sign_query(query, key, algo='sha256', orig=None, timestamp=None, nonce=None):
|
||
if timestamp is None:
|
||
timestamp = datetime.datetime.utcnow()
|
||
timestamp = timestamp.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||
if nonce is None:
|
||
nonce = hex(random.getrandbits(128))[2:].rstrip('L')
|
||
new_query = query
|
||
if new_query:
|
||
new_query += '&'
|
||
new_query += urllib.parse.urlencode((('algo', algo), ('timestamp', timestamp), ('nonce', nonce)))
|
||
if orig is not None:
|
||
new_query += '&' + urllib.parse.urlencode({'orig': orig})
|
||
signature = base64.b64encode(sign_string(new_query, key, algo=algo))
|
||
new_query += '&' + urllib.parse.urlencode({'signature': signature})
|
||
return new_query
|
||
|
||
|
||
def sign_string(s, key, algo='sha256', timedelta=30):
|
||
if not isinstance(key, bytes):
|
||
key = key.encode('utf-8')
|
||
if not isinstance(s, bytes):
|
||
s = s.encode('utf-8')
|
||
digestmod = getattr(hashlib, algo)
|
||
hash = hmac.HMAC(key, digestmod=digestmod, msg=s)
|
||
return hash.digest()
|
||
|
||
|
||
# usage:
|
||
url = sign_url('https://www.example.net/uri/?arg=val&arg2=val2', 'user-key', orig='user')
|
||
</code>
|
||
</listing>
|
||
|
||
<listing>
|
||
<title>PHP</title>
|
||
<code mime="application/x-php">
|
||
<?php
|
||
|
||
function sign_url(string $url, string $orig, string $key) {
|
||
$parsed_url = parse_url($url);
|
||
$timestamp = gmstrftime("%Y-%m-%dT%H:%M:%SZ");
|
||
$nonce = bin2hex(random_bytes(16));
|
||
$new_query = '';
|
||
if (isset($parsed_url['query'])) {
|
||
$new_query .= $parsed_url['query'] . '&';
|
||
}
|
||
$new_query .= http_build_query(array(
|
||
'algo' => 'sha256',
|
||
'timestamp' => $timestamp,
|
||
'nonce' => $nonce,
|
||
'orig' => $orig));
|
||
$signature = base64_encode(hash_hmac('sha256', $new_query, $key, $raw_output = true));
|
||
$new_query .= '&' . http_build_query(array('signature' => $signature));
|
||
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
|
||
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
|
||
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
|
||
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
|
||
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
|
||
$pass = ($user || $pass) ? "$pass@" : '';
|
||
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
|
||
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
|
||
return "$scheme$user$pass$host$port$path?$new_query$fragment";
|
||
}
|
||
|
||
# usage:
|
||
url = sign_url("http://www.example.net/uri/?arg=val&arg2=val2", "user", "user-key");
|
||
|
||
?>
|
||
</code>
|
||
</listing>
|
||
|
||
<listing>
|
||
<title>Shell (bash)</title>
|
||
<code mime="application/x-shellscript">
|
||
#!/bin/bash
|
||
|
||
url="http://www.example.net/uri/?arg=val&arg2=val2"
|
||
orig="user"
|
||
key="user-key"
|
||
|
||
function rawurlencode() {
|
||
local string="${1}"
|
||
local strlen=${#string}
|
||
local encoded=""
|
||
local pos c o
|
||
for ((pos=0; pos<strlen; pos++)); do
|
||
c=${string:$pos:1}
|
||
case "$c" in
|
||
[-_.~a-zA-Z0-9] ) o="${c}" ;;
|
||
* ) printf -v o '%%%02x' "'$c"
|
||
esac
|
||
encoded+="${o}"
|
||
done
|
||
echo "${encoded}"
|
||
}
|
||
|
||
now=$(date -u +%FT%TZ);
|
||
nonce=$(od -N16 -txL /dev/urandom | cut -c8- | tr -d " ")
|
||
qs="algo=sha256&timestamp=$now&nonce=$nonce&orig=$orig"
|
||
sig=$(rawurlencode $(echo -n "$qs" | openssl dgst -binary -sha256 -hmac "$key" | base64))
|
||
signed="${url}?$qs&signature=$sig"
|
||
echo "$signed"
|
||
</code>
|
||
</listing>
|
||
|
||
</section>
|
||
</section>
|
||
|
||
</page>
|