Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/operation.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Existing cluster information can also be modified using the edit button.

![trino.gateway.io/entity](./assets/trinogateway_cluster_page.png)

**Note:** When adding or modifying a backend through the UI, a comment is required. Please provide a meaningful comment describing the reason for the change.

## Graceful shutdown

Expand Down Expand Up @@ -81,3 +82,8 @@ taking a long time for garbage collection.
completed initialization and is ready to serve requests. This means the initial
connection to the database and the first round of health check on Trino clusters
are completed. Otherwise, status code 503 is returned.

## Audit logging
Trino Gateway provides the AuditLogger interface for recording admin backend update events
to different pluggable outputs/sinks. Currently, there's implementations for logs.info and to
a database table.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.gateway.ha.audit;

public enum AuditAction {
CREATE,
UPDATE,
DELETE,
ACTIVATE,
DEACTIVATE,
UNKNOWN
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.gateway.ha.audit;

public enum AuditContext {
TRINO_GW_UI,
TRINO_GW_API
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.gateway.ha.audit;

import static java.util.Objects.requireNonNullElse;

public interface AuditLogger
{
void logAudit(String user, String ip, String backendName, AuditAction action, AuditContext context, boolean success, String userComment);

static String sanitizeComment(String comment)
{
String c = requireNonNullElse(comment, "");
return c.replaceAll("\\s+", " ").trim();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.gateway.ha.audit;

import com.google.inject.Inject;
import io.airlift.log.Logger;

import java.util.Collections;
import java.util.Set;

import static java.util.Objects.requireNonNullElse;

public class CompositeAuditLogger
implements AuditLogger
{
private static final Logger log = Logger.get(CompositeAuditLogger.class);
private final Set<AuditLogger> loggers;

@Inject
public CompositeAuditLogger(Set<AuditLogger> loggers)
{
this.loggers = requireNonNullElse(loggers, Collections.emptySet());
}

@Override
public void logAudit(String user, String ip, String backend, AuditAction action, AuditContext context, boolean success, String userComment)
{
String sanitizedComment = AuditLogger.sanitizeComment(userComment);
for (AuditLogger logger : loggers) {
try {
logger.logAudit(user, ip, backend, action, context, success, sanitizedComment);
}
catch (Exception e) {
log.error(e, "Audit sink %s failed", logger.getClass().getSimpleName());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.gateway.ha.audit;

import io.airlift.log.Logger;
import io.trino.gateway.ha.persistence.dao.AuditLogDao;
import org.jdbi.v3.core.Jdbi;

import java.sql.Timestamp;
import java.time.Instant;

import static java.util.Objects.requireNonNull;

public class DatabaseAuditLogger
implements AuditLogger
{
private static final Logger log = Logger.get(DatabaseAuditLogger.class);
private final AuditLogDao dao;

public DatabaseAuditLogger(Jdbi jdbi)
{
dao = requireNonNull(jdbi, "jdbi is null").onDemand(AuditLogDao.class);
}

@Override
public void logAudit(String user, String ip, String backendName, AuditAction action, AuditContext context, boolean success, String userComment)
{
try {
dao.log(user, ip, backendName, action.toString(), context.toString(), success,
AuditLogger.sanitizeComment(userComment), Timestamp.from(Instant.now()));
}
catch (Exception e) {
log.error("Failed to write audit log to database: %s", e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.gateway.ha.audit;

import io.airlift.log.Logger;

public class LogAuditLogger
implements AuditLogger
{
private static final Logger log = Logger.get(LogAuditLogger.class);

@Override
public void logAudit(String user, String ip, String backendName, AuditAction action, AuditContext context, boolean success, String userComment)
{
String comment = AuditLogger.sanitizeComment(userComment);
log.info("GW_AUDIT_LOG: user=%s, ipAddress=%s, backend=%s, action=%s, context=%s, success=%s, userComment=%s",
user, ip, backendName, action, context, success, comment);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.trino.gateway.ha.config;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import io.trino.gateway.ha.audit.AuditAction;
import io.trino.gateway.ha.audit.AuditContext;

public class AdminProxyBackendConfiguration
{
private final ProxyBackendConfiguration proxyBackendConfiguration = new ProxyBackendConfiguration();
private AuditAction action;
private String comment = "";
private AuditContext context;

@JsonProperty
public String getName()
{
return proxyBackendConfiguration.getName();
}

@JsonSetter
public void setName(String name)
{
proxyBackendConfiguration.setName(name);
}

@JsonProperty
public String getProxyTo()
{
return proxyBackendConfiguration.getProxyTo();
}

@JsonSetter
public void setProxyTo(String proxyTo)
{
proxyBackendConfiguration.setProxyTo(proxyTo);
}

@JsonProperty
public String getExternalUrl()
{
return proxyBackendConfiguration.getExternalUrl();
}

@JsonSetter
public void setExternalUrl(String externalUrl)
{
proxyBackendConfiguration.setExternalUrl(externalUrl);
}

@JsonProperty
public boolean isActive()
{
return proxyBackendConfiguration.isActive();
}

@JsonSetter
public void setActive(boolean active)
{
proxyBackendConfiguration.setActive(active);
}

@JsonProperty
public String getRoutingGroup()
{
return proxyBackendConfiguration.getRoutingGroup();
}

@JsonSetter
public void setRoutingGroup(String routingGroup)
{
proxyBackendConfiguration.setRoutingGroup(routingGroup);
}

@JsonProperty
public AuditAction getAction()
{
return this.action;
}

@JsonSetter
public void setAction(AuditAction action)
{
this.action = action;
}

@JsonProperty
public String getComment()
{
return this.comment;
}

@JsonSetter
public void setComment(String comment)
{
this.comment = comment;
}

@JsonProperty
public AuditContext getContext()
{
return this.context;
}

@JsonSetter
public void setContext(AuditContext context)
{
this.context = context;
}

public ProxyBackendConfiguration getProxyBackendConfiguration()
{
return proxyBackendConfiguration;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import io.airlift.http.client.HttpClient;
import io.trino.gateway.ha.audit.AuditLogger;
import io.trino.gateway.ha.audit.CompositeAuditLogger;
import io.trino.gateway.ha.audit.DatabaseAuditLogger;
import io.trino.gateway.ha.audit.LogAuditLogger;
import io.trino.gateway.ha.clustermonitor.ClusterStatsHttpMonitor;
import io.trino.gateway.ha.clustermonitor.ClusterStatsInfoApiMonitor;
import io.trino.gateway.ha.clustermonitor.ClusterStatsJdbcMonitor;
Expand Down Expand Up @@ -72,6 +76,7 @@

import java.util.List;
import java.util.Map;
import java.util.Set;

import static io.airlift.jaxrs.JaxrsBinder.jaxrsBinder;
import static io.trino.gateway.ha.config.ClusterStatsMonitorType.INFO_API;
Expand All @@ -90,6 +95,7 @@ public class HaGatewayProviderModule
private final GatewayBackendManager gatewayBackendManager;
private final QueryHistoryManager queryHistoryManager;
private final PathFilter pathFilter;
private final CompositeAuditLogger compositeAuditLogger;

@Override
protected void configure()
Expand All @@ -100,6 +106,7 @@ protected void configure()
binder().bind(QueryHistoryManager.class).toInstance(queryHistoryManager);
binder().bind(BackendStateManager.class).in(Scopes.SINGLETON);
binder().bind(PathFilter.class).toInstance(pathFilter);
binder().bind(AuditLogger.class).toInstance(compositeAuditLogger);
}

public HaGatewayProviderModule(HaGatewayConfiguration configuration)
Expand All @@ -125,6 +132,10 @@ public HaGatewayProviderModule(HaGatewayConfiguration configuration)
resourceGroupsManager = new HaResourceGroupsManager(connectionManager);
gatewayBackendManager = new HaGatewayManager(jdbi, configuration.getRouting());
queryHistoryManager = new HaQueryHistoryManager(jdbi, configuration.getDataStore().getJdbcUrl().startsWith("jdbc:oracle"));

LogAuditLogger logAuditLogger = new LogAuditLogger();
DatabaseAuditLogger dbAuditLogger = new DatabaseAuditLogger(jdbi);
compositeAuditLogger = new CompositeAuditLogger(Set.of(logAuditLogger, dbAuditLogger));
}

private LbOAuthManager getOAuthManager(HaGatewayConfiguration configuration)
Expand Down
Loading