Users and permissions
In the most recent update, the Iggy server as well as the clients for all the available transport protocols have been extended with the support for users and permissions. From now on, you can additionally secure your data using the authentication and authorization by specifying the granular set of permissions for each user.
Breaking changes
In general, there are no breaking changes, except a new field added to the ClientInfo
and ClientInfoDetails
structs returned by get_client()
and get_clients()
methods.
pub struct ClientInfo {
pub client_id: u32,
pub user_id: Option<u32>, // New optional field
pub address: String,
pub transport: String,
pub consumer_groups_count: u32,
}
The user_id
field is optional, and it's only returned when the user is authenticated. Otherwise, it's set to None
(0 value for binary transport) or null
(HTTP transport).
Keep in mind that the client
represents the application connected to the server - since it's possible that multiple applications would share the same user credentials, thus you can think of a client
as a single connection from the application to the server (1 user -> N clients).
This breaking change has been introduced with the commit #23b3309. The available iggy crate supports these changes since version 0.0.80.
Configuration
Currently, the authentication and authorization can be enabled in the server.toml
configuration via [system.user]
section:
[system.user]
authentication_enabled = true
authorization_enabled = true
This might change in the future releases (e.g. authentication and authorization will always be enabled), but in order to avoid the breaking changes to the existing clients SDKs, these options are configurable for now.
The additional settings for HTTP API which uses JWT (JSON Web Token), can be found in the [http.jwt]
section:
[http.jwt]
algorithm = "HS256"
audience = "iggy.rs"
expiry = 3600
encoding_secret = "top_secret$iggy.rs$_jwt_HS256_key#!"
decoding_secret = "top_secret$iggy.rs$_jwt_HS256_key#!"
use_base64_secret = false
Authentication and authorization
The overall implementation of the users authentication and authorization is rather typical. All the server methods except ping
and login
require the user to be authenticated. The login
method is used to authenticate the user, and it returns the optional token
which is then used to authenticate the user in the subsequent requests. For now, the token
is only used by HTTP API based on JWT, for the binary transport there's no token - since, it's a stateful protocol, the server keeps track of the authenticated connections.
The authorization is based on the permissions, which are defined for each user. All the server methods have the specific authorization policies applied. For example, you can allow the user to read all the streams, or manage all the topics, while on the other hand, you could also specify only the set of particular streams or topics to which the user can send and/or poll the message to/from.
Take a look at the following JSON example to get the overall idea:
{
"permissions": {
"global": {
"manage_servers": false,
"read_servers": true,
"manage_users": true,
"read_users": true,
"manage_streams": false,
"read_streams": true,
"manage_topics": false,
"read_topics": true,
"poll_messages": true,
"send_messages": true
},
"streams": {
"1": {
"manage_stream": false,
"read_stream": true,
"manage_topics": false,
"read_topics": true,
"poll_messages": true,
"send_messages": true,
"topics": {
"1": {
"manage_topic": false,
"read_topic": true,
"poll_messages": true,
"send_messages": true
}
}
}
}
}
}
Each user might have the global permissions which do apply to all the available streams and topics, as well as the granular permissions for each stream and its topics (this is a simple hashmap using the stream or topic ID as its key).
For example, the root user
, which has the default username and password iggy
and cannot be updated or deleted, can do everything, simply by having the following permissions:
{
"permissions": {
"global": {
"manage_servers": true,
"read_servers": true,
"manage_users": true,
"read_users": true,
"manage_streams": true,
"read_streams": true,
"manage_topics": true,
"read_topics": true,
"poll_messages": true,
"send_messages": true
},
"streams": null
}
}
SDK
The new UserClient
trait has been added in order to support the users and permissions feature. It is implemented for all the available transport protocols.
pub trait UserClient {
async fn get_user(&self, command: &GetUser) -> Result<UserInfoDetails, Error>;
async fn get_users(&self, command: &GetUsers) -> Result<Vec<UserInfo>, Error>;
async fn create_user(&self, command: &CreateUser) -> Result<(), Error>;
async fn delete_user(&self, command: &DeleteUser) -> Result<(), Error>;
async fn update_user(&self, command: &UpdateUser) -> Result<(), Error>;
async fn update_permissions(&self, command: &UpdatePermissions) -> Result<(), Error>;
async fn change_password(&self, command: &ChangePassword) -> Result<(), Error>;
async fn login_user(&self, command: &LoginUser) -> Result<IdentityInfo, Error>;
async fn logout_user(&self, command: &LogoutUser) -> Result<(), Error>;
}
Commands and models
The following commands and response models have been added:
pub struct IdentityInfo {
pub user_id: u32,
pub token: Option<String>,
}
pub struct UserInfo {
pub id: u32,
pub created_at: u64,
pub status: UserStatus,
pub username: String,
}
pub struct UserInfoDetails {
pub id: u32,
pub created_at: u64,
pub status: UserStatus,
pub username: String,
pub permissions: Option<Permissions>,
}
pub struct Permissions {
pub global: GlobalPermissions,
pub streams: Option<HashMap<u32, StreamPermissions>>,
}
pub struct GlobalPermissions {
pub manage_servers: bool,
pub read_servers: bool,
pub manage_users: bool,
pub read_users: bool,
pub manage_streams: bool,
pub read_streams: bool,
pub manage_topics: bool,
pub read_topics: bool,
pub poll_messages: bool,
pub send_messages: bool,
}
pub struct StreamPermissions {
pub manage_stream: bool,
pub read_stream: bool,
pub manage_topics: bool,
pub read_topics: bool,
pub poll_messages: bool,
pub send_messages: bool,
pub topics: Option<HashMap<u32, TopicPermissions>>,
}
pub struct TopicPermissions {
pub manage_topic: bool,
pub read_topic: bool,
pub poll_messages: bool,
pub send_messages: bool,
}
pub struct GetUser {
pub user_id: Identifier,
}
pub struct GetUsers {}
pub struct CreateUser {
pub username: String,
pub password: String,
pub status: UserStatus,
pub permissions: Option<Permissions>,
}
pub struct DeleteUser {
pub user_id: Identifier,
}
pub struct UpdateUser {
pub user_id: Identifier,
pub username: Option<String>,
pub status: Option<UserStatus>,
}
pub struct UpdatePermissions {
pub user_id: Identifier,
pub permissions: Option<Permissions>,
}
pub struct ChangePassword {
pub user_id: Identifier,
pub current_password: String,
pub new_password: String,
}
pub struct LoginUser {
pub username: String,
pub password: String,
}
pub struct LogoutUser {}
Similar to the stream and topic ID, the user will always have the unique numeric ID and the username. The username must be within the range of 3–50 chars and will always be lowercased, using the same regex as the stream and topic name ^[\w\.\-\s]+$
. The password must be within the range of 3–100 chars and will be hashed using the bcrypt algorithm.
HTTP API
The following endpoints have been added, and as always, you can find them in the server.http
file in the main repository.
POST {{url}}/users/login
Content-Type: application/json
{
"username": "iggy",
"password": "iggy"
}
POST {{url}}/users/logout
Authorization: Bearer {{token}}
Content-Type: application/json
{
}
POST {{url}}/users
Authorization: Bearer {{token}}
Content-Type: application/json
{
"username": "user1",
"password": "secret",
"status": "active",
"permissions": null
}
GET {{url}}/users
Authorization: Bearer {{token}}
GET {{url}}/users/user1
Authorization: Bearer {{token}}
PUT {{url}}/users/user1
Authorization: Bearer {{token}}
Content-Type: application/json
{
"username": "user1,
"status": "active",
"permissions": null
}
PUT {{url}}/users/user1/password
Authorization: Bearer {{token}}
Content-Type: application/json
{
"current_password": "secret",
"new_password": "secret1"
}
PUT {{url}}/users/user1permissions
Authorization: Bearer {{token}}
Content-Type: application/json
{
"permissions": {
"global": {
"manage_servers": false,
"read_servers": true,
"manage_users": true,
"read_users": true,
"manage_streams": false,
"read_streams": true,
"manage_topics": false,
"read_topics": true,
"poll_messages": true,
"send_messages": true
},
"streams": {
"1": {
"manage_stream": false,
"read_stream": true,
"manage_topics": false,
"read_topics": true,
"poll_messages": true,
"send_messages": true,
"topics": {
"1": {
"manage_topic": false,
"read_topic": true,
"poll_messages": true,
"send_messages": true
}
}
}
}
}
}
DELETE {{url}}/users/user1
Authorization: Bearer {{token}}