Access Control
Concepts
ReductStore provides straightforward access management based on token authentication and permissions.
Token Authentication
ReductStore uses token-based authentication to secure the HTTP API. A client must send a valid token in the Authorization
header in order to access it.
A ReductStore instance must have an initial token to enable authentication and access management. The initial token is configured using the RS_API_TOKEN
environment variable and has full access permissions.
Access tokens are immutable and can't be updated. If you need to change the token, you must create a new token and update the clients with the new token. The value of the token is a random string that is generated when the token is created and is only available at that time.
If you don't need access control, you can disable token authentication by leaving RS_API_TOKEN
unset.
Permissions and Access Control
Each token has a set of permissions that define what actions can be performed with the token:
-
full_access
: Allows a client to perform any action on the API without any restrictions. -
read
: Allows a client to read data from specific buckets. The list of buckets that can be accessed for reading is defined when the token is created. -
write
: Allows a client to write data to specific buckets, which includes adding new entries or deleting existing ones. The list of buckets that can be accessed for writing is defined when the token is created.
The table below shows the list of operations that can be performed for different permissions:
Operation | Anonymous | No permissions | Read | Write | Full Access |
---|---|---|---|---|---|
Alive check | ✅ | ✅ | ✅ | ✅ | ✅ |
Server Status | ❌ | ✅ | ✅ | ✅ | ✅ |
List Buckets | ❌ | ✅ | ✅ | ✅ | ✅ |
Create Bucket | ❌ | ❌ | ❌ | ❌ | ✅ |
Update Bucket Settings | ❌ | ❌ | ❌ | ❌ | ✅ |
Remove Bucket | ❌ | ❌ | ❌ | ❌ | ✅ |
Read Data | ❌ | ❌ | ✅ | ❌ | ✅ |
Update Data | ❌ | ❌ | ❌ | ✅ | ✅ |
Write Data | ❌ | ❌ | ❌ | ✅ | ✅ |
Remove Entry | ❌ | ❌ | ❌ | ✅ | ✅ |
Manage Tokens | ❌ | ❌ | ❌ | ❌ | ✅ |
Manage Replication Tasks | ❌ | ❌ | ❌ | ❌ | ✅ |
Anonymous
refers to clients that don't send an access token in the Authorization
header.
Managing Tokens
Here you will find examples of how to create, browse, retrieve, and delete access tokens using the ReductStore SDKs, REST API, CLI and Web Console.
Note that all the examples are written for a local ReductStore instance available at http://127.0.0.1:8383
with the API token my-token
.
For more information on setting up a local ReductStore instance, see the Getting Started guide.
Creating a Token
An access token can be created using the SDKs, CLI client, Web Console, or REST API. The token name must be unique within the store, and a client must have full access permission. You can also provision a token using environment variables. See the examples below:
- CLI
- Web Console
- Python
- JavaScript
- Rust
- C++
- cURL
- Provisioning
reduct-cli alias add local -L http://localhost:8383 -t "my-token"
reduct-cli token create local/new-token --read-bucket "example-bucket" --write-bucket "example-bucket"
Steps to create a new access token using the Web Console:
- Open the Web Console at http://127.0.0.1:8383 in your browser.
- Click on the "Security" tab in the left sidebar.
- Click on the plus icon in the top right corner to create a new token:
- Fill in the token name and select the permissions for the token:
-
Click on the "Create" button to create the token.
-
Copy the token value and save it in a secure place.
from reduct import Client, Permissions
async def create_token():
# Create a client with the base URL and API token
async with Client("http://localhost:8383", api_token="my-token") as client:
# Create a token with read/write access to the bucket "example-bucket"
permissions = Permissions(
full_access=False,
read=["example-bucket"],
write=["example-bucket"],
)
token = await client.create_token("new-token", permissions)
print(f"generated token: {token}")
if __name__ == "__main__":
import asyncio
asyncio.run(create_token())
import { Client } from "reduct-js";
// Create a new client with the server URL and an API token
const client = new Client("http://127.0.0.1:8383", { apiToken: "my-token" });
// Create a token with read/write access to the bucket "example-bucket"
const token = await client.createToken("new-token", {
fullAccess: false,
read: ["example-bucket"],
write: ["example-bucket"],
});
console.log(`Generated token: ${token}`);
use reduct_rs::{Permissions, QuotaType, ReductClient, ReductError};
use tokio;
#[tokio::main]
async fn main() -> Result<(), ReductError> {
// Create a new client with the API URL and API token
let client = ReductClient::builder()
.url("http://127.0.0.1:8383")
.api_token("my-token")
.build();
// Create a token with read/write access to the bucket "example-bucket"
let permissions = Permissions {
full_access: false,
read: vec![String::from("example-bucket")],
write: vec![String::from("example-bucket")],
};
let token = client.create_token("new-token", permissions).await;
println!("Generated token: {:?}", token);
Ok(())
}
#include <reduct/client.h>
#include <iostream>
#include <cassert>
using reduct::IBucket;
using reduct::IClient;
using reduct::Error;
std::string PrintTime(std::chrono::system_clock::time_point time) {
auto now_time_t = std::chrono::system_clock::to_time_t(time);
auto now_tm = std::localtime(&now_time_t);
char buf[32];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", now_tm);
return buf;
}
int main() {
// Create a client with the server URL
auto client = IClient::Build("http://127.0.0.1:8383", {
.api_token = "my-token"
});
// Create a token with read/write access to the bucket "example-bucket"
auto [token, create_err] = client->CreateToken("new-token", {
.full_access = true,
.read = {"example-bucket"},
.write = {"example-bucket"},
});
return 0;
}
#!/bin/bash
set -e -x
API_PATH="http://127.0.0.1:8383/api/v1"
AUTH_HEADER="Authorization: Bearer my-token"
# Create a token with read and write access to the example-bucket
curl -X POST -H "$AUTH_HEADER" -d '{
"full_access": false,
"read": ["example-bucket"],
"write": ["example-bucket"]
}' "$API_PATH/tokens/my-token"
version: "3"
services:
reductstore:
image: reduct/store:latest
ports:
- "8383:8383"
volumes:
- ./data:/data
environment:
- RS_API_TOKEN=my-api-token
- RS_TOKEN_1_NAME=new-token
- RS_TOKEN_1_VALUE=keep-it-secret
- RS_TOKEN_1_FULL_ACCESS=false
- RS_TOKEN_1_READ=example-bucket
- RS_TOKEN_1_WRITE=example-bucket
The token value is generated when the token is created and is only available at that time.
Browsing Tokens
You can list all the access tokens available in the store using the SDKs, CLI client, Web Console, or REST API. The client must have full access permission. See the examples below:
- CLI
- Web Console
- Python
- JavaScript
- Rust
- C++
- cURL
reduct-cli alias add local -L http://localhost:8383 -t "my-token"
reduct-cli token ls local
reduct-cli token show local/init-token
Steps to browse access tokens using the Web Console:
- Open the Web Console at http://127.0.0.1:8383 in your browser.
- Click on the "Security" tab in the left sidebar.
- You will see the list of access tokens:
- Click on the token name to view the token details
from typing import List
from reduct import Client, Permissions, Token, FullTokenInfo
async def browse_tokens():
# Create a client with the base URL and API token
async with Client("http://localhost:8383", api_token="my-token") as client:
# Browse all tokens and print their information
tokens: List[Token] = await client.get_token_list()
for token in tokens:
print(f"Token: {token.name}")
print(f"Created: {token.created_at}")
print(f"Provisioned: {token.is_provisioned}")
# Get detailed information about a specific token
details: FullTokenInfo = await client.get_token(token.name)
print(f"Permissions: {details.permissions}")
if __name__ == "__main__":
import asyncio
asyncio.run(browse_tokens())
import { Client } from "reduct-js";
// Create a new client with the server URL and an API token
const client = new Client("http://127.0.0.1:8383", { apiToken: "my-token" });
// Browse all tokens and print their information
const tokenList = await client.getTokenList();
for (const token of tokenList) {
console.log(`Token: ${token.name}`);
console.log(`Created: ${token.createdAt}`);
console.log(`Provisioned: ${token.isProvisioned}`);
// Get detailed information about a specific token
const tokenInfo = await client.getToken(token.name);
console.log(`Permissions: ${tokenInfo.permissions}`);
}
use reduct_rs::{Permissions, QuotaType, ReductClient, ReductError};
use tokio;
#[tokio::main]
async fn main() -> Result<(), ReductError> {
// Create a new client with the API URL and API token
let client = ReductClient::builder()
.url("http://127.0.0.1:8383")
.api_token("my-token")
.build();
// Browse all tokens and print their information
let tokens = client.list_tokens().await?;
for token in tokens {
println!("Token: {}", token.name);
println!("Created: {:?}", token.created_at);
println!("Provisioned: {:?}", token.is_provisioned);
// Get detailed information about the token
let token = client.get_token(&token.name).await?;
println!("Permissions: {:?}", token.permissions);
}
Ok(())
}
#include <reduct/client.h>
#include <iostream>
#include <cassert>
using reduct::IBucket;
using reduct::IClient;
using reduct::Error;
std::string PrintTime(std::chrono::system_clock::time_point time) {
auto now_time_t = std::chrono::system_clock::to_time_t(time);
auto now_tm = std::localtime(&now_time_t);
char buf[32];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", now_tm);
return buf;
}
std::ostream &operator<<(std::ostream &os, const std::vector<std::string> &v) {
os << "[";
for (size_t i = 0; i < v.size(); ++i) {
os << v[i];
if (i != v.size() - 1) {
os << ", ";
}
}
os << "]";
return os;
}
int main() {
// Create a client with the server URL
auto client = IClient::Build("http://127.0.0.1:8383", {
.api_token = "my-token"
});
// Browse all tokens and print their information
auto [list, list_err] = client->GetTokenList();
assert(list_err == Error::kOk);
for (const auto &token: list) {
std::cout << "Token: " << token.name << std::endl;
std::cout << "Created: " << PrintTime(token.created_at) << std::endl;
// Get detailed information about a specific token
auto [token_info, get_err] = client->GetToken(token.name);
assert(get_err == Error::kOk);
std::cout << "Full Access: " << token_info.permissions.full_access << std::endl;
std::cout << "Read Access: " << token_info.permissions.read << std::endl;
std::cout << "Write Access: " << token_info.permissions.write << std::endl;
}
return 0;
}
#!/bin/bash
set -e -x
API_PATH="http://127.0.0.1:8383/api/v1"
AUTH_HEADER="Authorization: Bearer my-token"
# Browse all tokens and print their information
curl -H "$AUTH_HEADER" "$API_PATH/tokens"
# Get detailed information about a specific token
curl -H "$AUTH_HEADER" "$API_PATH/tokens/my-token"
Removing a Token
You can remove an access token using the SDKs, CLI client, Web Console, or REST API. The token name must exist in the store, and a client must have full access permission. Refer to the following examples:
- CLI
- Web Console
- Python
- JavaScript
- Rust
- C++
- cURL
reduct-cli alias add local -L http://localhost:8383 -t "my-token"
reduct-cli token rm local/token-to-remove --yes
Steps to remove an access token using the Web Console:
- Open the Web Console at http://127.0.0.1:8383 in your browser.
- Click on the "Security" tab in the left sidebar.
- You will see the list of access tokens:
- Click on the token name to view the token details:
-
Click on the "Remove" button to delete the token.
-
Confirm the action by typing the token name and clicking on the "Remove" button.
from reduct import Client, Permissions
async def remove_token():
# Create a client with the base URL and API token
async with Client("http://localhost:8383", api_token="my-token") as client:
# Remove the token with the name "token-to-remove"
await client.remove_token("token-to-remove")
if __name__ == "__main__":
import asyncio
asyncio.run(remove_token())
import { Client } from "reduct-js";
// Create a new client with the server URL and an API token
const client = new Client("http://127.0.0.1:8383", { apiToken: "my-token" });
// Remove the token "token-to-remove"
await client.deleteToken("token-to-remove");
use reduct_rs::{Permissions, QuotaType, ReductClient, ReductError};
use tokio;
#[tokio::main]
async fn main() -> Result<(), ReductError> {
// Create a new client with the API URL and API token
let client = ReductClient::builder()
.url("http://127.0.0.1:8383")
.api_token("my-token")
.build();
// Remove the token "token-to-remove"
client.delete_token("token-to-remove").await?;
Ok(())
}
#include <reduct/client.h>
#include <iostream>
#include <cassert>
using reduct::IBucket;
using reduct::IClient;
using reduct::Error;
std::string PrintTime(std::chrono::system_clock::time_point time) {
auto now_time_t = std::chrono::system_clock::to_time_t(time);
auto now_tm = std::localtime(&now_time_t);
char buf[32];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", now_tm);
return buf;
}
int main() {
// Create a client with the server URL
auto client = IClient::Build("http://127.0.0.1:8383", {
.api_token = "my-token"
});
// Create a token with read/write access to the bucket "example-bucket"
auto [token, create_err] = client->CreateToken("new-token", {
.full_access = true,
.read = {"example-bucket"},
.write = {"example-bucket"},
});
return 0;
}
#!/bin/bash
set -e -x
API_PATH="http://127.0.0.1:8383/api/v1"
AUTH_HEADER="Authorization: Bearer my-token"
# Remove token
curl -X DELETE -H "$AUTH_HEADER" "$API_PATH/tokens/token-to-remove"