Applications proxy
L’orchestrateur d’identité Maverics peut être utilisé en tant que proxy HTTP pour protéger les applications. Cela peut s’avérer utile pour les applications utilisant l’identité via des en-têtes HTTP, tout comme pour les applications qui authentifient les utilisateurs de manière native au moyen d’autres mécanismes.
Options de configuration
Name
name
est un identifiant unique pour l’application.
Type
type
représente le type d’application. Lors de la définition des applications proxy, le type
doit être proxy
.
Acheminement de motifs
routePatterns
constituent la liste des motifs qui seront utilisés pour faire correspondre une requête à
l’application appropriée. L’orchestrateur établit une correspondance entre une requête donnée et le
motif le plus spécifique. La correspondance des motifs est insensible à la casse.
Un motif peut être un chemin d’accès racine tel que /index.html
ou encore un sous-arborescence racine telle que
/dashboard/
(notez la barre oblique finale). Étant donné qu’un motif se terminant par une barre oblique désigne une
sous-arborescence racine, le motif /
correspond à tous les chemins d’accès non couverts par d’autres
motifs enregistrés, et pas seulement à l’URL ayant un chemin d’accès contenant le caractère /
.
Les motifs peuvent éventuellement commencer par un nom d’hôte, ce qui limite les correspondances aux URL
de cet hôte. Les motifs spécifiques à l’hôte ont la priorité sur les motifs généraux. Par exemple,
un motif de type app.example.com
correspondrait à toutes les requêtes contenant l’hôte spécifié,
comme https://app.example.com/dashboard
.
Un motif peut être spécifié en incluant à la fois un nom d’hôte et un chemin d’accès. Par
exemple, un motif de type example.com/app/
ne correspondrait qu’aux requêtes qui contiennent à la fois
l’hôte et le chemin spécifiés, tels que https://example.com/app/reports
.
Amont
upstream
correspond à l’URL de l’application mandatée.
Préservation de l’hôte
preserveHost
est un indicateur utilisé pour déterminer si l’en-tête « Host » doit être
conservé pour les requêtes sortantes. Par défaut, l’orchestrateur définit
l’en-tête pour qu’il corresponde à l’hôte en amont.
Transport Layer Security
tls
définit une option de configuration TLS pour les requêtes sortantes vers
l’application en amont
. La valeur doit faire référence à un objet TLS défini dans la configuration
TLS de premier niveau. Ce champ est généralement utilisé lorsque la cible en
amont
utilise des certificats signés par une autorité de certification dont la fiabilité fait défaut.
Page non autorisée
unauthorizedPage
est l’URL vers laquelle l’utilisateur sera redirigé lorsque l’évaluation de la politique
conduit à un refus d’accès à l’application.
Fournisseurs d’attributs
attrProviders
définit une liste de systèmes d’accès ou de magasins de données à partir desquels sont chargés les
attributs nécessaires à l’évaluation des politiques
et à la construction des en-têtes
.
Les fournisseurs d’attributs ne doivent être définis qu’en cas de nécessité de chargement des attributs après l’authentification. De nombreux fournisseurs d’identité renverront un ensemble complet d’attributs dans le cadre du processus d’authentification via le jeton d’identification OIDC ou la réponse SAML.
Nom
name
fait référence au nom du fournisseur qui sera utilisé pour le chargement des attributs.
Mappage des noms d’utilisateur
usernameMapping
définit l’attribut à utiliser en tant que clé de recherche afin de charger
les attributs à partir du fournisseur d’attributs. La valeur provient généralement du fournisseur d’identité
utilisé pour l’authentification primaire, par exemple {{ azure.mail }}
. Pour charger
un attribut du cache de session, utilisez la syntaxe {{ namespace.value }}
.
En-têtes
headers
constituent la liste des en-têtes HTTP qui seront ajoutés aux requêtes adressées
aux applications en amont. Les en-têtes ne seront ajoutés à la requête que si l’emplacement
est protégé.
Name
name
correspond au nom de l’en-tête HTTP.
Valeur
value
correspond à la valeur de l’en-tête HTTP. La valeur peut être une chaîne ou un
attribut dynamique chargé depuis la session. Pour charger un attribut
du cache de session, utilisez la syntaxe {{ namespace.value }}
.
Extension de services CreateHeader
createHeaderSE
est une extension de services optionnelle utilisée pour créer un en-tête HTTP
personnalisé. Cette extension est souvent utilisée lorsqu’un attribut doit être enrichi ou
concaténé avec des données supplémentaires.
Politiques
policies
définit une liste de conditions qui déterminent si une requête donnée doit
être autorisée, définissant en fin de compte les utilisateurs autorisés à accéder à l’application. Si aucune politique
n’est définie, toutes les requêtes seront refusées.
Location
location
correspond à un chemin URL utilisé pour faire correspondre les ressources de l’application à une politique. Une
requête sera traitée en fonction de l’emplacement
le plus spécifique, les emplacements basés sur des expressions régulières
(regex) étant prioritaires. L’ordre de configuration des emplacements de politique sans expression régulière
n’a pas d’importance, mais les emplacements qui emploient des expressions régulières conservent
l’ordre dans lequel ils ont été définis.
La correspondance entre les emplacements des politiques simples n’est pas sensible à la casse. Si vous définissez l’emplacement
/EXAMPLE
, les requêtes présentant un chemin d’accès de type /example
ou /Example
seront prises en compte. Si
vous vous souhaitez que la politique soit sensible à la casse, utilisez une expression régulière.
Pour appliquer la correspondance des expressions régulières à un emplacement
, ajoutez ~
avant le motif (notez
l’espace à la fin). Par exemple :
- location: ~ \.(jpg|png|ico|svg|ttf|js|css|gif)$
Vous pouvez utiliser des outils tels que regex101 (sélectionnez Golang) pour tester votre regex contre les chemins URL que vous aimeriez faire correspondre.
^
ou $
) si possible, et des compteurs ou des plages (.{0,15}
) au lieu de métacaractères « gourmands »
(.*
).Utilisation de la correspondance des paramètres de requête
useQueryParamsMatching
est une configuration facultative utilisée pour faire correspondre les requêtes avec
des paramètres de requête à la politique appropriée. La correspondance des paramètres de requête ne fonctionne qu’avec
les emplacements qui utilisent des expressions régulières (c’est-à-dire précédés de ~
).
Afin de faire correspondre les URL qui incluent des paramètres de requête tels que l’URL
/dashboard?sort=asc
, vous devez définir un emplacement
similaire à :
location: ~ ^/dashboard\?sort=asc
.
Authentification
authentification
définit la politique d’authentification pour l’emplacement
.
Autorisation d’accès non authentifié
allowUnauthenticated
peut être utilisé pour permettre un accès libre à une ressource. Si la valeur est
« true
», la ressource correspondant à cette politique ne nécessite pas d’authentification. Cet
opérateur est généralement utilisé pour des ressources telles que les pages d’erreur ou de connexion.
IDPs
idps
contient une liste d’un ou de plusieurs noms de fournisseurs d’identité à utiliser pour l’authentification.
Extension de services IsAuthenticated
isAuthenticatedSE
est une extension de services optionnelle qui peut être utilisée pour modifier le
comportement par défaut qui détermine si un utilisateur est déjà authentifié. Cette extension
doit être utilisée avec authenticateSE
.
Extension de services Authenticate
authenticateSE
est une extension de services optionnelle utilisée pour contrôler la manière dont
l’authentification sera effectuée. Cette extension doit être utilisée avec isAuthenticatedSE
.
Authorization
authorization
définit les règles de contrôle d’accès nécessaires à l’accès à un ensemble
d’emplacements.
Tout autoriser
allowAll
permet un accès libre à un emplacement donné. Cette option est généralement utilisée lorsque lorsque
l’autorisation à grain fin n’est pas appliquée.
Règles
rules
définit une liste de conditions de contrôle d’accès. Toutes les règles doivent être évaluées avec une valeur « true »
pour permettre à l’utilisateur d’accéder à un emplacement donné.
And
and
définit une liste de conditions qui doivent toutes être remplies.
Or
or
définit une liste de conditions dont au moins une doit être remplie.
Opérateurs
Les opérateurs définis ci-dessous peuvent être répertoriés sous une règle or
ou and
. Tous les opérateurs
attendent exactement deux valeurs (opérandes).
Pour charger un attribut du cache de session qui sera exploité dans la politque,
utilisez la syntaxe {{ connectorName.attributeName }}
. Par exemple, {{ ldap.memberOf }}
pourrait être utilisé pour rédiger une politique basée sur l’appartenance à un groupe LDAP.
En outre, une méthode de requête HTTP peut être prise en compte lors de l’évaluation de la politique en
utilisant la syntaxe {{ http.request.method }}
. La méthode de requête HTTP sera
renvoyée sous la forme d’une chaîne en majuscules, par exemple, POST
. Veuillez noter que seul l’attribut de méthode de requête HTTP
peut être utilisé dans la politique, la prise en charge d’autres attributs HTTP
sera introduite progressivement.
equals
s’évalue avec une valeur « true » si les deux valeurs sont équivalentes.
notEquals
s’évalue avec une valeur « true » si les deux valeurs ne sont pas équivalentes.
contains
s’évalue avec une valeur « true » si l’opérande de gauche (la chaîne complète) contient
l’opérande de droite (une sous-chaîne).
notContains
s’évalue avec une valeur « true » si l’opérande de gauche (la chaîne complète)
ne contient pas l’opérande de droite (une sous-chaîne).
Extension de services IsAuthorized
isAuthorizedSE
est une extension de services optionnelle qui peut être utilisée pour remplacer le
comportement par défaut qui détermine si un utilisateur est autorisé.
En-têtes
headers
définit les en-têtes HTTP pour un emplacement de politique donné. Les
en-têtes définis au niveau de la politique remplaceront les en-têtes du même nom qui sont
définis au niveau de l’application.
Déconnexion
logout
définit les paramètres de déconnexion facultatifs pour l’application. Lors de
la déconnexion d’une application, l’utilisateur est déconnecté de tous les fournisseurs d’identité définis dans
la politique de l’application avec laquelle il s’est authentifié. De plus, toutes les données de session
mises en cache liées à l’application seront supprimées.
Le terminal de déconnexion spécifique à l’application présente des différences importantes par rapport au terminal de déconnexion unique (SLO, Single Logout). Plus précisément, le point de terminaison SLO déconnecte l’utilisateur de tous les fournisseurs d’identité authentifiés, tandis que la déconnexion spécifique à une application ne déconnecte l’utilisateur que des fournisseurs d’identité définis dans les politiques de l’application en question.
Si un fournisseur d’identité est partagé entre plusieurs applications, l’utilisateur sera déconnecté du fournisseur d’identité partagé. Cependant, Maverics maintient la session pour les applications qui n’ont pas été déconnectées. En d’autres termes, l’utilisateur ne doit pas s’authentifier à nouveau pour les applications qui n’on pas été déconnectées formellement.
URL de déconnexion
logoutURL
correspond au terminal qui sera exposé pour faciliter la déconnexion des
utilisateurs de l’application.
URL de redirection post-déconnexion
postLogoutRedirectURL
correspond à l’adresse vers laquelle l’utilisateur sera redirigé après une
déconnexion réussie.
Extension de services LoadAttributes
loadAttrsSE
est une extension de services optionnelle utilisée pour personnaliser la manière dont le chargement des
attributs est effectué. Cette extension est souvent utilisée pour charger des attributs à partir de
sources de données propriétaires telles que les API d’entreprise.
Extension de services ModifyRequest
modifyRequestSE
est une extension de services optionnelle qui peut être utilisée pour modifier chaque
requête passant par l’application.
Extension de services ModifyResponse
modifyResponseSE
est une extension de services optionnelle qui peut être utilisée pour modifier chaque
réponse passant par l’application.
Connexion en amont
upstreamLogin
est une configuration facultative utilisée pour déterminer si une requête adressée à une
application en amont est authentifiée et pour pouvoir se connecter à une application en amont.
De telles situations sont courantes lorsqu’une application gère ses propres sessions ou s’authentifie directement
auprès d’un magasin de données comme LDAP ou une base de données relationnelle.
Extension de services IsLoggedIn
isLoggedInSE
permet de déterminer si une application en amont a fait l’objet d’une connexion.
Extension de services Login
loginSE
permet de se connecter à une application en amont.
Extension de services Handle Unauthorized
handleUnauthorizedSE
est une extension de services optionnelle qui peut être utilisée pour remplacer le
comportement par défaut lorsqu’une évaluation de la politique refuse l’accès à l’application.
Exemple
Configuration standard
apps:
- name: exampleProxyApp
type: proxy
routePatterns:
- app.example.com
- www.example.com/app/
upstream: https://app-internal.example.com
tls: exampleAppTLS
unauthorizedPage: https://app.example.com/unauthorized
attrProviders:
- name: ldap
usernameMapping: "{{ azure.emailaddress }}"
headers:
- name: SM_USER
value: "{{ azure.emailaddress }}"
- name: groups
value: "{{ ldap.memberOf }}"
- name: Organization
value: Example Inc.
policies:
- location: /
authentication:
allowUnauthenticated: false
idps:
- azure
authorization:
allowAll: false
rules:
- and:
- equals: ["{{ ldap.department }}", "Engineering"]
- notEquals: ["{{ ldap.title }}", "Junior Software Engineer"]
- contains: ["{{ ldap.memberOf }}", "cn=admins,ou=groups,ou=example,ou=com"]
- or:
- contains: ["{{ azure.emailaddress }}", "@example.com"]
- contains: ["{{ azure.emailaddress }}", "@example.io"]
- or:
- equals: ["{{ http.request.method }}", "GET"]
- equals: ["{{ http.request.method }}", "OPTIONS"]
- location: /index.html
authentication:
allowUnauthenticated: false
idps:
- azure
authorization:
allowAll: true
headers:
- name: firstName
value: "{{ ldap.givenname }}"
- name: lastName
value: "{{ ldap.sn }}"
- location: ~ \.(jpg|png|ico|svg|ttf|js|css|gif)$
authentication:
allowUnauthenticated: true
authorization:
allowAll: true
logout:
logoutURL: app.example.com/logout
postLogoutRedirectURL: /login
Extensions de services Auth
apps:
- name: exampleProxyApp
type: proxy
routePatterns:
- app.example.com
upstream: https://app-internal.example.com
policies:
- location: /
authentication:
isAuthenticatedSE:
funcName: IsAuthenticated
file: /etc/maverics/auth.go
authenticateSE:
funcName: Authenticate
file: /etc/maverics/auth.go
authorization:
isAuthorizedSE:
funcName: IsAuthorized
file: /etc/maverics/auth.go
/etc/maverics/auth.go
package main
import (
"net/http"
"strings"
"github.com/strata-io/service-extension/orchestrator"
)
func IsAuthenticated(api orchestrator.Orchestrator, _ http.ResponseWriter, _ *http.Request) bool {
logger := api.Logger()
logger.Debug("se", "determining if user is authenticated")
session, err := api.Session()
if err != nil {
logger.Error("se", "unable to retrieve session", "error", err.Error())
return false
}
isAzureAuth, err := session.GetString("azure.authenticated")
if err != nil {
logger.Error("se", "unable to retrieve session value 'azure.authenticated'", "error", err.Error())
return false
}
if isAzureAuth == "true" {
return true
}
return false
}
func Authenticate(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) {
logger := api.Logger()
logger.Debug("se", "authenticating user")
azureIDP, err := api.IdentityProvider("azure")
if err != nil {
logger.Error(
"se", "failed to retrieve Azure IDP",
"error", err.Error(),
)
http.Error(
rw,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError,
)
return
}
azureIDP.Login(rw, req)
}
func IsAuthorized(api orchestrator.Orchestrator, _ http.ResponseWriter, _ *http.Request) bool {
logger := api.Logger()
logger.Debug("se", "determining if user is authorized")
session, err := api.Session()
if err != nil {
logger.Error("se", "unable to retrieve session", "error", err.Error())
return false
}
rawGroups, _ := session.GetString("azure.groups")
groups := strings.Split(rawGroups, ",")
for _, group := range groups {
if group == "executives" {
return true
}
}
return false
}
Extension de services Header Creation
apps:
- name: exampleProxyApp
type: proxy
routePatterns:
- app.example.com
upstream: https://app-internal.example.com
headers:
- name: firstName
value: "{{ azure.givenname }}"
- name: lastName
value: "{{ azure.surname }}"
- createHeaderSE:
funcName: CreateHeader
file: /etc/maverics/header.go
policies:
- location: /
authentication:
idps:
- azure
authorization:
allowAll: true
/etc/maverics/header.go
package main
import (
"fmt"
"net/http"
"github.com/strata-io/service-extension/orchestrator"
)
func CreateHeader(api orchestrator.Orchestrator, _ http.ResponseWriter, _ *http.Request) (http.Header, error) {
logger := api.Logger()
logger.Debug("se", "building custom header")
session, err := api.Session()
if err != nil {
logger.Error("se", "unable to retrieve session", "error", err.Error())
return nil, err
}
firstName, err := session.GetString("azure.givenname")
if err != nil {
return nil, fmt.Errorf("unable to retrieve attribute 'azure.givenname': %w", err)
}
surname, err := session.GetString("azure.surname")
if err != nil {
return nil, fmt.Errorf("unable to retrieve attribute 'azure.givenname': %w", err)
}
preferredName := fmt.Sprintf(`%s 'The Great' %s`, firstName, surname)
return http.Header{"preferredName": []string{preferredName}}, nil
}
Extension de services Attribute Loading
Cette extension est utilisée pour charger des informations concernant l’utilisateur authentifié. En règle générale, les informations relatives aux attributs sont stockées dans la session, ce qui permet de les utiliser pour prendre des décisions en matière de politique ou pour les utiliser dans les en-têtes transmis à la ressource protégée.
apps:
- name: exampleProxyApp
type: proxy
routePatterns:
- /
upstream: https://example.com
loadAttrsSE:
funcName: LoadAttributes
file: /etc/maverics/auth.go
headers:
- name: SM_USER
value: "{{ azure.mail }}"
- name: firstName
value: "{{ ldap.givenname }}"
- name: lastName
value: "{{ ldap.sn }}"
- name: mobile
value: "{{ ldap.mobile }}"
policies:
- location: /
authentication:
idps:
- azure
authorization:
allowAll: true
/etc/maverics/auth.go
package main
import (
"fmt"
"net/http"
"github.com/strata-io/service-extension/orchestrator"
)
func LoadAttributes(api orchestrator.Orchestrator, _ http.ResponseWriter, _ *http.Request) error {
logger := api.Logger()
logger.Debug("se", "loading custom attributes from LDAP")
session, err := api.Session()
if err != nil {
logger.Error("se", "unable to retrieve session", "error", err.Error())
return err
}
mail, err := session.GetString("azure.email")
if err != nil {
return fmt.Errorf("failed to find user email required for LDAP query: %w", err)
}
ldap, err := api.AttributeProvider("ldap")
if err != nil {
return fmt.Errorf("failed to find LDAP attribute provider")
}
attrs, err := ldap.Query(mail, []string{"givenname", "sn", "mobile"})
if err != nil {
return fmt.Errorf("failed to query LDAP: %w", err)
}
for k, v := range attrs {
logger.Debug(
"se", "setting LDAP attribute on session",
"attribute", k,
"value", v,
)
_ = session.SetString(k, v)
}
err = session.Save()
if err != nil {
return fmt.Errorf("unable to save session state: %w", err)
}
return nil
}
Extension de services Request and Response Modification
apps:
- name: exampleProxyApp
type: proxy
routePatterns:
- /
upstream: https://example.com
modifyRequestSE:
funcName: ModifyRequest
file: /etc/maverics/proxy.go
modifyResponseSE:
funcName: ModifyResponse
file: /etc/maverics/proxy.go
policies:
- location: /
authentication:
allowUnauthenticated: true
authorization:
allowAll: true
/etc/maverics/proxy.go
package main
import (
"net/http"
"time"
"github.com/strata-io/service-extension/orchestrator"
)
func ModifyRequest(api orchestrator.Orchestrator, req *http.Request) {
api.Logger().Debug("se", "setting header on request")
req.Header.Set("X-Req-Time", time.Now().String())
}
func ModifyResponse(api orchestrator.Orchestrator, resp *http.Response) {
if resp.Request.URL.Path != "/special/path/" {
return
}
api.Logger().Debug("se", "setting status code for '/special/path/' resource")
resp.StatusCode = http.StatusOK
}
Extensions de services Upstream Application Login
apps:
- name: exampleProxyApp
type: proxy
routePatterns:
- /
upstream: https://example.com
upstreamLogin:
isLoggedInSE:
funcName: IsLoggedIn
file: /etc/maverics/login.go
loginSE:
funcName: Login
file: /etc/maverics/login.go
policies:
- location: /
authentication:
allowUnauthenticated: true
authorization:
allowAll: true
- location: ~ \.(jpg|png|ico|svg|ttf|js|css|gif)$
authentication:
allowUnauthenticated: true
authorization:
allowAll: true
/etc/maverics/login.go
package main
import (
"net/http"
"github.com/strata-io/service-extension/orchestrator"
)
func IsLoggedIn(api orchestrator.Orchestrator, _ http.ResponseWriter, req *http.Request) bool {
logger := api.Logger()
logger.Debug("se", "determining if user has session with upstream app")
if req.URL.Path == "/login" {
return true
}
_, err := req.Cookie(".ASPXAUTH")
if err != nil {
logger.Debug("se", "user is not authenticated with upstream app")
return false
}
logger.Debug("se", "user is authenticated with upstream app")
return true
}
func Login(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) error {
api.Logger().Info("se", "redirecting user to upstream app's login page")
http.Redirect(rw, req, "/login", http.StatusFound)
return nil
}
Extension de services Handle Unauthorized
apps:
- name: exampleProxyApp
type: proxy
routePatterns:
- /
upstream: https://example.com
handleUnauthorizedSE:
funcName: HandleUnauthorized
file: /etc/maverics/unauthorized.go
policies:
- location: /
authentication:
idps:
- azure
authorization:
allowAll: true
- location: /reports
authentication:
idps:
- azure
authorization:
rules:
- or:
- equals: ["{{ azure.title }}", "CEO"]
- equals: ["{{ azure.title }}", "CFO"]
- equals: ["{{ azure.title }}", "COO"]
/etc/maverics/unauthorized.go
package main
import (
"fmt"
"net/http"
"github.com/strata-io/service-extension/orchestrator"
)
func HandleUnauthorized(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) {
api.Logger().Debug("se", "handling unauthorized request")
http.Error(
rw,
fmt.Sprintf(
"Access denied to %s. Please contact [email protected] for help.",
req.URL.Path,
),
http.StatusUnauthorized,
)
}