All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.snowflake.client.util.SecretDetector Maven / Gradle / Ivy

/*
 * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
 */

package net.snowflake.client.util;

import net.snowflake.client.log.SFLogger;
import net.snowflake.client.log.SFLoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Search for credentials in a sql text
 */
public class SecretDetector
{
  // We look for long base64 encoded (and perhaps also URL encoded - hence the
  // '%' character in the regex) strings.
  // This will match some things that it shouldn't. The minimum number of
  // characters is essentially a random choice - long enough to not mask other
  // strings but not so long that it might miss things.  
  private static final Pattern GENERIC_CREDS_PATTERN = Pattern.compile(
      "([a-z0-9+/%]{18,})", Pattern.CASE_INSENSITIVE);

  private static final Pattern AWS_KEY_PATTERN = Pattern.compile(
      "(aws_key_id)|(aws_secret_key)|(access_key_id)|(secret_access_key)",
      Pattern.CASE_INSENSITIVE);

  // Used for detecting tokens in serialized JSON
  private static final Pattern AWS_TOKEN_PATTERN = Pattern.compile(
      "(accessToken|tempToken|keySecret)\"\\s*:\\s*\"([a-z0-9/+]{32,}={0,2})\"",
      Pattern.CASE_INSENSITIVE);
  private static final Pattern SAS_TOKEN_PATTERN = Pattern.compile(
      "sig=([a-z0-9%]{32,})", Pattern.CASE_INSENSITIVE);

  private static final int LOOK_AHEAD = 10;

  // only attempt to find secrets in its leading 100Kb SNOW-30961
  private static final int MAX_LENGTH = 100 * 1000;

  private static final SFLogger LOGGER = SFLoggerFactory.getLogger(
      SecretDetector.class);

  /**
   * Find all the positions of aws key id and aws secret key.
   * The time complexity is O(n)
   *
   * @param text the sql text which may contain aws key
   * @return Return a list of begin/end positions of aws key id and
   * aws secret key.
   */
  private static List getAWSSecretPos(String text)
  {
    // log before and after in case this is causing StackOverflowError
    LOGGER.debug("pre-regex getAWSSecretPos");

    Matcher matcher = AWS_KEY_PATTERN.matcher(text);

    ArrayList awsSecretRanges = new ArrayList<>();

    while (matcher.find())
    {
      int beginPos = Math.min(matcher.end() + LOOK_AHEAD, text.length());

      while (beginPos > 0 && beginPos < text.length() &&
             isBase64(text.charAt(beginPos)))
      {
        beginPos--;
      }

      int endPos = Math.min(matcher.end() + LOOK_AHEAD, text.length());

      while (endPos < text.length() && isBase64(text.charAt(endPos)))
      {
        endPos++;
      }

      if (beginPos < text.length() && endPos <= text.length()
          && beginPos >= 0 && endPos >= 0)
      {
        awsSecretRanges.add(new SecretRange(beginPos + 1, endPos));
      }
    }

    LOGGER.debug("post-regex getAWSSecretPos");

    return awsSecretRanges;
  }

  /**
   * Find all the positions of long base64 encoded strings that are typically
   * indicative of secrets or other keys.
   * The time complexity is O(n)
   *
   * @param text the sql text which may contain secrets
   * @return Return a list of begin/end positions of keys in the string
   */
  private static List getGenericSecretPos(String text)
  {
    // log before and after in case this is causing StackOverflowError
    LOGGER.debug("pre-regex getGenericSecretPos");

    Matcher matcher = GENERIC_CREDS_PATTERN.matcher(
        text.length() <= MAX_LENGTH ? text : text.substring(0, MAX_LENGTH));

    ArrayList awsSecretRanges = new ArrayList<>();

    while (matcher.find())
    {
      awsSecretRanges.add(new SecretRange(matcher.start(), matcher.end()));
    }

    LOGGER.debug("post-regex getGenericSecretPos");

    return awsSecretRanges;
  }

  private static boolean isBase64(char ch)
  {
    return ('A' <= ch && ch <= 'Z')
           || ('a' <= ch && ch <= 'z')
           || ('0' <= ch && ch <= '9')
           || ch == '+'
           || ch == '/';
  }

  /**
   * mask AWS secret in the input string
   *
   * @param sql
   * @return masked string
   */
  public static String maskAWSSecret(String sql)
  {
    List secretRanges =
        SecretDetector.getAWSSecretPos(sql);
    for (SecretDetector.SecretRange secretRange : secretRanges)
    {
      sql = maskText(sql, secretRange.beginPos, secretRange.endPos);
    }
    return sql;
  }

  /**
   * Masks given text between begin position and end position.
   *
   * @param text   text to mask
   * @param begPos begin position (inclusive)
   * @param endPos end position (exclusive)
   * @return masked text
   */
  private static String maskText(String text, int begPos, int endPos)
  {
    // Convert the SQL statement to a char array to obe able to modify it.
    char[] chars = text.toCharArray();

    // Mask the value in the SQL statement using *.
    for (int curPos = begPos; curPos < endPos; curPos++)
    {
      chars[curPos] = '☺';
    }

    // Convert it back to a string
    return String.valueOf(chars);
  }

  static class SecretRange
  {
    final int beginPos;
    final int endPos;

    SecretRange(int beginPos, int endPos)
    {
      this.beginPos = beginPos;
      this.endPos = endPos;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy