Skip to main content

Programa asociado del escaneo de secretos

Como proveedor de servicios, puede asociarse con GitHub para que los formatos de token secretos estén protegidos a través del análisis de secretos, que busca confirmaciones accidentales del formato secreto y se pueden enviar al punto de conexión de comprobación de un proveedor de servicios.

¿Quién puede utilizar esta característica?

Alertas de escaneo de secretos para socios se ejecuta de manera predeterminada en los repositorios siguientes:

  • Repositorios públicos y paquetes npm públicos en GitHub.

GitHub analiza los repositorios en busca de formatos conocidos de secretos para evitar el uso fraudulento de credenciales incluidas accidentalmente. Secret scanning sucede de forma predeterminada en repositorios públicos y paquetes npm públicos. Los administradores de repositorios y los propietarios de organizaciones también pueden habilitar secret scanning en repositorios privados. Como proveedor de servicios, puede asociarse con GitHub para que los formatos secretos se incluyan en nuestro secret scanning.

Cuando se encuentra una coincidencia de tu formato secreto en un repositorio público, se envía una carga útil a un punto de conexión HTTP de tu elección.

Cuando se encuentra una coincidencia de su patrón secreto en un repositorio privado configurado para secret scanning, los administradores del repositorio y el autor del commit reciben una alerta y pueden ver y gestionar el resultado de secret scanning en GitHub. Para más información, consulta Administración de alertas de examen de secretos.

En este artículo se describe cómo puede asociarse con GitHub como proveedor de servicios y unirse al secret scanning programa de partners.

El secret scanning proceso

El siguiente diagrama resume el proceso secret scanning para repositorios públicos, y cualquier coincidencia se envía al extremo de verificación de un proveedor de servicios. Un proceso similar envía tokens de proveedores de servicios expuestos en paquetes públicos en el registro npm.

Diagrama que muestra el proceso de escaneo para un secreto y el envío de coincidencias a una terminal de verificación del proveedor de servicios.

Unirse al programa secret scanning en GitHub

  1. Póngase en contacto con GitHub para comenzar el proceso.
  2. Identifica los secretos relevantes que quieres escanear y crea expresiones regulares para capturarlos. Para obtener información y recomendaciones más detalladas, consulte Identificación de secretos y creación de expresiones regulares a continuación.
  3. Para las coincidencias secretas encontradas públicamente, cree un servicio de alertas secreto que acepte webhooks de GitHub que contengan la carga del secret scanning mensaje.
  4. Implementa la verificación de firmas en tu servicio de alerta secreto.
  5. Implementa la revocación de secretos y las notificaciones al usuario en tu servicio de alerta de secretos.
  6. Proporciona retroalimentación para los falsos positivos (opcional).

Póngase en contacto con GitHub para iniciar el proceso.

Para iniciar el proceso de inscripción, envíe un correo electrónico a [email protected].

Recibirá detalles sobre el secret scanning programa y tendrá que aceptar los GitHubtérminos de participación antes de continuar.

Identifica tus secretos y crea expresiones regulares

Para buscar secretos, GitHub necesita los siguientes fragmentos de información para cada secreto que desee incluir en el secret scanning programa:

  • Un nombre único y legible para las personas para el tipo de secreto. Lo usaremos para generar el valor Type en la carga del mensaje más adelante.

  • Una expresión regular que encuentre el tipo de secreto. Le recomendamos que sea tan preciso como sea posible, ya que esto ayudará a reducir la cantidad de falsos positivos. Algunos procedimientos recomendados para secretos identificables de alta calidad son:

    • Prefijo definido de forma única
    • Cadenas aleatorias de alta entropía
    • Suma de comprobación de 32 bits

    Captura de pantalla que muestra el desglose de un secreto en un prefijo y una suma de comprobación de 32 bits.

  • Una cuenta de prueba para el servicio. Esto nos permitirá generar y analizar ejemplos de los secretos, lo que reducirá aún más los falsos positivos.

  • Dirección URL del punto de conexión que recibe mensajes de GitHub. La URL no tiene que ser única para cada tipo de secreto.

Envíe esta información a [email protected].

Crea un servicio de alerta de secretos

Crea una terminal HTTP pública y accesible desde la internet en la URL que nos proporcionaste. Cuando se encuentre públicamente una coincidencia de la expresión regular, GitHub enviará un mensaje HTTP POST al punto de conexión.

Cuerpo de solicitud de ejemplo

[
  {
    "token":"NMIfyYncKcRALEXAMPLE",
    "type":"mycompany_api_token",
    "url":"https://github.com/octocat/Hello-World/blob/12345600b9cbe38a219f39a9941c9319b600c002/foo/bar.txt",
    "source":"content"
  }
]

El cuerpo del mensaje es una matriz JSON que contiene uno o varios objetos, cada uno de los cuales representa una coincidencia de secreto única. El punto de conexión debe poder controlar solicitudes con una gran cantidad de coincidencias sin que se agote el tiempo de espera. Las claves de cada coincidencia de secreto son las siguientes:

  • token: valor de la coincidencia del secreto.
  • tipo: nombre único que proporcionó para identificar la expresión regular.
  • url: dirección URL pública en la que se encontró la coincidencia (puede estar vacía).
  • source: Donde se encontró el token en GitHub.

Esta es una lista de valores válidos para source:

  • Content
  • Commit
  • Pull_request_title
  • Pull_request_description
  • Pull_request_comment
  • Issue_title
  • Issue_description
  • Issue_comment
  • Discussion_title
  • Discussion_body
  • Discussion_comment
  • Commit_comment
  • Gist_content
  • Gist_comment
  • Contenido_de_la_Wiki
  • Wiki_commit
  • Npm
  • Manual_submission
  • Desconocido

Implementa la verificación de firmas en tu servicio de alerta de secretos

La solicitud HTTP al servicio también contendrá encabezados que se recomienda encarecidamente usar para validar que los mensajes que recibe son originalmente de GitHuby no son malintencionados.

Los dos encabezados HTTP que se van a buscar son:

  • Github-Public-Key-Identifier: qué key_identifier se va a usar desde nuestra API.
  • Github-Public-Key-Signature: firma de la carga.

Puede obtener la GitHub clave pública de análisis de secretos de https://api.github.com/meta/public_keys/secret_scanning y validar el mensaje mediante el algoritmo ECDSA-NIST-P256V1-SHA256. El punto de conexión proporcionará varios elementos key_identifier y claves públicas. Puedes determinar qué clave pública se va a usar en función del valor de Github-Public-Key-Identifier.

Nota:

Al enviar una solicitud al punto de conexión de clave pública anterior, puede que alcance los límites de frecuencia. Para evitar superar los límites de tasa, puede usar un personal access token (classic) (sin alcances necesarios) o un fine-grained personal access token (solo se requiere acceso automático de lectura a los repositorios públicos), como se sugiere en los ejemplos siguientes, o usar una solicitud condicional. Para más información, consulta Introducción a la API REST.

Nota:

La firma se generó con el cuerpo del mensaje sin procesar. Así que es importante que también utilices el cuerpo del mensaje sin procesar para la validación de la firma en vez de interpretar y convertir en secuencias el JSON, para evitar volver a arreglar dicho mensaje o cambiar los espacios.

Código HTTP POST de ejemplo que se envía para comprobar un punto de conexión

POST / HTTP/2
Host: HOST
Accept: */*
Content-Length: 104
Content-Type: application/json
Github-Public-Key-Identifier: bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c
Github-Public-Key-Signature: MEQCIQDaMKqrGnE27S0kgMrEK0eYBmyG0LeZismAEz/BgZyt7AIfXt9fErtRS4XaeSt/AO1RtBY66YcAdjxji410VQV4xg==

[{"source":"commit","token":"some_token","type":"some_type","url":"https://example.com/base-repo-url/"}]

En los fragmentos de código siguientes se muestra cómo realizar la validación de la firma. Los ejemplos de código dan por hecho que has configurado una variable de entorno llamada GITHUB_PRODUCTION_TOKEN con un personal access token generado para evitar superar los límites de tasa. personal access token no necesita ningún ámbito o permisos.

Ejemplo de validación en Go

package main

import (
  "crypto/ecdsa"
  "crypto/sha256"
  "crypto/x509"
  "encoding/asn1"
  "encoding/base64"
  "encoding/json"
  "encoding/pem"
  "errors"
  "fmt"
  "math/big"
  "net/http"
  "os"
)

func main() {
  payload := `[{"source":"commit","token":"some_token","type":"some_type","url":"https://example.com/base-repo-url/"}]`

  kID := "bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c"

  kSig := "MEQCIQDaMKqrGnE27S0kgMrEK0eYBmyG0LeZismAEz/BgZyt7AIfXt9fErtRS4XaeSt/AO1RtBY66YcAdjxji410VQV4xg=="

  // Fetch the list of GitHub Public Keys
  req, err := http.NewRequest("GET", "https://api.github.com/meta/public_keys/secret_scanning", nil)
  if err != nil {
    fmt.Printf("Error preparing request: %s\n", err)
    os.Exit(1)
  }

  if len(os.Getenv("GITHUB_PRODUCTION_TOKEN")) == 0 {
    fmt.Println("Need to define environment variable GITHUB_PRODUCTION_TOKEN")
    os.Exit(1)
  }

  req.Header.Add("Authorization", "Bearer "+os.Getenv("GITHUB_PRODUCTION_TOKEN"))

  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    fmt.Printf("Error requesting GitHub signing keys: %s\n", err)
    os.Exit(2)
  }

  decoder := json.NewDecoder(resp.Body)
  var keys GitHubSigningKeys
  if err := decoder.Decode(&keys); err != nil {
    fmt.Printf("Error decoding GitHub signing key request: %s\n", err)
    os.Exit(3)
  }

  // Find the Key used to sign our webhook
  pubKey, err := func() (string, error) {
    for _, v := range keys.PublicKeys {
      if v.KeyIdentifier == kID {
        return v.Key, nil

      }
    }
    return "", errors.New("specified key was not found in GitHub key list")
  }()

  if err != nil {
    fmt.Printf("Error finding GitHub signing key: %s\n", err)
    os.Exit(4)
  }

  // Decode the Public Key
  block, _ := pem.Decode([]byte(pubKey))
  if block == nil {
    fmt.Println("Error parsing PEM block with GitHub public key")
    os.Exit(5)
  }

  // Create our ECDSA Public Key
  key, err := x509.ParsePKIXPublicKey(block.Bytes)
  if err != nil {
    fmt.Printf("Error parsing DER encoded public key: %s\n", err)
    os.Exit(6)
  }

  // Because of documentation, we know it's a *ecdsa.PublicKey
  ecdsaKey, ok := key.(*ecdsa.PublicKey)
  if !ok {
    fmt.Println("GitHub key was not ECDSA, what are they doing?!")
    os.Exit(7)
  }

  // Parse the Webhook Signature
  parsedSig := asn1Signature{}
  asnSig, err := base64.StdEncoding.DecodeString(kSig)
  if err != nil {
    fmt.Printf("unable to base64 decode signature: %s\n", err)
    os.Exit(8)
  }
  rest, err := asn1.Unmarshal(asnSig, &parsedSig)
  if err != nil || len(rest) != 0 {
    fmt.Printf("Error unmarshalling asn.1 signature: %s\n", err)
    os.Exit(9)
  }

  // Verify the SHA256 encoded payload against the signature with GitHub's Key
  digest := sha256.Sum256([]byte(payload))
  keyOk := ecdsa.Verify(ecdsaKey, digest[:], parsedSig.R, parsedSig.S)

  if keyOk {
    fmt.Println("THE PAYLOAD IS GOOD!!")
  } else {
    fmt.Println("the payload is invalid :(")
    os.Exit(10)
  }
}

type GitHubSigningKeys struct {
  PublicKeys []struct {
    KeyIdentifier string `json:"key_identifier"`
    Key           string `json:"key"`
    IsCurrent     bool   `json:"is_current"`
  } `json:"public_keys"`
}

// asn1Signature is a struct for ASN.1 serializing/parsing signatures.
type asn1Signature struct {
  R *big.Int
  S *big.Int
}

Ejemplo de validación en Ruby

require 'openssl'
require 'net/http'
require 'uri'
require 'json'
require 'base64'

payload = <<-EOL
[{"source":"commit","token":"some_token","type":"some_type","url":"https://example.com/base-repo-url/"}]
EOL

payload = payload

signature = "MEQCIQDaMKqrGnE27S0kgMrEK0eYBmyG0LeZismAEz/BgZyt7AIfXt9fErtRS4XaeSt/AO1RtBY66YcAdjxji410VQV4xg=="

key_id = "bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c"

url = URI.parse('https://api.github.com/meta/public_keys/secret_scanning')

raise "Need to define GITHUB_PRODUCTION_TOKEN environment variable" unless ENV['GITHUB_PRODUCTION_TOKEN']
request = Net::HTTP::Get.new(url.path)
request['Authorization'] = "Bearer #{ENV['GITHUB_PRODUCTION_TOKEN']}"

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = (url.scheme == "https")

response = http.request(request)

parsed_response = JSON.parse(response.body)

current_key_object = parsed_response["public_keys"].find { |key| key["key_identifier"] == key_id }

current_key = current_key_object["key"]

openssl_key = OpenSSL::PKey::EC.new(current_key)

puts openssl_key.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), payload.chomp)

Ejemplo de validación en JavaScript

const crypto = require("crypto");
const axios = require("axios");

const GITHUB_KEYS_URI = "https://api.github.com/meta/public_keys/secret_scanning";

/**
 * Verify a payload and signature against a public key
 * @param {String} payload the value to verify
 * @param {String} signature the expected value
 * @param {String} keyID the id of the key used to generated the signature
 * @return {void} throws if the signature is invalid
 */
const verify_signature = async (payload, signature, keyID) => {
  if (typeof payload !== "string" || payload.length === 0) {
    throw new Error("Invalid payload");
  }
  if (typeof signature !== "string" || signature.length === 0) {
    throw new Error("Invalid signature");
  }
  if (typeof keyID !== "string" || keyID.length === 0) {
    throw new Error("Invalid keyID");
  }

  const keys = (await axios.get(GITHUB_KEYS_URI)).data;
  if (!(keys?.public_keys instanceof Array) || keys.length === 0) {
    throw new Error("No public keys found");
  }

  const publicKey = keys.public_keys.find((k) => k.key_identifier === keyID) ?? null;
  if (publicKey === null) {
    throw new Error("No public key found matching key identifier");
  }

  const verify = crypto.createVerify("SHA256").update(payload);
  if (!verify.verify(publicKey.key, Buffer.from(signature, "base64"), "base64")) {
    throw new Error("Signature does not match payload");
  }
};

Implementa la revocación de secretos y la notificación a usuarios en tu servicio de alerta de secretos

Para secret scanning que se encuentre públicamente, puede mejorar el servicio de alertas secretas para revocar los secretos expuestos y notificar a los usuarios afectados. La forma de implementar esto en el servicio de alertas secretas es suya, pero se recomienda considerar los secretos que GitHub le envíen mensajes como públicos y comprometidos.

Proporciona retroalimentación sobre los falsos positivos

Recolectamos la retroalimentación sobre la validez de los secretos individuales que se detectan en las respuestas de los socios. Si quiere participar, envíanos un correo electrónico a [email protected].

Cuando te reportamos los secretos, enviamos un arreglo de JSON con cada elemento que contiene el token, identificador de tipo y URL de confirmación. Cuando envías retroalimentación, nos envías información sobre si el token que se detectó fue una credencial real o falsa. Aceptamos la retroalimentación en los siguientes formatos.

Puedes enviarnos el token sin procesar:

[
  {
    "token_raw": "The raw token",
    "token_type": "ACompany_API_token",
    "label": "true_positive"
  }
]

También puedes proporcionar el token en forma de hash después de realizar un hash criptográfico de una sola vía para el token sin procesar utilizando SHA-256:

[
  {
    "token_hash": "The SHA-256 hashed form of the raw token",
    "token_type": "ACompany_API_token",
    "label": "false_positive"
  }
]

Algunos puntos importantes:

  • Solo debes enviarnos ya sea la forma sin procesar del token ("token raw") o la forma en hash ("token_hash"), pero no ambas.
  • En el caso de la forma en hash del token sin procesar, solo puedes utilizar SHA-256 para crear el hash del token y no algún otro algoritmo.
  • La etiqueta indica si un token es un positivo verdadero ("true_positive") o falso ("false_positive"). Solo se permiten estas secuencias en minúsculas.

Nota:

Nuestro tiempo de espera se configura para que sea mayor (es decir, 30 segundos) para los partners que proporcionen datos sobre falsos positivos. Si necesita un tiempo de espera superior a 30 segundos, envíanos un correo electrónico a [email protected].