InstrumentationConfig part 4: DB sanitization (#6317)

* InstrumentationConfig part 4: DB sanitization

* fix couchbase unit tests

* code review comments

Co-authored-by: Trask Stalnaker <trask.stalnaker@gmail.com>
This commit is contained in:
Mateusz Rzeszutek 2022-07-16 06:47:33 +02:00 committed by GitHub
parent 3af56e7d22
commit 8d1ba17d29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 296 additions and 208 deletions

View File

@ -38,22 +38,4 @@ tasks {
sourcesJar {
dependsOn("generateJflex")
}
val testStatementSanitizerConfig by registering(Test::class) {
filter {
includeTestsMatching("StatementSanitizationConfigTest")
}
include("**/StatementSanitizationConfigTest.*")
jvmArgs("-Dotel.instrumentation.common.db-statement-sanitizer.enabled=false")
}
test {
filter {
excludeTestsMatching("StatementSanitizationConfigTest")
}
}
check {
dependsOn(testStatementSanitizerConfig)
}
}

View File

@ -8,10 +8,6 @@ package io.opentelemetry.instrumentation.api.db;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableMap;
import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer.CommandSanitizer.CommandAndNumArgs;
import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer.CommandSanitizer.Eval;
import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer.CommandSanitizer.KeepAllArgs;
import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer.CommandSanitizer.MultiKeyValue;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
@ -353,8 +349,18 @@ public final class RedisCommandSanitizer {
SANITIZERS = unmodifiableMap(sanitizers);
}
public static String sanitize(String command, List<?> args) {
if (!StatementSanitizationConfig.isStatementSanitizationEnabled()) {
public static RedisCommandSanitizer create(boolean statementSanitizationEnabled) {
return new RedisCommandSanitizer(statementSanitizationEnabled);
}
private final boolean statementSanitizationEnabled;
private RedisCommandSanitizer(boolean statementSanitizationEnabled) {
this.statementSanitizationEnabled = statementSanitizationEnabled;
}
public String sanitize(String command, List<?> args) {
if (!statementSanitizationEnabled) {
return KeepAllArgs.INSTANCE.sanitize(command, args);
}
return SANITIZERS
@ -362,107 +368,105 @@ public final class RedisCommandSanitizer {
.sanitize(command, args);
}
public interface CommandSanitizer {
interface CommandSanitizer {
String sanitize(String command, List<?> args);
}
static String argToString(Object arg) {
if (arg instanceof byte[]) {
return new String((byte[]) arg, StandardCharsets.UTF_8);
} else {
return String.valueOf(arg);
}
}
enum KeepAllArgs implements CommandSanitizer {
INSTANCE;
@Override
public String sanitize(String command, List<?> args) {
StringBuilder sanitized = new StringBuilder(command);
for (Object arg : args) {
sanitized.append(" ").append(argToString(arg));
}
return sanitized.toString();
}
}
// keeps only a chosen number of arguments
// example for num=2: CMD arg1 arg2 ? ?
class CommandAndNumArgs implements CommandSanitizer {
private final int numOfArgsToKeep;
public CommandAndNumArgs(int numOfArgsToKeep) {
this.numOfArgsToKeep = numOfArgsToKeep;
}
@Override
public String sanitize(String command, List<?> args) {
StringBuilder sanitized = new StringBuilder(command);
for (int i = 0; i < numOfArgsToKeep && i < args.size(); ++i) {
sanitized.append(" ").append(argToString(args.get(i)));
}
for (int i = numOfArgsToKeep; i < args.size(); ++i) {
sanitized.append(" ?");
}
return sanitized.toString();
}
}
// keeps only chosen number of arguments and then every second one
// example for num=2: CMD arg1 arg2 key1 ? key2 ?
class MultiKeyValue implements CommandSanitizer {
private final int numOfArgsBeforeKeyValue;
public MultiKeyValue(int numOfArgsBeforeKeyValue) {
this.numOfArgsBeforeKeyValue = numOfArgsBeforeKeyValue;
}
@Override
public String sanitize(String command, List<?> args) {
StringBuilder sanitized = new StringBuilder(command);
// append all "initial" arguments before key-value pairs start
for (int i = 0; i < numOfArgsBeforeKeyValue && i < args.size(); ++i) {
sanitized.append(" ").append(argToString(args.get(i)));
}
// loop over keys only
for (int i = numOfArgsBeforeKeyValue; i < args.size(); i += 2) {
sanitized.append(" ").append(argToString(args.get(i))).append(" ?");
}
return sanitized.toString();
}
}
enum Eval implements CommandSanitizer {
INSTANCE;
@Override
public String sanitize(String command, List<?> args) {
StringBuilder sanitized = new StringBuilder(command);
// get the number of keys passed from the command itself (second arg)
int numberOfKeys = 0;
if (args.size() > 2) {
try {
numberOfKeys = Integer.parseInt(argToString(args.get(1)));
} catch (NumberFormatException ignored) {
// Ignore
}
}
int i = 0;
// log the script, number of keys and all keys
for (; i < (numberOfKeys + 2) && i < args.size(); ++i) {
sanitized.append(" ").append(argToString(args.get(i)));
}
// mask the rest
for (; i < args.size(); ++i) {
sanitized.append(" ?");
}
return sanitized.toString();
enum KeepAllArgs implements CommandSanitizer {
INSTANCE;
@Override
public String sanitize(String command, List<?> args) {
StringBuilder sanitized = new StringBuilder(command);
for (Object arg : args) {
sanitized.append(" ").append(argToString(arg));
}
return sanitized.toString();
}
}
private RedisCommandSanitizer() {}
// keeps only a chosen number of arguments
// example for num=2: CMD arg1 arg2 ? ?
static final class CommandAndNumArgs implements CommandSanitizer {
private final int numOfArgsToKeep;
CommandAndNumArgs(int numOfArgsToKeep) {
this.numOfArgsToKeep = numOfArgsToKeep;
}
@Override
public String sanitize(String command, List<?> args) {
StringBuilder sanitized = new StringBuilder(command);
for (int i = 0; i < numOfArgsToKeep && i < args.size(); ++i) {
sanitized.append(" ").append(argToString(args.get(i)));
}
for (int i = numOfArgsToKeep; i < args.size(); ++i) {
sanitized.append(" ?");
}
return sanitized.toString();
}
}
// keeps only chosen number of arguments and then every second one
// example for num=2: CMD arg1 arg2 key1 ? key2 ?
static final class MultiKeyValue implements CommandSanitizer {
private final int numOfArgsBeforeKeyValue;
MultiKeyValue(int numOfArgsBeforeKeyValue) {
this.numOfArgsBeforeKeyValue = numOfArgsBeforeKeyValue;
}
@Override
public String sanitize(String command, List<?> args) {
StringBuilder sanitized = new StringBuilder(command);
// append all "initial" arguments before key-value pairs start
for (int i = 0; i < numOfArgsBeforeKeyValue && i < args.size(); ++i) {
sanitized.append(" ").append(argToString(args.get(i)));
}
// loop over keys only
for (int i = numOfArgsBeforeKeyValue; i < args.size(); i += 2) {
sanitized.append(" ").append(argToString(args.get(i))).append(" ?");
}
return sanitized.toString();
}
}
enum Eval implements CommandSanitizer {
INSTANCE;
@Override
public String sanitize(String command, List<?> args) {
StringBuilder sanitized = new StringBuilder(command);
// get the number of keys passed from the command itself (second arg)
int numberOfKeys = 0;
if (args.size() > 2) {
try {
numberOfKeys = Integer.parseInt(argToString(args.get(1)));
} catch (NumberFormatException ignored) {
// Ignore
}
}
int i = 0;
// log the script, number of keys and all keys
for (; i < (numberOfKeys + 2) && i < args.size(); ++i) {
sanitized.append(" ").append(argToString(args.get(i)));
}
// mask the rest
for (; i < args.size(); ++i) {
sanitized.append(" ?");
}
return sanitized.toString();
}
}
static String argToString(Object arg) {
if (arg instanceof byte[]) {
return new String((byte[]) arg, StandardCharsets.UTF_8);
} else {
return String.valueOf(arg);
}
}
}

View File

@ -5,7 +5,6 @@
package io.opentelemetry.instrumentation.api.db;
import static io.opentelemetry.instrumentation.api.db.StatementSanitizationConfig.isStatementSanitizationEnabled;
import static io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics.CounterNames.SQL_STATEMENT_SANITIZER_CACHE_MISS;
import com.google.auto.value.AutoValue;
@ -23,12 +22,22 @@ public final class SqlStatementSanitizer {
private static final Cache<CacheKey, SqlStatementInfo> sqlToStatementInfoCache =
Cache.bounded(1000);
public static SqlStatementInfo sanitize(@Nullable String statement) {
public static SqlStatementSanitizer create(boolean statementSanitizationEnabled) {
return new SqlStatementSanitizer(statementSanitizationEnabled);
}
private final boolean statementSanitizationEnabled;
private SqlStatementSanitizer(boolean statementSanitizationEnabled) {
this.statementSanitizationEnabled = statementSanitizationEnabled;
}
public SqlStatementInfo sanitize(@Nullable String statement) {
return sanitize(statement, SqlDialect.DEFAULT);
}
public static SqlStatementInfo sanitize(@Nullable String statement, SqlDialect dialect) {
if (!isStatementSanitizationEnabled() || statement == null) {
public SqlStatementInfo sanitize(@Nullable String statement, SqlDialect dialect) {
if (!statementSanitizationEnabled || statement == null) {
return SqlStatementInfo.create(statement, null, null);
}
return sqlToStatementInfoCache.computeIfAbsent(
@ -50,6 +59,4 @@ public final class SqlStatementSanitizer {
abstract SqlDialect getDialect();
}
private SqlStatementSanitizer() {}
}

View File

@ -1,21 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.api.db;
import io.opentelemetry.instrumentation.api.config.Config;
/** DB statement sanitization is always enabled by default, you have to manually disable it. */
final class StatementSanitizationConfig {
private static final boolean STATEMENT_SANITIZATION_ENABLED =
Config.get().getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true);
static boolean isStatementSanitizationEnabled() {
return STATEMENT_SANITIZATION_ENABLED;
}
private StatementSanitizationConfig() {}
}

View File

@ -82,6 +82,9 @@ public abstract class DbClientSpanNameExtractor<REQUEST> implements SpanNameExtr
private static final class SqlClientSpanNameExtractor<REQUEST>
extends DbClientSpanNameExtractor<REQUEST> {
// a dedicated sanitizer just for extracting the operation and table name
private static final SqlStatementSanitizer sanitizer = SqlStatementSanitizer.create(true);
private final SqlClientAttributesGetter<REQUEST> getter;
private SqlClientSpanNameExtractor(SqlClientAttributesGetter<REQUEST> getter) {
@ -91,8 +94,7 @@ public abstract class DbClientSpanNameExtractor<REQUEST> implements SpanNameExtr
@Override
public String extract(REQUEST request) {
String dbName = getter.name(request);
SqlStatementInfo sanitizedStatement =
SqlStatementSanitizer.sanitize(getter.rawStatement(request));
SqlStatementInfo sanitizedStatement = sanitizer.sanitize(getter.rawStatement(request));
return computeSpanName(
dbName, sanitizedStatement.getOperation(), sanitizedStatement.getTable());
}

View File

@ -44,19 +44,22 @@ public final class SqlClientAttributesExtractor<REQUEST, RESPONSE>
}
private final AttributeKey<String> dbTableAttribute;
private final SqlStatementSanitizer sanitizer;
SqlClientAttributesExtractor(
SqlClientAttributesGetter<REQUEST> getter, AttributeKey<String> dbTableAttribute) {
SqlClientAttributesGetter<REQUEST> getter,
AttributeKey<String> dbTableAttribute,
SqlStatementSanitizer sanitizer) {
super(getter);
this.dbTableAttribute = dbTableAttribute;
this.sanitizer = sanitizer;
}
@Override
public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST request) {
super.onStart(attributes, parentContext, request);
SqlStatementInfo sanitizedStatement =
SqlStatementSanitizer.sanitize(getter.rawStatement(request));
SqlStatementInfo sanitizedStatement = sanitizer.sanitize(getter.rawStatement(request));
internalSet(attributes, SemanticAttributes.DB_STATEMENT, sanitizedStatement.getFullStatement());
internalSet(attributes, SemanticAttributes.DB_OPERATION, sanitizedStatement.getOperation());
internalSet(attributes, dbTableAttribute, sanitizedStatement.getTable());

View File

@ -8,6 +8,7 @@ package io.opentelemetry.instrumentation.api.instrumenter.db;
import static java.util.Objects.requireNonNull;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
/** A builder of {@link SqlClientAttributesExtractor}. */
@ -15,6 +16,7 @@ public final class SqlClientAttributesExtractorBuilder<REQUEST, RESPONSE> {
final SqlClientAttributesGetter<REQUEST> getter;
AttributeKey<String> dbTableAttribute = SemanticAttributes.DB_SQL_TABLE;
boolean statementSanitizationEnabled = true;
SqlClientAttributesExtractorBuilder(SqlClientAttributesGetter<REQUEST> getter) {
this.getter = getter;
@ -34,11 +36,23 @@ public final class SqlClientAttributesExtractorBuilder<REQUEST, RESPONSE> {
return this;
}
/**
* Sets whether the {@code db.statement} attribute extracted by the constructed {@link
* SqlClientAttributesExtractor} should be sanitized. If set to {@code true}, all parameters that
* can potentially contain sensitive information will be masked. Enabled by default.
*/
public SqlClientAttributesExtractorBuilder<REQUEST, RESPONSE> setStatementSanitizationEnabled(
boolean statementSanitizationEnabled) {
this.statementSanitizationEnabled = statementSanitizationEnabled;
return this;
}
/**
* Returns a new {@link SqlClientAttributesExtractor} with the settings of this {@link
* SqlClientAttributesExtractorBuilder}.
*/
public SqlClientAttributesExtractor<REQUEST, RESPONSE> build() {
return new SqlClientAttributesExtractor<>(getter, dbTableAttribute);
return new SqlClientAttributesExtractor<>(
getter, dbTableAttribute, SqlStatementSanitizer.create(statementSanitizationEnabled));
}
}

View File

@ -12,7 +12,7 @@ class RedisCommandSanitizerTest extends Specification {
@Unroll
def "should sanitize #expected"() {
when:
def sanitized = RedisCommandSanitizer.sanitize(command, args)
def sanitized = RedisCommandSanitizer.create(true).sanitize(command, args)
then:
sanitized == expected
@ -90,7 +90,7 @@ class RedisCommandSanitizerTest extends Specification {
def args = ["arg1", "arg 2"]
when:
def sanitized = RedisCommandSanitizer.sanitize(command, args)
def sanitized = RedisCommandSanitizer.create(true).sanitize(command, args)
then:
sanitized == command + " " + args.join(" ")
@ -140,7 +140,7 @@ class RedisCommandSanitizerTest extends Specification {
def "should mask all arguments of an unknown command"() {
when:
def sanitized = RedisCommandSanitizer.sanitize("NEWAUTH", ["password", "secret"])
def sanitized = RedisCommandSanitizer.create(true).sanitize("NEWAUTH", ["password", "secret"])
then:
sanitized == "NEWAUTH ? ?"

View File

@ -12,7 +12,7 @@ class SqlStatementSanitizerTest extends Specification {
def "normalize #originalSql"() {
setup:
def actualSanitized = SqlStatementSanitizer.sanitize(originalSql)
def actualSanitized = SqlStatementSanitizer.create(true).sanitize(originalSql)
expect:
actualSanitized.getFullStatement() == sanitizedSql
@ -79,7 +79,7 @@ class SqlStatementSanitizerTest extends Specification {
def "normalize couchbase #originalSql"() {
setup:
def actualSanitized = SqlStatementSanitizer.sanitize(originalSql, SqlDialect.COUCHBASE)
def actualSanitized = SqlStatementSanitizer.create(true).sanitize(originalSql, SqlDialect.COUCHBASE)
expect:
actualSanitized.getFullStatement() == sanitizedSql
@ -99,7 +99,7 @@ class SqlStatementSanitizerTest extends Specification {
@Unroll
def "should simplify #sql"() {
expect:
SqlStatementSanitizer.sanitize(sql) == expected
SqlStatementSanitizer.create(true).sanitize(sql) == expected
where:
sql | expected
@ -169,14 +169,14 @@ class SqlStatementSanitizerTest extends Specification {
expect:
def sanitizedQuery = query.replace('=123', '=?').substring(0, AutoSqlSanitizer.LIMIT)
SqlStatementSanitizer.sanitize(query) == SqlStatementInfo.create(sanitizedQuery, "SELECT", "table")
SqlStatementSanitizer.create(true).sanitize(query) == SqlStatementInfo.create(sanitizedQuery, "SELECT", "table")
}
def "lots and lots of ticks don't cause stack overflow or long runtimes"() {
setup:
String s = "'"
for (int i = 0; i < 10000; i++) {
assert SqlStatementSanitizer.sanitize(s) != null
assert SqlStatementSanitizer.create(true).sanitize(s) != null
s += "'"
}
}
@ -187,7 +187,7 @@ class SqlStatementSanitizerTest extends Specification {
for (int i = 0; i < 10000; i++) {
s += String.valueOf(i)
}
assert "?" == SqlStatementSanitizer.sanitize(s).getFullStatement()
assert "?" == SqlStatementSanitizer.create(true).sanitize(s).getFullStatement()
}
def "very long numbers at end of table name don't cause problem"() {
@ -196,7 +196,7 @@ class SqlStatementSanitizerTest extends Specification {
for (int i = 0; i < 10000; i++) {
s += String.valueOf(i)
}
assert s.substring(0, AutoSqlSanitizer.LIMIT) == SqlStatementSanitizer.sanitize(s).getFullStatement()
assert s.substring(0, AutoSqlSanitizer.LIMIT) == SqlStatementSanitizer.create(true).sanitize(s).getFullStatement()
}
def "test 32k truncation"() {
@ -205,7 +205,7 @@ class SqlStatementSanitizerTest extends Specification {
for (int i = 0; i < 10000; i++) {
s.append("SELECT * FROM TABLE WHERE FIELD = 1234 AND ")
}
String sanitized = SqlStatementSanitizer.sanitize(s.toString()).getFullStatement()
String sanitized = SqlStatementSanitizer.create(true).sanitize(s.toString()).getFullStatement()
System.out.println(sanitized.length())
assert sanitized.length() <= AutoSqlSanitizer.LIMIT
assert !sanitized.contains("1234")
@ -219,7 +219,7 @@ class SqlStatementSanitizerTest extends Specification {
for (int c = 0; c < 1000; c++) {
sb.append((char) r.nextInt((int) Character.MAX_VALUE))
}
SqlStatementSanitizer.sanitize(sb.toString())
SqlStatementSanitizer.create(true).sanitize(sb.toString())
}
}
}

View File

@ -1,18 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.api.db;
import static org.junit.jupiter.api.Assertions.assertFalse;
import org.junit.jupiter.api.Test;
public class StatementSanitizationConfigTest {
@Test
void shouldGetFalse() {
assertFalse(StatementSanitizationConfig.isStatementSanitizationEnabled());
}
}

View File

@ -25,6 +25,7 @@ package io.opentelemetry.javaagent.instrumentation.apachecamel.decorators;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import io.opentelemetry.javaagent.instrumentation.apachecamel.CamelDirection;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.URI;
@ -34,6 +35,9 @@ import org.apache.camel.Exchange;
class DbSpanDecorator extends BaseSpanDecorator {
private static final SqlStatementSanitizer sanitizer =
SqlStatementSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
private final String component;
private final String system;
@ -65,19 +69,19 @@ class DbSpanDecorator extends BaseSpanDecorator {
case "cql":
Object cqlObj = exchange.getIn().getHeader("CamelCqlQuery");
if (cqlObj != null) {
return SqlStatementSanitizer.sanitize(cqlObj.toString()).getFullStatement();
return sanitizer.sanitize(cqlObj.toString()).getFullStatement();
}
return null;
case "jdbc":
Object body = exchange.getIn().getBody();
if (body instanceof String) {
return SqlStatementSanitizer.sanitize((String) body).getFullStatement();
return sanitizer.sanitize((String) body).getFullStatement();
}
return null;
case "sql":
Object sqlquery = exchange.getIn().getHeader("CamelSqlQuery");
if (sqlquery instanceof String) {
return SqlStatementSanitizer.sanitize((String) sqlquery).getFullStatement();
return sanitizer.sanitize((String) sqlquery).getFullStatement();
}
return null;
default:

View File

@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.db.SqlClientAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesExtractor;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
public final class CassandraSingletons {
@ -32,6 +33,8 @@ public final class CassandraSingletons {
.addAttributesExtractor(
SqlClientAttributesExtractor.builder(attributesGetter)
.setTableAttribute(SemanticAttributes.DB_CASSANDRA_TABLE)
.setStatementSanitizationEnabled(
CommonConfig.get().isStatementSanitizationEnabled())
.build())
.addAttributesExtractor(
NetClientAttributesExtractor.create(new CassandraNetAttributesGetter()))

View File

@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.db.SqlClientAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesExtractor;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
public final class CassandraSingletons {
@ -31,6 +32,8 @@ public final class CassandraSingletons {
.addAttributesExtractor(
SqlClientAttributesExtractor.builder(attributesGetter)
.setTableAttribute(SemanticAttributes.DB_CASSANDRA_TABLE)
.setStatementSanitizationEnabled(
CommonConfig.get().isStatementSanitizationEnabled())
.build())
.addAttributesExtractor(
NetClientAttributesExtractor.create(new CassandraNetAttributesGetter()))

View File

@ -7,6 +7,7 @@ dependencies {
testImplementation("org.spockframework:spock-core")
testImplementation(project(":instrumentation-api-semconv"))
testImplementation(project(":javaagent-extension-api"))
testImplementation(project(":instrumentation:couchbase:couchbase-2-common:javaagent"))
testImplementation("com.couchbase.client:java-client:2.5.0")
}

View File

@ -8,12 +8,17 @@ package io.opentelemetry.javaagent.instrumentation.couchbase.v2_0;
import io.opentelemetry.instrumentation.api.db.SqlDialect;
import io.opentelemetry.instrumentation.api.db.SqlStatementInfo;
import io.opentelemetry.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import javax.annotation.Nullable;
public final class CouchbaseQuerySanitizer {
private static final SqlStatementSanitizer sanitizer =
SqlStatementSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
@Nullable private static final Class<?> QUERY_CLASS;
@Nullable private static final Class<?> STATEMENT_CLASS;
@Nullable private static final Class<?> N1QL_QUERY_CLASS;
@ -116,7 +121,7 @@ public final class CouchbaseQuerySanitizer {
}
private static SqlStatementInfo sanitizeString(String query) {
return SqlStatementSanitizer.sanitize(query, SqlDialect.COUCHBASE);
return sanitizer.sanitize(query, SqlDialect.COUCHBASE);
}
private CouchbaseQuerySanitizer() {}

View File

@ -7,11 +7,15 @@ package io.opentelemetry.javaagent.instrumentation.geode;
import io.opentelemetry.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesGetter;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import javax.annotation.Nullable;
final class GeodeDbAttributesGetter implements DbClientAttributesGetter<GeodeRequest> {
private static final SqlStatementSanitizer sanitizer =
SqlStatementSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
@Override
public String system(GeodeRequest request) {
return SemanticAttributes.DbSystemValues.GEODE;
@ -38,7 +42,7 @@ final class GeodeDbAttributesGetter implements DbClientAttributesGetter<GeodeReq
@Nullable
public String statement(GeodeRequest request) {
// sanitized statement is cached
return SqlStatementSanitizer.sanitize(request.getQuery()).getFullStatement();
return sanitizer.sanitize(request.getQuery()).getFullStatement();
}
@Override

View File

@ -7,15 +7,19 @@ package io.opentelemetry.javaagent.instrumentation.hibernate;
import io.opentelemetry.instrumentation.api.db.SqlStatementInfo;
import io.opentelemetry.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import java.util.function.Function;
public final class OperationNameUtil {
private static final SqlStatementSanitizer sanitizer =
SqlStatementSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
public static String getOperationNameForQuery(String query) {
// set operation to default value that is used when sql sanitizer fails to extract
// operation name
String operation = "Hibernate Query";
SqlStatementInfo info = SqlStatementSanitizer.sanitize(query);
SqlStatementInfo info = sanitizer.sanitize(query);
if (info.getOperation() != null) {
operation = info.getOperation();
if (info.getTable() != null) {

View File

@ -31,7 +31,11 @@ public final class JdbcSingletons {
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
DbClientSpanNameExtractor.create(dbAttributesGetter))
.addAttributesExtractor(SqlClientAttributesExtractor.create(dbAttributesGetter))
.addAttributesExtractor(
SqlClientAttributesExtractor.builder(dbAttributesGetter)
.setStatementSanitizationEnabled(
CommonConfig.get().isStatementSanitizationEnabled())
.build())
.addAttributesExtractor(NetClientAttributesExtractor.create(netAttributesGetter))
.addAttributesExtractor(
PeerServiceAttributesExtractor.create(

View File

@ -11,6 +11,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientSpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.db.SqlClientAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesExtractor;
import io.opentelemetry.instrumentation.api.internal.ConfigPropertiesUtil;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
@ -30,7 +31,12 @@ public final class JdbcSingletons {
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
DbClientSpanNameExtractor.create(dbAttributesGetter))
.addAttributesExtractor(SqlClientAttributesExtractor.create(dbAttributesGetter))
.addAttributesExtractor(
SqlClientAttributesExtractor.builder(dbAttributesGetter)
.setStatementSanitizationEnabled(
ConfigPropertiesUtil.getBoolean(
"otel.instrumentation.common.db-statement-sanitizer.enabled", true))
.build())
.addAttributesExtractor(NetClientAttributesExtractor.create(netAttributesGetter))
.newInstrumenter(SpanKindExtractor.alwaysClient());
}

View File

@ -7,11 +7,15 @@ package io.opentelemetry.javaagent.instrumentation.jedis.v1_4;
import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer;
import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesGetter;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import javax.annotation.Nullable;
final class JedisDbAttributesGetter implements DbClientAttributesGetter<JedisRequest> {
private static final RedisCommandSanitizer sanitizer =
RedisCommandSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
@Override
public String system(JedisRequest request) {
return SemanticAttributes.DbSystemValues.REDIS;
@ -35,7 +39,7 @@ final class JedisDbAttributesGetter implements DbClientAttributesGetter<JedisReq
@Override
public String statement(JedisRequest request) {
return RedisCommandSanitizer.sanitize(request.getCommand().name(), request.getArgs());
return sanitizer.sanitize(request.getCommand().name(), request.getArgs());
}
@Override

View File

@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.jedis.v3_0;
import com.google.auto.value.AutoValue;
import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import java.nio.charset.StandardCharsets;
import java.util.List;
import redis.clients.jedis.Connection;
@ -16,6 +17,9 @@ import redis.clients.jedis.commands.ProtocolCommand;
@AutoValue
public abstract class JedisRequest {
private static final RedisCommandSanitizer sanitizer =
RedisCommandSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
public static JedisRequest create(
Connection connection, ProtocolCommand command, List<byte[]> args) {
return new AutoValue_JedisRequest(connection, command, args);
@ -39,6 +43,6 @@ public abstract class JedisRequest {
}
public String getStatement() {
return RedisCommandSanitizer.sanitize(getOperation(), getArgs());
return sanitizer.sanitize(getOperation(), getArgs());
}
}

View File

@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.jedis.v4_0;
import com.google.auto.value.AutoValue;
import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
@ -20,6 +21,9 @@ import redis.clients.jedis.commands.ProtocolCommand;
@AutoValue
public abstract class JedisRequest {
private static final RedisCommandSanitizer sanitizer =
RedisCommandSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
public static JedisRequest create(ProtocolCommand command, List<byte[]> args) {
return new AutoValue_JedisRequest(command, args);
}
@ -54,7 +58,7 @@ public abstract class JedisRequest {
}
public String getStatement() {
return RedisCommandSanitizer.sanitize(getOperation(), getArgs());
return sanitizer.sanitize(getOperation(), getArgs());
}
private SocketAddress remoteSocketAddress;

View File

@ -9,6 +9,7 @@ import io.lettuce.core.protocol.RedisCommand;
import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer;
import io.opentelemetry.instrumentation.api.instrumenter.db.DbClientAttributesGetter;
import io.opentelemetry.instrumentation.lettuce.common.LettuceArgSplitter;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.Collections;
import java.util.List;
@ -16,6 +17,9 @@ import javax.annotation.Nullable;
final class LettuceDbAttributesGetter implements DbClientAttributesGetter<RedisCommand<?, ?, ?>> {
private static final RedisCommandSanitizer sanitizer =
RedisCommandSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
@Override
public String system(RedisCommand<?, ?, ?> request) {
return SemanticAttributes.DbSystemValues.REDIS;
@ -46,7 +50,7 @@ final class LettuceDbAttributesGetter implements DbClientAttributesGetter<RedisC
request.getArgs() == null
? Collections.emptyList()
: LettuceArgSplitter.splitArgs(request.getArgs().toCommandString());
return RedisCommandSanitizer.sanitize(command, args);
return sanitizer.sanitize(command, args);
}
@Override

View File

@ -9,6 +9,7 @@ import io.lettuce.core.tracing.Tracing;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.TracerBuilder;
import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer;
import io.opentelemetry.instrumentation.api.internal.EmbeddedInstrumentationProperties;
/** Entrypoint for instrumenting Lettuce or clients. */
@ -18,18 +19,27 @@ public final class LettuceTelemetry {
/** Returns a new {@link LettuceTelemetry} configured with the given {@link OpenTelemetry}. */
public static LettuceTelemetry create(OpenTelemetry openTelemetry) {
return new LettuceTelemetry(openTelemetry);
return builder(openTelemetry).build();
}
/**
* Returns a new {@link LettuceTelemetryBuilder} configured with the given {@link OpenTelemetry}.
*/
public static LettuceTelemetryBuilder builder(OpenTelemetry openTelemetry) {
return new LettuceTelemetryBuilder(openTelemetry);
}
private final Tracer tracer;
private final RedisCommandSanitizer sanitizer;
private LettuceTelemetry(OpenTelemetry openTelemetry) {
LettuceTelemetry(OpenTelemetry openTelemetry, boolean statementSanitizationEnabled) {
TracerBuilder tracerBuilder = openTelemetry.tracerBuilder(INSTRUMENTATION_NAME);
String version = EmbeddedInstrumentationProperties.findVersion(INSTRUMENTATION_NAME);
if (version != null) {
tracerBuilder.setInstrumentationVersion(version);
}
tracer = tracerBuilder.build();
sanitizer = RedisCommandSanitizer.create(statementSanitizationEnabled);
}
/**
@ -37,6 +47,6 @@ public final class LettuceTelemetry {
* io.lettuce.core.resource.ClientResources.Builder#tracing(Tracing)}.
*/
public Tracing newTracing() {
return new OpenTelemetryTracing(tracer);
return new OpenTelemetryTracing(tracer, sanitizer);
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.lettuce.v5_1;
import io.opentelemetry.api.OpenTelemetry;
/** A builder of {@link LettuceTelemetry}. */
public final class LettuceTelemetryBuilder {
private final OpenTelemetry openTelemetry;
private boolean statementSanitizationEnabled = true;
LettuceTelemetryBuilder(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}
/**
* Sets whether the {@code db.statement} attribute on the spans emitted by the constructed {@link
* LettuceTelemetry} should be sanitized. If set to {@code true}, all parameters that can
* potentially contain sensitive information will be masked. Enabled by default.
*/
public LettuceTelemetryBuilder setStatementSanitizationEnabled(
boolean statementSanitizationEnabled) {
this.statementSanitizationEnabled = statementSanitizationEnabled;
return this;
}
/**
* Returns a new {@link LettuceTelemetry} with the settings of this {@link
* LettuceTelemetryBuilder}.
*/
public LettuceTelemetry build() {
return new LettuceTelemetry(openTelemetry, statementSanitizationEnabled);
}
}

View File

@ -40,8 +40,8 @@ final class OpenTelemetryTracing implements Tracing {
NetClientAttributesExtractor.create(new LettuceNetAttributesGetter());
private final TracerProvider tracerProvider;
OpenTelemetryTracing(io.opentelemetry.api.trace.Tracer tracer) {
this.tracerProvider = new OpenTelemetryTracerProvider(tracer);
OpenTelemetryTracing(io.opentelemetry.api.trace.Tracer tracer, RedisCommandSanitizer sanitizer) {
this.tracerProvider = new OpenTelemetryTracerProvider(tracer, sanitizer);
}
@Override
@ -81,8 +81,9 @@ final class OpenTelemetryTracing implements Tracing {
private final Tracer openTelemetryTracer;
OpenTelemetryTracerProvider(io.opentelemetry.api.trace.Tracer tracer) {
openTelemetryTracer = new OpenTelemetryTracer(tracer);
OpenTelemetryTracerProvider(
io.opentelemetry.api.trace.Tracer tracer, RedisCommandSanitizer sanitizer) {
openTelemetryTracer = new OpenTelemetryTracer(tracer, sanitizer);
}
@Override
@ -126,9 +127,11 @@ final class OpenTelemetryTracing implements Tracing {
private static class OpenTelemetryTracer extends Tracer {
private final io.opentelemetry.api.trace.Tracer tracer;
private final RedisCommandSanitizer sanitizer;
OpenTelemetryTracer(io.opentelemetry.api.trace.Tracer tracer) {
OpenTelemetryTracer(io.opentelemetry.api.trace.Tracer tracer, RedisCommandSanitizer sanitizer) {
this.tracer = tracer;
this.sanitizer = sanitizer;
}
@Override
@ -155,7 +158,7 @@ final class OpenTelemetryTracing implements Tracing {
.setSpanKind(SpanKind.CLIENT)
.setParent(context)
.setAttribute(SemanticAttributes.DB_SYSTEM, DbSystemValues.REDIS);
return new OpenTelemetrySpan(context, spanBuilder);
return new OpenTelemetrySpan(context, spanBuilder, sanitizer);
}
}
@ -167,6 +170,7 @@ final class OpenTelemetryTracing implements Tracing {
private final Context context;
private final SpanBuilder spanBuilder;
private final RedisCommandSanitizer sanitizer;
@Nullable private String name;
@Nullable private List<Object> events;
@ -174,9 +178,10 @@ final class OpenTelemetryTracing implements Tracing {
@Nullable private Span span;
@Nullable private String args;
OpenTelemetrySpan(Context context, SpanBuilder spanBuilder) {
OpenTelemetrySpan(Context context, SpanBuilder spanBuilder, RedisCommandSanitizer sanitizer) {
this.context = context;
this.spanBuilder = spanBuilder;
this.sanitizer = sanitizer;
}
@Override
@ -319,7 +324,7 @@ final class OpenTelemetryTracing implements Tracing {
private void finish(Span span) {
if (name != null) {
String statement = RedisCommandSanitizer.sanitize(name, splitArgs(args));
String statement = sanitizer.sanitize(name, splitArgs(args));
span.setAttribute(SemanticAttributes.DB_STATEMENT, statement);
}
span.end();

View File

@ -11,6 +11,7 @@ import static java.util.Collections.singletonList;
import com.google.auto.value.AutoValue;
import io.netty.buffer.ByteBuf;
import io.opentelemetry.instrumentation.api.db.RedisCommandSanitizer;
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
@ -27,6 +28,9 @@ import org.redisson.client.protocol.CommandsData;
@AutoValue
public abstract class RedissonRequest {
private static final RedisCommandSanitizer sanitizer =
RedisCommandSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
public static RedissonRequest create(InetSocketAddress address, Object command) {
return new AutoValue_RedissonRequest(address, command);
}
@ -98,7 +102,7 @@ public abstract class RedissonRequest {
args.add(param);
}
}
return RedisCommandSanitizer.sanitize(command.getCommand().getName(), args);
return sanitizer.sanitize(command.getCommand().getName(), args);
}
@Nullable

View File

@ -28,6 +28,7 @@ public final class CommonConfig {
private final List<String> clientResponseHeaders;
private final List<String> serverRequestHeaders;
private final List<String> serverResponseHeaders;
private final boolean statementSanitizationEnabled;
CommonConfig(InstrumentationConfig config) {
peerServiceMapping =
@ -40,6 +41,8 @@ public final class CommonConfig {
config.getList("otel.instrumentation.http.capture-headers.server.request", emptyList());
serverResponseHeaders =
config.getList("otel.instrumentation.http.capture-headers.server.response", emptyList());
statementSanitizationEnabled =
config.getBoolean("otel.instrumentation.common.db-statement-sanitizer.enabled", true);
}
public Map<String, String> getPeerServiceMapping() {
@ -61,4 +64,8 @@ public final class CommonConfig {
public List<String> getServerResponseHeaders() {
return serverResponseHeaders;
}
public boolean isStatementSanitizationEnabled() {
return statementSanitizationEnabled;
}
}