From b292fee6b2247d9b45512fca69df5cee64994eb9 Mon Sep 17 00:00:00 2001 From: David Boreham Date: Thu, 4 Nov 2021 15:42:06 -0600 Subject: [PATCH] SQL92 and CouchDB sanitizers - squash merge from old branch --- .../api/db/CouchStatementSanitizer.java | 38 ++ .../src/main/jflex/CouchSanitizer.jflex | 377 ++++++++++++++++++ .../src/main/jflex/SqlSanitizer.jflex | 2 +- .../api/db/CouchStatementSanitizerTest.groovy | 205 ++++++++++ .../api/db/SqlStatementSanitizerTest.groovy | 12 +- .../v2_0/CouchbaseQuerySanitizer.java | 4 +- 6 files changed, 626 insertions(+), 12 deletions(-) create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/CouchStatementSanitizer.java create mode 100644 instrumentation-api/src/main/jflex/CouchSanitizer.jflex create mode 100644 instrumentation-api/src/test/groovy/io/opentelemetry/instrumentation/api/db/CouchStatementSanitizerTest.groovy diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/CouchStatementSanitizer.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/CouchStatementSanitizer.java new file mode 100644 index 000000000000..a2a37a91a83e --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/db/CouchStatementSanitizer.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +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 io.opentelemetry.instrumentation.api.caching.Cache; +import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics; +import javax.annotation.Nullable; + +/** + * This class is responsible for masking potentially sensitive parameters in SQL (and SQL-like) + * statements and queries. + */ +public final class CouchStatementSanitizer { + private static final SupportabilityMetrics supportability = SupportabilityMetrics.instance(); + + private static final Cache sqlToStatementInfoCache = + Cache.builder().setMaximumSize(1000).build(); + + public static SqlStatementInfo sanitize(@Nullable String statement) { + if (!isStatementSanitizationEnabled() || statement == null) { + return SqlStatementInfo.create(statement, null, null); + } + return sqlToStatementInfoCache.computeIfAbsent( + statement, + k -> { + supportability.incrementCounter(SQL_STATEMENT_SANITIZER_CACHE_MISS); + return AutoCouchSanitizer.sanitize(statement); + }); + } + + private CouchStatementSanitizer() {} +} diff --git a/instrumentation-api/src/main/jflex/CouchSanitizer.jflex b/instrumentation-api/src/main/jflex/CouchSanitizer.jflex new file mode 100644 index 000000000000..762f44795372 --- /dev/null +++ b/instrumentation-api/src/main/jflex/CouchSanitizer.jflex @@ -0,0 +1,377 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.db; + +%% + +%final +%class AutoCouchSanitizer +%apiprivate +%int +%buffer 2048 + +%unicode +%ignorecase + +COMMA = "," +OPEN_PAREN = "(" +CLOSE_PAREN = ")" +OPEN_COMMENT = "/*" +CLOSE_COMMENT = "*/" +IDENTIFIER = ([:letter:] | "_") ([:letter:] | [0-9] | [_.])* +BASIC_NUM = [.+-]* [0-9] ([0-9] | [eE.+-])* +HEX_NUM = "0x" ([a-f] | [A-F] | [0-9])+ +QUOTED_STR = "'" ("''" | [^'])* "'" +DOUBLE_QUOTED_STR = "\"" ("\"\"" | [^\"])* "\"" +DOLLAR_QUOTED_STR = "$$" [^$]* "$$" +WHITESPACE = [ \t\r\n]+ + +%{ + static SqlStatementInfo sanitize(String statement) { + AutoCouchSanitizer sanitizer = new AutoCouchSanitizer(new java.io.StringReader(statement)); + try { + while (!sanitizer.yyatEOF()) { + int token = sanitizer.yylex(); + // YYEOF token may be used to stop processing + if (token == YYEOF) { + break; + } + } + return sanitizer.getResult(); + } catch (java.io.IOException e) { + // should never happen + return SqlStatementInfo.create(null, null, null); + } + } + + // max length of the sanitized statement - SQLs longer than this will be trimmed + private static final int LIMIT = 32 * 1024; + + private final StringBuilder builder = new StringBuilder(); + + private void appendCurrentFragment() { + builder.append(zzBuffer, zzStartRead, zzMarkedPos - zzStartRead); + } + + private boolean isOverLimit() { + return builder.length() > LIMIT; + } + + // you can reference a table in the FROM clause in one of the following ways: + // table + // table t + // table as t + // in other words, you need max 3 identifiers to reference a table + private static final int FROM_TABLE_REF_MAX_IDENTIFIERS = 3; + + private int parenLevel = 0; + private boolean insideComment = false; + private Operation operation = NoOp.INSTANCE; + private boolean extractionDone = false; + + private void setOperation(Operation operation) { + if (this.operation == NoOp.INSTANCE) { + this.operation = operation; + } + } + + private static abstract class Operation { + String mainTable = null; + + /** @return true if all statement info is gathered */ + boolean handleFrom() { + return false; + } + + /** @return true if all statement info is gathered */ + boolean handleInto() { + return false; + } + + /** @return true if all statement info is gathered */ + boolean handleJoin() { + return false; + } + + /** @return true if all statement info is gathered */ + boolean handleIdentifier() { + return false; + } + + /** @return true if all statement info is gathered */ + boolean handleComma() { + return false; + } + + SqlStatementInfo getResult(String fullStatement) { + return SqlStatementInfo.create(fullStatement, getClass().getSimpleName().toUpperCase(java.util.Locale.ROOT), mainTable); + } + } + + private static class NoOp extends Operation { + static final Operation INSTANCE = new NoOp(); + + SqlStatementInfo getResult(String fullStatement) { + return SqlStatementInfo.create(fullStatement, null, null); + } + } + + private class Select extends Operation { + // you can reference a table in the FROM clause in one of the following ways: + // table + // table t + // table as t + // in other words, you need max 3 identifiers to reference a table + private static final int FROM_TABLE_REF_MAX_IDENTIFIERS = 3; + + boolean expectingTableName = false; + boolean mainTableSetAlready = false; + int identifiersAfterMainFromClause = 0; + + boolean handleFrom() { + if (parenLevel == 0) { + // main query FROM clause + expectingTableName = true; + return false; + } + + // subquery in WITH or SELECT clause, before main FROM clause; skipping + mainTable = null; + return true; + } + + boolean handleJoin() { + // for SELECT statements with joined tables there's no main table + mainTable = null; + return true; + } + + boolean handleIdentifier() { + if (identifiersAfterMainFromClause > 0) { + ++identifiersAfterMainFromClause; + } + + if (!expectingTableName) { + return false; + } + + // SELECT FROM (subquery) case + if (parenLevel != 0) { + mainTable = null; + return true; + } + + // whenever >1 table is used there is no main table (e.g. unions) + if (mainTableSetAlready) { + mainTable = null; + return true; + } + + mainTable = yytext(); + mainTableSetAlready = true; + expectingTableName = false; + // start counting identifiers after encountering main from clause + identifiersAfterMainFromClause = 1; + + // continue scanning the query, there may be more than one table (e.g. joins) + return false; + } + + boolean handleComma() { + // comma was encountered in the FROM clause, i.e. implicit join + // (if less than 3 identifiers have appeared before first comma then it means that it's a table list; + // any other list that can appear later needs at least 4 idents) + if (identifiersAfterMainFromClause > 0 + && identifiersAfterMainFromClause <= FROM_TABLE_REF_MAX_IDENTIFIERS) { + mainTable = null; + return true; + } + return false; + } + } + + private class Insert extends Operation { + boolean expectingTableName = false; + + boolean handleInto() { + expectingTableName = true; + return false; + } + + boolean handleIdentifier() { + if (!expectingTableName) { + return false; + } + + mainTable = yytext(); + return true; + } + } + + private class Delete extends Operation { + boolean expectingTableName = false; + + boolean handleFrom() { + expectingTableName = true; + return false; + } + + boolean handleIdentifier() { + if (!expectingTableName) { + return false; + } + + mainTable = yytext(); + return true; + } + } + + private class Update extends Operation { + boolean handleIdentifier() { + mainTable = yytext(); + return true; + } + } + + private class Merge extends Operation { + boolean handleIdentifier() { + mainTable = yytext(); + return true; + } + } + + private SqlStatementInfo getResult() { + if (builder.length() > LIMIT) { + builder.delete(LIMIT, builder.length()); + } + String fullStatement = builder.toString(); + return operation.getResult(fullStatement); + } + +%} + +%% + + { + + "SELECT" { + if (!insideComment) { + setOperation(new Select()); + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + "INSERT" { + if (!insideComment) { + setOperation(new Insert()); + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + "DELETE" { + if (!insideComment) { + setOperation(new Delete()); + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + "UPDATE" { + if (!insideComment) { + setOperation(new Update()); + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + "MERGE" { + if (!insideComment) { + setOperation(new Merge()); + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + + "FROM" { + if (!insideComment && !extractionDone) { + if (operation == NoOp.INSTANCE) { + // hql/jpql queries may skip SELECT and start with FROM clause + // treat such queries as SELECT queries + setOperation(new Select()); + } + extractionDone = operation.handleFrom(); + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + "INTO" { + if (!insideComment && !extractionDone) { + extractionDone = operation.handleInto(); + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + "JOIN" { + if (!insideComment && !extractionDone) { + extractionDone = operation.handleJoin(); + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + {COMMA} { + if (!insideComment && !extractionDone) { + extractionDone = operation.handleComma(); + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + {IDENTIFIER} { + if (!insideComment && !extractionDone) { + extractionDone = operation.handleIdentifier(); + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + + {OPEN_PAREN} { + if (!insideComment) { + parenLevel += 1; + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + {CLOSE_PAREN} { + if (!insideComment) { + parenLevel -= 1; + } + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + + {OPEN_COMMENT} { + insideComment = true; + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + {CLOSE_COMMENT} { + insideComment = false; + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } + + // here is where the actual sanitization happens + {BASIC_NUM} | {HEX_NUM} | {QUOTED_STR} | {DOUBLE_QUOTED_STR} | {DOLLAR_QUOTED_STR} { + builder.append('?'); + if (isOverLimit()) return YYEOF; + } + + {WHITESPACE} { + builder.append(' '); + if (isOverLimit()) return YYEOF; + } + [^] { + appendCurrentFragment(); + if (isOverLimit()) return YYEOF; + } +} diff --git a/instrumentation-api/src/main/jflex/SqlSanitizer.jflex b/instrumentation-api/src/main/jflex/SqlSanitizer.jflex index 759659107935..9c529f7eec72 100644 --- a/instrumentation-api/src/main/jflex/SqlSanitizer.jflex +++ b/instrumentation-api/src/main/jflex/SqlSanitizer.jflex @@ -361,7 +361,7 @@ WHITESPACE = [ \t\r\n]+ } // here is where the actual sanitization happens - {BASIC_NUM} | {HEX_NUM} | {QUOTED_STR} | {DOUBLE_QUOTED_STR} | {DOLLAR_QUOTED_STR} { + {BASIC_NUM} | {HEX_NUM} | {QUOTED_STR} | {DOLLAR_QUOTED_STR} { builder.append('?'); if (isOverLimit()) return YYEOF; } diff --git a/instrumentation-api/src/test/groovy/io/opentelemetry/instrumentation/api/db/CouchStatementSanitizerTest.groovy b/instrumentation-api/src/test/groovy/io/opentelemetry/instrumentation/api/db/CouchStatementSanitizerTest.groovy new file mode 100644 index 000000000000..927c197e8a06 --- /dev/null +++ b/instrumentation-api/src/test/groovy/io/opentelemetry/instrumentation/api/db/CouchStatementSanitizerTest.groovy @@ -0,0 +1,205 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.db + +import spock.lang.Specification +import spock.lang.Unroll + +class CouchStatementSanitizerTest extends Specification { + + def "normalize #originalSql"() { + setup: + def actualSanitized = CouchStatementSanitizer.sanitize(originalSql) + + expect: + actualSanitized.getFullStatement() == sanitizedSql + + where: + originalSql | sanitizedSql + // Numbers + "SELECT * FROM TABLE WHERE FIELD=1234" | "SELECT * FROM TABLE WHERE FIELD=?" + "SELECT * FROM TABLE WHERE FIELD = 1234" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD>=-1234" | "SELECT * FROM TABLE WHERE FIELD>=?" + "SELECT * FROM TABLE WHERE FIELD<-1234" | "SELECT * FROM TABLE WHERE FIELD7" | "SELECT FIELD2 FROM TABLE_123 WHERE X<>?" + + // Semi-nonsensical almost-numbers to elide or not + "SELECT --83--...--8e+76e3E-1" | "SELECT ?" + "SELECT DEADBEEF" | "SELECT DEADBEEF" + "SELECT 123-45-6789" | "SELECT ?" + "SELECT 1/2/34" | "SELECT ?/?/?" + + // Basic ' strings + "SELECT * FROM TABLE WHERE FIELD = ''" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = 'words and spaces'" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = ' an escaped '' quote mark inside'" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = '\\\\'" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = '\"inside doubles\"'" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = '\"\$\$\$\$\"'" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = 'a single \" doublequote inside'" | "SELECT * FROM TABLE WHERE FIELD = ?" + + // Some databases support/encourage " instead of ' with same escape rules + "SELECT * FROM TABLE WHERE FIELD = \"\"" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = \"words and spaces'\"" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = \" an escaped \"\" quote mark inside\"" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = \"\\\\\"" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = \"'inside singles'\"" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = \"'\$\$\$\$'\"" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = \"a single ' singlequote inside\"" | "SELECT * FROM TABLE WHERE FIELD = ?" + + // Some databases allow using dollar-quoted strings + "SELECT * FROM TABLE WHERE FIELD = \$\$\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = \$\$words and spaces\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = \$\$quotes '\" inside\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = \$\$\"''\"\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?" + "SELECT * FROM TABLE WHERE FIELD = \$\$\\\\\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?" + + // Unicode, including a unicode identifier with a trailing number + "SELECT * FROM TABLE\u09137 WHERE FIELD = '\u0194'" | "SELECT * FROM TABLE\u09137 WHERE FIELD = ?" + + // whitespace normalization + "SELECT * \t\r\nFROM TABLE WHERE FIELD1 = 12344 AND FIELD2 = 5678" | "SELECT * FROM TABLE WHERE FIELD1 = ? AND FIELD2 = ?" + + // hibernate/jpa query language + "FROM TABLE WHERE FIELD=1234" | "FROM TABLE WHERE FIELD=?" + + } + + @Unroll + def "should simplify #sql"() { + expect: + SqlStatementSanitizer.sanitize(sql) == expected + + where: + sql | expected + // Select + 'SELECT x, y, z FROM schema.table' | SqlStatementInfo.create(sql, 'SELECT', 'schema.table') + 'WITH subquery as (select a from b) SELECT x, y, z FROM table' | SqlStatementInfo.create(sql, 'SELECT', null) + 'SELECT x, y, (select a from b) as z FROM table' | SqlStatementInfo.create(sql, 'SELECT', null) + 'select delete, insert into, merge, update from table' | SqlStatementInfo.create(sql, 'SELECT', 'table') + 'select col /* from table2 */ from table' | SqlStatementInfo.create(sql, 'SELECT', 'table') + 'select col from table join anotherTable' | SqlStatementInfo.create(sql, 'SELECT', null) + 'select col from (select * from anotherTable)' | SqlStatementInfo.create(sql, 'SELECT', null) + 'select col from (select * from anotherTable) alias' | SqlStatementInfo.create(sql, 'SELECT', null) + 'select col from table1 union select col from table2' | SqlStatementInfo.create(sql, 'SELECT', null) + 'select col from table where col in (select * from anotherTable)' | SqlStatementInfo.create(sql, 'SELECT', null) + 'select col from table1, table2' | SqlStatementInfo.create(sql, 'SELECT', null) + 'select col from table1 t1, table2 t2' | SqlStatementInfo.create(sql, 'SELECT', null) + 'select col from table1 as t1, table2 as t2' | SqlStatementInfo.create(sql, 'SELECT', null) + 'select col from table where col in (1, 2, 3)' | SqlStatementInfo.create('select col from table where col in (?, ?, ?)', 'SELECT', 'table') + 'select col from table order by col, col2' | SqlStatementInfo.create(sql, 'SELECT', 'table') + 'select ąś∂ń© from źćļńĶ order by col, col2' | SqlStatementInfo.create(sql, 'SELECT', 'źćļńĶ') + 'select 12345678' | SqlStatementInfo.create('select ?', 'SELECT', null) + '/* update comment */ select * from table1' | SqlStatementInfo.create(sql, 'SELECT', 'table1') + 'select /*((*/abc from table' | SqlStatementInfo.create(sql, 'SELECT', 'table') + 'SeLeCT * FrOm TAblE' | SqlStatementInfo.create(sql, 'SELECT', 'TAblE') + // hibernate/jpa + 'FROM schema.table' | SqlStatementInfo.create(sql, 'SELECT', 'schema.table') + '/* update comment */ from table1' | SqlStatementInfo.create(sql, 'SELECT', 'table1') + // Insert + ' insert into table where lalala' | SqlStatementInfo.create(sql, 'INSERT', 'table') + 'insert insert into table where lalala' | SqlStatementInfo.create(sql, 'INSERT', 'table') + 'insert into db.table where lalala' | SqlStatementInfo.create(sql, 'INSERT', 'db.table') + 'insert without i-n-t-o' | SqlStatementInfo.create(sql, 'INSERT', null) + // Delete + 'delete from table where something something' | SqlStatementInfo.create(sql, 'DELETE', 'table') + 'delete from 12345678' | SqlStatementInfo.create('delete from ?', 'DELETE', null) + 'delete (((' | SqlStatementInfo.create('delete (((', 'DELETE', null) + // Update + 'update table set answer=42' | SqlStatementInfo.create('update table set answer=?', 'UPDATE', 'table') + 'update /*table' | SqlStatementInfo.create(sql, 'UPDATE', null) + // Merge + 'merge into table' | SqlStatementInfo.create(sql, 'MERGE', 'table') + 'merge table (into is optional in some dbs)' | SqlStatementInfo.create(sql, 'MERGE', 'table') + 'merge (into )))' | SqlStatementInfo.create(sql, 'MERGE', null) + // Unknown operation + 'and now for something completely different' | SqlStatementInfo.create(sql, null, null) + '' | SqlStatementInfo.create(sql, null, null) + null | SqlStatementInfo.create(sql, null, null) + } + + def "very long SELECT statements don't cause problems"() { + given: + def sb = new StringBuilder("SELECT * FROM table WHERE") + for (int i = 0; i < 2000; i++) { + sb.append(" column").append(i).append("=123 and") + } + def query = sb.toString() + + expect: + def sanitizedQuery = query.replace('=123', '=?').substring(0, AutoSqlSanitizer.LIMIT) + SqlStatementSanitizer.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 + s += "'" + } + } + + def "very long numbers don't cause a problem"() { + setup: + String s = "" + for (int i = 0; i < 10000; i++) { + s += String.valueOf(i) + } + assert "?" == SqlStatementSanitizer.sanitize(s).getFullStatement() + } + + def "very long numbers at end of table name don't cause problem"() { + setup: + String s = "A" + for (int i = 0; i < 10000; i++) { + s += String.valueOf(i) + } + assert s.substring(0, AutoSqlSanitizer.LIMIT) == SqlStatementSanitizer.sanitize(s).getFullStatement() + } + + def "test 32k truncation"() { + setup: + StringBuffer s = new StringBuffer() + for (int i = 0; i < 10000; i++) { + s.append("SELECT * FROM TABLE WHERE FIELD = 1234 AND ") + } + String sanitized = SqlStatementSanitizer.sanitize(s.toString()).getFullStatement() + System.out.println(sanitized.length()) + assert sanitized.length() <= AutoSqlSanitizer.LIMIT + assert !sanitized.contains("1234") + } + + def "random bytes don't cause exceptions or timeouts"() { + setup: + Random r = new Random(0) + for (int i = 0; i < 1000; i++) { + StringBuffer sb = new StringBuffer() + for (int c = 0; c < 1000; c++) { + sb.append((char) r.nextInt((int) Character.MAX_VALUE)) + } + SqlStatementSanitizer.sanitize(sb.toString()) + } + } +} diff --git a/instrumentation-api/src/test/groovy/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizerTest.groovy b/instrumentation-api/src/test/groovy/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizerTest.groovy index 991198e447d5..6b07b0e6ace9 100644 --- a/instrumentation-api/src/test/groovy/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizerTest.groovy +++ b/instrumentation-api/src/test/groovy/io/opentelemetry/instrumentation/api/db/SqlStatementSanitizerTest.groovy @@ -59,15 +59,6 @@ class SqlStatementSanitizerTest extends Specification { "SELECT * FROM TABLE WHERE FIELD = '\"\$\$\$\$\"'" | "SELECT * FROM TABLE WHERE FIELD = ?" "SELECT * FROM TABLE WHERE FIELD = 'a single \" doublequote inside'" | "SELECT * FROM TABLE WHERE FIELD = ?" - // Some databases support/encourage " instead of ' with same escape rules - "SELECT * FROM TABLE WHERE FIELD = \"\"" | "SELECT * FROM TABLE WHERE FIELD = ?" - "SELECT * FROM TABLE WHERE FIELD = \"words and spaces'\"" | "SELECT * FROM TABLE WHERE FIELD = ?" - "SELECT * FROM TABLE WHERE FIELD = \" an escaped \"\" quote mark inside\"" | "SELECT * FROM TABLE WHERE FIELD = ?" - "SELECT * FROM TABLE WHERE FIELD = \"\\\\\"" | "SELECT * FROM TABLE WHERE FIELD = ?" - "SELECT * FROM TABLE WHERE FIELD = \"'inside singles'\"" | "SELECT * FROM TABLE WHERE FIELD = ?" - "SELECT * FROM TABLE WHERE FIELD = \"'\$\$\$\$'\"" | "SELECT * FROM TABLE WHERE FIELD = ?" - "SELECT * FROM TABLE WHERE FIELD = \"a single ' singlequote inside\"" | "SELECT * FROM TABLE WHERE FIELD = ?" - // Some databases allow using dollar-quoted strings "SELECT * FROM TABLE WHERE FIELD = \$\$\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?" "SELECT * FROM TABLE WHERE FIELD = \$\$words and spaces\$\$" | "SELECT * FROM TABLE WHERE FIELD = ?" @@ -83,6 +74,9 @@ class SqlStatementSanitizerTest extends Specification { // hibernate/jpa query language "FROM TABLE WHERE FIELD=1234" | "FROM TABLE WHERE FIELD=?" + + // Double-quoted identifier names should not be sanitized + "SELECT * FROM \"TABLE\" WHERE FIELD = ''" | "SELECT * FROM \"TABLE\" WHERE FIELD = ?" } @Unroll diff --git a/instrumentation/couchbase/couchbase-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/couchbase/v2_0/CouchbaseQuerySanitizer.java b/instrumentation/couchbase/couchbase-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/couchbase/v2_0/CouchbaseQuerySanitizer.java index 23ddbe808872..82d47d5069dc 100644 --- a/instrumentation/couchbase/couchbase-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/couchbase/v2_0/CouchbaseQuerySanitizer.java +++ b/instrumentation/couchbase/couchbase-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/couchbase/v2_0/CouchbaseQuerySanitizer.java @@ -5,8 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.couchbase.v2_0; +import io.opentelemetry.instrumentation.api.db.CouchStatementSanitizer; import io.opentelemetry.instrumentation.api.db.SqlStatementInfo; -import io.opentelemetry.instrumentation.api.db.SqlStatementSanitizer; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -115,7 +115,7 @@ private static String getStatementString(MethodHandle handle, Object query) { } private static SqlStatementInfo sanitizeString(String query) { - return SqlStatementSanitizer.sanitize(query); + return CouchStatementSanitizer.sanitize(query); } private CouchbaseQuerySanitizer() {}