Protecting GraphQL APIs

Authorization Control Plane (ACP) Istio Authorizer has the capability to discover your GraphQL APIs so that you can protect them with policies. You can either assign policies in ACP or use the declarative approach and assign them directly in your GraphQL schema files, and this instruction explains how to do it.

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. For more high-level information on GraphQL authorization in ACP, read Authorization for GraphQL.

Prerequisites

  • Istio Authorizer up and running (other authorizers are not yet supported)
  • GraphQL service deployed in a k8s namespace monitored by Istio with the required annotations

GraphQL API discovery

In order to be recognized by ACP Istio Authorizer, your GraphQL service deployment must have the following metadata annotations in the manifest file:

  • services.k8s.cloudentity.com/spec-url pointing to the GraphQL schema definition
  • services.k8s.cloudentity.com/graphql-path pointing to the GraphQL endpoint path
apiVersion: apps/v1
kind: Deployment
metadata:
  name: countries
  labels:
    app: countries
  annotations:
    services.k8s.cloudentity.com/spec-url: "https://example.com/schema.graphql"
    services.k8s.cloudentity.com/graphql-path: "/graphql"

After you rollout your Istio Authorizer deployment, the GraphQL APIs are discovered and ACP fetches them from your authorizer.

Sample services

To quickly set up a k8s cluster with ACP, Istio Gateway, Istio Authorizer, and a GraphQL service, you can use the ACP on k8s repository which provides simple deployment commands you can use out of the box as well as mock GraphQL services for you to try out.

Bind the GraphQL service

Automatic service binding

If you selected the option to bind services automatically when creating the authorizer, your services should already be bound and you can skip this section.

  1. Go to APIs -> Gateways -> YOUR_GATEWAY -> APIs. A list of APIs discovered by your authorizer appears.

    APIs not visible

    If you cannot see your GraphQL service, make sure that:

    • The service is deployed.

    • Istio is actually scanning the service’s namespace (read Discovering APIs on Istio for more details). By default, Istio only scans the default namespace.

  2. Select Connect -> Connect to new service on your GraphQL API.

  3. Enter the name for the service and click Connect. Your service is now available from the APIs menu in ACP.

Your GraphQL service discovered by the authorizer is now bound to the workspace. You can now assign policies to GraphQL objects and fields.

Assign GraphQL policies in ACP

  1. Go to APIs and find your GraphQL service in the API’s Management page.

  2. To apply a policy to the whole service, select the policy button (Unrestricted/Protected depending on the current status) from the API’sManagement page and assign an API policy.

  3. To apply a policy to individual GraphQL objects, select the GraphQL service. You can now see the service Types and Schema. The Schema page is read only - it simply shows what’s currently bound to ACP.

    Query highlighting

    If you’re not sure which policies will be evaluated on your query, enter the query into the Query box on the right. All queried objects are highlighted so that you know which items are controlled via policies!

Assign policies declaratively

As an alternative to managing GraphQL policies in ACP, you can assign policies directly in the GraphQL schema.

  1. First, you need to assign the following directive to your schema:

    directive @auth(
      policy: ID,
    ) on FIELD_DEFINITION | OBJECT | INTERFACE
    
  2. Annotate the fields, objects, and interfaces with an ID matching the ACP API policy name (as in type Continent @auth(policy: "nist-aal-1_default_api")).

    directive @auth(
      policy: ID,
    ) on FIELD_DEFINITION | OBJECT | INTERFACE
    
    type Continent @auth(policy: "nist-aal-1_default_api" {
      code: ID!
      name: String!
      countries: [Country!]!
    }
    
    input ContinentFilterInput {
      code: StringQueryOperatorInput
    }
    
    type Country @auth(policy: "nist-aal-1_default_api" {
      code: ID!
      name: String!
      native: String!
      phone: String! @auth(policy: "block_default_api"
      continent: Continent!
      capital: String
      currency: String
      languages: [Language!]!
      emoji: String!
      emojiU: String!
      states: [State!]!
    }
    
    type Language @auth(policy: "nist-aal-1_default_api" {
      code: ID!
      name: String
      native: String
      rtl: Boolean!
    }
    
    type Query @auth(policy: "nist-aal-1_default_api" {
      continents(filter: ContinentFilterInput): [Continent!]!
      continent(code: ID!): Continent
      countries(filter: CountryFilterInput): [Country!]!
      country(code: ID!): Country
      languages(filter: LanguageFilterInput): [Language!]!
      language(code: ID!): Language
    }
    

When the above GraphQL schema is picked up by the authorizer, policies are assigned out of the box, in accordance with your annotations. You cannot manage declarative policy assignments in ACP - you need to update the schema to make changes.

Verify your GraphQL policy

  1. Assign the Block API policy to any queried object (this policy is available in ACP out of the box).

    Tip

    If you’re assigning the policy in ACP, use the query highlighting to see which objects are queried.

  2. Run the query on your service

    Whitespace characters

    Your curl request data must include the whitespace characters formatting present in the GraphQL syntax.

    curl 'http://example.com/graphql' --data-binary '{"query":"# Write your query or mutation here"}'
    

    Result

    Since you assigned the Block API policy, you should get a 403 Forbidden response with the following message:

    {"errors":[{"message":"policy validatation: block_default_api failed"}]}
    
  3. Remove the Block API policy. You should now get the 200 OK response with the data you queried for.