Skip to content

Commit

Permalink
Add authorization
Browse files Browse the repository at this point in the history
Co-authored-by: Raymond Welgosh <[email protected]>
Co-authored-by: Saranya Krishnakumar <[email protected]>
  • Loading branch information
sarankk and rwelgosh committed Dec 20, 2024
1 parent ba7e58c commit d88b2db
Show file tree
Hide file tree
Showing 90 changed files with 4,051 additions and 250 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
1.0.0
-----
* Add RBAC Authorization support in Sidecar (CASSSIDECAR-161)
* Mechanism to have a reduced number of Sidecar instances run operations (CASSSIDECAR-174)
* Adding support for CDC APIs into sidecar client (CASSSIDECAR-172)
* Stopping Sidecar can take a long time (CASSSIDECAR-178)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ public void prepareStatements(@NotNull Session session)
connectionsByUserStatement = prepare(connectionsByUserStatement, session, selectConnectionsByUserStatement());
}

@Override
protected void unprepareStatements()
{
statsStatement = null;
connectionsByUserStatement = null;
}

@Override
protected String tableName()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ public final class ApiEndpointsV1

public static final String NATIVE = "/native";
public static final String JMX = "/jmx";
public static final String KEYSPACE_PATH_PARAM = ":keyspace";
public static final String TABLE_PATH_PARAM = ":table";
public static final String KEYSPACE = "keyspace";
public static final String TABLE = "table";
public static final String KEYSPACE_PATH_PARAM = ":" + KEYSPACE;
public static final String TABLE_PATH_PARAM = ":" + TABLE;
public static final String SNAPSHOT_PATH_PARAM = ":snapshot";
public static final String COMPONENT_PATH_PARAM = ":component";
public static final String INDEX_PATH_PARAM = ":index";
Expand Down
7 changes: 7 additions & 0 deletions conf/sidecar.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ access_control:
#
# other options are, io.vertx.ext.auth.mtls.impl.SpiffeIdentityExtractor.
certificate_identity_extractor: org.apache.cassandra.sidecar.acl.authentication.CassandraIdentityExtractor
authorizer:
# AuthorizationProvider provides authorizations an authenticated user holds.
#
# org.apache.cassandra.sidecar.acl.authorization.AllowAllAuthorizationProvider marks all requests as authorized.
# Other options are org.apache.cassandra.sidecar.acl.authorization.RoleBaseAuthorizationProvider, it validates
# role associated with authenticated user has permission for resource it accesses.
- class_name: org.apache.cassandra.sidecar.acl.authorization.AllowAllAuthorizationProvider
# Identities that are authenticated and authorized.
admin_identities:
# - spiffe://authorized/admin/identities
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@
* limitations under the License.
*/

package org.apache.cassandra.sidecar.exceptions;
package org.apache.cassandra.sidecar.common.server.exceptions;

/**
* Exception thrown when {@link org.apache.cassandra.sidecar.db.schema.TableSchema} is not prepared or expected
* operations are unavailable.
* Exception thrown when {@link org.apache.cassandra.sidecar.db.schema.TableSchema} does not exist.
* For instance, the connected Cassandra no longer has such table
*/
public class SchemaUnavailableException extends RuntimeException
{
public SchemaUnavailableException(String message)
public SchemaUnavailableException(String keyspace, String table)
{
super(message);
super(makeErrorMessage(keyspace, table));
}

public SchemaUnavailableException(String message, Throwable cause)
private static String makeErrorMessage(String keyspace, String table)
{
super(message, cause);
return "Table " + keyspace + '/' + table + " does not exist";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Session;
import org.apache.cassandra.sidecar.common.server.exceptions.SchemaUnavailableException;
import org.apache.cassandra.sidecar.exceptions.SidecarSchemaModificationException;
import org.jetbrains.annotations.NotNull;

Expand All @@ -45,6 +46,17 @@ public synchronized boolean initialize(@NotNull Session session, @NotNull Predic
return initialized;
}

public synchronized void reset()
{
initialized = false;
unprepareStatements();
}

protected void ensureSchemaAvailable() throws SchemaUnavailableException
{
// no-op
}

protected PreparedStatement prepare(PreparedStatement cached, Session session, String cqlLiteral)
{
return cached == null ? session.prepare(cqlLiteral).setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM) : cached;
Expand Down Expand Up @@ -80,6 +92,7 @@ protected boolean initializeInternal(@NotNull Session session,
}

prepareStatements(session);
logger.info("{} is initialized!", this.getClass().getSimpleName());
return true;
}

Expand All @@ -95,6 +108,8 @@ protected boolean initializeInternal(@NotNull Session session,
*/
protected abstract void prepareStatements(@NotNull Session session);

protected abstract void unprepareStatements();

/**
* @param metadata the cluster metadata
* @return {@code true} if the schema already exists in the database, {@code false} otherwise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.github.benmanes.caffeine.cache.LoadingCache;
import io.vertx.core.Vertx;
import io.vertx.core.eventbus.EventBus;
import org.apache.cassandra.sidecar.common.server.exceptions.SchemaUnavailableException;
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
import org.apache.cassandra.sidecar.concurrent.TaskExecutorPool;
import org.apache.cassandra.sidecar.config.CacheConfiguration;
Expand Down Expand Up @@ -164,6 +165,10 @@ protected void warmUp(int availableRetries)
{
cache.putAll(bulkLoadFunction.get());
}
catch (SchemaUnavailableException sue)
{
LOGGER.warn("Auth schema is unavailable. Skip warming up cache", sue);
}
catch (Exception e)
{
LOGGER.warn("Unexpected error encountered during pre-warming of cache={} ", name, e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.vertx.core.Vertx;
import org.apache.cassandra.sidecar.common.server.exceptions.SchemaUnavailableException;
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
import org.apache.cassandra.sidecar.config.SidecarConfiguration;
import org.apache.cassandra.sidecar.db.SystemAuthDatabaseAccessor;
import org.apache.cassandra.sidecar.exceptions.SchemaUnavailableException;

/**
* Caches entries from system_auth.identity_to_role table. The table maps valid certificate identities to Cassandra
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cassandra.sidecar.acl.authorization;

import io.vertx.ext.auth.authorization.Authorization;

/**
* Represents an action that can be granted to a user on a resource or across resources.
*/
public interface Action
{
/**
* @return {@link Authorization}.
*/
default Authorization toAuthorization()
{
return toAuthorization(null);
}

/**
* @return {@link Authorization} created for a resource
*/
Authorization toAuthorization(String resource);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cassandra.sidecar.acl.authorization;

import java.util.Set;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.ext.web.handler.HttpException;
import org.apache.cassandra.sidecar.acl.IdentityToRoleCache;
import org.apache.cassandra.sidecar.config.SidecarConfiguration;

/**
* Evaluates if provided identity is an admin identity.
*/
@Singleton
public class AdminIdentityResolver
{
private final IdentityToRoleCache identityToRoleCache;
private final SuperUserCache superUserCache;
private final Set<String> adminIdentities;

@Inject
public AdminIdentityResolver(IdentityToRoleCache identityToRoleCache,
SuperUserCache superUserCache,
SidecarConfiguration sidecarConfiguration)
{
this.identityToRoleCache = identityToRoleCache;
this.superUserCache = superUserCache;
this.adminIdentities = sidecarConfiguration.accessControlConfiguration().adminIdentities();
}

public boolean isAdmin(String identity)
{
String role = identityToRoleCache.get(identity);
if (role == null)
{
throw new HttpException(HttpResponseStatus.FORBIDDEN.code(), "No matching Cassandra role found");
}
// Sidecar configured and Cassandra superusers have admin privileges
return adminIdentities.contains(identity) || superUserCache.isSuperUser(role);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cassandra.sidecar.acl.authorization;

import io.vertx.ext.auth.authorization.Authorization;
import io.vertx.ext.auth.authorization.AuthorizationContext;

/**
* {@code Authorization} implementation to allow access for all users regardless of their authorizations.
*/
public class AllowAllAuthorization implements Authorization
{
public static final AllowAllAuthorization INSTANCE = new AllowAllAuthorization();

// use static INSTANCE
private AllowAllAuthorization()
{
}

/**
* Marks match as true regardless of the {@link AuthorizationContext} shared
*/
@Override
public boolean match(AuthorizationContext context)
{
return true;
}

/**
* Allows access regardless of {@link Authorization} shared.
*/
@Override
public boolean verify(Authorization authorization)
{
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cassandra.sidecar.acl.authorization;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.authorization.AuthorizationProvider;

/**
* {@link AuthorizationProvider} implementation to allow all requests regardless of authorizations user holds.
*/
public class AllowAllAuthorizationProvider implements AuthorizationProvider
{
public static final AllowAllAuthorizationProvider INSTANCE = new AllowAllAuthorizationProvider();

// use static INSTANCE
private AllowAllAuthorizationProvider()
{
}

/**
* @return unique id representing {@code AllowAllAuthorizationProvider}
*/
@Override
public String getId()
{
return "AllowAll";
}

@Override
public void getAuthorizations(User user, Handler<AsyncResult<Void>> handler)
{
getAuthorizations(user).onComplete(handler);
}

@Override
public Future<Void> getAuthorizations(User user)
{
if (user == null)
{
return Future.failedFuture("User cannot be null");
}

user.authorizations().add(getId(), AllowAllAuthorization.INSTANCE);
return Future.succeededFuture();
}
}
Loading

0 comments on commit d88b2db

Please sign in to comment.