-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Redact sensitive information in catalog queries #24563
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
67fa908
e0919af
0352081
20aee8e
362bc90
6437e20
e32ef86
5060c7b
152bd6a
76ec66f
0fa189b
3c0af7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
*/ | ||
package io.trino.connector; | ||
|
||
import com.google.common.collect.ImmutableSet; | ||
import com.google.errorprone.annotations.ThreadSafe; | ||
import com.google.inject.Inject; | ||
import io.airlift.configuration.secrets.SecretsResolver; | ||
|
@@ -45,6 +46,7 @@ | |
|
||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.concurrent.ConcurrentMap; | ||
|
||
|
@@ -144,6 +146,25 @@ public CatalogConnector createCatalog(CatalogHandle catalogHandle, ConnectorName | |
return createCatalog(catalogHandle, connectorName, connector, Optional.empty()); | ||
} | ||
|
||
@Override | ||
public Set<String> getSecuritySensitivePropertyNames(CatalogProperties catalogProperties) | ||
{ | ||
ConnectorFactory connectorFactory = connectorFactories.get(catalogProperties.connectorName()); | ||
if (connectorFactory == null) { | ||
// If someone tries to use a non-existent connector, we assume they | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be an error, actually. It should throw IllegalArgumentException. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But then the query will fail during redaction. The idea is to avoid disrupting the natural flow and let it fail where it normally would if redaction didn't exist. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would a query fail during redaction if it hasn’t first failed during analysis? I.e., it’s a condition that should never occur. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We perform redaction at a very early stage (before the query state machine is created) to modify query text exposed in query events and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m very confused about the purpose of this change, then. It redaction happens before analysis, how is the analyzer and execution engine able to see the unredacted values so that it can to its job? Can you describe the technical approach at a high level so that I don’t have to reverse engineer what the code is trying to achieve? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please let me know if the following helps: ProblemCurrently, when we execute a CREATE CATALOG statement containing plaintext secrets, unredacted query text is exposed via the REST API, the GoalInstead of displaying the query text in its raw form in the locations mentioned above, such as: CREATE CATALOG test USING postgresql
WITH (
"connection-user" = 'bob',
"connection-password" = '1234'
) we aim to redact security-sensitive property values: CREATE CATALOG test USING postgresql
WITH (
"connection-user" = 'bob',
"connection-password" = '***'
) Proposed SolutionThe REST API, the The Since redaction occurs at an early stage of query processing, we need to duplicate some logic that is typically performed during analysis and execution. For example, this includes evaluating catalog properties. Additionally, we do not want to disrupt the normal query processing flow; therefore, we ensure the query never fails due to redaction. If, for any reason, redaction is not possible, we will resort to masking all properties. To identify security-sensitive properties for a given connector, we propose introducing a new SPI to expose them: #24562 |
||
// misspelled the name and, for safety, we redact all the properties. | ||
return ImmutableSet.copyOf(catalogProperties.properties().keySet()); | ||
} | ||
|
||
ConnectorContext context = createConnectorContext(catalogProperties.catalogHandle()); | ||
String catalogName = catalogProperties.catalogHandle().getCatalogName().toString(); | ||
Map<String, String> config = secretsResolver.getResolvedConfiguration(catalogProperties.properties()); | ||
|
||
try (ThreadContextClassLoader _ = new ThreadContextClassLoader(connectorFactory.getClass().getClassLoader())) { | ||
return connectorFactory.getSecuritySensitivePropertyNames(catalogName, config, context); | ||
} | ||
} | ||
|
||
private CatalogConnector createCatalog(CatalogHandle catalogHandle, ConnectorName connectorName, Connector connector, Optional<CatalogProperties> catalogProperties) | ||
{ | ||
Tracer tracer = createTracer(catalogHandle); | ||
|
@@ -196,7 +217,16 @@ private Connector createConnector( | |
ConnectorFactory connectorFactory, | ||
Map<String, String> properties) | ||
{ | ||
ConnectorContext context = new ConnectorContextInstance( | ||
ConnectorContext context = createConnectorContext(catalogHandle); | ||
|
||
try (ThreadContextClassLoader _ = new ThreadContextClassLoader(connectorFactory.getClass().getClassLoader())) { | ||
return connectorFactory.create(catalogName, properties, context); | ||
} | ||
} | ||
|
||
private ConnectorContext createConnectorContext(CatalogHandle catalogHandle) | ||
{ | ||
return new ConnectorContextInstance( | ||
catalogHandle, | ||
openTelemetry, | ||
createTracer(catalogHandle), | ||
|
@@ -206,10 +236,6 @@ private Connector createConnector( | |
new InternalMetadataProvider(metadata, typeManager), | ||
pageSorter, | ||
pageIndexerFactory); | ||
|
||
try (ThreadContextClassLoader _ = new ThreadContextClassLoader(connectorFactory.getClass().getClassLoader())) { | ||
return connectorFactory.create(catalogName, properties, context); | ||
} | ||
} | ||
|
||
private Tracer createTracer(CatalogHandle catalogHandle) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
*/ | ||
package io.trino.dispatcher; | ||
|
||
import com.google.common.base.Function; | ||
import com.google.common.util.concurrent.AbstractFuture; | ||
import com.google.common.util.concurrent.Futures; | ||
import com.google.common.util.concurrent.ListenableFuture; | ||
|
@@ -44,6 +45,8 @@ | |
import io.trino.spi.TrinoException; | ||
import io.trino.spi.resourcegroups.SelectionContext; | ||
import io.trino.spi.resourcegroups.SelectionCriteria; | ||
import io.trino.sql.RedactedQuery; | ||
import io.trino.sql.SensitiveStatementRedactor; | ||
import jakarta.annotation.PostConstruct; | ||
import jakarta.annotation.PreDestroy; | ||
import org.weakref.jmx.Flatten; | ||
|
@@ -84,6 +87,7 @@ public class DispatchManager | |
private final SessionPropertyDefaults sessionPropertyDefaults; | ||
private final SessionPropertyManager sessionPropertyManager; | ||
private final Tracer tracer; | ||
private final SensitiveStatementRedactor sensitiveStatementRedactor; | ||
|
||
private final int maxQueryLength; | ||
|
||
|
@@ -107,6 +111,7 @@ public DispatchManager( | |
SessionPropertyDefaults sessionPropertyDefaults, | ||
SessionPropertyManager sessionPropertyManager, | ||
Tracer tracer, | ||
SensitiveStatementRedactor sensitiveStatementRedactor, | ||
QueryManagerConfig queryManagerConfig, | ||
DispatchExecutor dispatchExecutor, | ||
QueryMonitor queryMonitor) | ||
|
@@ -121,6 +126,7 @@ public DispatchManager( | |
this.sessionPropertyDefaults = requireNonNull(sessionPropertyDefaults, "sessionPropertyDefaults is null"); | ||
this.sessionPropertyManager = sessionPropertyManager; | ||
this.tracer = requireNonNull(tracer, "tracer is null"); | ||
this.sensitiveStatementRedactor = requireNonNull(sensitiveStatementRedactor, "sensitiveStatementRedactor is null"); | ||
|
||
this.maxQueryLength = queryManagerConfig.getMaxQueryLength(); | ||
|
||
|
@@ -240,7 +246,7 @@ private <C> void createQueryInternal(QueryId queryId, Span querySpan, Slug slug, | |
DispatchQuery dispatchQuery = dispatchQueryFactory.createDispatchQuery( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this automatically also handles things like event listener and Might be worth to explicitly call it out in the commit message (although you do imply that by mentioning anything using QueryInfo/BasicQueryInfo). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Correct. I extracted tests confirming that to separate commits into separate commits to avoid distracting from the core functionality of redacting. I refined the commit message and included your suggestion. |
||
session, | ||
sessionContext.getTransactionId(), | ||
query, | ||
getRedactedQueryProvider(preparedQuery, query), | ||
preparedQuery, | ||
slug, | ||
selectionContext.getResourceGroupId()); | ||
|
@@ -280,6 +286,11 @@ private <C> void createQueryInternal(QueryId queryId, Span querySpan, Slug slug, | |
} | ||
} | ||
|
||
private Function<Session, RedactedQuery> getRedactedQueryProvider(PreparedQuery preparedQuery, String query) | ||
{ | ||
return session -> sensitiveStatementRedactor.redact(query, preparedQuery, session); | ||
} | ||
|
||
private boolean queryCreated(DispatchQuery dispatchQuery) | ||
{ | ||
boolean queryAdded = queryTracker.addQuery(dispatchQuery); | ||
|
Uh oh!
There was an error while loading. Please reload this page.