Skip to content

Authentication and Authorization in Pulsar Manager

tuteng edited this page Nov 27, 2019 · 7 revisions

Status

Motivation

Apache Pulsar is a multi-tenant distributed event streaming platform. It already provides comprehensive security-related features such as authentication and authorization. Pulsar Manager, as the management console for Apache Pulsar, should also support authentication and authorization in order to manage Apache Pulsar clusters with security features turned on. This proposal introduces a user management system for authentication and role-based access control (RBAC) system for regulating access to Pulsar resources in Pulsar Manager.

Proposal

Overview

The proposed authentication and authorization system in Pulsar Manager is illustrated below.

auth.png

Authentication

In Pulsar Manager, Users are the ones who logged into Pulsar Manager through an identity and authentication provider, such as Google Cloud Identity, LDAP, Github, OpenID, and etc. A simple registration and login system can be implemented for users to get started easily. Pulsar Manager adopters can also customize their own implementation. Once a user logs in Pulsar Manager, he/she is authentication to interact with Pulsar Manager. Pulsar Manager will authorize he/she permissions on accessing resources based on his/her associated role bindings.

Authorization

Pulsar Manager uses an RBAC system for regulating access to Pulsar resources based on the roles of individual users it manages. In the RBAC system, there are 2 top-level types.

  • Role
  • RoleBinding
Role

In the RBAC system, a role contains rules that represent a set of permissions. Permissions are purely additive (there are no “deny” rules). Permission is comprised of resources and verbs. resources indicates the resource that this permission is applied on; while verbs indicates what accesses that this permission is granted for. We use <resource-type>#<resource-name>: [ verb1, verb2 ] for describing the permission in the remaining of this proposal. For example, cluster#my-cluster: [admin] indicates that the role has admin permission on my-cluster cluster; while namespace#my-tenant/my-namespace: [admin, produce] indicates that the role has permissions to administer my-tenant/my-namespace and produce messages to it.

Currently, the available resources and the corresponding supported verbs are listed in the following table.

Resource Type Resource Name Verbs
Clusters <cluster-name> Admin
Brokers <cluster-name> Admin
Ns Isolation Policy <cluster-name> Admin
Failure Domain <cluster-name> Admin
Tenants <tenant-name> Admin
Namespaces <namespace-name> Admin, Produce, Consume, Function
Topics <topic-name> Produce, Consume
Roles <role name> Admin
RoleBinding <rolebinding name> Admin

RoleBinding

A role binding grants the permissions defined in a role to a user or a set of users. It holds a list of subjects (users, or groups if we add in the future), and a reference to the role being granted.

Implementation

Backend Changes

Back-end services We continue to maintain the existing architecture, we only need to add a few more tables.

Define interface
public interface TenantsRepository {
  // Create a tenant
  void save(TenantEntity tenantsEntity);
  // Get tenant information by tenant id
  Optional<TenantEntity> findById(long tenantId);
  // Get tenant information by tenant name
  Optional<TenantEntity> findByName(String tenant);
  // Get tenant information, support page
  Page<TenantEntity> getTenantsList(Integer pageNum, Integer pageSize);
  // Delete a tenant
  void remove(Long tenantId);
  // Update tenant
  void updateByTenant(TenantEntity tenantsEntity);
}
public interface NamespacesRepository {
    // Create a namespace
    void save(NamespaceEntity namespacesEntity);
    // Get a namespace by namespace id
    Optional<NamespaceEntity> findById(long namespaceId);
    // Get a namespace by tenant and namespace
    Optional<NamespaceEntity> findByTenantNamespace(String tenant, String namespace);
    // Get namespace list by name of tenant or namespace
    Page<NamespaceEntity> findByTenantOrNamespace(Integer pageNum, Integer pageSize, String tenantOrNamespace);
    // Get namespace list by name of namespace
    Page<NamespaceEntity> findByNamespace(Integer pageNum, Integer pageSize, String namespace);
    // Get namespace list
    Page<NamespaceEntity> getNamespacesList(Integer pageNum, Integer pageSize);
    // Get namespace list by a tenant
    Page<NamespaceEntity> findByTenant(Integer pageNum, Integer pageSize, String tenant);
    // Delete a namespace by name of tenant and namespace
    void remove(String tenant, String namespace);
    // Update namespace
    void update(NamespaceEntity namespacesEntity);
}
public interface UsersRepository {
  // Create a user
  void save(UserInfoEntity userInfoEntity);
  // Obtaining user information according to id
  Optional<UserInfoEntity> findById(long userId);
  // Obtaining user information according to name
  Optional<UserInfoEntity> findByUserName(String name);
  // Get user list
  Page<UserInfoEntity> findUsersList(Integer pageNum, Integer pageSize);
  // Delete a user by user name
  void delete(String name);
  // Update user info
  void update(UserInfoEntity userInfoEntity);
}
public interface RolesRepository {
  // Create a role
  void save(RoleEntity roleEntity);
  // Get a role by role id
  Optional<RoleEntity> findById(long roleId);
  // Get a role by name of role
   Optional<RoleEntity> findByName(String name);
  // Get role list, support page
  Page<RoleEntity> getRolesList(Integer pageNum, Integer pageSize);
  // Delete a role by role
  void remove(Long roleId);
  // Update a role
  void updateByRole(RoleEntity roleEntity);
}
public interface RoleBindingRepository {
  // Create a role binding
  void save(RoleBindingEntitty roleBindingEntitty);
  // Get a role binding by binding id
  Optional<RoleBindingEntitty> findById(long roleBindingId);
  // Get a role binding by binding name
  Optional<RoleBindingEntitty> findByName(String name);
  // Get a role binding list, support page
  Page<RoleBindingEntitty> getRoleBindingList(Integer pageNum, Integer pageSize);
   // Delete a role binding by binding id
  void remove(Long roleBindingId);
  // Update role binding
  void updateByRoleBinding(RoleBindingEntitty roleBindingEntitty);
}

TenantEntity, NamespaceEntity, UserEntity, RoleEntity, RoleBindingEntitty, these five objects are defined according to the following table mechanism information.

Define table structure
CREATE TABLE IF NOT EXISTS tenants (
   tenant_id INTEGER NOT NULL PRIMARY KEY,
  tenant varchar(255) NOT NULL,
  allowed_clusters TEXT,
  admin_roles TEXt,
  UNIQUE (tenant)
);
CREATE TABLE IF NOT EXISTS namespaces (
  namespace_id INTEGER NOT NULL PRIMARY KEY,
  tenant varchar(255) NOT NULL,
  namespace varchar(255) NOT NULL,
  UNIQUE (tenant, namespace)
);

These two tables are used to store tenants and namespaces. Paging query and filtering query cannot be implemented in pulsar at present. Therefore, after users create tenants and namespaces, they need to cache one here. After users log in, they can only see their own tenants and namespaces.

CREATE TABLE IF NOT EXISTS users (
  user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
  access_token varchar(256) NOT NULL,
  name varchar(256) NOT NULL,
  description varchar(128),
  email varchar(256),
  phone_number varchar(48),
  location varchar(256),
  company varchar(256),
  expire LONG NOT NULL,
  UNIQUE (name)
);

The user name should be unique, whether the platform logs in or the third party logs in.

CREATE TABLE IF NOT EXISTS roles (
  role_id BIGINT PRIMARY KEY AUTO_INCREMENT,
  role_name varchar(256) NOT NULL,
  description varchar(128),
  resource_type varchar(48),
  resouce_name varchar(48),
  resource_verbs varchar(1024),
  UNIQUE (role_name)
);

Table roles are used to save the role, which is the same as the role in pulsar. The role table includes resource types, resource names, and resource verbs, as described above.

CREATE TABLE IF NOT EXISTS role_binding(
  role_binding_id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name varchar(256) NOT NULL,
  description varchar(256),
  role_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  UNIQUE (name)
);

Frontend Changes

The front-end content needs to be displayed according to the role of the backend user. For example, if a user only has the permissions of namespace A, the user should not see and operate other resources. The current frontend is for super administrators, so the contents of the frontend should be redesigned and implemented.

Super User & Pulsar Permissions

Currently, Pulsar doesn’t provide fine granularity on access controls. Hence when implementing RBAC in Pulsar Manager, we have to map the permissions to corresponding permission in Pulsar.

For example, the clusters, brokers, ns isolation policy, failure domain can only be allocated to a super-user role that is configured in Pulsar Cluster configurations.

In order to support managing super-user management, we can improve the existing cluster creation workflow in Pulsar Manager. We can allow the administrator to add super-user role for this cluster.

Test Plan

We have the following test plans: Add unit tests for all operation Rejected Alternatives