Split off raw SQL access into its own permission

Allowing raw SQL access (via the gsql tool) is potentially
a very risky operation--especially on public servers.

Administrators no longer implicity have this right, and
have to be granted it manually.

Change-Id: I67f3896af24653a92a36538a31a9ce3b1d2f48a1
This commit is contained in:
Chad Horohoe 2013-02-14 16:27:34 -05:00
parent 1c830c11e7
commit abd6d4e1d4
7 changed files with 52 additions and 2 deletions

View File

@ -1328,6 +1328,13 @@ This limit applies not only to the link:cmd-query.html[`gerrit query`]
command, but also to the web UI results pagination size.
[[capability_accessDatabase]]
Access Database
~~~~~~~~~~~~~~~
Allow users to access the database using the `gsql` command.
[[capability_startReplication]]
Start Replication
~~~~~~~~~~~~~~~~~

View File

@ -21,6 +21,9 @@ import java.util.List;
/** Server wide capabilities. Represented as {@link Permission} objects. */
public class GlobalCapability {
/** Ability to access the database (with gsql). */
public static final String ACCESS_DATABASE = "accessDatabase";
/**
* Denotes the server's administrators.
* <p>
@ -81,6 +84,7 @@ public class GlobalCapability {
static {
NAMES_ALL = new ArrayList<String>();
NAMES_ALL.add(ACCESS_DATABASE);
NAMES_ALL.add(ADMINISTRATE_SERVER);
NAMES_ALL.add(CREATE_ACCOUNT);
NAMES_ALL.add(CREATE_GROUP);

View File

@ -144,6 +144,7 @@ errorsMustBeFixed = Errors must be fixed before committing changes.
# Capability Names
capabilityNames = \
accessDatabase, \
administrateServer, \
createAccount, \
createGroup, \
@ -157,6 +158,7 @@ capabilityNames = \
viewCaches, \
viewConnections, \
viewQueue
accessDatabase = Access Database
administrateServer = Administrate Server
createAccount = Create Account
createGroup = Create Group

View File

@ -130,6 +130,12 @@ public class CapabilityControl {
|| canAdministrateServer();
}
/** @return true if the user can access the database (with gsql). */
public boolean canAccessDatabase() {
return canPerform(GlobalCapability.ACCESS_DATABASE);
}
/** @return true if the user can force replication to any configured destination. */
public boolean canStartReplication() {
return canPerform(GlobalCapability.START_REPLICATION)

View File

@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
import static com.google.gerrit.common.data.GlobalCapability.ACCESS_DATABASE;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_ACCOUNT;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
@ -109,6 +110,7 @@ class GetCapabilities implements RestReadView<AccountResource> {
have.put(VIEW_CONNECTIONS, cc.canViewConnections());
have.put(VIEW_QUEUE, cc.canViewQueue());
have.put(START_REPLICATION, cc.canStartReplication());
have.put(ACCESS_DATABASE, cc.canAccessDatabase());
QueueProvider.QueueType queue = cc.getQueueType();
if (queue != QueueProvider.QueueType.INTERACTIVE

View File

@ -25,7 +25,7 @@ import org.kohsuke.args4j.Option;
/** Opens a query processor. */
@AdminHighPriorityCommand
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@RequiresCapability(GlobalCapability.ACCESS_DATABASE)
@CommandMetaData(name = "gsql", descr = "Administrative interface to active database")
final class AdminQueryShell extends SshCommand {
@Inject

View File

@ -15,7 +15,9 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.Version;
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gson.JsonObject;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
@ -55,6 +57,7 @@ public class QueryShell {
private final BufferedReader in;
private final PrintWriter out;
private final SchemaFactory<ReviewDb> dbFactory;
private final IdentifiedUser currentUser;
private OutputFormat outputFormat = OutputFormat.PRETTY;
private ReviewDb db;
@ -63,12 +66,14 @@ public class QueryShell {
@Inject
QueryShell(final SchemaFactory<ReviewDb> dbFactory,
final IdentifiedUser currentUser,
@Assisted final InputStream in, @Assisted final OutputStream out)
throws UnsupportedEncodingException {
this.dbFactory = dbFactory;
this.in = new BufferedReader(new InputStreamReader(in, "UTF-8"));
this.out = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
this.currentUser = currentUser;
}
public void setOutputFormat(OutputFormat fmt) {
@ -77,6 +82,7 @@ public class QueryShell {
public void run() {
try {
checkPermission();
db = dbFactory.open();
try {
connection = ((JdbcSchema) db).getConnection();
@ -99,6 +105,8 @@ public class QueryShell {
} catch (SQLException err) {
out.println("fatal: Cannot open connection: " + err.getMessage());
} catch (PermissionDeniedException err) {
out.println("fatal: " + err.getMessage());
} finally {
out.flush();
}
@ -106,6 +114,7 @@ public class QueryShell {
public void execute(String query) {
try {
checkPermission();
db = dbFactory.open();
try {
connection = ((JdbcSchema) db).getConnection();
@ -127,11 +136,31 @@ public class QueryShell {
} catch (SQLException err) {
out.println("fatal: Cannot open connection: " + err.getMessage());
} catch (PermissionDeniedException err) {
out.println("fatal: " + err.getMessage());
} finally {
out.flush();
}
}
/**
* Assert that the current user is permitted to perform raw queries.
* <p>
* As the @RequireCapability guards at various entry points of internal
* commands implicitly add administrators (which we want to avoid), we also
* check permissions within QueryShell and grant access only to those who
* canPerformRawQuery, regardless of whether they are administrators or not.
*
* @throws PermissionDeniedException
*/
private void checkPermission() throws PermissionDeniedException {
if (!currentUser.getCapabilities().canAccessDatabase()) {
throw new PermissionDeniedException(String.format(
"%s does not have \"Perform Raw Query\" capability.",
currentUser.getUserName()));
}
}
private void readEvalPrintLoop() {
final StringBuilder buffer = new StringBuilder();
boolean executed = false;