Aller au contenu principal

Authentification

Disponible

Fonctionnalité déployée et prise en charge. Les champs et commandes décrits ici sont stables au sein de la version v1alpha1.

Toute requête sur votre tenant porte une session de tenant : un jeton signé par StackNGO, valable 1 heure, qui dit qui vous êtes et dans quel tenant vous agissez. Vous l'obtenez en deux temps : un jeton d'identité (preuve de qui vous êtes), puis un échange POST /v1/iam/session.

API=https://api.dev.stackngo.fr/v1

# 1. identité (ici, celle que sng sso login a enregistrée)
ID_TOKEN=$(yq '.access_token' ~/.config/stackngo/config.yaml)

# 2. session sur le tenant acme
SESSION=$(curl -fsS -X POST "$API/iam/session" \
-H "Authorization: Bearer $ID_TOKEN" -H "Content-Type: application/json" \
-d '{"tenant":"acme"}' | jq -r .access_token)

# 3. appel d'une route du tenant
curl -fsS "$API/secrets" -H "Authorization: Bearer $SESSION"

Le modèle (pourquoi deux jetons, la frontière de tenant) est expliqué dans Le modèle identité, session, tenant.

Un seul jeton par requête​

RoutesJeton attendu dans Authorization: Bearer
POST /v1/iam/sessionle jeton d'identité (un des quatre ci-dessous)
POST /v1/iam/session/assumeune session de tenant
GET /v1/iam/me, GET /v1/tenants, POST /v1/tenants, GET /v1/templates[/{kind}]le jeton d'identité utilisateur du SSO
POST /v1/iam/logoutune session de tenant, ou le jeton d'identité (voir Se déconnecter)
toutes les autres routes /v1/… (/v1/apply, /v1/secrets, /v1/iam/policies, /v1/tenants/{tenant}/…, /v1/tier…)la session de tenant
GET /v1/iam/jwks.jsonaucun (public)
  • Un seul jeton, toujours dans Authorization: Bearer <jeton>.
  • Le tenant d'une requête vient uniquement de la session. Aucun en-tête ni paramètre ne permet d'en désigner un autre.
  • L'ancien en-tête X-Tenant-Token est refusé sur toutes les routes /v1, y compris POST /v1/iam/session et /v1/iam/policies : 400 {"error": "X-Tenant-Token is no longer supported: upgrade sng"}. Retirez-le : la session de tenant dans Authorization suffit.
  • Présenter le mauvais jeton (une identité sur une route du tenant, une session sur une route d'identité) donne 401.

1. Obtenir un jeton d'identité​

Quatre identités sont reconnues. Chacune devient un sujet auquel les policies du tenant s'appliquent.

IdentitéSujetTenant d'origineEntrer dans son tenant d'origine
Utilisateur du SSO (navigateur)user:<sub> + rôles org/nomaucun—
Application machine (client_credentials)app:<nom SSO>le tenant de son IAMApplication, si elle est enregistrée comme machinesans policy
Application qui tourne sur la plateforme (jeton d'application, audience stackngo-api)sa:<tenant>/<nom>son tenantsans policy
Job CI (jeton OIDC GitLab ou GitHub, déclaré par un IAMOIDCTrust)oidc:<tenant>/<trust>le tenant du trustsans policy

Entrer dans un autre tenant, ou entrer pour un utilisateur, exige l'action iam:assumerole sur tenant/<tenant>, accordée par une policy du tenant visé (policy gérée assume-role).

Utilisateur : navigateur (PKCE)​

Le SSO de la plateforme est https://auth.dev.stackngo.app. C'est ce que fait sng sso login ; pour l'implémenter dans un outil à vous :

  1. Ouvrir https://auth.dev.stackngo.app/login/oauth/authorize avec client_id, response_type=code, redirect_uri, scope=openid profile email, state, code_challenge et code_challenge_method=S256.
  2. Au retour sur redirect_uri, échanger le code :
curl -fsS -X POST https://auth.dev.stackngo.app/api/login/oauth/access_token \
-d grant_type=authorization_code \
-d client_id="$CLIENT_ID" \
-d code="$CODE" \
-d code_verifier="$VERIFIER" \
-d redirect_uri="http://127.0.0.1:53817/callback"
{ "access_token": "eyJhbGciOi…", "refresh_token": "eyJhbGciOi…", "token_type": "Bearer", "expires_in": 604800 }

Le client_id est celui d'une application OAuth2 du SSO de la plateforme ; celui de la CLI est intégré au binaire sng. L'enregistrement d'une application tierce sur le SSO de la plateforme n'est pas documenté dans cette version : pour un outil à vous, le plus simple est de réutiliser le jeton obtenu par sng sso login (access_token du fichier de configuration).

Seuls les comptes de l'organisation de la plateforme ouvrent une session : un utilisateur de l'annuaire de votre tenant (sng iam user invite) est refusé avec 401 invalid identity token. La durée du jeton d'identité est fixée par le SSO ; la valeur ci-dessus est illustrative.

Machine externe : client_credentials​

Déclarez une IAMApplication avec grantTypes: [client_credentials], lisez clientId et clientSecret dans son Secret <nom>-oidc, puis :

ACCESS=$(curl -fsS -X POST https://auth.dev.stackngo.app/api/login/oauth/access_token \
-d grant_type=client_credentials \
-d client_id="$CLIENT_ID" \
-d client_secret="$CLIENT_SECRET" | jq -r .access_token)

SESSION=$(curl -fsS -X POST https://api.dev.stackngo.fr/v1/iam/session \
-H "Authorization: Bearer $ACCESS" -H "Content-Type: application/json" \
-d '{"tenant":"acme"}' | jq -r .access_token)
Tenant d'origine d'une application

Une application n'entre dans son tenant sans policy que si elle est enregistrée comme machine par la plateforme ; une IAMApplication que vous déclarez ne l'est pas dans cette version. Sinon elle est traitée comme une identité sans tenant d'origine : attachez-lui assume-role (sng tenant policy attach assume-role --app <nom SSO>) et précisez toujours tenant. Voir Machines et comptes de service.

Application qui tourne sur la plateforme​

Une application déployée dans votre tenant présente le jeton d'application que la plateforme met à sa disposition, avec l'audience stackngo-api. Le corps est facultatif : sans tenant, la session est ouverte sur le tenant de l'application.

APP_TOKEN=$(cat /var/run/secrets/stackngo/token) # fichier fourni à l'application, audience stackngo-api
SESSION=$(curl -fsS -X POST https://api.dev.stackngo.fr/v1/iam/session \
-H "Authorization: Bearer $APP_TOKEN" | jq -r .access_token)

Un jeton sans l'audience stackngo-api, ou expiré, est refusé : 401 invalid identity token.

Bêta

Fonctionnalité en cours de déploiement. L'interface décrite ici est celle de la prochaine livraison : elle peut encore évoluer, et n'est pas forcément activée sur votre tenant.

La fédération des pipelines CI est activée progressivement.

Job CI : jeton OIDC​

GitLab CI (id_tokens) et GitHub Actions délivrent à chaque job un jeton OIDC signé. Si un IAMOIDCTrust du tenant le reconnaît, il s'échange contre une session. L'audience à demander est l'URL de l'API, https://api.dev.stackngo.fr, sauf si le trust en déclare une autre.

.gitlab-ci.yml
deploy:
id_tokens:
STNG_OIDC_TOKEN:
aud: https://api.dev.stackngo.fr
script:
- |
SESSION=$(curl -fsS -X POST https://api.dev.stackngo.fr/v1/iam/session \
-H "Authorization: Bearer $STNG_OIDC_TOKEN" -H "Content-Type: application/json" \
-d '{"tenant":"acme"}' | jq -r .access_token)

Pour un job CI, tenant est obligatoire. trust désigne le trust à utiliser quand plusieurs trusts du tenant pourraient correspondre ; sans lui, exactement un trust doit correspondre. La session dure au plus la maxSessionDuration du trust (1 h par défaut). Avec la CLI, rien de tout cela n'est nécessaire : voir Déployer sans secret (OIDC).

2. Ouvrir une session : POST /v1/iam/session​

POST /v1/iam/session
Authorization: Bearer <jeton d'identité>
Content-Type: application/json

Corps (facultatif pour une application de la plateforme ou une machine qui entre dans son propre tenant) :

ChampTypeDescription
tenantstringtenant visé ; requis pour un utilisateur et pour un job CI
truststringjeton OIDC de CI uniquement : nom du trust à utiliser

Tout autre champ est refusé (400 invalid JSON body).

curl -sS -X POST https://api.dev.stackngo.fr/v1/iam/session \
-H "Authorization: Bearer $ID_TOKEN" -H "Content-Type: application/json" \
-d '{"tenant":"acme"}'

Réponse 200 :

{
"tenant": "acme",
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ii4uLiJ9…",
"token_type": "Bearer",
"expires_in": 3599
}
ChampTypeDescription
tenantstringtenant de la session
origin_tenantstringtenant d'origine du sujet ; absent pour un utilisateur
access_tokenstringla session, JWT RS256
token_typestringtoujours Bearer
expires_inintegersecondes avant expiration (3600 au plus)

Tenant tout juste créé : 409 policies_pending​

Pendant les 5 minutes qui suivent la création d'un tenant, ses policies par défaut ne sont peut-être pas encore appliquées. Si votre droit d'entrée manque encore, l'API répond :

HTTP/1.1 409 Conflict
Retry-After: 10
Content-Type: application/json

{"error":"policies_pending"}

Attendez Retry-After secondes et rejouez la requête. sng tenant use le fait tout seul, jusqu'à 6 minutes. Passé 5 minutes, un droit absent donne 403.

Codes de réponse​

CodeerrorCause
400invalid JSON bodycorps illisible ou champ inconnu
400tenant requiredpas de tenant pour un utilisateur, un job CI ou une application sans tenant d'origine
400invalid tenantnom de tenant mal formé
401missing or invalid Authorization headerpas d'en-tête Authorization: Bearer
401invalid identity tokenjeton expiré, mauvaise audience, compte hors organisation de la plateforme ; pour un job CI : aucun trust (ou plusieurs) ne correspond
401session cannot be issuedsession impossible à émettre
403forbidden: no iam:AssumeRole on tenant/acmeaucune policy du tenant visé n'accorde iam:assumerole au sujet
404unknown tenantle tenant n'existe pas
409policies_pendingtenant créé il y a moins de 5 minutes ; voir Retry-After
429rate limit exceededtrop de requêtes depuis votre adresse (300 par minute) ; voir Retry-After
503authorization service unavailableservice d'autorisation injoignable : jamais d'accord par défaut, réessayez
503tenant registry unavailableregistre des tenants injoignable
503oidc federation unavailableémetteur OIDC du CI injoignable

3. Changer de tenant : POST /v1/iam/session/assume​

Échange une session du tenant A contre une session du tenant B, sans rejouer l'identité. C'est sng tenant assume.

POST /v1/iam/session/assume
Authorization: Bearer <session du tenant A>
Content-Type: application/json

{"tenant": "acme-staging"}
ChampTypeDescription
tenantstringtenant visé, requis
curl -sS -X POST https://api.dev.stackngo.fr/v1/iam/session/assume \
-H "Authorization: Bearer $SESSION" -H "Content-Type: application/json" \
-d '{"tenant":"acme-staging"}'
{
"tenant": "acme-staging",
"origin_tenant": "acme",
"access_token": "eyJhbGciOiJSUzI1NiIs…",
"token_type": "Bearer",
"expires_in": 2140
}
  • Une policy d'acme-staging doit accorder iam:assumerole sur tenant/acme-staging au sujet de la session, même s'il s'agit d'une machine.
  • La nouvelle session n'expire jamais après la session source : enchaîner des assume ne prolonge pas un accès (expires_in ci-dessus est plus court que 3600).
  • origin_tenant est conservé pour l'audit.

Codes : 401 missing or invalid Authorization header ; 401 invalid session (session absente, invalide ou expirée) ; 400 tenant required (corps absent ou sans tenant) ; puis les mêmes codes que POST /v1/iam/session (400 invalid tenant, 403, 404, 409 policies_pending, 503).

4. Utiliser la session​

curl -fsS https://api.dev.stackngo.fr/v1/secrets -H "Authorization: Bearer $SESSION"

La session ne se rafraîchit pas : à l'expiration (401), rejouez POST /v1/iam/session avec votre identité, ou POST /v1/iam/session/assume avec une session encore valide. La CLI le fait d'elle-même.

401 ou 403 ?​

RéponseRouteSignificationQue faire
401 missing or invalid Authorization headertoutespas de Bearerajouter l'en-tête
401 tenant session required: run sng tenant use NAMEroutes du tenantsession absente, invalide, expirée ou terminée par une déconnexion, ou jeton d'identité présenté à la placerouvrir une session
401 tenant session required/v1/iam/policies…, /v1/iam/bindings…, /v1/iam/oidc-trusts…idemidem
401 invalid tokenroutes d'identitéjeton d'identité invalide ou expiré, ou session présentée à la placesng sso login
403 organization missing in tokenroutes d'identitéjeton du SSO sans organisationse connecter avec un compte de la plateforme
403 forbiddenroutes du tenantla session est valide, mais aucune policy du tenant n'autorise l'action (ou le {tenant} du chemin n'est pas celui de la session)voir le catalogue des actions, attacher une policy
403 forbidden: insufficient permissions/v1/iam/policies…, /v1/iam/oidc-trusts…pas de policy:* ou oidctrust:*policy admin
503 authorization service unavailableroutes du tenantdécision impossible : refus par défautréessayer

Un retrait de policy prend effet en quelques minutes ; un retrait de rôle SSO, à l'expiration de la session (1 h au plus).

5. Vérifier une session : GET /v1/iam/jwks.json​

La session est un JWT RS256. Les clés publiques sont servies sans authentification, avec Cache-Control: public, max-age=300 :

curl -fsS https://api.dev.stackngo.fr/v1/iam/jwks.json
{
"keys": [
{ "kty": "RSA", "kid": "pW3mK1q…", "use": "sig", "alg": "RS256", "n": "xGq…", "e": "AQAB" }
]
}

Claims de la session :

ClaimContenu
issURL de l'API du cluster qui a émis la session (ex. https://ovh-gra-0.api.dev.stackngo.fr) ; ses clés publiques sont sous <iss>/v1/iam/jwks.json
audstackngo
subidentifiant du sujet : sub SSO d'un utilisateur, <tenant>/<nom> d'une application de la plateforme ou d'un trust, nom SSO d'une application
kinduser, sa, app ou oidc
namenom lisible (pour un job CI : le sub du jeton OIDC)
orgorganisation SSO (utilisateur, application)
tenanttenant de la session
origin_tenanttenant d'origine, s'il y en a un
rolesrôles SSO org/nom (utilisateur)
iat, nbf, exp, jtiémission, validité (nbf = émission − 5 s), expiration, identifiant unique

Vérifiez la signature avec la clé de même kid, puis aud = stackngo, iss et exp.

Exemple complet : un script qui lit un secret
#!/usr/bin/env bash
set -euo pipefail
API=https://api.dev.stackngo.fr/v1

# Identité : ici une machine externe (client_credentials)
ACCESS=$(curl -fsS -X POST https://auth.dev.stackngo.app/api/login/oauth/access_token \
-d grant_type=client_credentials -d client_id="$CLIENT_ID" -d client_secret="$CLIENT_SECRET" \
| jq -r .access_token)

# Session, en respectant Retry-After si le tenant est tout neuf
for i in 1 2 3 4 5 6; do
RESP=$(curl -sS -D headers.txt -o body.json -w '%{http_code}' -X POST "$API/iam/session" \
-H "Authorization: Bearer $ACCESS" -H "Content-Type: application/json" -d '{"tenant":"acme"}')
[ "$RESP" = 200 ] && break
[ "$RESP" = 409 ] || { cat body.json; exit 1; }
sleep "$(awk -F': ' 'tolower($1)=="retry-after"{print $2+0}' headers.txt)"
done
SESSION=$(jq -r .access_token body.json)

# Lecture d'un secret (ssm:read sur secret/db-password)
curl -fsS "$API/secrets/db-password/value" -H "Authorization: Bearer $SESSION"

6. Se déconnecter : POST /v1/iam/logout​

La déconnexion met fin à des sessions avant leur expiration. L'effet dépend du jeton présenté :

Jeton présentéSessions terminées
une session de tenantcette session seule ; vos autres sessions (autre terminal, autre tenant) restent valides
le jeton d'identitétoutes les sessions de tenant ouvertes avec ce compte jusqu'ici, où qu'elles soient
# fermer la session courante seulement
curl -fsS -X POST https://api.dev.stackngo.fr/v1/iam/logout -H "Authorization: Bearer $SESSION"
# fermer toutes les sessions du compte (c'est ce que fait sng sso logout)
curl -fsS -X POST https://api.dev.stackngo.fr/v1/iam/logout -H "Authorization: Bearer $ID_TOKEN"

Réponse : 204, aussi pour une session déjà terminée. L'effet est visible en quelques secondes sur toutes les routes : une session terminée reçoit 401, comme une session expirée (401 tenant session required: run sng tenant use NAME sur les routes du tenant). Elle ne peut pas non plus servir à POST /v1/iam/session/assume.

  • Le jeton d'identité lui-même n'est pas révoqué : oubliez-le. Tant qu'il n'a pas expiré, il peut encore ouvrir une nouvelle session.
  • 401 invalid token : jeton absent ou invalide. 503 : déconnexion momentanément impossible, réessayez ; dans ce cas, aucune session n'est acceptée non plus.

Voir aussi​