Skip to content

Commit

Permalink
feat(BOUN-1236): add registry scraping for acls (#2425)
Browse files Browse the repository at this point in the history
This PR makes the canister periodically poll the registry to list the
principles of known API Boundary Nodes. These are then used in ACLs to
only allow access to the canister by those principals (will be added in
a follow-up PR).
  • Loading branch information
rikonor authored Nov 7, 2024
1 parent 45fc54b commit 02fbf21
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions rs/boundary_node/anonymization/backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ name = "anonymization-backend"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]
[[bin]]
name = "anonymization-backend"
path = "src/lib.rs"

[dependencies]
anyhow = { workspace = true }
Expand All @@ -13,6 +14,7 @@ candid = { workspace = true }
ic-cdk = { workspace = true }
ic-cdk-timers = { workspace = true }
ic-stable-structures = { workspace = true }
ic-nns-constants = { path = "../../../nns/constants" }
lazy_static = { workspace = true }
prometheus = { workspace = true }
serde = { workspace = true, features = ["derive"] }
Expand Down
118 changes: 116 additions & 2 deletions rs/boundary_node/anonymization/backend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,116 @@
// I put this here for CI, will remove on the next PR
pub const TMP: u32 = 0;
use std::{cell::RefCell, time::Duration};

use anonymization_interface::{
self as ifc, InitArg, QueryResponse, RegisterResponse, SubmitResponse,
};
use candid::Principal;
use ic_cdk::{id, spawn};
use ic_cdk_timers::set_timer_interval;
use ic_nns_constants::REGISTRY_CANISTER_ID;
use ic_stable_structures::{
memory_manager::{MemoryId, MemoryManager, VirtualMemory},
DefaultMemoryImpl, StableBTreeMap,
};
use lazy_static::lazy_static;
use registry::{Client, List};

mod registry;

type Memory = VirtualMemory<DefaultMemoryImpl>;

type StableMap<K, V> = StableBTreeMap<K, V, Memory>;
type StableSet<T> = StableMap<T, ()>;

thread_local! {
static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> =
RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));
}

const MEMORY_ID_ALLOWED_PRINCIPALS: u8 = 0;

lazy_static! {
static ref API_BOUNDARY_NODES_LISTER: Box<dyn List> = {
let cid = Principal::from(REGISTRY_CANISTER_ID);

let v = Client::new(cid);
Box::new(v)
};
}

thread_local! {
static ALLOWED_PRINCIPALS: RefCell<StableSet<Principal>> = RefCell::new(
StableSet::init(
MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(MEMORY_ID_ALLOWED_PRINCIPALS))),
)
);
}

// Timers

fn timers() {
// ACLs
set_timer_interval(Duration::from_secs(10), || {
// Switch to async
spawn(async {
// List registry entries
let ids = match API_BOUNDARY_NODES_LISTER.list().await {
Ok(ids) => ids,

// Abort on failure
Err(_) => return,
};

// Update allowed principals
ALLOWED_PRINCIPALS.with(|ps| {
let mut ps = ps.borrow_mut();

// Clear allowed principals
ps.clear_new();

ids.iter().for_each(|p| {
ps.insert(p.to_owned(), ());
});
});
});
});
}

// Service

#[allow(dead_code)]
fn main() {}

#[ic_cdk::init]
fn init(_arg: InitArg) {
// Self-authorize
ALLOWED_PRINCIPALS.with(|m| {
m.borrow_mut().insert(
id(), // canister id
(), // unit
)
});

// Start timers
timers();
}

#[ic_cdk::post_upgrade]
fn post_upgrade() {
// Start timers
timers();
}

#[ic_cdk::update]
fn register(_pubkey: Vec<u8>) -> RegisterResponse {
unimplemented!()
}

#[ic_cdk::query]
fn query() -> QueryResponse {
unimplemented!()
}

#[ic_cdk::update]
fn submit(_vs: Vec<ifc::Pair>) -> SubmitResponse {
unimplemented!()
}
55 changes: 55 additions & 0 deletions rs/boundary_node/anonymization/backend/src/registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use anyhow::anyhow;
use async_trait::async_trait;
use candid::{CandidType, Principal};
use serde::Deserialize;

#[derive(Debug, thiserror::Error)]
pub enum ListError {
#[error(transparent)]
UnexpectedError(#[from] anyhow::Error),
}

#[async_trait]
pub trait List: Sync + Send {
async fn list(&self) -> Result<Vec<Principal>, ListError>;
}

pub struct Client {
cid: Principal,
}

impl Client {
pub fn new(cid: Principal) -> Self {
Self { cid }
}
}

#[derive(CandidType)]
pub struct EmptyStruct {}

#[derive(Debug, CandidType, Deserialize)]
pub struct IdRecord {
id: Option<Principal>,
}

type CallResult<T> = (Result<T, String>,);

#[async_trait]
impl List for Client {
async fn list(&self) -> Result<Vec<Principal>, ListError> {
// Fetch IDs
let r: CallResult<Vec<IdRecord>> =
ic_cdk::call(self.cid, "get_api_boundary_node_ids", (EmptyStruct {},))
.await
.map_err(|err| anyhow!("failed to call canister method: {err:?}"))?;

// Flatten IDs
let ids: Vec<Principal> =
r.0.map_err(|err| anyhow!("canister method returned error: {err}"))?
.into_iter()
.filter_map(|r| r.id)
.collect();

return Ok(ids);
}
}

0 comments on commit 02fbf21

Please sign in to comment.