Authorization service extension (Legacy)

Authorization service extension (Legacy)

ℹ️
This topic refers to legacy configuration syntax. App gateways are now defined as Proxy apps. A new example of the authorization service extension is documented here.

Service Extensions allow complete flexibility over the process of controlling access into protected resources.

How AppGateways Serve Traffic

A little context will be helpful to understand how the authorization works and where it fits into the identity flow of a user. Here is how traffic flows into an AppGateway (in pseudocode).

func Serve(appGateway *app.AppGateway, rw http.ResponseWriter, req *http.Request) {
  if !isAuthenticated(){
    Authenticate(resp, req)
  }
  LoadAttrs()
  if IsAuthorized() {
    SetHeaders()
    Proxy()
  }
}

This is a simplification. The actual implementation caches the information about the session for efficiency, including the results of the authentication and authorization check. As such, each of these methods is not usually called for each request, but rather only for the first pass through for the user.

Authorization

As seen above in pseudocode, authorization will occur after the user’s identity has been established via authentication and user attributes are loaded. Authorization is then applied to control access to the requested resource.

ℹ️

AuthN vs. AuthZ

Authentication (AuthN) is used simply to assert the user’s identity, not to determine if they have access. A user may be authenticated, but may still be denied access at the Authorization (AuthZ) stage.

IsAuthorized Service Extension

This Service Extension returns a boolean value indicating if access is allowed or denied. Typically, it will apply its logic based on attributes already available on the user’s session (e.g. loaded in the LoadAttrsSE phase), or on intrinsic properties of the request.

⚠️
Although this Service Extension is being passed the http.ResponseWriter, typically it will not send any response directly to the user. Writing to the http.ResponseWriter’s body or headers may produce undesired behavior.

Here is an example of the isAuthorizedSE Service Extension implemented at both the AppGateway and location level.

appgateways:
  - name: alpha
    # ...

    isAuthorizedSE:
      funcName: DefaultIsAuthorized
      file: /etc/maverics/extensions/alpha/auth.go

    policies:
      - location: /
        authentication:
          idps:
            - azure

      - location: /admin
        authentication:
          idps:
            - azure
        authorization:
          isAuthorizedSE:
            funcName: AdminIsAuthorized
            file: /etc/maverics/extensions/alpha/auth.go

/etc/maverics/extensions/alpha/auth.go

package main

import (
	"net/http"
	"strings"

	"maverics/app"
	"maverics/log"
	"maverics/session"
)

// DefaultIsAuthorized ensures the user is a Canary Bank staffer.
func DefaultIsAuthorized(ag *app.AppGateway, rw http.ResponseWriter, req *http.Request) bool {
	log.Debug(
		"msg", "determining if user is authorized",
		"extension", "DefaultIsAuthorized",
	)

	if isCanaryStaffer(req) {
		return true
	}

	return false
}

// AdminIsAuthorized ensures the user is a Canary Bank staffer and that they
// work in the accounting department.
func AdminIsAuthorized(ag *app.AppGateway, rw http.ResponseWriter, req *http.Request) bool {
	log.Debug(
		"msg", "determining if user is authorized",
		"extension", "AdminIsAuthorized",
	)

	if isCanaryStaffer(req) && isAdmin(req) {
		return true
	}

	return false
}

func isCanaryStaffer(req *http.Request) bool {
	email := session.GetString(req, "azure.email")
	return strings.HasSuffix(email, "@canarybank.io")
}

func isAdmin(req *http.Request) bool {
	department := session.GetString(req, "ldap.department")
	return department == "Accounting"
}