GraphQL API Documentation

GraphQL API Documentation

This document is intended to be a simple introduction to our GraphQL API, and (in this current alpha phase) explicitly definining a particular subset which can be considered officially suupported. Ultimately the entire graph should be considered “supported” unless otherwise annotated (e.g. with @deprecated or @experimental warnings).

The base url of our graph API has recently been changed to api.nowsecure.com. All requests to lab-api.nowsecure.com/graphql will be forwarded here.

Using the Graph API

There are many ways to consume the GraphQL API, and there are quite a few good resources online detailing some of the possible ways to interface with a Graph, such as this guide. There are also a variety of GraphQL client libraries in any languages that can leverage a GraphQL API advertised service and type definitions. While all of this helps aid explorability of the API, but when it comes to consuming data, a simple HTTP POST request is all you’ll need.

HTTP POST

Interacting with the Graph API from code, e.g. through curl or via HTTP client library, is as simple as an HTTP POST with the relevant GraphQL query.

Here’s an simple example query to list out NowSecure’s findings, resolving just the finding id and title for each:

curl \
  -X POST \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{ "query": "{ findings { list { id title } } }" }' \
  https://api.nowsecure.com/graphql

For graphql server requires a content-type to be specified on each posted request. We will likely override this behavior in the future and attempt to interpret request POST bodies without a Content-Type.

The results will come back as a JSON with a data key. The values will be structured as requested (with results formatted and elided for clarity):

{
  "data": {
    "findings": {
      "list": [
        {
          "id": "asl",
          "title": "System Log Messages (ASL)"
        },
        {
          "id": "oslog",
          "title": "System Log Messages (OSLog)"
        },
        {
          "id": "geoip",
          "title": "Network Connections"
        },
        {
          "id": "snoop_network_hosts",
          "title": "Network Connections"
        },
        // ...
      ]
    }
  }
}

As described in the field documentation, the finding id is a case and space normalized version of the legacy finding id used by the legacy lab auto api. You may also resolve the unnormalized key for each finding within the legacyFindingKey field. If you have existing logic depending on identifying individual findings, it is this legacyFindingKey you want to use.

Errors

If the query issued contains one or more errors, these will be listed in a top level "errors" array in the response. For example, this query:

curl \
  -X POST \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{ "query": "{ findings { list { id title nonExistentField } } }" }' \
  https://api.nowsecure.com/graphql

Would result in a response which looks something like this:

{
  "errors": [
    {
      "message": "Cannot query field \"nonExistentField\" on type \"FindingCheck\"."
      // ...
    }
  ]
}

The same is true if your graphql query is syntactically invalid:

curl \
  -X POST \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{ "query": "{ findings { list { id title"' \
  https://api.nowsecure.com/graphql

Note the lack of closing } braces. The response would look something like this:

{
  "errors": [
    {
      "message": "Syntax Error: Expected Name, found <EOF>"
      // ...
    }
  ]
}

If your query contains invalid json, the default behavior of the graphql server is to return an HTTP response with the error text in a <pre> tag. We will likely override this behavior in the future to force JSON responses in all cases. of our graph API will change in the near future to api.nowsecure.com, though we will continue to support GraphQL API calls to api.nowsecure.com/graphql in the near term.

GraphQL Playground

The GraphQL playground – available at https://api.nowsecure.com/graphql – is a web app for browsing and interacting with NowSecure’s GraphQL API. This is a standard tool within the graphql toolbox, and makes learning, browsing and prodding at any given GraphQL API much more accessible for everybody, even for those users who have no interest in interacting with our API programmatically. Among many other features, it provides auto-completion when authoring queries, syntax highlighting and auto-formatting of queries and results, and a powerful, searchable “Documentation Explorer” panel. This allows the entire API to be explored one type at a time. Any documentation available for a given type, and/or for any function arguments or record attributes, are surfaced right inline.

Authentication

In order to authenticate, you must also provide a valid Authorization: Bearer ... header with the request, as for any other API request. In the graphql playground, this can be accomplished within the “HTTP HEADERS”, specifying any headers as a JSON object, e.g.:

{
  "Authorization": "Bearer INSERT_YOUR_JWT_API_TOKEN_HERE"
}

Pre-release Supported Subset

This is preliminary, pre-release documentation of our GraphQL API. While there is a variety of features accessible via the GraphQL API, and reachable from within the explorer, the bulk of the graph is still somewhat in flux, as it has grown somewhat organically and is not yet internally self-consistent. The query patterns documented below of graph nodes available from within the explorer, the API itself is still in flux.

Alerts

To query for alerts for the authenticated user, you would use the myAlerts query node. For example, to query for all unread alerts, sorted such that most recent alerts come first, it might look something like this:

query {
  alerts {
    myAlerts(isRead: false, sortDescending: true) {
      id
      eventType
      createdAt
      readAt
    }
  }
}

The GraphQL JSON response:

{
  "data": {
    "alerts": {
      "myAlerts": [
        {
          "id": 6172,
          "eventType": "ASSESSMENT_DONE",
          "createdAt": "2019-02-08T19:11:21.109Z",
          "readAt": true
        },
        {
          "id": 6171,
          "eventType": "ASSESSMENT_DONE",
          "createdAt": "2019-02-08T16:10:56.258Z",
          "readAt": true
        }
      ]
    }
  }
}

The myAlerts query node takes two arguments: the isRead parameter allows filtering strictly for alerts which have been either read or unread (if unspecified, both read and unread alerts will be returend). The sortDescending parameter allows for fetching alerts by most recent first, rather than the default chronological order (the order alerts were created in).

We will likely rework how we surface the sort specification (e.g. we may define an explicit enumeration of available sorts, allowing us to support whatever can be efficiently serviced from available indexes). Regardless, we can maintain backward compatibility, and continue to support this particular sortDescending argument indefinitely.

In the near future this likely be annotated with a deprecation warning, with instructions about what to use instead to get semantically equivalent behavior.

Change History Audit Logs

The audit > changes node can be queried to see all changes on audited attributes for all entities across the org. (For the time being, this feature requires org admin privileges – as org admins inherently have access to all of this data via existing APIs – but as this feature solidifies we will be able to integrate it with existing RBAC mechanisms.)

For example, to resolve all information for every historical change across the org:

query {
  audit {
    changes {
      entityType
      entityRef
      attribute
      value
      updatedAt
      user {
        ref
      }
    }
  }
}

This result set can be sliced by timeframe using the optional since and until arguments, and an integer limit argument is also available. The results may also be filtered for specific entityTypes, and/or for a given set of attributes. For example, to see all cvss edits this year:

query {
  audit {
    changes(since: "2019-01-01T00:00:00Z", attributes: ["edit:cvss"]) {
      value
      updatedAt
      user {
        ref
      }
    }
  }
}

Due to limitations of the graphql DateTime type we require a full ISO 8601 timestamp for the time being.

Assuming change events exist in the date range, the result set should look something like this:

{
  "data": {
    "audit": {
      "changes": [
        {
          "value": 4.5,
          "updatedAt": "2019-01-16T23:40:28.611Z",
          "user": {
            "ref": "81e3b350-0325-457e-8d32-f7202ce8a2a8"
          }
        },
        {
          "value": 5.4,
          "updatedAt": "2019-01-22T17:26:58.562Z",
          "user": {
            "ref": "81e3b350-0325-457e-8d32-f7202ce8a2a8"
          }
        },
        // ...
      ]
    }
  }
}

There are still some change events where the user responsible for the change is not being logged. These gaps will be remedied in subsequent releases.

Long-term deprecation policy

As the graph API structure stabilizes, any graph nodes we would like to remove will be annotated with a deprecation warning, which should include instructions on where best to query for the relevant data instead. We will continue to support queries on these deprecated nodes, but using them when authoring new queries in the graphql playground, for instance, will issue warnings. While it is not adviseable to develop new application logic against deprecated nodes, we will not remove any deprecated nodes/functionality without a great deal of advanced warning. We intend to be extremely conservative about preserving backward-compatibility, warts and all – we consider any breakage of backward-compatibility to be a bug.