Skip to content

Commit f33267c

Browse files
beobalfrankgh
andcommitted
Tighten up permissions on system keyspaces
* Restrict which permissions can be granted on system keyspaces * Ensure that GRANT... ON ALL KEYSPACES excludes system keyspaces * Add system_traces to the always readable set Patch by Sam Tunnicliffe and Francisco Guerrero; reviewed by Sam Tunnicliffe and Francisco Guerrero for CASSANDRA-20090 Co-authored-by: Francisco Guerrero <frankgh@apache.org>
1 parent f41c2e2 commit f33267c

File tree

9 files changed

+531
-31
lines changed

9 files changed

+531
-31
lines changed

Diff for: CHANGES.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
3.0.31
2-
* Fix incorrect column identifier bytes problem when renaming a column (CASSANDRA-18965)
2+
* Tighten up permissions on system keyspaces (CASSANDRA-20090)
3+
* Fix incorrect column identifier bytes problem when renaming a column (CASSANDRA-18956)
34
* Upgrade OWASP to 10.0.0 (CASSANDRA-19738)
45
Merged from 2.2:
56
* Add termin-8-jdk as a valid jdk8 candidate in the debian package (CASSANDRA-19752)

Diff for: src/java/org/apache/cassandra/auth/Permission.java

+7
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,11 @@ public enum Permission
6666
public static final Set<Permission> ALL =
6767
Sets.immutableEnumSet(EnumSet.range(Permission.CREATE, Permission.EXECUTE));
6868
public static final Set<Permission> NONE = ImmutableSet.of();
69+
70+
/**
71+
* Set of Permissions which may never be granted on any system keyspace, or table in a system keyspace, to any role.
72+
* (Only SELECT, DESCRIBE and ALTER may ever be granted).
73+
*/
74+
public static final Set<Permission> INVALID_FOR_SYSTEM_KEYSPACES =
75+
Sets.immutableEnumSet(EnumSet.complementOf(EnumSet.of(Permission.SELECT, Permission.DESCRIBE, Permission.ALTER)));
6976
}

Diff for: src/java/org/apache/cassandra/auth/Resources.java

+22-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.util.ArrayList;
2121
import java.util.List;
22+
import java.util.function.Predicate;
2223

2324
import org.apache.cassandra.utils.Hex;
2425

@@ -27,18 +28,33 @@ public final class Resources
2728
/**
2829
* Construct a chain of resource parents starting with the resource and ending with the root.
2930
*
30-
* @param resource The staring point.
31+
* @param resource The starting point.
3132
* @return list of resource in the chain form start to the root.
3233
*/
3334
public static List<? extends IResource> chain(IResource resource)
3435
{
35-
List<IResource> chain = new ArrayList<IResource>();
36+
return chain(resource, (r) -> true);
37+
}
38+
39+
/**
40+
* Construct a chain of resource parents starting with the resource and ending with the root. Only resources which
41+
* satisfy the supplied predicate will be included.
42+
*
43+
* @param resource The starting point.
44+
* @param filter can be used to omit specific resources from the chain
45+
* @return list of resource in the chain form start to the root.
46+
*/
47+
public static List<? extends IResource> chain(IResource resource, Predicate<IResource> filter)
48+
{
49+
50+
List<IResource> chain = new ArrayList<>(4);
3651
while (true)
3752
{
38-
chain.add(resource);
39-
if (!resource.hasParent())
40-
break;
41-
resource = resource.getParent();
53+
if (filter.test(resource))
54+
chain.add(resource);
55+
if (!resource.hasParent())
56+
break;
57+
resource = resource.getParent();
4258
}
4359
return chain;
4460
}

Diff for: src/java/org/apache/cassandra/config/DatabaseDescriptor.java

+15
Original file line numberDiff line numberDiff line change
@@ -862,16 +862,31 @@ public static IAuthenticator getAuthenticator()
862862
return authenticator;
863863
}
864864

865+
public static void setAuthenticator(IAuthenticator authenticator)
866+
{
867+
DatabaseDescriptor.authenticator = authenticator;
868+
}
869+
865870
public static IAuthorizer getAuthorizer()
866871
{
867872
return authorizer;
868873
}
869874

875+
public static void setAuthorizer(IAuthorizer authorizer)
876+
{
877+
DatabaseDescriptor.authorizer = authorizer;
878+
}
879+
870880
public static IRoleManager getRoleManager()
871881
{
872882
return roleManager;
873883
}
874884

885+
public static void setRoleManager(IRoleManager roleManager)
886+
{
887+
DatabaseDescriptor.roleManager = roleManager;
888+
}
889+
875890
public static int getPermissionsValidity()
876891
{
877892
return conf.permissions_validity_in_ms;

Diff for: src/java/org/apache/cassandra/config/Schema.java

+6
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ public static boolean isReplicatedSystemKeyspace(String keyspaceName)
125125
return REPLICATED_SYSTEM_KEYSPACE_NAMES.contains(keyspaceName.toLowerCase());
126126
}
127127

128+
public static boolean isSystemKeyspace(String keyspaceName)
129+
{
130+
final String lowercaseKeyspaceName = keyspaceName.toLowerCase();
131+
return LOCAL_SYSTEM_KEYSPACE_NAMES.contains(lowercaseKeyspaceName)
132+
|| REPLICATED_SYSTEM_KEYSPACE_NAMES.contains(lowercaseKeyspaceName);
133+
}
128134
/**
129135
* load keyspace (keyspace) definitions, but do not initialize the keyspace instances.
130136
* Schema version may be updated as the result.

Diff for: src/java/org/apache/cassandra/cql3/statements/GrantPermissionsStatement.java

+21
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
*/
1818
package org.apache.cassandra.cql3.statements;
1919

20+
import java.util.Collections;
2021
import java.util.Set;
2122

23+
import org.apache.cassandra.auth.DataResource;
2224
import org.apache.cassandra.auth.IResource;
2325
import org.apache.cassandra.auth.Permission;
2426
import org.apache.cassandra.config.DatabaseDescriptor;
27+
import org.apache.cassandra.config.Schema;
2528
import org.apache.cassandra.cql3.RoleName;
2629
import org.apache.cassandra.exceptions.RequestExecutionException;
2730
import org.apache.cassandra.exceptions.RequestValidationException;
31+
import org.apache.cassandra.exceptions.UnauthorizedException;
2832
import org.apache.cassandra.service.ClientState;
2933
import org.apache.cassandra.transport.messages.ResultMessage;
3034

@@ -35,6 +39,23 @@ public GrantPermissionsStatement(Set<Permission> permissions, IResource resource
3539
super(permissions, resource, grantee);
3640
}
3741

42+
public void validate(ClientState state) throws RequestValidationException
43+
{
44+
super.validate(state);
45+
if (resource instanceof DataResource)
46+
{
47+
DataResource data = (DataResource) resource;
48+
// Only a subset of permissions can be granted on system keyspaces
49+
if (!data.isRootLevel()
50+
&& Schema.isSystemKeyspace(data.getKeyspace())
51+
&& !Collections.disjoint(permissions, Permission.INVALID_FOR_SYSTEM_KEYSPACES))
52+
{
53+
throw new UnauthorizedException("Granting permissions on system keyspaces is strictly limited, " +
54+
"this operation is not permitted");
55+
}
56+
}
57+
}
58+
3859
public ResultMessage execute(ClientState state) throws RequestValidationException, RequestExecutionException
3960
{
4061
DatabaseDescriptor.getAuthorizer().grant(state.getUser(), permissions, resource, grantee);

Diff for: src/java/org/apache/cassandra/service/ClientState.java

+60-20
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020
import java.net.InetSocketAddress;
2121
import java.net.SocketAddress;
2222
import java.util.Arrays;
23-
import java.util.HashSet;
23+
import java.util.List;
2424
import java.util.Set;
2525
import java.util.concurrent.atomic.AtomicLong;
2626

27+
import com.google.common.collect.ImmutableSet;
2728
import org.slf4j.Logger;
2829
import org.slf4j.LoggerFactory;
2930

@@ -41,6 +42,7 @@
4142
import org.apache.cassandra.exceptions.UnauthorizedException;
4243
import org.apache.cassandra.schema.SchemaKeyspace;
4344
import org.apache.cassandra.thrift.ThriftValidation;
45+
import org.apache.cassandra.tracing.TraceKeyspace;
4446
import org.apache.cassandra.utils.FBUtilities;
4547
import org.apache.cassandra.utils.JVMStabilityInspector;
4648
import org.apache.cassandra.utils.CassandraVersion;
@@ -54,29 +56,40 @@ public class ClientState
5456
private static final Logger logger = LoggerFactory.getLogger(ClientState.class);
5557
public static final CassandraVersion DEFAULT_CQL_VERSION = org.apache.cassandra.cql3.QueryProcessor.CQL_VERSION;
5658

57-
private static final Set<IResource> READABLE_SYSTEM_RESOURCES = new HashSet<>();
58-
private static final Set<IResource> PROTECTED_AUTH_RESOURCES = new HashSet<>();
59-
private static final Set<IResource> DROPPABLE_SYSTEM_AUTH_TABLES = new HashSet<>();
59+
public static final ImmutableSet<IResource> READABLE_SYSTEM_RESOURCES;
60+
public static final ImmutableSet<IResource> PROTECTED_AUTH_RESOURCES;
61+
public static final ImmutableSet<IResource> DROPPABLE_SYSTEM_AUTH_TABLES;
6062
static
6163
{
6264
// We want these system cfs to be always readable to authenticated users since many tools rely on them
6365
// (nodetool, cqlsh, bulkloader, etc.)
64-
for (String cf : Arrays.asList(SystemKeyspace.LOCAL, SystemKeyspace.PEERS))
65-
READABLE_SYSTEM_RESOURCES.add(DataResource.table(SystemKeyspace.NAME, cf));
66+
ImmutableSet.Builder<IResource> readableBuilder = ImmutableSet.builder();
67+
for (String cf : Arrays.asList(SystemKeyspace.LOCAL, SystemKeyspace.PEERS, SystemKeyspace.SIZE_ESTIMATES))
68+
readableBuilder.add(DataResource.table(SystemKeyspace.NAME, cf));
6669

67-
SchemaKeyspace.ALL.forEach(table -> READABLE_SYSTEM_RESOURCES.add(DataResource.table(SchemaKeyspace.NAME, table)));
70+
// make all schema tables readable by default (required by the drivers)
71+
SchemaKeyspace.ALL.forEach(table -> readableBuilder.add(DataResource.table(SchemaKeyspace.NAME, table)));
6872

73+
// make system_traces readable by all or else tracing will require explicit grants
74+
readableBuilder.add(DataResource.table(TraceKeyspace.NAME, TraceKeyspace.EVENTS));
75+
readableBuilder.add(DataResource.table(TraceKeyspace.NAME, TraceKeyspace.SESSIONS));
76+
READABLE_SYSTEM_RESOURCES = readableBuilder.build();
77+
78+
ImmutableSet.Builder<IResource> protectedBuilder = ImmutableSet.builder();
79+
// neither clients nor tools need authentication/authorization
6980
if (!Config.isClientMode())
7081
{
71-
PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getAuthenticator().protectedResources());
72-
PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getAuthorizer().protectedResources());
73-
PROTECTED_AUTH_RESOURCES.addAll(DatabaseDescriptor.getRoleManager().protectedResources());
82+
protectedBuilder.addAll(DatabaseDescriptor.getAuthenticator().protectedResources());
83+
protectedBuilder.addAll(DatabaseDescriptor.getAuthorizer().protectedResources());
84+
protectedBuilder.addAll(DatabaseDescriptor.getRoleManager().protectedResources());
7485
}
75-
86+
PROTECTED_AUTH_RESOURCES = protectedBuilder.build();
87+
ImmutableSet.Builder<IResource> droppableBuilder = ImmutableSet.builder();
7688
// allow users with sufficient privileges to drop legacy tables (users, credentials, permissions) from AUTH_KS
77-
DROPPABLE_SYSTEM_AUTH_TABLES.add(DataResource.table(AuthKeyspace.NAME, PasswordAuthenticator.LEGACY_CREDENTIALS_TABLE));
78-
DROPPABLE_SYSTEM_AUTH_TABLES.add(DataResource.table(AuthKeyspace.NAME, CassandraRoleManager.LEGACY_USERS_TABLE));
79-
DROPPABLE_SYSTEM_AUTH_TABLES.add(DataResource.table(AuthKeyspace.NAME, CassandraAuthorizer.USER_PERMISSIONS));
89+
droppableBuilder.add(DataResource.table(AuthKeyspace.NAME, PasswordAuthenticator.LEGACY_CREDENTIALS_TABLE));
90+
droppableBuilder.add(DataResource.table(AuthKeyspace.NAME, CassandraRoleManager.LEGACY_USERS_TABLE));
91+
droppableBuilder.add(DataResource.table(AuthKeyspace.NAME, CassandraAuthorizer.USER_PERMISSIONS));
92+
DROPPABLE_SYSTEM_AUTH_TABLES = droppableBuilder.build();
8093
}
8194

8295
// Current user for the session
@@ -328,9 +341,12 @@ private void hasAccess(String keyspace, Permission perm, DataResource resource)
328341

329342
preventSystemKSSchemaModification(keyspace, resource, perm);
330343

344+
// Some system data is always readable
331345
if ((perm == Permission.SELECT) && READABLE_SYSTEM_RESOURCES.contains(resource))
332346
return;
333347

348+
// Modifications to any resource upon which the authenticator, authorizer or role manager depend should not be
349+
// be performed by users
334350
if (PROTECTED_AUTH_RESOURCES.contains(resource))
335351
if ((perm == Permission.CREATE) || (perm == Permission.ALTER) || (perm == Permission.DROP))
336352
throw new UnauthorizedException(String.format("%s schema is protected", resource));
@@ -348,7 +364,25 @@ public void ensureHasPermission(Permission perm, IResource resource) throws Unau
348364
if (((FunctionResource)resource).getKeyspace().equals(SystemKeyspace.NAME))
349365
return;
350366

351-
checkPermissionOnResourceChain(perm, resource);
367+
if (resource instanceof DataResource && !(user.isSuper() || user.isSystem()))
368+
{
369+
DataResource dataResource = (DataResource)resource;
370+
if (!dataResource.isRootLevel())
371+
{
372+
String keyspace = dataResource.getKeyspace();
373+
// A user may have permissions granted on ALL KEYSPACES, but this should exclude system keyspaces. Any
374+
// permission on those keyspaces or their tables must be granted to the user either explicitly or
375+
// transitively. The set of grantable permissions for system keyspaces is further limited,
376+
// see the Permission enum for details.
377+
if (Schema.isSystemKeyspace(keyspace))
378+
{
379+
ensurePermissionOnResourceChain(perm, Resources.chain(dataResource, IResource::hasParent));
380+
return;
381+
}
382+
}
383+
}
384+
385+
ensurePermissionOnResourceChain(perm, resource);
352386
}
353387

354388
// Convenience method called from checkAccess method of CQLStatement
@@ -363,14 +397,20 @@ public void ensureHasPermission(Permission permission, Function function)
363397
if (function.isNative())
364398
return;
365399

366-
checkPermissionOnResourceChain(permission, FunctionResource.function(function.name().keyspace,
367-
function.name().name,
368-
function.argTypes()));
400+
ensurePermissionOnResourceChain(permission, FunctionResource.function(function.name().keyspace,
401+
function.name().name,
402+
function.argTypes()));
403+
}
404+
405+
private void ensurePermissionOnResourceChain(Permission perm, IResource resource)
406+
{
407+
ensurePermissionOnResourceChain(perm, Resources.chain(resource));
369408
}
370409

371-
private void checkPermissionOnResourceChain(Permission perm, IResource resource)
410+
private void ensurePermissionOnResourceChain(Permission perm, List<? extends IResource> resources)
372411
{
373-
for (IResource r : Resources.chain(resource))
412+
IResource resource = resources.get(0);
413+
for (IResource r : resources)
374414
if (authorize(r).contains(perm))
375415
return;
376416

0 commit comments

Comments
 (0)