GraphQL Schema support

This page may contain information related to upcoming products, features and functionality. It is important to note that the information presented is for informational purposes only, so please do not rely on the information for purchasing or planning purposes. Just like with all projects, the items mentioned on the page are subject to change or delay, and the development, release, and timing of any products, features, or functionality remain at the sole discretion of GitLab Inc.

We currently support scanning GraphQL endpoints, but need a HAR file or Postman collection in order to scan it. In order to expand our support for GraphQL, we need to be able to support defining GraphQL endpoints through a GraphQL schema. We should support defining the endpoints via the GraphQL SDL, by allowing them to provide the path to a schema file.

Proposal

Open Questions:

  • Can we implement this w/o some form of parameter dependency resolution.
  • Should we create multiple exchanges per item? For example if their are optional inputs.

The GraphQL endpoint can also provide the full schema. This schema defines all of the things API Security will want to test, both for DAST and API Fuzzing. The proposed method of supporting the GraphQL schema for automatic testing is the following:

  1. Add a new variable _GRAPHQL, allowing the user to provide a GraphQL endpoint address or schema file
  2. Refactor the IReader readers to produce Operation+Request instead of just Request.
  3. Create a new IReader for GraphQL that provides each query/mutation as an Operation

Refactor Readers to produce Operations and Request pairs

The current reader implementation produces Request instances which are then converted into Operation instances by the RunnerService. Unfortunately that causes information loss for API specifications that include information about the parameters such as type or constraints.

This work will be required prior to creating the GraphQlSchemaReader.

Proposed new IReader interface:

namespace Peach.Web.Runner.Interfaces
{
    public interface IReader
    {
        IEnumerable<(Operation Operation, Request Request)> GetOperations(IRunnerOptions runnerOptions);
    }
}

Questions:

  1. For existing readers, do they use WebApiService.OperationFromRequest to create the Operation to pair with the Request they are already returning?

GraphQlSChemaReader (Peach.Web.Runner.Services.Readers)

Implement a new reader that creates a series of exchanges that provide coverage of all parameterized queries and mutations.

  1. Use our existing GraphQL.NET dependency to parse schema
    1. When schema is provided as a file, read file parse schema
    2. When schema is provided as a url:
      1. Get schema by running the query provided below
        1. Make sure to use overrides when making this request so it's authenticated
        2. The example query assumes max 10 levels of nested types. What do we do if the target schema is deeper? Should we pick a limit and say anything beyond that is not supported? Can we detect if there are more types that we didn't receive and issue a new query to get them?
      2. Extract schema from query result
  2. Iterate over the Queries and Mutations section of the Schema instance, generating (Operation, Request) pairs
    1. Generate an Operation with a GraphQLBody
      1. Option: Create a GraphQL AST doc directly, or create a GraphQL query and parse it.
        1. We have some concern that building the AST may be complex or fiddly, but initial investigation suggests it won't be too bad. It would be nice to have a library that was specifically designed to dynamically build GraphQL queries, but the only ones we can find assume that there is a statically-typed model.
        2. If we end up parsing a request, we need to ensure that we still end up with the correct type information
    2. Convert the Operation into a Request
      1. Is WebApiService.OperationIntoRequest sufficient for this?
Interpreting the schema response
  • Interesting part is data, __schema, types object
    • Query root name is __schema[queryType][name]
    • Mutation root name is __schema[mutationType][name]
    • Queries are listed as types[query root][fields]
    • Mutations as types[mutation root][fields]
  • So far unable to find a way to read json response into the Schema instance
    • might need to construct by hand

Testing

API Security GraphQL Target

Implementation Checklist

  1. Mike: Review Herb's feedback
  2. David: Get GraphQlIntegrationTests.GraphQlPathOfSlash test to pass
  3. Complete work on GraphqlController.Validate
    1. Mike: Hook up the GraphqlController.Validate method to call the GraphQlReader and return a correct validation response.
    2. Do we need to hook up GraphQlOptionValidationErrorCode.ErrorOnValidation to workerentry?
    3. Update worker-entry and add test to support GraphQL validation
    4. Need tests, especially for unexpected exceptions which are not well handled yet.
  4. [ ] David: Do we need to perform overrides on the GraphQL introspection query (yes probably)?
    1. Yes, but we will do this along with other readers during #357052 (closed)
  5. David: Add good error message when we are unable to run an introspection query on the target
  6. David: Add proper error handling to the introspection query run
  7. Mike: Add ReaderErrorContext to GraphQlReader
    1. GetOperations
    2. GetGraphQlOperations
    3. Rest of in scope methods (moved to non-essentials)
  8. David: Implement unsupported types in GraphQlValueElement.cs
    1. ListValue
    2. ObjectValue
    3. GuidValue
  9. Mike: Verify new worker-entry tests for GraphQL are passing
  10. Mike: Add variable prefix to exception messages in GraphQlReader.GetOperations
  11. Non-essentials (can push this week w/o)
    1. Do we need additional error handling around LoadJsonDocument in GraphQlReader.GetOperations
    2. Should we mark subscriptions as excluded? (perhaps this is a follow on issue)
    3. Mike: Investigate all of the network errors from the Python GraphQL example
    4. Add ReaderErrorContext to rest of in scope GraphQlReader methods
    5. Investigate if we need to support other types from this list of GraphQL.NET types. We probably do not need too.
    6. Investigate why the ReaderException exception message is not being displayed in the worker-entry console output
  12. Documentation
    1. Document usage of new variables and graphql schema support
    2. Add section on getting a copy of the GraphQL Schema in JSON format
      1. Move this to it's own issue
    3. Update documentation with new variable _SCHEMA
    4. Mike: Update Example GraphQL project

Appendix

Utilities

GitLab GraphQL Explorer
Example of variables separate from query inputs

Schema Query

This is the query used by the interactive GraphQL web thing:

Click this to show/hide query.
query IntrospectionQuery {
  __schema {
    queryType {
      name
    }
    mutationType {
      name
    }
    subscriptionType {
      name
    }
    types {
      ...FullType
    }
    directives {
      name
      description
      locations
      args {
        ...InputValue
      }
    }
  }
}

fragment FullType on __Type {
  kind
  name
  description
  fields(includeDeprecated: true) {
    name
    description
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    description
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}

fragment InputValue on __InputValue {
  name
  description
  type {
    ...TypeRef
  }
  defaultValue
}

fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}
Edited by Michael Eddington