Перейти к содержанию

Соответствие ограничениям GraphQL

Вы можете настроить ПроAPI Защита для проверки входящих запросов GraphQL на соответствие предопределенным ограничениям запросов. Придерживаясь этих ограничений, вы можете защитить свой GraphQL API от вредоносных запросов, включая потенциальные DoS-атаки. В этом руководстве объясняется, как ПроAPI Защита вычисляет атрибуты запроса, такие как запросы узлов, глубину запроса и сложность, прежде чем согласовывать их с заданными вами параметрами.

При запуске Docker-контейнера ПроAPI Защита для API GraphQL вы устанавливаете ограничения, используя следующие переменные среды:

Переменная среды Описание
APIFW_GRAPHQL_MAX_QUERY_COMPLEXITY Определяет максимальное количество запросов узла, которое может потребоваться для выполнения запроса. Установка значения «0» отключает проверку сложности.
APIFW_GRAPHQL_MAX_QUERY_DEPTH Указывает максимально допустимую глубину запроса GraphQL. Значение «0» означает, что проверка глубины запроса пропускается.
APIFW_GRAPHQL_NODE_COUNT_LIMIT Устанавливает верхний предел количества узлов в запросе. Если установлено значение «0», проверка ограничения количества узлов пропускается.
APIFW_GRAPHQL_MAX_ALIASES_NUM Устанавливает ограничение на количество псевдонимов, которые можно использовать в документе GraphQL. Если для этой переменной установлено значение «0», это означает, что количество используемых псевдонимов не ограничено.
APIFW_GRAPHQL_FIELD_DUPLICATION Определяет, разрешить или запретить дублирование полей в документе GraphQL. Значение по умолчанию — false (предотвратить).
APIFW_GRAPHQL_BATCH_QUERY_LIMIT Устанавливает ограничение на количество запросов, которые можно объединить в один запрос GraphQL. Если для этой переменной установлено значение «0», это означает, что количество пакетных запросов не ограничено.

Как работает расчет лимита

ПроAPI Защита использует библиотеку wundergraph/graphql-go-tools, которая использует алгоритмы, аналогичные тем, которые используются GitHub для расчета сложности запросов GraphQL. Центральное место в этом занимает функция OperationComplexityEstimator, которая обрабатывает определение схемы и запрос, итеративно исследуя запрос, чтобы определить его сложность и глубину.

Вы можете уточнить этот расчет, интегрируя целочисленные аргументы в поля, которые обозначают количество узлов, возвращаемых полем:

  • directive @nodeCountMultiply on ARGUMENT_DEFINITION

    Указывает, что значение Int, к которому применяется директива, должно использоваться в качестве множителя Ноды.

  • directive @nodeCountSkip on FIELD
    Указывает, что алгоритм должна пропускать эта Нода. Это полезно для внесения в белый список определенных путей запроса, например, для самоанализа.

Для документов с несколькими запросами вычисленная сложность, глубина и количество узлов применяются ко всему документу, а не только к одному выполняемому запросу.

Примеры расчета

Ниже приведены несколько примеров, которые дадут более четкое представление о расчетах. Они основаны на следующей схеме GraphQL

type User {
    name: String!
    messages(first: Int! @nodeCountMultiply): [Message]
}

type Message {
    id: ID!
    text: String!
    createdBy: String!
    createdAt: Time!
}

type Query {
        __schema: __Schema! @nodeCountSkip
    users(first: Int! @nodeCountMultiply): [User]
    messages(first: Int! @nodeCountMultiply): [Message]
}

type Mutation {
    post(text: String!, username: String!, roomName: String!): Message!
}

type Subscription {
    messageAdded(roomName: String!): Message!
}

scalar Time

directive @nodeCountMultiply on ARGUMENT_DEFINITION
directive @nodeCountSkip on FIELD

Глубина всегда представляет уровни вложенности полей. Например, запрос ниже имеет глубину 3:

{
    a {
        b {
            c
        }
    }
}

Пример 1

query {
  users(first: 10) {
    name
    messages(first:100) {
      id
      text
    }
  }
}
  • NodeCount = {int} 1010

    Node count = 10 [users(first: 10)] + 10*100 [messages(first:100)] = 1010
    
  • Complexity = {int} 11

    Complexity = 1 [users(first: 10)] + 10 [messages(first:100)] = 11
    
  • Depth = {int} 3

Пример 2

query {
  users(first: 10) {
    name
  }
}
  • NodeCount = {int} 10

    Node count = 10 [users(first: 10)] = 10
    
  • Complexity = {int} 1

    Complexity = 1 [users(first: 10)] = 1
    
  • Depth = {int} 2

Пример 3

query {
  message(id:1) {
    id
    text
  }
}
  • NodeCount = {int} 1

    Node count = 1 [message(fid:1)] = 1
    
  • Complexity = {int} 1

    Complexity = 1 [messages(first:1)] = 1
    
  • Depth = {int} 2

Пример 4

query {
  users(first: 10) {
    name
    messages(first:1) {
      id
      text
    }
  }
}
  • NodeCount = {int} 20

    Node count = 10 [users(first: 10)] + 10*1 [messages(first:1)] = 20
    
  • Complexity = {int} 11

    Complexity = 1 [users(first: 10)] + 10 [messages(first:1)] = 11
    
  • Depth = {int} 3

Пример 5 (интроспекционный запрос)

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
              }
            }
          }
        }
      }
    }
  }
}
  • NodeCount = {int} 0

  • Complexity = {int} 0

  • Depth = {int} 0

Поскольку в схеме присутствует директива __schema: __Schema! @nodeCountSkip вычисленные NodeCount, Complexity и Depth равны 0.

Пример 6 (ограничение пакетных запросов)

Предположим, вы установили для переменной среды APIFW_GRAPHQL_BATCH_QUERY_LIMIT значение 2. Если вы попытаетесь последовательно выполнить следующий пакет из 3 запросов GraphQL к бэкэнду:

[
  {"query":"query {\n  systemHealth\n}","variables":[]},
  {"query":"query {\n  systemHealth\n}","variables":[]},
  {"query":"query {\n  systemHealth\n}","variables":[]}
]

ПроAPI Защита перехватит этот запрос и зарегистрирует ошибку, указывающую, что количество запросов в пакете превышает настроенный лимит. Зарегистрированное сообщение об ошибке будет иметь вид:

ERROR GraphQL query validation error=the batch query limit has been exceeded. The number of queries in the batch is 3. The current batch query limit is 2 protocol=HTTP