Search
K

Service extensions

Integrating identity systems requires extreme configurability. Service Extensions are short Golang programs that hook into extension points within orchestrator to modify or exend features. They give administrators the ability to customize the behavior of the orchestrator to suit the particular needs of their integration.

Service Extension Points

All major components in the Orchestrator expose Service Extension. Please reference the component specific docs for more details on the exact extension points.

Local Development Environment

Service extensions can be written in the Maverics UI or with any text editor. If you prefer to use a local development environment to take advantage of common IDE features such as auto-complete, linting, code compilation, etc., refer to the instructions in the service extension library repository.

Go Library Documentation

The Orchestrator exposes a library to aid in the development of extensions and to hook into underlying functionality in the Orchestrator. For example, you may want to log in to an IDP, query an attribute provider, or pull secrets from a secret store. To understand what functionality is available and how to use it, please see the library documentation.

Configuration options

Service extension code can be defined directly in the configuration file using a code block, or in a separate file using file.

Function name

funcName is a unique identifier for the service extension function name. Function names in Go must not contain spaces, and the first letter should be capitalized so that it can be exported (see idioms).

Code

code is the Golang code of the service extension specified directly in the config file.

File

file is the filesystem path to a Go service extension file.

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.
Metadata is not shared between different service extensions.

Examples

Defining a Service Extension in Config

To specify a service extension using code, use the |+ block scalar YAML construct.
apps:
- name: exampleOIDCApp
type: oidc
# ...
authentication:
isAuthenticatedSE:
funcName: IsAuthenticated
code: |+
package main
import (
"net/http"
"github.com/strata-io/service-extension/orchestrator"
"github.com/strata-io/service-extension/session"
)
func IsAuthenticated(api orchestrator.Orchestrator, _ http.ResponseWriter, req *http.Request) bool {
return false
}

Defining Service Extensions in Separate Files

Managing service extensions is often easier when they are written and saved as separate .go files. Use file to specify the filesystem location.
apps:
- name: exampleOIDCApp
type: oidc
# ...
authentication:
authenticateSE:
funcName: IsAuthenticated
file: /etc/maverics/extensions/auth.go
The file /etc/maverics/extensions/auth.go contains the service extension code:
package main
import (
"net/http"
"github.com/strata-io/service-extension/orchestrator"
)
func IsAuthenticated(orchestrator.Orchestrator, http.ResponseWriter, *http.Request) bool {
return false
}

Service Extension using Metadata

The following example sets a boolean attribute debug: false in metadata:
authentication:
isAuthenticatedSE:
funcName: IsAuthenticated
file: /etc/maverics/extensions/auth.go
metadata:
debug: true
authenticateSE:
funcName: Authenticate
file: /etc/maverics/extensions/auth.go
metadata:
debug: false
This can then be referenced in /etc/maverics/extensions/auth.go:
package main
import (
"net/http"
"github.com/strata-io/service-extension/orchestrator"
)
func IsAuthenticated(api orchestrator.Orchestrator, _ http.ResponseWriter, _ *http.Request) bool {
var (
metadata = api.Metadata()
logger = api.Logger()
)
session, err := api.Session()
if err != nil {
logger.Error("se", "unable to retrieve session", "error", err.Error())
return false
}
debugEnabled := metadata["debug"] == true // Will be set to 'true'
isOktaAuth, err := session.GetString("okta.authenticated")
if err != nil {
logger.Error("se", "unable to retrieve session value 'okta.authenticated'", "error", err.Error())
return false
}
if isOktaAuth == "true" {
if debugEnabled {
logger.Debug("se", "user is not authenticated")
}
return true
}
if debugEnabled {
logger.Debug("se", "user is authenticated")
}
return false
}
func Authenticate(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) {
var (
metadata = api.Metadata()
logger = api.Logger()
)
debugEnabled := metadata["debug"] == true // Will be set to 'false'
okta, err := api.IdentityProvider("okta")
if err != nil {
http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
logger.Error("se", "missing okta IDP", "error", err.Error())
return
}
if debugEnabled {
logger.Debug("se", "logging in via okta")
}
okta.Login(rw, req)
}

Third party packages support

The orchestrator supports a number of third-party packages that could be imported in service extensions. Please see the third-party packages documentation for a list of libraries that can be used.
The supported packages can be imported in Service Extensions by specifying the import path at the top of the service extension code.
package main
import (
ldap "github.com/go-ldap/ldap/v3"
)

Idioms

Exported Functions

When defining a Service Extension, the function name should be capitalized in order to indicate that the function is exported and will be the entry point called by the orchestrator runtime. Capitalizing extension names helps to differentiate between the public Service Extension API and internal utilities.

Effective Go

The Go language defines a broad set of idioms for writing Effective Go. Service Extensions should generally follow these idioms in order to align with best practices.