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:
parent
1c830c11e7
commit
abd6d4e1d4
@ -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
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user