SGNL for AWS API Gateway

Leveraging SGNL for AWS API Gateway continuous access management

Aldo Pietropaolo, Director of Solutions Engineering, SGNL
August 10, 2023
Follow us on

Bringing enterprise-scale access management to API Gateways ensures least-privilege access to your APIs. By extending zero-trust principles with run-time access management, you can protect your organization against a variety of risks that arise from API attackers or insiders performing unauthorized requests against your APIs. The SGNL platform brings centralized, continuous access management to your AWS API Gateway.

What is the AWS API Gateway

The AWS API Gateway is a proxy that accepts client application requests (REST, HTTP, Web Sockets) and performs a variety of critical functions. These functions include traffic management, authorization, access control, monitoring, and API version management. See the full list of features for more information.

The API Gateway acts as an entry point for applications making requests to APIs which then may access data, business logic, or other AWS services, such as functions running on AWS Lambda.

The following diagram describes the AWS API Gateway architecture. Within this architecture, you can configure a Lambda authorizer to call an external access management policy engine. In this post, we examine how you implement a Lambda authorizer for delegating authorizations decisions to the SGNL policy engine and take advantage of continuous access management for your APIs.

Source: AWS API Gateway architecture

Continuous Access Management

Continuous access management is the combination of methods, technologies, and business processes used to manage access to applications, infrastructure, or data. This uses a dynamic and real-time authorization check for every request made by a user, device, or application to a resource provider (e.g., Application, Microservice, Database).

Continuous Access Management for AWS API Gateway

Continuous access management is dynamic and fine-grained, and must be considered as a part of a combined defense in depth and zero-trust security strategy for accessing critical resources. Here are some reasons why implementing continuous access management for the AWS API Gateway makes sense:

  • Adopt a least-privilege strategy for your APIs - It is common to see audit findings that demonstrate over-provisioned permissions accumulated over time on API resources. Role-based access control (RBAC) is commonly used to allow access to API resources, but the burden of managing these roles and associated permissions on all API resources tends to introduce unwanted risk and complexity.
  • Eliminate two of the top 10 OWASP API security risks - OWASP identifies two main risks regarding authorization in their top 10 list for 2023. The first is lack of proper authorization controls for broken object property permission. The other is for broken function level authorization. By using a continuous access management platform for API authorization, you can reduce or eliminate the risks associated with these two common vulnerabilities.
  • API security is more than just OAuth 2 / OIDC implementation - Although supporting a proper OAuth 2 flow (see RFC 6749) and an appropriate token (see RFC 6750) for accessing your API resources is critical, API security requires a layered approach of authentication, coarse-grained authorization, real time session state (CAEP), and continuous access management. Not only is continuous access management real time authorization for every request, but also a key strategy for a defense in depth - zero-trust strategy.

API Defense In Depth

As seen above, API defense in depth consists of three main request layers that play a critical role in providing a comprehensive defense strategy against API attacks. In many cases, the identity provider (IDP) provides the user’s identity and profile information for the incoming request.

The API Gateway orchestrates the authentication and authorization processes for every request. It is common to externalize both functions to an external IDP and continuous access management platform. The final layer is a continuous access management layer that provides externalized, centralized fine-grained access control for every request. Although not a layer in the inline request flow, a CAEP Transmitter (such as an Identity Provider) and the continuous access management platform can work in concert to respond to critical session events such as a session revocation event.

The idea is that every request goes through a series of defense layers (which all must be green) before it gains access to a protected resource such as an API. If one layer detects a security violation, the request will be denied and audited. Finally, a complimentary set of capabilities that are part of the defense in depth zero-trust approach is API discovery, threat protection, and analytics. These capabilities are not in the inline request stream, but important to getting a big picture of your current API landscape, vulnerabilities, and risk.

Using SGNL For AWS API Gateway

SGNL provides continuous access management by building a dynamic graph of sanctioned enterprise activity through continuous data ingestion from systems of record. With SGNL, access control is enforced consistently and easily across all protected services, infrastructure, applications, and data using human-readable policies. By using SGNL for the AWS API Gateway, you can externalize API access management to a scalable and centralized modern platform, minimize security exposure, and implement a truly least-privileged approach to securing your APIs.

  1. Business Data Driven Continuous Access Management. Any changes recorded in an organization’s business systems (e.g. Jira, Salesforce, Workday) of record are automatically and rapidly reflected in the AWS API Gateway’s access decisions without changing local proxy configuration.
  2. Real-time Audit and Comprehensive Reporting. With SGNL, all AWS API Gateway access decisions are logged centrally with the policies that contributed to the decision to grant or deny access.
  3. Centralized Human-Readable Policies. Human-readable policies using reusable snippets enable organizations to scale policies without losing manageability and consistency. SGNL allows you to create reusable snippets, and reuse them in multiple authorization policies. This allows for easier maintenance, time savings, and broader coverage for an authorization policy.
  4. Consistent Authorization Decisions Across all APIs. Authorization services exposed by the AWS API Gateway, using SGNL, provide the same access decisions consistently because they are based on centrally managed policies.
  5. Contextual Authorization Decisions. Any access decision is determined considering the context of the request coming from API clients at runtime. The request context received from the AWS API Gateway is kept intact through the SGNL access service and back to the AWS API Gateway.

With SGNL, you can externalize access management for your AWS API Gateway, to provide consistent, fine-grained decisions that are based on human readable policies, which are based on the dynamic data in your systems of record.

Solution Approach

The solution consists of an SGNL policy engine (integration via the access service API), graph directory, data ingestion adapters, and human-readable policy used for making authorization decisions. An AWS Lambda authorizer is configured to send authorization requests to the SGNL access service via the SGNL REST API. The SGNL access service responds to the Lambda authorizer with allow or deny decisions considering the context of the authorization request. For example, context may consist of the principal API being accessed, and the HTTP verb (e.g. POST, GET, PATCH). If the request is denied, the authorizer does not permit the request to complete and responds with a forbidden status code to the calling API client.

Logical Diagram Of Solution

How Does SGNL Work

SGNL continuously ingests data from systems of record to a central graph directory via resilient and performant data ingest adapters. This data includes identity data, such as users and groups, and any relevant data required to define access policies, such as CMDB, ITSM cases, tickets from JIRA, or customers from a CRM.

In this post, we use Salesforce and Okta as the Systems of Record. Okta is our Identity Provider system of record, and Salesforce provides the assigned customer information for the user. This assigned customer and customer status justifies the user’s ability to perform API transactions against a specific customer’s data.

Once the system of record data sources are set up, administrators can quickly author and manage human-readable policies based on the ingested data for granting or denying access to applications, APIs, servers, and sensitive data. By implementing a centralized approach, SGNL provides consistency in centralized policy management, audit logging, and reporting across all assets an organization desires to protect.

Instead of developing complex API Gateway authorization logic, you use SGNL to graphically build a human-readable policy that ensures that every protected API request is evaluated against the latest data ingested from systems of record and the policies defined in SGNL.

An example of a real human-readable policy for managing access to Salesforce account data

This policy checks if the principal is a valid sales team member (based on Okta profile, roles, groups, relationships to other business entities etc.) and has a valid Salesforce customer assigned. SGNL applies this policy to an incoming access request from the AWS API Gateway. The AWS API Gateway sends the authenticated user and the API request resource (e.g. /customer/order/data) and HTTP verb (POST). SGNL will return an “Allow” decision if the principal has a valid Salesforce customer assigned and is a valid and active sales team member or a “Deny” decision if either of those conditions fails to match.

Sample Request Flow

An API security administrator may simplify authorization policy for accessing APIs behind the AWS API Gateway by delegating complex authorization logic to the SGNL policy engine. The authorization decisions are made by the SGNL policy engine which is calculating policy against continuously ingested data from various systems of record (e.g. Salesforce, Okta). In addition, SGNL automatically creates the appropriate relationships for that ingested data, and it calculates multiple human-readable policies in milliseconds. Below is an example sequence diagram for an API request, and having that request be authorized by the SGNL policy engine.

  1. An API client (e.g. application, microservice) makes a request to the AWS API Gateway containing the request context (e.g. principal (e.g. user), API resource, and HTTP verb “POST”).
  2. The AWS API Gateway instantiates the configured Lambda authorizer, and passes the request context information and sends an authorization query request to the SGNL access service API.
  3. The Lambda authorizer sends an authorization request to the SGNL policy engine.
  4. The SGNL policy engine calculates the human-readable policies associated with the authorization request in milliseconds and determines a final decision. (A) The policy calculation results in an allow. (B) The policy calculation results in a denial.
  5. (A) The Lambda authorizer returns an allow IAM policy to the AWS API Gateway. (B) The Lambda authorizer returns a deny IAM policy to the AWS API Gateway.
  6. (A) If the decision returned by the Lambda authorizer is denied, the AWS API Gateway denies the request for the API resource. (B) If the decision is allowed, the AWS API Gateway proxies the request to the backend API.
  7. The backend API service sends a response back to the AWS API Gateway.
  8. The AWS API Gateway sends the response to the API client along with an HTTP 200 status.

Lambda Authorizer Sample Code

The following code snippet is an example implementation of the Lambda authorizer handler. This is the function the AWS API Gateway calls for every protected API request. The request context is passed in the function’s request parameter.

You can download the full Lambda authorizer example from the SGNL examples repository.

Code snippet from Lambda authorizer

// Lambda Authorizer Handler

func sgnlAuthorizer(ctx context.Context, request events.APIGatewayCustomAuthorizerRequestTypeRequest) (events.APIGatewayCustomAuthorizerResponse, error) {

	// Print MethodArn for debug and testing
	log.Println("SGNL Authorizer: Method ARN: " + request.MethodArn)

	// Get bearer token for calling the SGNL API from the configured "token" environment variable. In production it's advisable to get the toekn from a key vault.
	token := os.Getenv("token")

	// Get the SGNL Access Service URL from the Lambda environment variables.
	sgnlUrl := os.Getenv("sgnl_url")

	// Get the principal id from the request. In production implementations it's advisable to get the principal from an encrypted and signed IDP JWT.

	principalID := request.QueryStringParameters["principal"]

	// Parse MethodArn
	tmp := strings.Split(request.MethodArn, ":")
	apiGatewayArnTmp := strings.Split(tmp[5], "/")
	awsAccountID := tmp[4]
	method := request.HTTPMethod
	path := request.Path

	//Initialize response document

	resp := NewAuthorizerResponse(principalID, awsAccountID)
	resp.Region = tmp[3]
	resp.APIID = apiGatewayArnTmp[0]
	resp.Stage = apiGatewayArnTmp[1]

	// you can send a 401 Unauthorized response to the client by failing like so:

	if len(principalID) == 0 {
		log.Println("SGNL Authorizer: Error, empty principal id.")
		return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized")
	}

	// Construct the query string. You can build this string from any input source such as an HTTP(S) request context or API client.

	principalJson := `{"Id":"` + principalID + `"}`
	queriesJson := `[{"assetID":"` + path + `","action":"` + method + `"}]`

        // Log for test purposes
        log.Println("Principal ID:", principalID)
	log.Println("AssetID:", path)

	// Initialize the query variable as an array of SGNL queries, based on the queries struct.
	var queries []Queries
	var principal Principal

	// Unmarshall the query string into the query array struct.

	json.Unmarshal([]byte(queriesJson), &queries)

	// Unmarshall the principal string into the principal struct.

	json.Unmarshal([]byte(principalJson), &principal)

	// Build final JSON request body using the SGNL request struct.

	sgnlReq1 := &sgnlRequest{
		Principal: principal,
		Queries:   queries}

	// Finally marshall the JSON into a final request variable containing the JSON request body.

	sgnlReq2, _ := json.Marshal(sgnlReq1)

	// Log final request body for test purposes
log.Println("SGNL Authorizer: SGNL Request:", string(sgnlReq2))

       // Initialize http client

	client := &http.Client{}

	// Initialize request

	req, err := http.NewRequest(method, sgnlUrl, bytes.NewBuffer(sgnlReq2))

	if err != nil {
		fmt.Print("Error while initializing request.")
		return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized")
	}

	// Add headers to request

	req.Header.Add("Authorization", token)
	req.Header.Add("Content-Type", "application/json")

	// Make the request to the SGNL Access Service API
	response, err := client.Do(req)

	if err != nil {
		fmt.Print("Error when making SGNL Request.")
		return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized")
	}

	defer response.Body.Close()
	body, err := io.ReadAll(response.Body)

	//Print the JSON response body in case of error. Return unauthorized to the AWS API Gateway.
	if err != nil {
		fmt.Print("Error while reading response body.")
		return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized")
	}

	// Check response from SGNL access service API
	var jsonResponse sgnlResponse

	json.Unmarshal([]byte(body), &jsonResponse)

       // If error, make sure to return unauthorized to the AWS API Gateway.
	if err != nil {
		log.Println("could not unmarshal json: %s\n", err)
		return events.APIGatewayCustomAuthorizerResponse{}, errors.New("Unauthorized")
	}

	// Prepare policy document based on SGNL access service API.

      // Check decision from the SGNL access service API.
	if jsonResponse.Decisions[0].Decision == "Allow" {
		log.Println("SGNL Authorizer Decision:", jsonResponse.Decisions[0].Decision)
		resp.AllowMethod(method, path)
		return resp.APIGatewayCustomAuthorizerResponse, nil

	} else {
		// Deny request
              log.Println("SGNL Authorizer Decision:",        jsonResponse.Decisions[0].Decision)
		resp.DenyAllMethods()
		return resp.APIGatewayCustomAuthorizerResponse, nil

	}
}

Configuration

For steps to build, install, and configure the Lambda authorizer visit the SGNL examples Git repo’s readme file.

Conclusion

Using SGNL for managing access to your APIs, especially high-risk APIs and data - organizations can reduce risk, security administrators can minimize authorization logic complexity, and teams can centralize access management policy in a single platform for making complex, consistent, and continuous authorization decisions.

Schedule time with a SGNLer to see how all this can work in your context.

Best practices and the latest security trends delivered to your inbox