How r-nacos Implements Namespace Isolation for Configurations and Service Registrations
r-nacos enforces strict namespace isolation by embedding a namespace identifier into every configuration key and service key, then validating user privileges against whitelist and blacklist rules before allowing any data access.
r-nacos is a lightweight, high-performance Rust implementation of the Nacos service discovery and configuration management platform. This article examines how the project achieves namespace isolation for configurations and service registrations by analyzing the source code architecture in the nacos-group/r-nacos repository. The isolation mechanism operates through three integrated layers: namespace identifier management, user privilege validation, and data model scoping.
Namespace Identifier Architecture
Default Namespace Handling
The system ensures backward compatibility through automatic namespace assignment in src/namespace/mod.rs. The default_namespace function injects the built-in identifier "public" whenever a request omits an explicit namespace value:
// src/namespace/mod.rs
pub fn default_namespace(val: String) -> String {
if val.is_empty() { "public".to_string() } else { val }
}
This helper is invoked throughout the API layer, including in src/openapi/naming/service.rs, ensuring that empty namespace parameters never propagate to the storage layer. The companion function is_default_namespace identifies the built-in namespace, allowing privilege checks to short-circuit validation for globally readable public data.
Namespace Management Utilities
Administrative operations on namespaces themselves are handled by NamespaceUtils in src/console/mod.rs. This utility provides CRUD operations for namespace objects, persisting them in the dedicated tb_tenant table. Every new namespace receives a unique tenant_id, which downstream components reference as the namespace_id in all data keys.
Privilege-Based Access Control
User Privilege Model
Each user record defined in src/user/model.rs carries specific namespace access controls through the UserDO struct:
// src/user/model.rs
pub struct UserDO {
pub namespace_privilege_flags: Option<u32>,
pub namespace_white_list: Vec<String>,
pub namespace_black_list: Vec<String>,
}
The build_namespace_privilege method constructs a PrivilegeGroup<Arc<String>> that encodes access rights. This group evaluates three conditions: an explicit whitelist of allowed namespaces, a blacklist of denied namespaces, and a flag that disables namespace checks entirely for super-administrators.
Permission Checking in API Handlers
The permission evaluation logic resides in src/common/model/privilege.rs. The PrivilegeGroup implementation provides the check_permission method, which returns true only if the requested namespace passes the whitelist and blacklist filters.
All API handlers obtain the current user's privilege set via the user_namespace_privilege! macro, visible in entry points like src/naming/ops/ops_api.rs. Before executing any read or write operation, handlers invoke check_permission(&namespace_id) to ensure the user cannot cross namespace boundaries:
// Conceptual flow in src/naming/ops/ops_api.rs
let privilege = user_namespace_privilege!();
if !privilege.check_permission(&namespace_id) {
return Err(PermissionDenied);
}
Data Model Isolation
Configuration Keys
Configuration data isolation is enforced through the ConfigKey struct defined in src/config/model.rs:
// src/config/model.rs
pub struct ConfigKey {
pub data_id: Arc<String>,
pub group: Arc<String>,
pub tenant: Arc<String>, // namespace identifier
}
The tenant field carries the namespace identifier. All CRUD operations in src/config/api.rs construct this key after applying the default namespace logic. The underlying SQLite or MySQL storage engines persist data in tables (tb_config, tb_config_history) that include a tenant_id column, ensuring physical separation at the database level.
Service Registration Keys
Service registration follows an identical pattern using ServiceKey in src/naming/service_key.rs:
// src/naming/service_key.rs
pub struct ServiceKey {
pub namespace_id: Arc<String>,
pub group_name: Arc<String>,
pub service_name: Arc<String>,
}
The ServiceIndex in src/naming/service_index.rs maintains a per-namespace map structure (namespace_group: HashMap<Arc<String>, ServiceIndex>). When processing registration or discovery requests, the system first validates the caller's privilege against the target namespace_id (lines 237-244 in src/naming/service_index.rs) before accessing the service map.
End-to-End Request Flow
The following example demonstrates how a client interaction triggers the isolation mechanisms across all layers:
use r_nacos::client::ConfigClient;
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Authenticate as user with whitelist containing only "dev"
let client = ConfigClient::builder()
.endpoint("http://127.0.0.1:8848")
.username("alice")
.password("pwd")
.build()
.await?;
// Publish to the "dev" namespace
let namespace_id = Some(Arc::new("dev".to_string()));
client
.publish_config(
"my-key".into(),
"DEFAULT_GROUP".into(),
"value".into(),
namespace_id.clone(),
)
.await?;
// Attempt to read from unauthorized "prod" namespace
let other_ns = Some(Arc::new("prod".to_string()));
let result = client
.get_config("my-key".into(), "DEFAULT_GROUP".into(), other_ns)
.await;
assert!(result.is_err(), "Cross-namespace access denied");
Ok(())
}
Execution flow:
- Key Construction: The
publish_configcall builds aConfigKeywithtenant = "dev"after the API layer appliesNamingUtils::default_namespace. - Privilege Validation: The handler extracts Alice's
NamespacePrivilegeGroupfromsrc/user/model.rs. Since"dev"appears in the whitelist,check_permission("dev")returnstrue. - Storage: The configuration persists to
tb_configwithtenant_id = "dev". - Access Denial: When
get_configtargets"prod", the same privilege check fails (check_permission("prod") == false), and the handler returns a permission error before querying the database.
Summary
- Every data entity carries an explicit namespace identifier through
ConfigKey.tenantorServiceKey.namespace_id. - User-level privilege groups defined in
src/common/model/privilege.rsenforce access control via whitelist and blacklist validation. - Default namespace handling in
src/namespace/mod.rsensures backward compatibility while maintaining security boundaries. - API handlers in
src/console/api.rsandsrc/naming/ops/ops_api.rsgate all requests through theuser_namespace_privilege!macro before delegating to storage engines.
Frequently Asked Questions
What is the default namespace in r-nacos?
The default namespace is the string "public", generated by the default_namespace function in src/namespace/mod.rs. When API requests omit a namespace parameter, this value is injected automatically, ensuring all data carries a namespace identifier while maintaining compatibility with legacy clients.
How does r-nacos prevent unauthorized cross-namespace access?
The system prevents unauthorized access through the PrivilegeGroup implementation in src/common/model/privilege.rs. Every request invokes check_permission against the user's configured whitelist and blacklist before the handler accesses any data storage. If the requested namespace is not explicitly allowed, the request terminates with a permission error.
Where is the namespace identifier physically stored for configurations?
The namespace identifier is stored in the tenant field of the ConfigKey struct (defined in src/config/model.rs) and persisted in the tenant_id column of the tb_config and tb_config_history database tables. This ensures physical data separation at the storage layer.
Are service registrations isolated using the same mechanism?
Yes, service registrations utilize the identical isolation pattern. The ServiceKey struct in src/naming/service_key.rs includes a namespace_id field, and the ServiceIndex in src/naming/service_index.rs maintains separate internal maps per namespace. All naming API handlers perform privilege checks using the same NamespacePrivilegeGroup logic as configuration handlers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →