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:
@@ -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.
|
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]]
|
[[capability_startReplication]]
|
||||||
Start Replication
|
Start Replication
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ import java.util.List;
|
|||||||
|
|
||||||
/** Server wide capabilities. Represented as {@link Permission} objects. */
|
/** Server wide capabilities. Represented as {@link Permission} objects. */
|
||||||
public class GlobalCapability {
|
public class GlobalCapability {
|
||||||
|
/** Ability to access the database (with gsql). */
|
||||||
|
public static final String ACCESS_DATABASE = "accessDatabase";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Denotes the server's administrators.
|
* Denotes the server's administrators.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -81,6 +84,7 @@ public class GlobalCapability {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
NAMES_ALL = new ArrayList<String>();
|
NAMES_ALL = new ArrayList<String>();
|
||||||
|
NAMES_ALL.add(ACCESS_DATABASE);
|
||||||
NAMES_ALL.add(ADMINISTRATE_SERVER);
|
NAMES_ALL.add(ADMINISTRATE_SERVER);
|
||||||
NAMES_ALL.add(CREATE_ACCOUNT);
|
NAMES_ALL.add(CREATE_ACCOUNT);
|
||||||
NAMES_ALL.add(CREATE_GROUP);
|
NAMES_ALL.add(CREATE_GROUP);
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ errorsMustBeFixed = Errors must be fixed before committing changes.
|
|||||||
|
|
||||||
# Capability Names
|
# Capability Names
|
||||||
capabilityNames = \
|
capabilityNames = \
|
||||||
|
accessDatabase, \
|
||||||
administrateServer, \
|
administrateServer, \
|
||||||
createAccount, \
|
createAccount, \
|
||||||
createGroup, \
|
createGroup, \
|
||||||
@@ -157,7 +158,8 @@ capabilityNames = \
|
|||||||
viewCaches, \
|
viewCaches, \
|
||||||
viewConnections, \
|
viewConnections, \
|
||||||
viewQueue
|
viewQueue
|
||||||
administrateServer = Administrate Server
|
accessDatabase = Access Database
|
||||||
|
administrateServer = Administrate Server
|
||||||
createAccount = Create Account
|
createAccount = Create Account
|
||||||
createGroup = Create Group
|
createGroup = Create Group
|
||||||
createProject = Create Project
|
createProject = Create Project
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ public class CapabilityControl {
|
|||||||
|| canAdministrateServer();
|
|| 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. */
|
/** @return true if the user can force replication to any configured destination. */
|
||||||
public boolean canStartReplication() {
|
public boolean canStartReplication() {
|
||||||
return canPerform(GlobalCapability.START_REPLICATION)
|
return canPerform(GlobalCapability.START_REPLICATION)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.account;
|
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_ACCOUNT;
|
||||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
|
import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
|
||||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
|
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_CONNECTIONS, cc.canViewConnections());
|
||||||
have.put(VIEW_QUEUE, cc.canViewQueue());
|
have.put(VIEW_QUEUE, cc.canViewQueue());
|
||||||
have.put(START_REPLICATION, cc.canStartReplication());
|
have.put(START_REPLICATION, cc.canStartReplication());
|
||||||
|
have.put(ACCESS_DATABASE, cc.canAccessDatabase());
|
||||||
|
|
||||||
QueueProvider.QueueType queue = cc.getQueueType();
|
QueueProvider.QueueType queue = cc.getQueueType();
|
||||||
if (queue != QueueProvider.QueueType.INTERACTIVE
|
if (queue != QueueProvider.QueueType.INTERACTIVE
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import org.kohsuke.args4j.Option;
|
|||||||
|
|
||||||
/** Opens a query processor. */
|
/** Opens a query processor. */
|
||||||
@AdminHighPriorityCommand
|
@AdminHighPriorityCommand
|
||||||
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
|
@RequiresCapability(GlobalCapability.ACCESS_DATABASE)
|
||||||
@CommandMetaData(name = "gsql", descr = "Administrative interface to active database")
|
@CommandMetaData(name = "gsql", descr = "Administrative interface to active database")
|
||||||
final class AdminQueryShell extends SshCommand {
|
final class AdminQueryShell extends SshCommand {
|
||||||
@Inject
|
@Inject
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
package com.google.gerrit.sshd.commands;
|
package com.google.gerrit.sshd.commands;
|
||||||
|
|
||||||
import com.google.gerrit.common.Version;
|
import com.google.gerrit.common.Version;
|
||||||
|
import com.google.gerrit.common.errors.PermissionDeniedException;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.IdentifiedUser;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gwtorm.jdbc.JdbcSchema;
|
import com.google.gwtorm.jdbc.JdbcSchema;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
@@ -55,6 +57,7 @@ public class QueryShell {
|
|||||||
private final BufferedReader in;
|
private final BufferedReader in;
|
||||||
private final PrintWriter out;
|
private final PrintWriter out;
|
||||||
private final SchemaFactory<ReviewDb> dbFactory;
|
private final SchemaFactory<ReviewDb> dbFactory;
|
||||||
|
private final IdentifiedUser currentUser;
|
||||||
private OutputFormat outputFormat = OutputFormat.PRETTY;
|
private OutputFormat outputFormat = OutputFormat.PRETTY;
|
||||||
|
|
||||||
private ReviewDb db;
|
private ReviewDb db;
|
||||||
@@ -63,12 +66,14 @@ public class QueryShell {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
QueryShell(final SchemaFactory<ReviewDb> dbFactory,
|
QueryShell(final SchemaFactory<ReviewDb> dbFactory,
|
||||||
|
final IdentifiedUser currentUser,
|
||||||
|
|
||||||
@Assisted final InputStream in, @Assisted final OutputStream out)
|
@Assisted final InputStream in, @Assisted final OutputStream out)
|
||||||
throws UnsupportedEncodingException {
|
throws UnsupportedEncodingException {
|
||||||
this.dbFactory = dbFactory;
|
this.dbFactory = dbFactory;
|
||||||
this.in = new BufferedReader(new InputStreamReader(in, "UTF-8"));
|
this.in = new BufferedReader(new InputStreamReader(in, "UTF-8"));
|
||||||
this.out = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
|
this.out = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
|
||||||
|
this.currentUser = currentUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOutputFormat(OutputFormat fmt) {
|
public void setOutputFormat(OutputFormat fmt) {
|
||||||
@@ -77,6 +82,7 @@ public class QueryShell {
|
|||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
|
checkPermission();
|
||||||
db = dbFactory.open();
|
db = dbFactory.open();
|
||||||
try {
|
try {
|
||||||
connection = ((JdbcSchema) db).getConnection();
|
connection = ((JdbcSchema) db).getConnection();
|
||||||
@@ -99,6 +105,8 @@ public class QueryShell {
|
|||||||
|
|
||||||
} catch (SQLException err) {
|
} catch (SQLException err) {
|
||||||
out.println("fatal: Cannot open connection: " + err.getMessage());
|
out.println("fatal: Cannot open connection: " + err.getMessage());
|
||||||
|
} catch (PermissionDeniedException err) {
|
||||||
|
out.println("fatal: " + err.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
out.flush();
|
out.flush();
|
||||||
}
|
}
|
||||||
@@ -106,6 +114,7 @@ public class QueryShell {
|
|||||||
|
|
||||||
public void execute(String query) {
|
public void execute(String query) {
|
||||||
try {
|
try {
|
||||||
|
checkPermission();
|
||||||
db = dbFactory.open();
|
db = dbFactory.open();
|
||||||
try {
|
try {
|
||||||
connection = ((JdbcSchema) db).getConnection();
|
connection = ((JdbcSchema) db).getConnection();
|
||||||
@@ -127,11 +136,31 @@ public class QueryShell {
|
|||||||
|
|
||||||
} catch (SQLException err) {
|
} catch (SQLException err) {
|
||||||
out.println("fatal: Cannot open connection: " + err.getMessage());
|
out.println("fatal: Cannot open connection: " + err.getMessage());
|
||||||
|
} catch (PermissionDeniedException err) {
|
||||||
|
out.println("fatal: " + err.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
out.flush();
|
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() {
|
private void readEvalPrintLoop() {
|
||||||
final StringBuilder buffer = new StringBuilder();
|
final StringBuilder buffer = new StringBuilder();
|
||||||
boolean executed = false;
|
boolean executed = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user