SAML applications
The Maverics Identity Orchestrator can be used as an IDP to protect SAML apps.
samlProvider
must be defined.Configuration options
Name
name
is a unique identifier for the app.
Type
type
represents the application type. When defining SAML apps, the type
should be saml
.
Audience
audience
is a unique identifier of the application. The audience
value is typically a URL, and it should match the Issuer field provided by the SAML service provider.
Consumer Service URL
consumerServiceURL
is the URL where SAML responses will be sent.
IDP Initiated Login
The idpInitiatedLogin
key is an optional configuration which when specified will
enable the auth provider to perform IDP initiated login to the client.
Login URL
loginURL
is the endpoint that the user will visit from their browser to initiate
the IDP login flow. This endpoint needs to be unique on the Orchestrator.
Relay State URL
relayStateURL
is the endpoint that gets passed to the service provider and is
intended to be the landing page for the user after the authentication flow is complete.
Build Relay State Service Extension
buildRelayStateSE
can optionally be used to build the RelayState
parameter in an
IDP-initiated login flow. This extension can be used when the relay state is dynamic
and therefore cannot be defined with a static relayStateURL
.
Duration
duration
is the time in seconds for which the SAML assertions are valid. If a value
is not defined, a duration of five minutes (300 seconds) will be used.
Name ID
nameID
is an optional field used to define the properties of the NameID
element
in a SAML response.
Format
format
is an optional field used to define the NameID Format that will be used
in the SAML assertion. When not defined, a value of
'urn:oasis:names:tc:SAML:1.0:nameid-format:unspecified'
will be used. If a
NameIDPolicy is defined on the SAML Authentication request, it must match the
NameID Format defined on the client. For more details on NameID Format, please see
section 2.2, 3.4.1, and 8.3 of the SAML spec.
Attribute Mapping
attrMapping
sets the NameID
value in the SAML response. When not defined, the user’s
subject will be used. In order to load an attribute from the session cache, use
the connectorName.attributeValue
syntax.
Request Verification
requestVerification
defines the public key used to validate the signature of incoming
requests. Alternatively, it can be used to disable request signing verification.
Certificate
certificate
is the RSA x509 certificate, and will be used to verify the signatures of incoming requests. It may be defined inline or with a secret provider. It must be RSA compatible. Currently this auth provider only supports SHA-256 for request signing and digest algorithm.
Skip Verification
skipVerification
is a boolean value and is used when the client does not want to validate the signatures of incoming requests.
Authentication
authentication
defines how users are authenticated for this app.
IDPs
idps
lists the IDPs which will be used to authenticate the user.
IsAuthenticated Service Extension
isAuthenticatedSE
is an optional Service Extension that can be used to override the
default behavior that determines if a user is already authenticated. This extension
must be used with authenticateSE
.
Authenticate Service Extension
authenticateSE
is an optional Service Extension used to take control of how
authentication will be done. This extension must be used with isAuthenticatedSE
.
Attribute Providers
The attrProviders
is an optional configuration for an identity system or
data store from which the SAMLProvider may retrieve additional attributes used
in claimsMapping
.
Connector
Connector is a reference to the name of the defined connector which will be used as an attribute provider.
usernameMapping
The usernameMapping
configuration makes sure the Attribute Provider (i.e. LDAP Connector) has the correct attribute it needs to successfully query for the user’s attributes.
It specifies the attribute used to look up user attributes from the defined attribute provider.
Encryption
encryption
is an optional configuration used for encrypting the SAML assertion.
Key Encrypt Method
keyEncryptMethod
configures the encryption method for encrypting the symmetric key
which is used to encrypt the assertion. Currently, we support two values here which
are RSA_OAEP
and RSA-1_5.
RSA-1_5
is not recommended according to the
XML encryption spec due to
security risks associated with the algorithm.
Data Encrypt Method
dataEncryptMethod
configures the encryption method for encrypting the actual data
of the assertion. Valid values are AES128CBC
, AES192CBC
, and AES256CBC
.
Digest Method
digestMethod
is the message digest algorithm use to compute a message digest as
part of the encryption process. Valid values are SHA256
, and SHA512
.
Certificate
certificate
is the PEM encoded string that can be defined inline or via a
secret provider. This certificate is typically retrieved from the Service Provider.
Claims Mapping
claimsMapping
defines how to map attributes from the session to a SAML assertion.
Build Claims Service Extensions
buildClaimsSE
is an optional Service Extension that can customize which attributes
will be added to the SAML 2.0 AttributeStatement.
bool
, int
, int8
, int32
, int64
, float32
, float64
, and string
.Examples
Basic SAML App Config Example
apps:
- name: exampleSAMLApp
type: saml
audience: https://app.enterprise.com
consumerServiceURL: https://app.enterprise.com/acs
idpInitiatedLogin:
loginURL: https://idp.enterprise.com/sso/example-app
relayStateURL: https://app.enterprise.com/index.html
duration: 300
requestVerification:
certificate: <appSigningCertificate>
nameID:
format: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
attrMapping: azure.email
authentication:
idps:
- azure
attrProviders:
- connector: ldap
usernameMapping: azure.email
encryption:
keyEncryptMethod: RSA_OAEP
dataEncryptMethod: AES256CBC
digestMethod: SHA256
certificate: <encryptionCert>
claimsMapping:
id: azure.name
email: azure.email
lastName: azure.surname
groups: ldap.groupMembership
SAML App With Service Extension
apps:
- name: exampleSAMLApp
type: saml
audience: https://app.enterprise.com
consumerServiceURL: https://app.enterprise.com/acs
duration: 300
requestVerification:
certificate: <appSigningCertificate>
idpInitiatedLogin:
loginURL: https://idp.enterprise.com/sso/example-app
buildRelayStateSE:
funcName: BuildRelayState
file: /etc/maverics/extensions/auth.go
authentication:
isAuthenticatedSE:
funcName: IsAuthenticated
file: /etc/maverics/extensions/auth.go
authenticateSE:
funcName: Authenticate
file: /etc/maverics/extensions/auth.go
attrProviders:
- connector: ldap
usernameMapping: azure.email
buildClaimsSE:
funcName: BuildClaims
file: /etc/maverics/extensions/auth.go
/etc/maverics/extensions/auth.go
package main
import (
"fmt"
"net/http"
"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 attribute 'azure.authenticated'", "error", err)
return false
}
if isAzureAuth == "true" {
return true
}
return false
}
func Authenticate(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) {
logger := api.Logger()
logger.Info("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 BuildClaims(api orchestrator.Orchestrator, _ http.ResponseWriter, _ *http.Request) (map[string]any, error) {
logger := api.Logger()
logger.Info("se", "building custom claims")
session, err := api.Session()
if err != nil {
logger.Error("se", "unable to retrieve session", "error", err.Error())
return nil, err
}
name, _ := session.GetString("azure.email")
firstName, _ := session.GetString("azure.givenname")
lastName, _ := session.GetString("azure.surname")
return map[string]any{
"name": name,
"firstName": firstName,
"lastName": lastName,
}, nil
}
func BuildRelayState(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) string {
const fallbackRelayState = "https://app.enterprise.com/index.html"
logger := api.Logger()
logger.Info("se", "building custom relay state")
session, err := api.Session()
if err != nil {
logger.Error(
"se", "failed to build RelayState: failed to retrieve session",
"error", err.Error(),
)
return fallbackRelayState
}
userID, err := session.GetString("azure.objectidentifier")
if err != nil {
logger.Error(
"se", "failed to build RelayState: failed to retrieve value from session",
"error", err.Error(),
)
return fallbackRelayState
}
if userID == "" {
logger.Error(
"se", "failed to build RelayState: user ID value not found session",
)
return fallbackRelayState
}
return fmt.Sprintf("https://app.enterprise.com/user/%s", userID)
}