Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CASSSIDECAR-161: Add RBAC Authorization support in Sidecar #165

Open
wants to merge 7 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
sarankk marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it the default authorizer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

# 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";
sarankk marked this conversation as resolved.
Show resolved Hide resolved
}
}
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());
sarankk marked this conversation as resolved.
Show resolved Hide resolved
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();
sarankk marked this conversation as resolved.
Show resolved Hide resolved

/**
* @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)
sarankk marked this conversation as resolved.
Show resolved Hide resolved
sarankk marked this conversation as resolved.
Show resolved Hide resolved
{
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,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 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)
{
if (adminIdentities.contains(identity))
{
return true;
}

String role = identityToRoleCache.get(identity);
if (role == null)
{
return false;
}
// Cassandra superusers have admin privileges
return 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();
sarankk marked this conversation as resolved.
Show resolved Hide resolved

// use static INSTANCE
private AllowAllAuthorization()
{
}

/**
* Marks match as true regardless of the {@link AuthorizationContext} shared
*/
@Override
public boolean match(AuthorizationContext context)
sarankk marked this conversation as resolved.
Show resolved Hide resolved
{
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();
sarankk marked this conversation as resolved.
Show resolved Hide resolved

// 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