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 all 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 @@ -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 @@ -80,6 +80,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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
import org.apache.cassandra.sidecar.concurrent.TaskExecutorPool;
import org.apache.cassandra.sidecar.config.CacheConfiguration;
import org.apache.cassandra.sidecar.exceptions.SchemaUnavailableException;
import org.jetbrains.annotations.VisibleForTesting;

import static org.apache.cassandra.sidecar.server.SidecarServerEvents.ON_SIDECAR_SCHEMA_INITIALIZED;
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
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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.List;

import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.ext.auth.authorization.Authorization;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.HttpException;
import io.vertx.ext.web.handler.impl.AuthorizationHandlerImpl;

import static org.apache.cassandra.sidecar.utils.AuthUtils.extractIdentities;

/**
* Verifies user has required authorizations. Allows admin identities to bypass authorization checks.
*/
public class AuthorizationWithAdminBypassHandler extends AuthorizationHandlerImpl
{
private final AdminIdentityResolver adminIdentityResolver;

public AuthorizationWithAdminBypassHandler(AdminIdentityResolver adminIdentityResolver,
Authorization authorization)
{
super(authorization);
this.adminIdentityResolver = adminIdentityResolver;
}

@Override
public void handle(RoutingContext ctx)
{
List<String> identities = extractIdentities(ctx.user());

if (identities.isEmpty())
{
throw new HttpException(HttpResponseStatus.FORBIDDEN.code(), "Missing client identities");
}

// Admin identities bypass route specific authorization checks
if (identities.stream().anyMatch(adminIdentityResolver::isAdmin))
sarankk marked this conversation as resolved.
Show resolved Hide resolved
{
ctx.next();
return;
}

super.handle(ctx);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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;

/**
* Cassandra permissions allowed.
*/
public class CassandraPermissions
{
public static final Permission CREATE = new StandardPermission("CREATE");
public static final Permission ALTER = new StandardPermission("ALTER");
public static final Permission DROP = new StandardPermission("DROP");
public static final Permission SELECT = new StandardPermission("SELECT");
public static final Permission MODIFY = new StandardPermission("MODIFY");
public static final Permission AUTHORIZE = new StandardPermission("AUTHORIZE");
public static final Permission DESCRIBE = new StandardPermission("DESCRIBE");
public static final Permission EXECUTE = new StandardPermission("EXECUTE");
public static final Permission UNMASK = new StandardPermission("UNMASK");
public static final Permission SELECT_MASKED = new StandardPermission("SELECT_MASKED");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 a permission that can be granted to a user
*/
public interface Permission
{
/**
* @return name of permission
*/
String name();

/**
* @return {@link Authorization} created from permission. Most sidecar endpoints require a resource.
* This method is used in testing
*/
default Authorization toAuthorization()
{
return toAuthorization(null);
}

/**
* @return {@link Authorization} created from permission for a resource.
*/
Authorization toAuthorization(String resource);
}
Loading