Configure service extensions

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.

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.

Examples

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
}