{"meta":{"title":"Партнерская программа сканирования секретов","intro":"Поставщики услуг могут сотрудничать с GitHub для защиты форматов секретных маркеров с помощью проверки секретов, в рамках которой выполняется поиск случайных фиксаций в формате секрета, результаты которого можно отправить в конечную точку проверки поставщика услуг.","product":"Безопасность и качество кода","breadcrumbs":[{"href":"/ru/code-security","title":"Безопасность и качество кода"},{"href":"/ru/code-security/tutorials","title":"Tutorials"},{"href":"/ru/code-security/tutorials/secret-scanning-partner-program","title":"Партнерская программа"}],"documentType":"article"},"body":"# Партнерская программа сканирования секретов\n\nПоставщики услуг могут сотрудничать с GitHub для защиты форматов секретных маркеров с помощью проверки секретов, в рамках которой выполняется поиск случайных фиксаций в формате секрета, результаты которого можно отправить в конечную точку проверки поставщика услуг.\n\nGitHub сканирует репозитории известных форматов секретов, чтобы предотвратить случайное использование учетных данных, которые были зафиксированы случайно. Secret scanning по умолчанию выполняется в общедоступных репозиториях и общедоступных пакетах npm. Администраторы репозитория и владелец организации также могут включать secret scanning в частных репозиториях. Как поставщик услуг вы можете сотрудничать с GitHub, чтобы форматы секретов были включены в наше secret scanning.\n\nЕсли совпадение формата секрета найдено в общедоступном источнике, полезные данные отправляются в конечную точку HTTP по вашему выбору.\n\nПри обнаружении соответствия формата секрета в частном репозитории, настроенном для secret scanning, об этом оповещаются администраторы репозитория и средство фиксации, которые могут просматривать результат secret scanning в GitHub. Дополнительные сведения см. в разделе [Управление оповещениями проверки секретов](/ru/code-security/secret-scanning/managing-alerts-from-secret-scanning).\n\nВ этой статье описывается способ сотрудничества с GitHub в качестве поставщика услуг и присоединения к партнерской программе secret scanning.\n\n## Процесс secret scanning\n\nНа следующей схеме показан процесс secret scanning для общедоступных репозиториев с любыми совпадениями, отправленными в конечную точку проверки поставщика услуг. Аналогичный процесс отправляет маркеры поставщиков услуг, предоставляемые в общедоступных пакетах в реестре npm.\n\n![Схема, показывающая процесс сканирования секрета и отправки совпадений в конечную точку поставщика услуг.](/assets/images/help/security/secret-scanning-flow.png)\n\n## Присоединение программы secret scanning на GitHub\n\n1. Чтобы начать процесс, обратитесь к GitHub.\n2. Определите соответствующие секреты, которые необходимо сканировать, и создайте регулярные выражения для их записи. Дополнительные сведения и рекомендации см. в разделе [\"Определение секретов\" и создание регулярных выражений](#identify-your-secrets-and-create-regular-expressions) ниже.\n3. Для открытых совпадений секретов создайте службу предупреждений секрета, которая принимает веб-перехватчики из GitHub с полезными данными secret scanning.\n4. Реализуйте проверку подписи в службе оповещений о секретах.\n5. Реализуйте отзыв секретов и уведомление пользователя в службе оповещений о секретах.\n6. Предоставьте отзыв о ложноположительных результатах (необязательно).\n\n### Чтобы начать процесс, обратитесь к GitHub\n\nЧтобы начать процесс регистрации, отправьте сообщение электронной почты <a href=\"mailto:secret-scanning@github.com\"><secret-scanning@github.com></a>.\n\nВы получите сведения о программе secret scanning, и перед продолжением необходимо согласиться с условиями участия GitHub.\n\n### Определение секретов и создание регулярных выражений\n\nДля сканирования секретов GitHub требуются следующие фрагменты информации для каждого секрета, который требуется включить в программу secret scanning:\n\n* Уникальное, удобочитаемое пользователем имя для типа секрета. Мы будем использовать его позже для создания значения `Type` в полезных данных сообщения.\n\n* Регулярное выражение, которое позволяет найти тип секрета. Рекомендуется максимально точно, так как это поможет сократить количество ложных срабатываний. Ниже приведены некоторые рекомендации по обеспечению высокого качества идентифицируемых секретов:\n\n  * Уникально определенный префикс\n  * Случайные строки высокой энтропии\n  * 32-разрядная контрольная сумма\n\n  ![Снимок экрана: разбивка секрета на префикс и 32-разрядную контрольную сумму.](/assets/images/help/security/regular-expression-guidance.png)\n\n* Тестовая учетная запись для службы. Это позволит нам создавать и анализировать примеры секретов, уменьшая ложные срабатывания.\n\n* URL-адрес конечной точки, получающей сообщения от GitHub. URL-адрес не должен быть уникальным для каждого типа секрета.\n\nОтправьте эти сведения в <a href=\"mailto:secret-scanning@github.com\"><secret-scanning@github.com></a>.\n\n### Создание службы оповещений о секрете\n\nСоздайте общедоступную в Интернете конечную точку HTTP по URL-адресу, который вы нам предоставили. Когда совпадение регулярного выражения найдено публично, GitHub отправляет HTTP-сообщение `POST` в конечную точку.\n\n#### Примеры текста запроса\n\n```json\n[\n  {\n    \"token\":\"NMIfyYncKcRALEXAMPLE\",\n    \"type\":\"mycompany_api_token\",\n    \"url\":\"https://github.com/octocat/Hello-World/blob/12345600b9cbe38a219f39a9941c9319b600c002/foo/bar.txt\",\n    \"source\":\"content\"\n  }\n]\n```\n\nТекст сообщения — это массив JSON, содержащий один или несколько объектов, с каждым объектом, представляющим одно совпадение секретов. Конечная точка должна иметь возможность обрабатывать запросы с большим количеством совпадений без истечения времени ожидания. Ключи для каждого соответствия секрета:\n\n* **токен:** значение совпадения секрета.\n* **тип:** уникальное имя, предоставленное для идентификации регулярного выражения.\n* **URL-адрес:** общедоступный URL-адрес, в котором найден совпадение (может быть пустым)\n* **источник:** где найден маркер на GitHub.\n\nСписок допустимых значений `source` :\n\n* Content\n* Зафиксировать\n* Pull\\_request\\_title\n* Pull\\_request\\_description\n* Pull\\_request\\_comment\n* Issue\\_title\n* Issue\\_description\n* Issue\\_comment\n* Discussion\\_title\n* Discussion\\_body\n* Discussion\\_comment\n* Commit\\_comment\n* Gist\\_content\n* Gist\\_comment\n* Wiki\\_content\n* Wiki\\_commit\n* Npm\n* Manual\\_submission\n* Неизвестно\n\n### Реализуйте проверку подписи в службе оповещений о секретах\n\nHTTP-запрос к вашей службе также будет содержать заголовки, которые мы настоятельно рекомендуем использовать для проверки получаемых сообщений из GitHub, и не являются вредоносными.\n\nДва заголовка HTTP для поиска:\n\n* `Github-Public-Key-Identifier`: что `key_identifier` следует использовать из нашего API\n* `Github-Public-Key-Signature`: подпись полезных данных\n\nОткрытый ключ сканирования секрета GitHub можно получить из <https://api.github.com/meta/public_keys/secret_scanning> и проверить сообщение с помощью алгоритма `ECDSA-NIST-P256V1-SHA256`. Конечная точка предоставляет несколько `key_identifier` и открытых ключей. Вы можете определить, какой открытый ключ следует использовать на основе значения `Github-Public-Key-Identifier`.\n\n> \\[!NOTE]\n> При отправке запроса в конечную точку открытого ключа выше можно ударить по ограничениям скорости. Чтобы избежать ограничений скорости, можно использовать personal access token (classic) (без областей) или fine-grained personal access token (только автоматический доступ на чтение общедоступных репозиториев), как показано в приведенных ниже примерах, или использовать условный запрос. Дополнительные сведения см. в разделе [Начало работы с REST API](/ru/rest/guides/getting-started-with-the-rest-api#conditional-requests).\n\n> \\[!NOTE]\n> Подпись была создана с помощью текста необработанного сообщения. Поэтому так важно для проверки подписи использовать необработанный текст сообщения, а не синтаксический анализ и преобразование JSON в строку, чтобы избежать изменения содержимого сообщения или изменения интервала.\n\n```\n          **Пример HTTP POST, отправляемый для проверки конечной точки**\n```\n\n```http\nPOST / HTTP/2\nHost: HOST\nAccept: */*\nContent-Length: 104\nContent-Type: application/json\nGithub-Public-Key-Identifier: bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c\nGithub-Public-Key-Signature: MEQCIQDaMKqrGnE27S0kgMrEK0eYBmyG0LeZismAEz/BgZyt7AIfXt9fErtRS4XaeSt/AO1RtBY66YcAdjxji410VQV4xg==\n\n[{\"source\":\"commit\",\"token\":\"some_token\",\"type\":\"some_type\",\"url\":\"https://example.com/base-repo-url/\"}]\n```\n\nВ следующих фрагментах кода показано, как можно выполнить проверку подписи.\nВ примерах кода предполагается, что вы задали переменную среды, вызываемую `GITHUB_PRODUCTION_TOKEN` с созданными [personal access token](https://github.com/settings/tokens) , чтобы избежать ограничений скорости попадания. Для personal access token не требуется никаких областей и разрешений.\n\n```\n          **Пример проверки в Go**\n```\n\n```golang\npackage main\n\nimport (\n  \"crypto/ecdsa\"\n  \"crypto/sha256\"\n  \"crypto/x509\"\n  \"encoding/asn1\"\n  \"encoding/base64\"\n  \"encoding/json\"\n  \"encoding/pem\"\n  \"errors\"\n  \"fmt\"\n  \"math/big\"\n  \"net/http\"\n  \"os\"\n)\n\nfunc main() {\n  payload := `[{\"source\":\"commit\",\"token\":\"some_token\",\"type\":\"some_type\",\"url\":\"https://example.com/base-repo-url/\"}]`\n\n  kID := \"bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c\"\n\n  kSig := \"MEQCIQDaMKqrGnE27S0kgMrEK0eYBmyG0LeZismAEz/BgZyt7AIfXt9fErtRS4XaeSt/AO1RtBY66YcAdjxji410VQV4xg==\"\n\n  // Fetch the list of GitHub Public Keys\n  req, err := http.NewRequest(\"GET\", \"https://api.github.com/meta/public_keys/secret_scanning\", nil)\n  if err != nil {\n    fmt.Printf(\"Error preparing request: %s\\n\", err)\n    os.Exit(1)\n  }\n\n  if len(os.Getenv(\"GITHUB_PRODUCTION_TOKEN\")) == 0 {\n    fmt.Println(\"Need to define environment variable GITHUB_PRODUCTION_TOKEN\")\n    os.Exit(1)\n  }\n\n  req.Header.Add(\"Authorization\", \"Bearer \"+os.Getenv(\"GITHUB_PRODUCTION_TOKEN\"))\n\n  resp, err := http.DefaultClient.Do(req)\n  if err != nil {\n    fmt.Printf(\"Error requesting GitHub signing keys: %s\\n\", err)\n    os.Exit(2)\n  }\n\n  decoder := json.NewDecoder(resp.Body)\n  var keys GitHubSigningKeys\n  if err := decoder.Decode(&keys); err != nil {\n    fmt.Printf(\"Error decoding GitHub signing key request: %s\\n\", err)\n    os.Exit(3)\n  }\n\n  // Find the Key used to sign our webhook\n  pubKey, err := func() (string, error) {\n    for _, v := range keys.PublicKeys {\n      if v.KeyIdentifier == kID {\n        return v.Key, nil\n\n      }\n    }\n    return \"\", errors.New(\"specified key was not found in GitHub key list\")\n  }()\n\n  if err != nil {\n    fmt.Printf(\"Error finding GitHub signing key: %s\\n\", err)\n    os.Exit(4)\n  }\n\n  // Decode the Public Key\n  block, _ := pem.Decode([]byte(pubKey))\n  if block == nil {\n    fmt.Println(\"Error parsing PEM block with GitHub public key\")\n    os.Exit(5)\n  }\n\n  // Create our ECDSA Public Key\n  key, err := x509.ParsePKIXPublicKey(block.Bytes)\n  if err != nil {\n    fmt.Printf(\"Error parsing DER encoded public key: %s\\n\", err)\n    os.Exit(6)\n  }\n\n  // Because of documentation, we know it's a *ecdsa.PublicKey\n  ecdsaKey, ok := key.(*ecdsa.PublicKey)\n  if !ok {\n    fmt.Println(\"GitHub key was not ECDSA, what are they doing?!\")\n    os.Exit(7)\n  }\n\n  // Parse the Webhook Signature\n  parsedSig := asn1Signature{}\n  asnSig, err := base64.StdEncoding.DecodeString(kSig)\n  if err != nil {\n    fmt.Printf(\"unable to base64 decode signature: %s\\n\", err)\n    os.Exit(8)\n  }\n  rest, err := asn1.Unmarshal(asnSig, &parsedSig)\n  if err != nil || len(rest) != 0 {\n    fmt.Printf(\"Error unmarshalling asn.1 signature: %s\\n\", err)\n    os.Exit(9)\n  }\n\n  // Verify the SHA256 encoded payload against the signature with GitHub's Key\n  digest := sha256.Sum256([]byte(payload))\n  keyOk := ecdsa.Verify(ecdsaKey, digest[:], parsedSig.R, parsedSig.S)\n\n  if keyOk {\n    fmt.Println(\"THE PAYLOAD IS GOOD!!\")\n  } else {\n    fmt.Println(\"the payload is invalid :(\")\n    os.Exit(10)\n  }\n}\n\ntype GitHubSigningKeys struct {\n  PublicKeys []struct {\n    KeyIdentifier string `json:\"key_identifier\"`\n    Key           string `json:\"key\"`\n    IsCurrent     bool   `json:\"is_current\"`\n  } `json:\"public_keys\"`\n}\n\n// asn1Signature is a struct for ASN.1 serializing/parsing signatures.\ntype asn1Signature struct {\n  R *big.Int\n  S *big.Int\n}\n```\n\n```\n          **Пример проверки в Ruby**\n```\n\n```ruby\nrequire 'openssl'\nrequire 'net/http'\nrequire 'uri'\nrequire 'json'\nrequire 'base64'\n\npayload = <<-EOL\n[{\"source\":\"commit\",\"token\":\"some_token\",\"type\":\"some_type\",\"url\":\"https://example.com/base-repo-url/\"}]\nEOL\n\npayload = payload\n\nsignature = \"MEQCIQDaMKqrGnE27S0kgMrEK0eYBmyG0LeZismAEz/BgZyt7AIfXt9fErtRS4XaeSt/AO1RtBY66YcAdjxji410VQV4xg==\"\n\nkey_id = \"bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c\"\n\nurl = URI.parse('https://api.github.com/meta/public_keys/secret_scanning')\n\nraise \"Need to define GITHUB_PRODUCTION_TOKEN environment variable\" unless ENV['GITHUB_PRODUCTION_TOKEN']\nrequest = Net::HTTP::Get.new(url.path)\nrequest['Authorization'] = \"Bearer #{ENV['GITHUB_PRODUCTION_TOKEN']}\"\n\nhttp = Net::HTTP.new(url.host, url.port)\nhttp.use_ssl = (url.scheme == \"https\")\n\nresponse = http.request(request)\n\nparsed_response = JSON.parse(response.body)\n\ncurrent_key_object = parsed_response[\"public_keys\"].find { |key| key[\"key_identifier\"] == key_id }\n\ncurrent_key = current_key_object[\"key\"]\n\nopenssl_key = OpenSSL::PKey::EC.new(current_key)\n\nputs openssl_key.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), payload.chomp)\n```\n\n```\n          **Пример проверки в JavaScript**\n```\n\n```javascript\nconst crypto = require(\"crypto\");\nconst axios = require(\"axios\");\n\nconst GITHUB_KEYS_URI = \"https://api.github.com/meta/public_keys/secret_scanning\";\n\n/**\n * Verify a payload and signature against a public key\n * @param {String} payload the value to verify\n * @param {String} signature the expected value\n * @param {String} keyID the id of the key used to generated the signature\n * @return {void} throws if the signature is invalid\n */\nconst verify_signature = async (payload, signature, keyID) => {\n  if (typeof payload !== \"string\" || payload.length === 0) {\n    throw new Error(\"Invalid payload\");\n  }\n  if (typeof signature !== \"string\" || signature.length === 0) {\n    throw new Error(\"Invalid signature\");\n  }\n  if (typeof keyID !== \"string\" || keyID.length === 0) {\n    throw new Error(\"Invalid keyID\");\n  }\n\n  const keys = (await axios.get(GITHUB_KEYS_URI)).data;\n  if (!(keys?.public_keys instanceof Array) || keys.length === 0) {\n    throw new Error(\"No public keys found\");\n  }\n\n  const publicKey = keys.public_keys.find((k) => k.key_identifier === keyID) ?? null;\n  if (publicKey === null) {\n    throw new Error(\"No public key found matching key identifier\");\n  }\n\n  const verify = crypto.createVerify(\"SHA256\").update(payload);\n  if (!verify.verify(publicKey.key, Buffer.from(signature, \"base64\"), \"base64\")) {\n    throw new Error(\"Signature does not match payload\");\n  }\n};\n```\n\n### Реализуйте отзыв секретов и уведомление пользователя в службе оповещений о секретах\n\nДля secret scanning вы можете улучшить службу оповещений секретов, чтобы отозвать предоставленные секреты и уведомить затронутых пользователей. Как вы реализуете это в своей службе оповещения о секретах зависит от вас. Рекомендуется учитывать все секреты, о которых GitHub отправляет вам сообщения как общедоступные и скомпрометированные.\n\n### Отправка отзыва о ложноположительных результатах\n\nМы собираем отзывы о допустимости обнаруженных отдельных секретах в ответах партнера. Если вы хотите принять участие в этом, отправьте нам письмо по адресу <a href=\"mailto:secret-scanning@github.com\"><secret-scanning@github.com></a>.\n\nПри передаче вам секретов мы отправляем массив JSON с каждым элементом, содержащим маркер, идентификатор типа и URL-адрес фиксации. При передаче нам вашего отзыва вы отправляете нам сведения о том, является ли обнаруженный маркер реальными или ложными учетными данными. Мы принимаем отзывы в следующих форматах.\n\nВы можете отправить нам необработанный маркер:\n\n```json\n[\n  {\n    \"token_raw\": \"The raw token\",\n    \"token_type\": \"ACompany_API_token\",\n    \"label\": \"true_positive\"\n  }\n]\n```\n\nВы также можете указать маркер в хэшированных формах после выполнения одностороннего криптографического хэша необработанного маркера с помощью SHA-256:\n\n```json\n[\n  {\n    \"token_hash\": \"The SHA-256 hashed form of the raw token\",\n    \"token_type\": \"ACompany_API_token\",\n    \"label\": \"false_positive\"\n  }\n]\n```\n\nНекоторые важные моменты:\n\n* Вы должны отправить нам либо необработанную форму маркера (\"token\\_raw\"), либо хэшированную форму (\"token\\_hash\"), но не обе одновременно.\n* Для хэшированной формы необработанного маркера вы можете использовать только SHA-256 для хэширования маркера, а не любой другой хэш-алгоритм.\n* Метка указывает на то, является ли маркер истинноположительным (\"true\\_positive\") или ложноположительным результатом (\"false\\_positive\"). Допускаются только эти две строки литерала в нижнем регистре.\n\n> \\[!NOTE]\n> Время ожидания запроса должно быть выше (т. е. 30 секунд) для партнеров, которые предоставляют данные о ложных срабатываниях. Если вам требуется время ожидания, превышающее 30 секунд, отправьте нам сообщение по адресу <a href=\"mailto:secret-scanning@github.com\"><secret-scanning@github.com></a>."}