org.sonar.iac.docker.checks.SecretsHandlingCheck Maven / Gradle / Ivy
/*
* SonarQube IaC Plugin
* Copyright (C) 2021-2023 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.iac.docker.checks;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.iac.common.api.checks.CheckContext;
import org.sonar.iac.common.api.checks.IacCheck;
import org.sonar.iac.common.api.checks.InitContext;
import org.sonar.iac.docker.symbols.ArgumentResolution;
import org.sonar.iac.docker.tree.api.ArgInstruction;
import org.sonar.iac.docker.tree.api.Argument;
import org.sonar.iac.docker.tree.api.EnvInstruction;
import org.sonar.iac.docker.tree.api.Expression;
import org.sonar.iac.docker.tree.api.KeyValuePair;
import org.sonar.iac.docker.tree.api.Variable;
@Rule(key = "S6472")
public class SecretsHandlingCheck implements IacCheck {
private static final String MESSAGE = "Make sure that using %s to handle a secret is safe here.";
private static final Set ENTITIES = Set.of("ACCESS", "AMPLITUDE", "ANSIBLE", "ADMIN", "API",
"APP", "AUTH", "CLIENT", "CONFIG", "DATABASE", "DB", "ENCRYPTION", "ENV", "FACEBOOK", "FIREBASE", "FTP", "GIT",
"GITHUB", "GITLAB", "HONEYCOMB", "JWT", "KEYCLOAK", "KEYRING", "LDAP", "MAIL", "MASTER", "MARIADB", "MSSQL",
"MYSQL", "NPM", "OAUTH", "OAUTH2", "PG", "POSTGRES", "REDIS", "REFRESH", "REPLICATION", "ROOT", "RPC", "SA",
"SECRET", "SERVER", "SIGN", "SIGNING", "SLACK", "SVN", "USER", "VNC", "WEBHOOK", "JDBC");
private static final Set SECRETS = Set.of("CREDENTIALS", "KEY", "PASS", "PASSPHRASE", "PASSWD", "PASSWORD",
"SECRET", "TOKEN");
private static final Set EXCLUSIONS = Set.of("ALLOW", "DIR", "EXPIRE", "EXPIRY", "FILE", "ID",
"LOCATION", "NAME", "OWNER", "PATH", "URL", "SIZE");
// Patterns to split the identifier of a variable into separate words
private static final Pattern UNDERSCORE_NAME_PATTERN = Pattern.compile("^\\w+$");
private static final Pattern DASH_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9-]+$");
private static final Pattern CAMELCASE_NAME_PATTERN = Pattern.compile("^[A-Za-z]+$");
private static final Pattern CAMELCASE_SPLIT_PATTERN = Pattern.compile("(? checkAssignments(ctx, envInstruction.environmentVariables(), AssignmentType.ENV));
init.register(ArgInstruction.class, (ctx, argInstruction) -> checkAssignments(ctx, argInstruction.keyValuePairs(), AssignmentType.ARG));
}
private static void checkAssignments(CheckContext ctx, List assignments, AssignmentType type) {
for (KeyValuePair assignment : assignments) {
if (isSensitiveName(assignment.key()) && isSensitiveValue(assignment.value(), type)) {
ctx.reportIssue(assignment.key(), String.format(MESSAGE, type.name()));
}
}
}
/**
* Check if the left hand of the assignment is a sensitive
*/
private static boolean isSensitiveName(Argument nameArgument) {
ArgumentResolution nameResolution = ArgumentResolution.of(nameArgument);
return nameResolution.isResolved() && isSensitiveVariableName(nameResolution.value());
}
/**
* Check if the right hand of the assignment is sensitive
*/
private static boolean isSensitiveValue(@Nullable Argument secret, AssignmentType type) {
if (secret == null) {
return type.equals(AssignmentType.ARG);
}
ArgumentResolution valueResolution = ArgumentResolution.of(secret);
if (valueResolution.isUnresolved()) {
return type.equals(AssignmentType.ARG) || isSensitiveVariableName(secret);
}
String value = valueResolution.value();
if (value.isBlank()) {
return type.equals(AssignmentType.ARG);
}
return !isUrl(value) && !isPath(value);
}
/**
* Check if the argument contains of a single variable expression and check if its name is sensitive
*/
private static boolean isSensitiveVariableName(Argument secret) {
List expressions = secret.expressions();
if (expressions.size() == 1 && expressions.get(0) instanceof Variable) {
String identifier = ((Variable) expressions.get(0)).identifier();
return isSensitiveVariableName(identifier);
}
return false;
}
/**
* Check if the identifier of a variable is sensitive
*/
private static boolean isSensitiveVariableName(String identifier) {
List words = VarNameSplitter.split(identifier);
return isSecretWordOnly(words) || containsSecretEntityWordCombination(words);
}
private static boolean isSecretWordOnly(List words) {
return words.size() == 1 && SECRETS.contains(words.get(0));
}
private static boolean containsSecretEntityWordCombination(List words) {
if (words.stream().anyMatch(EXCLUSIONS::contains)) {
return false;
}
for (int i = 0; i < words.size(); i++) {
if (ENTITIES.contains(words.get(i)) && i < words.size() - 1 && SECRETS.contains(words.get(i + 1))) {
return true;
}
}
return false;
}
private static boolean isUrl(String value) {
return URL_PATTERN.matcher(value).find();
}
private static boolean isPath(String value) {
return PATH_PATTERN.matcher(value).find();
}
private static class VarNameSplitter {
private static List split(String name) {
if (UNDERSCORE_NAME_PATTERN.matcher(name).matches() && name.contains("_")) {
return toUpperCase(name.split("_"));
}
if (DASH_NAME_PATTERN.matcher(name).matches() && name.contains("-")) {
return toUpperCase(name.split("-"));
}
if (CAMELCASE_NAME_PATTERN.matcher(name).matches()) {
return toUpperCase(CAMELCASE_SPLIT_PATTERN.split(name));
}
return Collections.emptyList();
}
private static List toUpperCase(String[] strings) {
return Stream.of(strings).map(s -> s.toUpperCase(Locale.ROOT)).collect(Collectors.toList());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy