Configure service extensions
Maverics can be extended using service extensions, which are custom, Golang code that support arbitrary functionality such as retrieving and constructing complex attributes or defining and evaluating policies with logic not pre-built into an Orchestrator. Service extensions give administrators the ability to customize the behavior of the Maverics Orchestrator to suit the particular needs of their integration.
Service extension selections are based on app type as follows:
SAML service extensions
- Authentication:
isAuthenticatedSE
andauthenticateSE
service extensions are available to determine if a user is already authenticated and to control the authentication behavior. - Authorization:
isAuthorizedSE
is an optional service extension that can be used to override the default behavior that determines if a user is authorized. - Custom Claims:
buildClaimsSE
can customize which attributes will be added to the SAML 2.0 AttributeStatement. - Relay State:
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 staticrelayStateURL
. - Load Attributes:
loadAttrsSE
is an optional service extension used to customize how the loading of attributes is done. This extension is often used to load attributes from proprietary data sources such as enterprise APIs.
OIDC service extensions
- Authentication:
isAuthenticatedSE
andauthenticateSE
service extensions are available to determine if a user is already authenticated and to control the authentication behavior. - Load Attributes:
loadAttrsSE
is an optional service extension used to customize how the loading of attributes is done. This extension is often used to load attributes from proprietary data sources such as enterprise APIs. - Authorization:
isAuthorizedSE
is an optional service extension that can be used to override the default behavior that determines if a user is authorized. - Access Token:
accessToken
defines the configuration for the OAuth access token. - ID Token: The
buildIDTokenClaimsSE
is an optional service extension that can customize how claims in the ID token are built.buildAccessTokenClaimsSE
can customize how claims in the access token are built.
Proxy app service extensions
- Authentication:
isAuthenticatedSE
andauthenticateSE
service extensions are available to determine if a user is already authenticated and to control the authentication behavior. - Upstream Login:
upstreamLogin
is an optional configuration used to determine if a request to an upstream application is authenticated and to be able to log in to an upstream app. Such situations are common when an app manages its own sessions or directly authenticates against a backing data store like LDAP or a relational database. - Load Attributes:
loadAttrsSE
is an optional service extension used to customize how the loading of attributes is done. This extension is often used to load attributes from proprietary data sources such as enterprise APIs. - Header Creation:
createHeaderSE
is an optional service extension used to create a custom HTTP header. This extension is often used when an attribute needs to be enriched or concatenated with additional data. - Authorization:
isAuthorizedSE
is an optional service extension that can be used to override the default behavior that determines if a user is authorized. - Handle Unauthorized:
handleUnauthorizedSE
is an optional service extension that can be used to override the default behavior when a policy evaluation denies access to the app. - Modify Request:
modifyRequestSE
is an optional service extension that can be used to modify every request that passes through the app. - Modify Response:
modifyResponseSE
is an optional service extension that can be used to modify every response that passes through the app.
Additional settings and configuration
After you’ve created a service extension, you can use the following fields to make additional selections on the service extension:
- Assets: allows you to upload additional files to reference in your service extension. Maverics also supports allowed protected packages.
- Providers: allows you to select one or more providers to use in the service extension. These provider configurations will be used in the user flow when the service extension is invoked.
- Claims: allows you to include one or more claims for use in the headers section. (Available for the Authentication and Load Attributes service extensions only).
- Metadata: Metadata is an arbitrary set of key-value pairs that can be made available to a given extension. The values can be referenced from within the Go code, making service extensions more flexible and the configuration more obvious.
Use the service extension code editor to configure a custom service extension. Our code editor provides errors when attempting to compile malformed code.
For more information on configuring a custom service extension and code examples, see the service extension repository. Additionally, the on-premises Orchestrator documentation on service extensions can serve as a helpful reference.
Using service extensions in a user flow
Service extensions can be referenced in user flows in various ways. For instance, the Authentication service extension can be selected anywhere you are setting an authentication policy in a user flow or needing claims from a session for attributes, headers, or authorization policies.
Each user flow may have multiple places to reference different types of service extensions depending on the context.
Deploying service extensions
When a service extension is updated, all user flows that reference the service extension will display an Uncommitted Changes status on the user flows page. To deploy your user flows with the latest updates, open an affected user flow, click Commit, then click Deploy.
The Deploy preview step displays a diff view on service extension and non-binary asset (txt, html, .js, etc) changes between deployments.
If you have multiple user flows deployed that reference the same service extension to the same environment, all of the user flows will use the updated service extension.
If you deploy a previous version of a user flow with a service extension, it will roll back to a snapshot of the service extension from the time of the previous deploy.
Examples
For more information on configuring a custom service extension and code examples, see the service extension repository. Additionally, the on-premises Orchestrator documentation on service extensions can serve as a helpful reference.
Reference Assets within Service Extension
There are two ways to reference assets files uploaded with the service extension.
ReadFile
returns the contents of the file as a[]byte
. The path of the file is relative to how it was uploaded. Currently, the Maverics UI only allows for files to be uploaded without specifying sub-directories.FS
returns a filesystem object that contains all the asset files uploaded with the related service extension. That is, it will not contain all the assets for all service extensions deployed to the orchestrator.
Read a single file from the uploaded assets
In the example below we are reading the contents of a single file that was uploaded as a service extension asset.
func Authenticate(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) {
logger := api.Logger()
logger.Debug("se", "authenticating user")
if req.Method == http.MethodGet {
// ServiceExtensionAssets exposes any assets that may have been bundled with the
// service extension.
assets := api.ServiceExtensionAssets()
// ReadFile returns the file contents as a []byte.
idpForm, err := assets.ReadFile("idpForm.html")
if err != nil {
logger.Error("se", "failed to read service extension asset file", "error", err.Error())
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
logger.Info("se", "received GET request, rendering IDP selector form")
_, _ = rw.Write(idpForm)
return
}
if req.Method != http.MethodPost {
logger.Error("se", fmt.Sprintf("received unexpected request method '%s', expected POST", req.Method))
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
logger.Debug("se", "parsing form from request")
err := req.ParseForm()
if err != nil {
logger.Error("se", "failed to parse form from request", "error", err.Error())
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
selectedIDP := req.Form.Get("idp")
logger.Info("se", fmt.Sprintf("user selected '%s' IDP for authentication", selectedIDP))
idp, err := api.IdentityProvider(selectedIDP)
if err != nil {
logger.Error("se", "unable to lookup idp", "error", err.Error())
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
idp.Login(rw, req)
}
Read all the uploaded assets as a filesystem
In the example below we are reading all the uploaded service extension assets as a filesystem.
func ServeWebAssets(api orchestrator.Orchestrator) error {
router := api.Router()
router.HandleFunc("/assets/idpform.html", func(rw http.ResponseWriter, req *http.Request) {
logger := api.Logger()
logger.Info("se", "serving idp picker form")
session, err := api.Session(session.WithRequest(req))
if err != nil {
logger.Error("se", "unable to retrieve session", "error", err.Error())
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return false
}
username, _ := session.GetString("idp.username")
seAssets := api.ServiceExtensionAssets()
// FS returns a filesystem of type (fs.FS) containing the assets associated
// with the service extension.
sefs, err := seAssets.FS()
if err != nil {
logger.Error("se", "failed to get service extension filesystem", "error", err.Error())
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
tmpl, err := template.ParseFS(sefs, "idp-form.html")
if err != nil {
logger.Error("se", "unable to parse template", "error", err.Error())
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
data := struct {
Title string
}{
Title: fmt.Sprintf("Pick your IDP %s", username),
}
err = tmpl.Execute(rw, data)
if err != nil {
logger.Error("se", "unable to execute template", "error", err.Error())
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
})
return nil
}