Authorize remote modules with bearer proofs
This commit is contained in:
parent
72cd018a3e
commit
7e39a19c18
8 changed files with 572 additions and 120 deletions
|
|
@ -179,8 +179,10 @@ Roadmap items should be actionable and checkable:
|
||||||
Bearer access metadata can be created/listed/revoked as resource-scoped auth
|
Bearer access metadata can be created/listed/revoked as resource-scoped auth
|
||||||
ops and must not allow trust graph mutation capabilities. Bearer
|
ops and must not allow trust graph mutation capabilities. Bearer
|
||||||
challenge/proof/verify commands exist for resource-scoped possession checks.
|
challenge/proof/verify commands exist for resource-scoped possession checks.
|
||||||
Payload encryption, key envelopes, and wiring bearer proofs into remote module
|
Remote module authorization paths can accept optional bearer proofs without
|
||||||
authorization are still roadmap work.
|
enrolling the caller as a trusted node. Payload encryption, key envelopes, and
|
||||||
|
separating public bearer ids from private bearer tokens are still roadmap
|
||||||
|
work.
|
||||||
- SSH revocations can be exported as JSONL, OpenSSH KRL specification text, or
|
- SSH revocations can be exported as JSONL, OpenSSH KRL specification text, or
|
||||||
binary OpenSSH KRL files generated through `ssh-keygen`. JSONL and OpenSSH KRL
|
binary OpenSSH KRL files generated through `ssh-keygen`. JSONL and OpenSSH KRL
|
||||||
specification imports are supported; binary KRL import is unsupported because
|
specification imports are supported; binary KRL import is unsupported because
|
||||||
|
|
|
||||||
20
README.md
20
README.md
|
|
@ -105,7 +105,8 @@ The bootstrap implementation provides:
|
||||||
- `geth auth grant <subject> <resource> <capability> [--grant-id <id>]`
|
- `geth auth grant <subject> <resource> <capability> [--grant-id <id>]`
|
||||||
- `geth auth revoke <resource> <grant-id>`
|
- `geth auth revoke <resource> <grant-id>`
|
||||||
- local filesystem CAS commands: `add`, `get`, `fetch`, `hash`, `has`, `pin`,
|
- local filesystem CAS commands: `add`, `get`, `fetch`, `hash`, `has`, `pin`,
|
||||||
`unpin`, `cleanup`, `providers`, `list`
|
`unpin`, `cleanup`, `providers`, `list`; remote fetch accepts
|
||||||
|
`--bearer-secret <secret>`
|
||||||
- local CAS tree objects describe file trees and are stored as CAS blobs
|
- local CAS tree objects describe file trees and are stored as CAS blobs
|
||||||
- local file-root commands: `geth cas root add/list/scan`
|
- local file-root commands: `geth cas root add/list/scan`
|
||||||
- local file conflict metadata commands:
|
- local file conflict metadata commands:
|
||||||
|
|
@ -117,9 +118,11 @@ The bootstrap implementation provides:
|
||||||
`geth db sync <node-id> <name>`
|
`geth db sync <node-id> <name>`
|
||||||
- local SQLite-backed KV commands: `geth kv create/set/get`; `kv set` accepts
|
- local SQLite-backed KV commands: `geth kv create/set/get`; `kv set` accepts
|
||||||
`--subject <principal>` to exercise local capability checks for non-local
|
`--subject <principal>` to exercise local capability checks for non-local
|
||||||
callers; `geth kv sync <node-id> <name>` pulls authorized remote updates
|
callers; `geth kv sync <node-id> <name> [--bearer-secret <secret>]` pulls
|
||||||
|
authorized remote updates
|
||||||
- local JSON document commands: `geth document create/status/set/get`; `geth
|
- local JSON document commands: `geth document create/status/set/get`; `geth
|
||||||
document sync <node-id> <name>` pulls authorized remote JSON state
|
document sync <node-id> <name> [--bearer-secret <secret>]` pulls authorized
|
||||||
|
remote JSON state
|
||||||
- local daemon-lifetime pubsub snapshots: `geth pubsub pub/sub`; `geth pubsub
|
- local daemon-lifetime pubsub snapshots: `geth pubsub pub/sub`; `geth pubsub
|
||||||
pub <topic> <message> --node <node-id>` publishes to an authorized peer;
|
pub <topic> <message> --node <node-id>` publishes to an authorized peer;
|
||||||
`geth pubsub sub <topic> --node <node-id>` reads an authorized peer snapshot
|
`geth pubsub sub <topic> --node <node-id>` reads an authorized peer snapshot
|
||||||
|
|
@ -129,15 +132,15 @@ The bootstrap implementation provides:
|
||||||
- `geth ssh cert approve <request-id> --ca-key <path> [--sign] [--subject <principal>]`
|
- `geth ssh cert approve <request-id> --ca-key <path> [--sign] [--subject <principal>]`
|
||||||
- `geth ssh cert import <request-id> --cert <path> [--subject <principal>]`
|
- `geth ssh cert import <request-id> --cert <path> [--subject <principal>]`
|
||||||
- `geth ssh cert list [--subject <principal>]`
|
- `geth ssh cert list [--subject <principal>]`
|
||||||
- `geth ssh cert sync <node-id>`
|
- `geth ssh cert sync <node-id> [--bearer-secret <secret>]`
|
||||||
- `geth ssh revocation add <kind> <target> [--subject <principal>]`
|
- `geth ssh revocation add <kind> <target> [--subject <principal>]`
|
||||||
- `geth ssh revocation list [--subject <principal>]`
|
- `geth ssh revocation list [--subject <principal>]`
|
||||||
- `geth ssh revocation export --out <path> [--format jsonl|openssh-krl-spec|openssh-krl] [--subject <principal>]`
|
- `geth ssh revocation export --out <path> [--format jsonl|openssh-krl-spec|openssh-krl] [--subject <principal>]`
|
||||||
- `geth ssh revocation import <path> [--format jsonl|openssh-krl-spec] [--subject <principal>]`
|
- `geth ssh revocation import <path> [--format jsonl|openssh-krl-spec] [--subject <principal>]`
|
||||||
- `geth ssh revocation sync <node-id>`
|
- `geth ssh revocation sync <node-id> [--bearer-secret <secret>]`
|
||||||
- SSH proxy authorization probe: `geth ssh proxy <node-id>`
|
- SSH proxy authorization probe: `geth ssh proxy <node-id> [--bearer-secret <secret>]`
|
||||||
- pipe registry/connect commands: `geth pipe listen <name>` and
|
- pipe registry/connect commands: `geth pipe listen <name>` and
|
||||||
`geth pipe connect <name> [--node <node-id>]`
|
`geth pipe connect <name> [--node <node-id>] [--bearer-secret <secret>]`
|
||||||
|
|
||||||
`geth peer export/import/list` is for untrusted peer-card exchange. Peer cards
|
`geth peer export/import/list` is for untrusted peer-card exchange. Peer cards
|
||||||
include the Iroh EndpointID plus currently known relay/direct addresses.
|
include the Iroh EndpointID plus currently known relay/direct addresses.
|
||||||
|
|
@ -153,6 +156,9 @@ hash to the requested BLAKE3 CAS hash before storing them locally. Successful
|
||||||
fetches record the serving peer as a local provider, visible with
|
fetches record the serving peer as a local provider, visible with
|
||||||
`geth cas providers <hash>`. This is the bootstrap transfer path; future work
|
`geth cas providers <hash>`. This is the bootstrap transfer path; future work
|
||||||
will move provider/fetch behavior to `iroh-blobs`.
|
will move provider/fetch behavior to `iroh-blobs`.
|
||||||
|
Remote resource commands that accept `--bearer-secret` can also authorize with a
|
||||||
|
resource-scoped bearer proof. This does not enroll the caller as a trusted node;
|
||||||
|
it only unlocks the requested capability on that one resource.
|
||||||
`geth ssh cert sync <node-id>` requires `ssh_cert.sync` on `resource:ssh:certs`
|
`geth ssh cert sync <node-id>` requires `ssh_cert.sync` on `resource:ssh:certs`
|
||||||
at the peer. `geth ssh revocation sync <node-id>` requires
|
at the peer. `geth ssh revocation sync <node-id>` requires
|
||||||
`ssh_revocation.sync` on `resource:ssh:revocations`. Both commands merge
|
`ssh_revocation.sync` on `resource:ssh:revocations`. Both commands merge
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,8 @@ pub enum CasCommand {
|
||||||
Fetch {
|
Fetch {
|
||||||
node: String,
|
node: String,
|
||||||
hash: String,
|
hash: String,
|
||||||
|
#[arg(long)]
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
Hash {
|
Hash {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
|
@ -335,6 +337,8 @@ pub enum KvCommand {
|
||||||
Sync {
|
Sync {
|
||||||
node: String,
|
node: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
#[arg(long)]
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,11 +349,15 @@ pub enum PubsubCommand {
|
||||||
message: String,
|
message: String,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
node: Option<String>,
|
node: Option<String>,
|
||||||
|
#[arg(long)]
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
Sub {
|
Sub {
|
||||||
topic: String,
|
topic: String,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
node: Option<String>,
|
node: Option<String>,
|
||||||
|
#[arg(long)]
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -362,6 +370,8 @@ pub enum PipeCommand {
|
||||||
target: String,
|
target: String,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
node: Option<String>,
|
node: Option<String>,
|
||||||
|
#[arg(long)]
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,22 +396,40 @@ pub enum DbCommand {
|
||||||
name: String,
|
name: String,
|
||||||
#[arg(long, default_value_t = 100)]
|
#[arg(long, default_value_t = 100)]
|
||||||
limit: u32,
|
limit: u32,
|
||||||
|
#[arg(long)]
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum DocumentCommand {
|
pub enum DocumentCommand {
|
||||||
Create { name: String },
|
Create {
|
||||||
Status { name: String },
|
name: String,
|
||||||
Set { name: String, state_json: String },
|
},
|
||||||
Get { name: String },
|
Status {
|
||||||
Sync { node: String, name: String },
|
name: String,
|
||||||
|
},
|
||||||
|
Set {
|
||||||
|
name: String,
|
||||||
|
state_json: String,
|
||||||
|
},
|
||||||
|
Get {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
Sync {
|
||||||
|
node: String,
|
||||||
|
name: String,
|
||||||
|
#[arg(long)]
|
||||||
|
bearer_secret: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum SshCommand {
|
pub enum SshCommand {
|
||||||
Proxy {
|
Proxy {
|
||||||
node: String,
|
node: String,
|
||||||
|
#[arg(long)]
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
Cert {
|
Cert {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|
@ -463,6 +491,8 @@ pub enum SshCertCommand {
|
||||||
},
|
},
|
||||||
Sync {
|
Sync {
|
||||||
node: String,
|
node: String,
|
||||||
|
#[arg(long)]
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -499,6 +529,8 @@ pub enum SshRevocationCommand {
|
||||||
},
|
},
|
||||||
Sync {
|
Sync {
|
||||||
node: String,
|
node: String,
|
||||||
|
#[arg(long)]
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -671,9 +703,14 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
|
||||||
hash: hash.into(),
|
hash: hash.into(),
|
||||||
out,
|
out,
|
||||||
},
|
},
|
||||||
CasCommand::Fetch { node, hash } => ControlRequest::CasFetch {
|
CasCommand::Fetch {
|
||||||
|
node,
|
||||||
|
hash,
|
||||||
|
bearer_secret,
|
||||||
|
} => ControlRequest::CasFetch {
|
||||||
node,
|
node,
|
||||||
hash: hash.into(),
|
hash: hash.into(),
|
||||||
|
bearer_secret,
|
||||||
},
|
},
|
||||||
CasCommand::Hash { path } => ControlRequest::CasHash { path },
|
CasCommand::Hash { path } => ControlRequest::CasHash { path },
|
||||||
CasCommand::Has { hash } => ControlRequest::CasHas { hash: hash.into() },
|
CasCommand::Has { hash } => ControlRequest::CasHas { hash: hash.into() },
|
||||||
|
|
@ -731,23 +768,49 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
|
||||||
subject,
|
subject,
|
||||||
},
|
},
|
||||||
KvCommand::Get { name, key } => ControlRequest::KvGet { name, key },
|
KvCommand::Get { name, key } => ControlRequest::KvGet { name, key },
|
||||||
KvCommand::Sync { node, name } => ControlRequest::KvSync { node, name },
|
KvCommand::Sync {
|
||||||
|
node,
|
||||||
|
name,
|
||||||
|
bearer_secret,
|
||||||
|
} => ControlRequest::KvSync {
|
||||||
|
node,
|
||||||
|
name,
|
||||||
|
bearer_secret,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Command::Pubsub { command } => match command {
|
Command::Pubsub { command } => match command {
|
||||||
PubsubCommand::Pub {
|
PubsubCommand::Pub {
|
||||||
topic,
|
topic,
|
||||||
message,
|
message,
|
||||||
node,
|
node,
|
||||||
|
bearer_secret,
|
||||||
} => ControlRequest::PubsubPub {
|
} => ControlRequest::PubsubPub {
|
||||||
topic,
|
topic,
|
||||||
message,
|
message,
|
||||||
node,
|
node,
|
||||||
|
bearer_secret,
|
||||||
|
},
|
||||||
|
PubsubCommand::Sub {
|
||||||
|
topic,
|
||||||
|
node,
|
||||||
|
bearer_secret,
|
||||||
|
} => ControlRequest::PubsubSub {
|
||||||
|
topic,
|
||||||
|
node,
|
||||||
|
bearer_secret,
|
||||||
},
|
},
|
||||||
PubsubCommand::Sub { topic, node } => ControlRequest::PubsubSub { topic, node },
|
|
||||||
},
|
},
|
||||||
Command::Pipe { command } => match command {
|
Command::Pipe { command } => match command {
|
||||||
PipeCommand::Listen { name } => ControlRequest::PipeListen { name },
|
PipeCommand::Listen { name } => ControlRequest::PipeListen { name },
|
||||||
PipeCommand::Connect { target, node } => ControlRequest::PipeConnect { target, node },
|
PipeCommand::Connect {
|
||||||
|
target,
|
||||||
|
node,
|
||||||
|
bearer_secret,
|
||||||
|
} => ControlRequest::PipeConnect {
|
||||||
|
target,
|
||||||
|
node,
|
||||||
|
bearer_secret,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Command::Db { command } => match command {
|
Command::Db { command } => match command {
|
||||||
DbCommand::Add { name, path } => ControlRequest::DbAdd { name, path },
|
DbCommand::Add { name, path } => ControlRequest::DbAdd { name, path },
|
||||||
|
|
@ -761,7 +824,17 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
|
||||||
after_db_version,
|
after_db_version,
|
||||||
limit,
|
limit,
|
||||||
},
|
},
|
||||||
DbCommand::Sync { node, name, limit } => ControlRequest::DbSync { node, name, limit },
|
DbCommand::Sync {
|
||||||
|
node,
|
||||||
|
name,
|
||||||
|
limit,
|
||||||
|
bearer_secret,
|
||||||
|
} => ControlRequest::DbSync {
|
||||||
|
node,
|
||||||
|
name,
|
||||||
|
limit,
|
||||||
|
bearer_secret,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Command::Document { command } => match command {
|
Command::Document { command } => match command {
|
||||||
DocumentCommand::Create { name } => ControlRequest::DocumentCreate { name },
|
DocumentCommand::Create { name } => ControlRequest::DocumentCreate { name },
|
||||||
|
|
@ -770,10 +843,24 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
|
||||||
ControlRequest::DocumentSet { name, state_json }
|
ControlRequest::DocumentSet { name, state_json }
|
||||||
}
|
}
|
||||||
DocumentCommand::Get { name } => ControlRequest::DocumentGet { name },
|
DocumentCommand::Get { name } => ControlRequest::DocumentGet { name },
|
||||||
DocumentCommand::Sync { node, name } => ControlRequest::DocumentSync { node, name },
|
DocumentCommand::Sync {
|
||||||
|
node,
|
||||||
|
name,
|
||||||
|
bearer_secret,
|
||||||
|
} => ControlRequest::DocumentSync {
|
||||||
|
node,
|
||||||
|
name,
|
||||||
|
bearer_secret,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Command::Ssh { command } => match command {
|
Command::Ssh { command } => match command {
|
||||||
SshCommand::Proxy { node } => ControlRequest::SshProxyConnect { node },
|
SshCommand::Proxy {
|
||||||
|
node,
|
||||||
|
bearer_secret,
|
||||||
|
} => ControlRequest::SshProxyConnect {
|
||||||
|
node,
|
||||||
|
bearer_secret,
|
||||||
|
},
|
||||||
SshCommand::Cert { command } => match command {
|
SshCommand::Cert { command } => match command {
|
||||||
SshCertCommand::Request {
|
SshCertCommand::Request {
|
||||||
public_key,
|
public_key,
|
||||||
|
|
@ -820,7 +907,13 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
|
||||||
subject,
|
subject,
|
||||||
},
|
},
|
||||||
SshCertCommand::List { subject } => ControlRequest::SshCertList { subject },
|
SshCertCommand::List { subject } => ControlRequest::SshCertList { subject },
|
||||||
SshCertCommand::Sync { node } => ControlRequest::SshCertSync { node },
|
SshCertCommand::Sync {
|
||||||
|
node,
|
||||||
|
bearer_secret,
|
||||||
|
} => ControlRequest::SshCertSync {
|
||||||
|
node,
|
||||||
|
bearer_secret,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
SshCommand::Revocation { command } => match command {
|
SshCommand::Revocation { command } => match command {
|
||||||
SshRevocationCommand::Add {
|
SshRevocationCommand::Add {
|
||||||
|
|
@ -857,7 +950,13 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
|
||||||
format,
|
format,
|
||||||
subject,
|
subject,
|
||||||
},
|
},
|
||||||
SshRevocationCommand::Sync { node } => ControlRequest::SshRevocationSync { node },
|
SshRevocationCommand::Sync {
|
||||||
|
node,
|
||||||
|
bearer_secret,
|
||||||
|
} => ControlRequest::SshRevocationSync {
|
||||||
|
node,
|
||||||
|
bearer_secret,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Command::Init | Command::Daemon { .. } => bail!("command is handled directly"),
|
Command::Init | Command::Daemon { .. } => bail!("command is handled directly"),
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ pub enum ControlRequest {
|
||||||
CasFetch {
|
CasFetch {
|
||||||
node: String,
|
node: String,
|
||||||
hash: BlobHash,
|
hash: BlobHash,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
CasHash {
|
CasHash {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
|
@ -182,6 +183,7 @@ pub enum ControlRequest {
|
||||||
},
|
},
|
||||||
SshCertSync {
|
SshCertSync {
|
||||||
node: String,
|
node: String,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
SshRevocationAdd {
|
SshRevocationAdd {
|
||||||
kind: String,
|
kind: String,
|
||||||
|
|
@ -205,9 +207,11 @@ pub enum ControlRequest {
|
||||||
},
|
},
|
||||||
SshRevocationSync {
|
SshRevocationSync {
|
||||||
node: String,
|
node: String,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
SshProxyConnect {
|
SshProxyConnect {
|
||||||
node: String,
|
node: String,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
DbAdd {
|
DbAdd {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
@ -225,6 +229,7 @@ pub enum ControlRequest {
|
||||||
node: String,
|
node: String,
|
||||||
name: String,
|
name: String,
|
||||||
limit: u32,
|
limit: u32,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
KvCreate {
|
KvCreate {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
@ -242,6 +247,7 @@ pub enum ControlRequest {
|
||||||
KvSync {
|
KvSync {
|
||||||
node: String,
|
node: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
DocumentCreate {
|
DocumentCreate {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
@ -259,15 +265,18 @@ pub enum ControlRequest {
|
||||||
DocumentSync {
|
DocumentSync {
|
||||||
node: String,
|
node: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
PubsubPub {
|
PubsubPub {
|
||||||
topic: String,
|
topic: String,
|
||||||
message: String,
|
message: String,
|
||||||
node: Option<String>,
|
node: Option<String>,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
PubsubSub {
|
PubsubSub {
|
||||||
topic: String,
|
topic: String,
|
||||||
node: Option<String>,
|
node: Option<String>,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
PipeListen {
|
PipeListen {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
@ -275,6 +284,7 @@ pub enum ControlRequest {
|
||||||
PipeConnect {
|
PipeConnect {
|
||||||
target: String,
|
target: String,
|
||||||
node: Option<String>,
|
node: Option<String>,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
},
|
},
|
||||||
ModuleStub {
|
ModuleStub {
|
||||||
module: String,
|
module: String,
|
||||||
|
|
@ -666,48 +676,57 @@ pub enum PeerControlRequest {
|
||||||
peer_card: PeerCard,
|
peer_card: PeerCard,
|
||||||
hash: BlobHash,
|
hash: BlobHash,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
bearer_proof: Option<BearerProof>,
|
||||||
},
|
},
|
||||||
SshCertSync {
|
SshCertSync {
|
||||||
peer_card: PeerCard,
|
peer_card: PeerCard,
|
||||||
since_ms: i64,
|
since_ms: i64,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
bearer_proof: Option<BearerProof>,
|
||||||
},
|
},
|
||||||
SshRevocationSync {
|
SshRevocationSync {
|
||||||
peer_card: PeerCard,
|
peer_card: PeerCard,
|
||||||
since_ms: i64,
|
since_ms: i64,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
bearer_proof: Option<BearerProof>,
|
||||||
},
|
},
|
||||||
KvSync {
|
KvSync {
|
||||||
peer_card: PeerCard,
|
peer_card: PeerCard,
|
||||||
name: String,
|
name: String,
|
||||||
since_ms: i64,
|
since_ms: i64,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
bearer_proof: Option<BearerProof>,
|
||||||
},
|
},
|
||||||
PubsubPublish {
|
PubsubPublish {
|
||||||
peer_card: PeerCard,
|
peer_card: PeerCard,
|
||||||
topic: String,
|
topic: String,
|
||||||
message: String,
|
message: String,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
bearer_proof: Option<BearerProof>,
|
||||||
},
|
},
|
||||||
PubsubSubscribe {
|
PubsubSubscribe {
|
||||||
peer_card: PeerCard,
|
peer_card: PeerCard,
|
||||||
topic: String,
|
topic: String,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
bearer_proof: Option<BearerProof>,
|
||||||
},
|
},
|
||||||
PipeConnect {
|
PipeConnect {
|
||||||
peer_card: PeerCard,
|
peer_card: PeerCard,
|
||||||
target: String,
|
target: String,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
bearer_proof: Option<BearerProof>,
|
||||||
},
|
},
|
||||||
SshProxyConnect {
|
SshProxyConnect {
|
||||||
peer_card: PeerCard,
|
peer_card: PeerCard,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
bearer_proof: Option<BearerProof>,
|
||||||
},
|
},
|
||||||
DocumentSync {
|
DocumentSync {
|
||||||
peer_card: PeerCard,
|
peer_card: PeerCard,
|
||||||
name: String,
|
name: String,
|
||||||
since_ms: i64,
|
since_ms: i64,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
bearer_proof: Option<BearerProof>,
|
||||||
},
|
},
|
||||||
DbSync {
|
DbSync {
|
||||||
peer_card: PeerCard,
|
peer_card: PeerCard,
|
||||||
|
|
@ -715,6 +734,7 @@ pub enum PeerControlRequest {
|
||||||
after_db_version: Option<i64>,
|
after_db_version: Option<i64>,
|
||||||
limit: u32,
|
limit: u32,
|
||||||
nonce: String,
|
nonce: String,
|
||||||
|
bearer_proof: Option<BearerProof>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1000,6 +1020,7 @@ mod tests {
|
||||||
let request = ControlRequest::CasFetch {
|
let request = ControlRequest::CasFetch {
|
||||||
node: "node:peer".to_owned(),
|
node: "node:peer".to_owned(),
|
||||||
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
|
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
|
||||||
|
bearer_secret: Some("bearer:test".to_owned()),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1050,6 +1071,7 @@ mod tests {
|
||||||
|
|
||||||
let request = ControlRequest::SshCertSync {
|
let request = ControlRequest::SshCertSync {
|
||||||
node: "node:ca".to_owned(),
|
node: "node:ca".to_owned(),
|
||||||
|
bearer_secret: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1058,6 +1080,7 @@ mod tests {
|
||||||
|
|
||||||
let request = ControlRequest::SshRevocationSync {
|
let request = ControlRequest::SshRevocationSync {
|
||||||
node: "node:ca".to_owned(),
|
node: "node:ca".to_owned(),
|
||||||
|
bearer_secret: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1107,6 +1130,7 @@ mod tests {
|
||||||
let request = ControlRequest::KvSync {
|
let request = ControlRequest::KvSync {
|
||||||
node: "node:peer".to_owned(),
|
node: "node:peer".to_owned(),
|
||||||
name: "prefs".to_owned(),
|
name: "prefs".to_owned(),
|
||||||
|
bearer_secret: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1148,6 +1172,7 @@ mod tests {
|
||||||
let request = ControlRequest::PipeConnect {
|
let request = ControlRequest::PipeConnect {
|
||||||
target: "inbox".to_owned(),
|
target: "inbox".to_owned(),
|
||||||
node: Some("node:peer".to_owned()),
|
node: Some("node:peer".to_owned()),
|
||||||
|
bearer_secret: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1158,6 +1183,7 @@ mod tests {
|
||||||
topic: "presence/test".to_owned(),
|
topic: "presence/test".to_owned(),
|
||||||
message: "online".to_owned(),
|
message: "online".to_owned(),
|
||||||
node: Some("node:peer".to_owned()),
|
node: Some("node:peer".to_owned()),
|
||||||
|
bearer_secret: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1167,6 +1193,7 @@ mod tests {
|
||||||
let request = ControlRequest::PubsubSub {
|
let request = ControlRequest::PubsubSub {
|
||||||
topic: "presence/test".to_owned(),
|
topic: "presence/test".to_owned(),
|
||||||
node: Some("node:peer".to_owned()),
|
node: Some("node:peer".to_owned()),
|
||||||
|
bearer_secret: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1231,6 +1258,7 @@ mod tests {
|
||||||
|
|
||||||
let request = ControlRequest::SshProxyConnect {
|
let request = ControlRequest::SshProxyConnect {
|
||||||
node: "node:peer".to_owned(),
|
node: "node:peer".to_owned(),
|
||||||
|
bearer_secret: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1271,6 +1299,7 @@ mod tests {
|
||||||
node: "node:peer".to_owned(),
|
node: "node:peer".to_owned(),
|
||||||
name: "notes".to_owned(),
|
name: "notes".to_owned(),
|
||||||
limit: 50,
|
limit: 50,
|
||||||
|
bearer_secret: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1426,6 +1455,7 @@ mod tests {
|
||||||
after_db_version: Some(7),
|
after_db_version: Some(7),
|
||||||
limit: 10,
|
limit: 10,
|
||||||
nonce: "nonce".to_owned(),
|
nonce: "nonce".to_owned(),
|
||||||
|
bearer_proof: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
|
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1552,6 +1582,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
topic: "presence/test".to_owned(),
|
topic: "presence/test".to_owned(),
|
||||||
nonce: "nonce".to_owned(),
|
nonce: "nonce".to_owned(),
|
||||||
|
bearer_proof: None,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
|
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
@ -1618,6 +1649,13 @@ mod tests {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nonce: "nonce".to_owned(),
|
nonce: "nonce".to_owned(),
|
||||||
|
bearer_proof: Some(BearerProof {
|
||||||
|
secret: "bearer:test".into(),
|
||||||
|
resource: "resource:ssh-proxy:local".into(),
|
||||||
|
capabilities: vec!["ssh_proxy.connect".into()],
|
||||||
|
nonce: "nonce".to_owned(),
|
||||||
|
response: "response".to_owned(),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
|
decode_peer_request(&encode_peer_request(&request).expect("encode")).expect("decode"),
|
||||||
|
|
|
||||||
|
|
@ -255,42 +255,52 @@ pub async fn handle_request_async(
|
||||||
ControlRequest::CasFetch {
|
ControlRequest::CasFetch {
|
||||||
node: peer_node,
|
node: peer_node,
|
||||||
hash,
|
hash,
|
||||||
} => cas_fetch_from_peer(node, &peer_node, hash).await,
|
bearer_secret,
|
||||||
ControlRequest::SshCertSync { node: peer_node } => {
|
} => cas_fetch_from_peer(node, &peer_node, hash, bearer_secret).await,
|
||||||
ssh_cert_sync_from_peer(node, &peer_node).await
|
ControlRequest::SshCertSync {
|
||||||
}
|
node: peer_node,
|
||||||
ControlRequest::SshRevocationSync { node: peer_node } => {
|
bearer_secret,
|
||||||
ssh_revocation_sync_from_peer(node, &peer_node).await
|
} => ssh_cert_sync_from_peer(node, &peer_node, bearer_secret).await,
|
||||||
}
|
ControlRequest::SshRevocationSync {
|
||||||
|
node: peer_node,
|
||||||
|
bearer_secret,
|
||||||
|
} => ssh_revocation_sync_from_peer(node, &peer_node, bearer_secret).await,
|
||||||
ControlRequest::KvSync {
|
ControlRequest::KvSync {
|
||||||
node: peer_node,
|
node: peer_node,
|
||||||
name,
|
name,
|
||||||
} => kv_sync_from_peer(node, &peer_node, &name).await,
|
bearer_secret,
|
||||||
|
} => kv_sync_from_peer(node, &peer_node, &name, bearer_secret).await,
|
||||||
ControlRequest::DbSync {
|
ControlRequest::DbSync {
|
||||||
node: peer_node,
|
node: peer_node,
|
||||||
name,
|
name,
|
||||||
limit,
|
limit,
|
||||||
} => db_sync_from_peer(node, &peer_node, &name, limit).await,
|
bearer_secret,
|
||||||
|
} => db_sync_from_peer(node, &peer_node, &name, limit, bearer_secret).await,
|
||||||
ControlRequest::PubsubPub {
|
ControlRequest::PubsubPub {
|
||||||
topic,
|
topic,
|
||||||
message,
|
message,
|
||||||
node: Some(peer_node),
|
node: Some(peer_node),
|
||||||
} => pubsub_publish_to_peer(node, &peer_node, topic, message).await,
|
bearer_secret,
|
||||||
|
} => pubsub_publish_to_peer(node, &peer_node, topic, message, bearer_secret).await,
|
||||||
ControlRequest::PubsubSub {
|
ControlRequest::PubsubSub {
|
||||||
topic,
|
topic,
|
||||||
node: Some(peer_node),
|
node: Some(peer_node),
|
||||||
} => pubsub_subscribe_from_peer(node, &peer_node, topic).await,
|
bearer_secret,
|
||||||
|
} => pubsub_subscribe_from_peer(node, &peer_node, topic, bearer_secret).await,
|
||||||
ControlRequest::PipeConnect {
|
ControlRequest::PipeConnect {
|
||||||
target,
|
target,
|
||||||
node: Some(peer_node),
|
node: Some(peer_node),
|
||||||
} => pipe_connect_to_peer(node, &peer_node, target).await,
|
bearer_secret,
|
||||||
ControlRequest::SshProxyConnect { node: peer_node } => {
|
} => pipe_connect_to_peer(node, &peer_node, target, bearer_secret).await,
|
||||||
ssh_proxy_connect_to_peer(node, &peer_node).await
|
ControlRequest::SshProxyConnect {
|
||||||
}
|
node: peer_node,
|
||||||
|
bearer_secret,
|
||||||
|
} => ssh_proxy_connect_to_peer(node, &peer_node, bearer_secret).await,
|
||||||
ControlRequest::DocumentSync {
|
ControlRequest::DocumentSync {
|
||||||
node: peer_node,
|
node: peer_node,
|
||||||
name,
|
name,
|
||||||
} => document_sync_from_peer(node, &peer_node, &name).await,
|
bearer_secret,
|
||||||
|
} => document_sync_from_peer(node, &peer_node, &name, bearer_secret).await,
|
||||||
other => handle_request(node, other),
|
other => handle_request(node, other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -699,6 +709,7 @@ async fn cas_fetch_from_peer(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
hash: BlobHash,
|
hash: BlobHash,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
) -> Result<ControlResponse, NodeError> {
|
) -> Result<ControlResponse, NodeError> {
|
||||||
let store = Store::open(&node.paths.metadata_db())?;
|
let store = Store::open(&node.paths.metadata_db())?;
|
||||||
let stored = store
|
let stored = store
|
||||||
|
|
@ -733,6 +744,7 @@ async fn cas_fetch_from_peer(
|
||||||
peer_card: self_card,
|
peer_card: self_card,
|
||||||
hash: hash.clone(),
|
hash: hash.clone(),
|
||||||
nonce: nonce.clone(),
|
nonce: nonce.clone(),
|
||||||
|
bearer_proof: bearer_proof(bearer_secret, "resource:cas:local", "cas.fetch", &nonce),
|
||||||
};
|
};
|
||||||
|
|
||||||
let conn = endpoint
|
let conn = endpoint
|
||||||
|
|
@ -843,6 +855,7 @@ async fn cas_fetch_from_peer(
|
||||||
async fn ssh_cert_sync_from_peer(
|
async fn ssh_cert_sync_from_peer(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
) -> Result<ControlResponse, NodeError> {
|
) -> Result<ControlResponse, NodeError> {
|
||||||
let since_ms = load_live_sync_cursor(
|
let since_ms = load_live_sync_cursor(
|
||||||
&Store::open(&node.paths.metadata_db())?,
|
&Store::open(&node.paths.metadata_db())?,
|
||||||
|
|
@ -853,7 +866,13 @@ async fn ssh_cert_sync_from_peer(
|
||||||
PeerControlRequest::SshCertSync {
|
PeerControlRequest::SshCertSync {
|
||||||
peer_card,
|
peer_card,
|
||||||
since_ms,
|
since_ms,
|
||||||
nonce,
|
nonce: nonce.clone(),
|
||||||
|
bearer_proof: bearer_proof(
|
||||||
|
bearer_secret,
|
||||||
|
"resource:ssh:certs",
|
||||||
|
"ssh_cert.sync",
|
||||||
|
&nonce,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -913,6 +932,7 @@ async fn ssh_cert_sync_from_peer(
|
||||||
async fn ssh_revocation_sync_from_peer(
|
async fn ssh_revocation_sync_from_peer(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
) -> Result<ControlResponse, NodeError> {
|
) -> Result<ControlResponse, NodeError> {
|
||||||
let since_ms = load_live_sync_cursor(
|
let since_ms = load_live_sync_cursor(
|
||||||
&Store::open(&node.paths.metadata_db())?,
|
&Store::open(&node.paths.metadata_db())?,
|
||||||
|
|
@ -926,7 +946,13 @@ async fn ssh_revocation_sync_from_peer(
|
||||||
|peer_card, nonce| PeerControlRequest::SshRevocationSync {
|
|peer_card, nonce| PeerControlRequest::SshRevocationSync {
|
||||||
peer_card,
|
peer_card,
|
||||||
since_ms,
|
since_ms,
|
||||||
nonce,
|
nonce: nonce.clone(),
|
||||||
|
bearer_proof: bearer_proof(
|
||||||
|
bearer_secret,
|
||||||
|
"resource:ssh:revocations",
|
||||||
|
"ssh_revocation.sync",
|
||||||
|
&nonce,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -980,6 +1006,7 @@ async fn kv_sync_from_peer(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
) -> Result<ControlResponse, NodeError> {
|
) -> Result<ControlResponse, NodeError> {
|
||||||
geth_kv::validate_kv_name(name).map_err(|_| NodeError::InvalidKvName(name.to_owned()))?;
|
geth_kv::validate_kv_name(name).map_err(|_| NodeError::InvalidKvName(name.to_owned()))?;
|
||||||
let stream = format!("kv:{name}");
|
let stream = format!("kv:{name}");
|
||||||
|
|
@ -990,7 +1017,13 @@ async fn kv_sync_from_peer(
|
||||||
peer_card,
|
peer_card,
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
since_ms,
|
since_ms,
|
||||||
nonce,
|
nonce: nonce.clone(),
|
||||||
|
bearer_proof: bearer_proof(
|
||||||
|
bearer_secret,
|
||||||
|
&format!("resource:kv:{name}"),
|
||||||
|
"kv.read",
|
||||||
|
&nonce,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -1065,6 +1098,7 @@ async fn pubsub_publish_to_peer(
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
topic: String,
|
topic: String,
|
||||||
message: String,
|
message: String,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
) -> Result<ControlResponse, NodeError> {
|
) -> Result<ControlResponse, NodeError> {
|
||||||
geth_pubsub::validate_topic(&topic)?;
|
geth_pubsub::validate_topic(&topic)?;
|
||||||
geth_pubsub::validate_message(&message)?;
|
geth_pubsub::validate_message(&message)?;
|
||||||
|
|
@ -1073,7 +1107,13 @@ async fn pubsub_publish_to_peer(
|
||||||
peer_card,
|
peer_card,
|
||||||
topic: topic.clone(),
|
topic: topic.clone(),
|
||||||
message: message.clone(),
|
message: message.clone(),
|
||||||
nonce,
|
nonce: nonce.clone(),
|
||||||
|
bearer_proof: bearer_proof(
|
||||||
|
bearer_secret,
|
||||||
|
&format!("resource:pubsub:{topic}"),
|
||||||
|
"pubsub.publish",
|
||||||
|
&nonce,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -1114,13 +1154,20 @@ async fn pubsub_subscribe_from_peer(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
topic: String,
|
topic: String,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
) -> Result<ControlResponse, NodeError> {
|
) -> Result<ControlResponse, NodeError> {
|
||||||
geth_pubsub::validate_topic(&topic)?;
|
geth_pubsub::validate_topic(&topic)?;
|
||||||
let response = request_peer_control(node, peer_node, "pubsub-subscribe", |peer_card, nonce| {
|
let response = request_peer_control(node, peer_node, "pubsub-subscribe", |peer_card, nonce| {
|
||||||
PeerControlRequest::PubsubSubscribe {
|
PeerControlRequest::PubsubSubscribe {
|
||||||
peer_card,
|
peer_card,
|
||||||
topic: topic.clone(),
|
topic: topic.clone(),
|
||||||
nonce,
|
nonce: nonce.clone(),
|
||||||
|
bearer_proof: bearer_proof(
|
||||||
|
bearer_secret,
|
||||||
|
&format!("resource:pubsub:{topic}"),
|
||||||
|
"pubsub.subscribe",
|
||||||
|
&nonce,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -1159,13 +1206,20 @@ async fn pipe_connect_to_peer(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
target: String,
|
target: String,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
) -> Result<ControlResponse, NodeError> {
|
) -> Result<ControlResponse, NodeError> {
|
||||||
geth_pipe::validate_pipe_name(&target)?;
|
geth_pipe::validate_pipe_name(&target)?;
|
||||||
let response = request_peer_control(node, peer_node, "pipe-connect", |peer_card, nonce| {
|
let response = request_peer_control(node, peer_node, "pipe-connect", |peer_card, nonce| {
|
||||||
PeerControlRequest::PipeConnect {
|
PeerControlRequest::PipeConnect {
|
||||||
peer_card,
|
peer_card,
|
||||||
target: target.clone(),
|
target: target.clone(),
|
||||||
nonce,
|
nonce: nonce.clone(),
|
||||||
|
bearer_proof: bearer_proof(
|
||||||
|
bearer_secret,
|
||||||
|
&format!("resource:pipe:{target}"),
|
||||||
|
"pipe.connect",
|
||||||
|
&nonce,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -1206,10 +1260,20 @@ async fn pipe_connect_to_peer(
|
||||||
async fn ssh_proxy_connect_to_peer(
|
async fn ssh_proxy_connect_to_peer(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
) -> Result<ControlResponse, NodeError> {
|
) -> Result<ControlResponse, NodeError> {
|
||||||
let response =
|
let response =
|
||||||
request_peer_control(node, peer_node, "ssh-proxy-connect", |peer_card, nonce| {
|
request_peer_control(node, peer_node, "ssh-proxy-connect", |peer_card, nonce| {
|
||||||
PeerControlRequest::SshProxyConnect { peer_card, nonce }
|
PeerControlRequest::SshProxyConnect {
|
||||||
|
peer_card,
|
||||||
|
nonce: nonce.clone(),
|
||||||
|
bearer_proof: bearer_proof(
|
||||||
|
bearer_secret,
|
||||||
|
"resource:ssh-proxy:local",
|
||||||
|
"ssh_proxy.connect",
|
||||||
|
&nonce,
|
||||||
|
),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
match response {
|
match response {
|
||||||
|
|
@ -1242,6 +1306,7 @@ async fn document_sync_from_peer(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
) -> Result<ControlResponse, NodeError> {
|
) -> Result<ControlResponse, NodeError> {
|
||||||
geth_document::validate_document_name(name)
|
geth_document::validate_document_name(name)
|
||||||
.map_err(|_| NodeError::InvalidDocumentName(name.to_owned()))?;
|
.map_err(|_| NodeError::InvalidDocumentName(name.to_owned()))?;
|
||||||
|
|
@ -1253,7 +1318,13 @@ async fn document_sync_from_peer(
|
||||||
peer_card,
|
peer_card,
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
since_ms,
|
since_ms,
|
||||||
nonce,
|
nonce: nonce.clone(),
|
||||||
|
bearer_proof: bearer_proof(
|
||||||
|
bearer_secret,
|
||||||
|
&format!("resource:document:{name}"),
|
||||||
|
"document.read",
|
||||||
|
&nonce,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -1347,6 +1418,7 @@ async fn db_sync_from_peer(
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
limit: u32,
|
limit: u32,
|
||||||
|
bearer_secret: Option<String>,
|
||||||
) -> Result<ControlResponse, NodeError> {
|
) -> Result<ControlResponse, NodeError> {
|
||||||
geth_db::validate_db_name(name).map_err(|_| NodeError::InvalidDbName(name.to_owned()))?;
|
geth_db::validate_db_name(name).map_err(|_| NodeError::InvalidDbName(name.to_owned()))?;
|
||||||
let store = Store::open(&node.paths.metadata_db())?;
|
let store = Store::open(&node.paths.metadata_db())?;
|
||||||
|
|
@ -1362,7 +1434,13 @@ async fn db_sync_from_peer(
|
||||||
name: name.to_owned(),
|
name: name.to_owned(),
|
||||||
after_db_version,
|
after_db_version,
|
||||||
limit,
|
limit,
|
||||||
nonce,
|
nonce: nonce.clone(),
|
||||||
|
bearer_proof: bearer_proof(
|
||||||
|
bearer_secret,
|
||||||
|
&format!("resource:db:{name}"),
|
||||||
|
"db.sync",
|
||||||
|
&nonce,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -1585,6 +1663,123 @@ fn can_sync_resource(
|
||||||
.allowed)
|
.allowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bearer_proof(
|
||||||
|
bearer_secret: Option<String>,
|
||||||
|
resource: &str,
|
||||||
|
capability: &str,
|
||||||
|
nonce: &str,
|
||||||
|
) -> Option<BearerProof> {
|
||||||
|
bearer_secret.map(|secret| {
|
||||||
|
let secret = geth_types::SecretId::new(secret);
|
||||||
|
let resource = ResourceId::new(resource.to_owned());
|
||||||
|
let capabilities = vec![Capability::new(capability.to_owned())];
|
||||||
|
let response = geth_secrets::bearer_response(&secret, &resource, &capabilities, nonce);
|
||||||
|
BearerProof {
|
||||||
|
secret,
|
||||||
|
resource,
|
||||||
|
capabilities,
|
||||||
|
nonce: nonce.to_owned(),
|
||||||
|
response,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn explain_peer_or_bearer(
|
||||||
|
store: &Store,
|
||||||
|
peer_node: &str,
|
||||||
|
resource: &str,
|
||||||
|
capability: &str,
|
||||||
|
nonce: &str,
|
||||||
|
bearer_proof: Option<&BearerProof>,
|
||||||
|
) -> Result<AuthExplanation, NodeError> {
|
||||||
|
let ops = load_auth_ops_for_resource(store, resource)?;
|
||||||
|
let resource_id = ResourceId::new(resource.to_owned());
|
||||||
|
let capability_id = Capability::new(capability.to_owned());
|
||||||
|
let peer_explanation = geth_auth::explain_auth_ops(
|
||||||
|
&ops,
|
||||||
|
PrincipalId::new(peer_node.to_owned()),
|
||||||
|
resource_id.clone(),
|
||||||
|
capability_id.clone(),
|
||||||
|
);
|
||||||
|
if peer_explanation.allowed {
|
||||||
|
return Ok(peer_explanation);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(proof) = bearer_proof else {
|
||||||
|
return Ok(peer_explanation);
|
||||||
|
};
|
||||||
|
let bearer_subject = format!("bearer:{}", proof.secret);
|
||||||
|
let denied = |reason: String| AuthExplanation {
|
||||||
|
subject: bearer_subject.clone(),
|
||||||
|
resource: resource.to_owned(),
|
||||||
|
capability: capability.to_owned(),
|
||||||
|
allowed: false,
|
||||||
|
reason,
|
||||||
|
evaluated_ops: peer_explanation.evaluated_ops,
|
||||||
|
};
|
||||||
|
|
||||||
|
if proof.resource != resource_id {
|
||||||
|
return Ok(denied(format!(
|
||||||
|
"bearer proof resource {} does not match requested resource {resource}",
|
||||||
|
proof.resource
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if proof.nonce != nonce {
|
||||||
|
return Ok(denied(
|
||||||
|
"bearer proof nonce does not match request".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !proof
|
||||||
|
.capabilities
|
||||||
|
.iter()
|
||||||
|
.any(|granted| geth_auth::capability_allows(granted, &capability_id))
|
||||||
|
{
|
||||||
|
return Ok(denied(
|
||||||
|
"bearer proof does not include the requested capability".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !geth_secrets::verify_bearer_response(
|
||||||
|
&proof.secret,
|
||||||
|
&proof.resource,
|
||||||
|
&proof.capabilities,
|
||||||
|
nonce,
|
||||||
|
&proof.response,
|
||||||
|
) {
|
||||||
|
return Ok(denied("bearer proof response is invalid".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = UnixMillis(geth_store::now_ms());
|
||||||
|
let Some(access) = load_bearer_access(store)?.into_iter().find(|access| {
|
||||||
|
access.secret == proof.secret
|
||||||
|
&& access.resource == proof.resource
|
||||||
|
&& access.expires_at.is_none_or(|expires| expires.0 > now.0)
|
||||||
|
}) else {
|
||||||
|
return Ok(denied(
|
||||||
|
"bearer access is not active for requested resource".to_owned(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if !access
|
||||||
|
.capabilities
|
||||||
|
.iter()
|
||||||
|
.any(|granted| geth_auth::capability_allows(granted, &capability_id))
|
||||||
|
{
|
||||||
|
return Ok(denied(
|
||||||
|
"bearer access lacks requested capability".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(AuthExplanation {
|
||||||
|
subject: bearer_subject,
|
||||||
|
resource: resource.to_owned(),
|
||||||
|
capability: capability.to_owned(),
|
||||||
|
allowed: true,
|
||||||
|
reason:
|
||||||
|
"bearer proof allows this resource-scoped capability without granting node identity"
|
||||||
|
.to_owned(),
|
||||||
|
evaluated_ops: peer_explanation.evaluated_ops,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn request_peer_control(
|
async fn request_peer_control(
|
||||||
node: &LocalNode,
|
node: &LocalNode,
|
||||||
peer_node: &str,
|
peer_node: &str,
|
||||||
|
|
@ -1754,7 +1949,7 @@ async fn run_live_sync_once(node: &LocalNode) -> Result<(), NodeError> {
|
||||||
"ssh-certs",
|
"ssh-certs",
|
||||||
remote_watermarks.as_ref(),
|
remote_watermarks.as_ref(),
|
||||||
)? {
|
)? {
|
||||||
if let Err(error) = ssh_cert_sync_from_peer(node, &peer.peer_id).await {
|
if let Err(error) = ssh_cert_sync_from_peer(node, &peer.peer_id, None).await {
|
||||||
tracing::debug!(peer = %peer.peer_id, %error, "SSH cert live sync failed");
|
tracing::debug!(peer = %peer.peer_id, %error, "SSH cert live sync failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1764,7 +1959,7 @@ async fn run_live_sync_once(node: &LocalNode) -> Result<(), NodeError> {
|
||||||
"ssh-revocations",
|
"ssh-revocations",
|
||||||
remote_watermarks.as_ref(),
|
remote_watermarks.as_ref(),
|
||||||
)? {
|
)? {
|
||||||
if let Err(error) = ssh_revocation_sync_from_peer(node, &peer.peer_id).await {
|
if let Err(error) = ssh_revocation_sync_from_peer(node, &peer.peer_id, None).await {
|
||||||
tracing::debug!(peer = %peer.peer_id, %error, "SSH revocation live sync failed");
|
tracing::debug!(peer = %peer.peer_id, %error, "SSH revocation live sync failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1772,7 +1967,7 @@ async fn run_live_sync_once(node: &LocalNode) -> Result<(), NodeError> {
|
||||||
let stream = format!("kv:{}", kv.name);
|
let stream = format!("kv:{}", kv.name);
|
||||||
if should_live_sync_stream(&store, &peer.peer_id, &stream, remote_watermarks.as_ref())?
|
if should_live_sync_stream(&store, &peer.peer_id, &stream, remote_watermarks.as_ref())?
|
||||||
{
|
{
|
||||||
if let Err(error) = kv_sync_from_peer(node, &peer.peer_id, &kv.name).await {
|
if let Err(error) = kv_sync_from_peer(node, &peer.peer_id, &kv.name, None).await {
|
||||||
tracing::debug!(peer = %peer.peer_id, kv = %kv.name, %error, "KV live sync failed");
|
tracing::debug!(peer = %peer.peer_id, kv = %kv.name, %error, "KV live sync failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1782,7 +1977,7 @@ async fn run_live_sync_once(node: &LocalNode) -> Result<(), NodeError> {
|
||||||
if should_live_sync_stream(&store, &peer.peer_id, &stream, remote_watermarks.as_ref())?
|
if should_live_sync_stream(&store, &peer.peer_id, &stream, remote_watermarks.as_ref())?
|
||||||
{
|
{
|
||||||
if let Err(error) =
|
if let Err(error) =
|
||||||
document_sync_from_peer(node, &peer.peer_id, &document.name).await
|
document_sync_from_peer(node, &peer.peer_id, &document.name, None).await
|
||||||
{
|
{
|
||||||
tracing::debug!(peer = %peer.peer_id, document = %document.name, %error, "document live sync failed");
|
tracing::debug!(peer = %peer.peer_id, document = %document.name, %error, "document live sync failed");
|
||||||
}
|
}
|
||||||
|
|
@ -1792,7 +1987,9 @@ async fn run_live_sync_once(node: &LocalNode) -> Result<(), NodeError> {
|
||||||
let stream = format!("db:{}", db.name);
|
let stream = format!("db:{}", db.name);
|
||||||
if should_live_sync_stream(&store, &peer.peer_id, &stream, remote_watermarks.as_ref())?
|
if should_live_sync_stream(&store, &peer.peer_id, &stream, remote_watermarks.as_ref())?
|
||||||
{
|
{
|
||||||
if let Err(error) = db_sync_from_peer(node, &peer.peer_id, &db.name, 100).await {
|
if let Err(error) =
|
||||||
|
db_sync_from_peer(node, &peer.peer_id, &db.name, 100, None).await
|
||||||
|
{
|
||||||
tracing::debug!(peer = %peer.peer_id, db = %db.name, %error, "DB live sync failed");
|
tracing::debug!(peer = %peer.peer_id, db = %db.name, %error, "DB live sync failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1919,6 +2116,7 @@ async fn handle_iroh_control_connection(
|
||||||
peer_card,
|
peer_card,
|
||||||
hash,
|
hash,
|
||||||
nonce,
|
nonce,
|
||||||
|
bearer_proof,
|
||||||
} => {
|
} => {
|
||||||
peer_card.validate_candidate()?;
|
peer_card.validate_candidate()?;
|
||||||
ensure_peer_card_matches_endpoint(&peer_card, &remote_endpoint_id)?;
|
ensure_peer_card_matches_endpoint(&peer_card, &remote_endpoint_id)?;
|
||||||
|
|
@ -1935,12 +2133,14 @@ async fn handle_iroh_control_connection(
|
||||||
})?;
|
})?;
|
||||||
let resource = "resource:cas:local".to_owned();
|
let resource = "resource:cas:local".to_owned();
|
||||||
let capability = "cas.fetch".to_owned();
|
let capability = "cas.fetch".to_owned();
|
||||||
let explanation = geth_auth::explain_auth_ops(
|
let explanation = explain_peer_or_bearer(
|
||||||
&load_auth_ops_for_resource(&store, &resource)?,
|
&store,
|
||||||
PrincipalId::new(peer_card.node_id.to_string()),
|
peer_card.node_id.as_str(),
|
||||||
ResourceId::new(resource),
|
&resource,
|
||||||
Capability::new(capability),
|
&capability,
|
||||||
);
|
&nonce,
|
||||||
|
bearer_proof.as_ref(),
|
||||||
|
)?;
|
||||||
if explanation.allowed {
|
if explanation.allowed {
|
||||||
match LocalCas::new(node.paths.cas_dir()).read_bytes(&hash) {
|
match LocalCas::new(node.paths.cas_dir()).read_bytes(&hash) {
|
||||||
Ok(content) => {
|
Ok(content) => {
|
||||||
|
|
@ -1987,6 +2187,7 @@ async fn handle_iroh_control_connection(
|
||||||
peer_card,
|
peer_card,
|
||||||
since_ms,
|
since_ms,
|
||||||
nonce,
|
nonce,
|
||||||
|
bearer_proof,
|
||||||
} => {
|
} => {
|
||||||
peer_card.validate_candidate()?;
|
peer_card.validate_candidate()?;
|
||||||
ensure_peer_card_matches_endpoint(&peer_card, &remote_endpoint_id)?;
|
ensure_peer_card_matches_endpoint(&peer_card, &remote_endpoint_id)?;
|
||||||
|
|
@ -2004,12 +2205,14 @@ async fn handle_iroh_control_connection(
|
||||||
let resource = "resource:ssh:certs".to_owned();
|
let resource = "resource:ssh:certs".to_owned();
|
||||||
let capability = "ssh_cert.sync".to_owned();
|
let capability = "ssh_cert.sync".to_owned();
|
||||||
let high_water_ms = geth_store::now_ms();
|
let high_water_ms = geth_store::now_ms();
|
||||||
let explanation = geth_auth::explain_auth_ops(
|
let explanation = explain_peer_or_bearer(
|
||||||
&load_auth_ops_for_resource(&store, &resource)?,
|
&store,
|
||||||
PrincipalId::new(peer_card.node_id.to_string()),
|
peer_card.node_id.as_str(),
|
||||||
ResourceId::new(resource),
|
&resource,
|
||||||
Capability::new(capability),
|
&capability,
|
||||||
);
|
&nonce,
|
||||||
|
bearer_proof.as_ref(),
|
||||||
|
)?;
|
||||||
let (requests, certificates) = if explanation.allowed {
|
let (requests, certificates) = if explanation.allowed {
|
||||||
(
|
(
|
||||||
store
|
store
|
||||||
|
|
@ -2045,6 +2248,7 @@ async fn handle_iroh_control_connection(
|
||||||
peer_card,
|
peer_card,
|
||||||
since_ms,
|
since_ms,
|
||||||
nonce,
|
nonce,
|
||||||
|
bearer_proof,
|
||||||
} => {
|
} => {
|
||||||
peer_card.validate_candidate()?;
|
peer_card.validate_candidate()?;
|
||||||
ensure_peer_card_matches_endpoint(&peer_card, &remote_endpoint_id)?;
|
ensure_peer_card_matches_endpoint(&peer_card, &remote_endpoint_id)?;
|
||||||
|
|
@ -2062,12 +2266,14 @@ async fn handle_iroh_control_connection(
|
||||||
let resource = "resource:ssh:revocations".to_owned();
|
let resource = "resource:ssh:revocations".to_owned();
|
||||||
let capability = "ssh_revocation.sync".to_owned();
|
let capability = "ssh_revocation.sync".to_owned();
|
||||||
let high_water_ms = geth_store::now_ms();
|
let high_water_ms = geth_store::now_ms();
|
||||||
let explanation = geth_auth::explain_auth_ops(
|
let explanation = explain_peer_or_bearer(
|
||||||
&load_auth_ops_for_resource(&store, &resource)?,
|
&store,
|
||||||
PrincipalId::new(peer_card.node_id.to_string()),
|
peer_card.node_id.as_str(),
|
||||||
ResourceId::new(resource),
|
&resource,
|
||||||
Capability::new(capability),
|
&capability,
|
||||||
);
|
&nonce,
|
||||||
|
bearer_proof.as_ref(),
|
||||||
|
)?;
|
||||||
let revocations = if explanation.allowed {
|
let revocations = if explanation.allowed {
|
||||||
store
|
store
|
||||||
.list_ssh_revocations_since(since_ms)?
|
.list_ssh_revocations_since(since_ms)?
|
||||||
|
|
@ -2096,6 +2302,7 @@ async fn handle_iroh_control_connection(
|
||||||
name,
|
name,
|
||||||
since_ms,
|
since_ms,
|
||||||
nonce,
|
nonce,
|
||||||
|
bearer_proof,
|
||||||
} => {
|
} => {
|
||||||
geth_kv::validate_kv_name(&name).map_err(|_| NodeError::InvalidKvName(name.clone()))?;
|
geth_kv::validate_kv_name(&name).map_err(|_| NodeError::InvalidKvName(name.clone()))?;
|
||||||
peer_card.validate_candidate()?;
|
peer_card.validate_candidate()?;
|
||||||
|
|
@ -2114,12 +2321,14 @@ async fn handle_iroh_control_connection(
|
||||||
if let Some(kv) = store.get_kv_store_by_name(&name)? {
|
if let Some(kv) = store.get_kv_store_by_name(&name)? {
|
||||||
let capability = "kv.read".to_owned();
|
let capability = "kv.read".to_owned();
|
||||||
let high_water_ms = geth_store::now_ms();
|
let high_water_ms = geth_store::now_ms();
|
||||||
let explanation = geth_auth::explain_auth_ops(
|
let explanation = explain_peer_or_bearer(
|
||||||
&load_auth_ops_for_resource(&store, &kv.resource_id)?,
|
&store,
|
||||||
PrincipalId::new(peer_card.node_id.to_string()),
|
peer_card.node_id.as_str(),
|
||||||
ResourceId::new(kv.resource_id.clone()),
|
&kv.resource_id,
|
||||||
Capability::new(capability),
|
&capability,
|
||||||
);
|
&nonce,
|
||||||
|
bearer_proof.as_ref(),
|
||||||
|
)?;
|
||||||
let entries = if explanation.allowed {
|
let entries = if explanation.allowed {
|
||||||
store
|
store
|
||||||
.list_kv_entries_since(&kv.kv_id, since_ms)?
|
.list_kv_entries_since(&kv.kv_id, since_ms)?
|
||||||
|
|
@ -2158,6 +2367,7 @@ async fn handle_iroh_control_connection(
|
||||||
topic,
|
topic,
|
||||||
message,
|
message,
|
||||||
nonce,
|
nonce,
|
||||||
|
bearer_proof,
|
||||||
} => {
|
} => {
|
||||||
geth_pubsub::validate_topic(&topic)?;
|
geth_pubsub::validate_topic(&topic)?;
|
||||||
geth_pubsub::validate_message(&message)?;
|
geth_pubsub::validate_message(&message)?;
|
||||||
|
|
@ -2176,12 +2386,14 @@ async fn handle_iroh_control_connection(
|
||||||
})?;
|
})?;
|
||||||
let resource = format!("resource:pubsub:{topic}");
|
let resource = format!("resource:pubsub:{topic}");
|
||||||
let capability = "pubsub.publish".to_owned();
|
let capability = "pubsub.publish".to_owned();
|
||||||
let explanation = geth_auth::explain_auth_ops(
|
let explanation = explain_peer_or_bearer(
|
||||||
&load_auth_ops_for_resource(&store, &resource)?,
|
&store,
|
||||||
PrincipalId::new(peer_card.node_id.to_string()),
|
peer_card.node_id.as_str(),
|
||||||
ResourceId::new(resource),
|
&resource,
|
||||||
Capability::new(capability),
|
&capability,
|
||||||
);
|
&nonce,
|
||||||
|
bearer_proof.as_ref(),
|
||||||
|
)?;
|
||||||
let published = if explanation.allowed {
|
let published = if explanation.allowed {
|
||||||
Some(record_pubsub_message(&node, topic, message)?)
|
Some(record_pubsub_message(&node, topic, message)?)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2204,6 +2416,7 @@ async fn handle_iroh_control_connection(
|
||||||
peer_card,
|
peer_card,
|
||||||
topic,
|
topic,
|
||||||
nonce,
|
nonce,
|
||||||
|
bearer_proof,
|
||||||
} => {
|
} => {
|
||||||
geth_pubsub::validate_topic(&topic)?;
|
geth_pubsub::validate_topic(&topic)?;
|
||||||
peer_card.validate_candidate()?;
|
peer_card.validate_candidate()?;
|
||||||
|
|
@ -2221,12 +2434,14 @@ async fn handle_iroh_control_connection(
|
||||||
})?;
|
})?;
|
||||||
let resource = format!("resource:pubsub:{topic}");
|
let resource = format!("resource:pubsub:{topic}");
|
||||||
let capability = "pubsub.subscribe".to_owned();
|
let capability = "pubsub.subscribe".to_owned();
|
||||||
let explanation = geth_auth::explain_auth_ops(
|
let explanation = explain_peer_or_bearer(
|
||||||
&load_auth_ops_for_resource(&store, &resource)?,
|
&store,
|
||||||
PrincipalId::new(peer_card.node_id.to_string()),
|
peer_card.node_id.as_str(),
|
||||||
ResourceId::new(resource),
|
&resource,
|
||||||
Capability::new(capability),
|
&capability,
|
||||||
);
|
&nonce,
|
||||||
|
bearer_proof.as_ref(),
|
||||||
|
)?;
|
||||||
let messages = if explanation.allowed {
|
let messages = if explanation.allowed {
|
||||||
pubsub_messages_for_topic(&node, &topic)?
|
pubsub_messages_for_topic(&node, &topic)?
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2250,6 +2465,7 @@ async fn handle_iroh_control_connection(
|
||||||
peer_card,
|
peer_card,
|
||||||
target,
|
target,
|
||||||
nonce,
|
nonce,
|
||||||
|
bearer_proof,
|
||||||
} => {
|
} => {
|
||||||
geth_pipe::validate_pipe_name(&target)?;
|
geth_pipe::validate_pipe_name(&target)?;
|
||||||
peer_card.validate_candidate()?;
|
peer_card.validate_candidate()?;
|
||||||
|
|
@ -2267,12 +2483,14 @@ async fn handle_iroh_control_connection(
|
||||||
})?;
|
})?;
|
||||||
let resource = format!("resource:pipe:{target}");
|
let resource = format!("resource:pipe:{target}");
|
||||||
let capability = "pipe.connect".to_owned();
|
let capability = "pipe.connect".to_owned();
|
||||||
let explanation = geth_auth::explain_auth_ops(
|
let explanation = explain_peer_or_bearer(
|
||||||
&load_auth_ops_for_resource(&store, &resource)?,
|
&store,
|
||||||
PrincipalId::new(peer_card.node_id.to_string()),
|
peer_card.node_id.as_str(),
|
||||||
ResourceId::new(resource),
|
&resource,
|
||||||
Capability::new(capability),
|
&capability,
|
||||||
);
|
&nonce,
|
||||||
|
bearer_proof.as_ref(),
|
||||||
|
)?;
|
||||||
let connection = if explanation.allowed {
|
let connection = if explanation.allowed {
|
||||||
Some(record_pipe_connection(
|
Some(record_pipe_connection(
|
||||||
&node,
|
&node,
|
||||||
|
|
@ -2295,7 +2513,11 @@ async fn handle_iroh_control_connection(
|
||||||
note: "pipe connect authenticated endpoint/card binding and required pipe.connect on the remote pipe resource; byte streams are not implemented yet".to_owned(),
|
note: "pipe connect authenticated endpoint/card binding and required pipe.connect on the remote pipe resource; byte streams are not implemented yet".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PeerControlRequest::SshProxyConnect { peer_card, nonce } => {
|
PeerControlRequest::SshProxyConnect {
|
||||||
|
peer_card,
|
||||||
|
nonce,
|
||||||
|
bearer_proof,
|
||||||
|
} => {
|
||||||
peer_card.validate_candidate()?;
|
peer_card.validate_candidate()?;
|
||||||
ensure_peer_card_matches_endpoint(&peer_card, &remote_endpoint_id)?;
|
ensure_peer_card_matches_endpoint(&peer_card, &remote_endpoint_id)?;
|
||||||
let discovered = DiscoveredPeer::candidate(
|
let discovered = DiscoveredPeer::candidate(
|
||||||
|
|
@ -2311,12 +2533,14 @@ async fn handle_iroh_control_connection(
|
||||||
})?;
|
})?;
|
||||||
let resource = "resource:ssh-proxy:local".to_owned();
|
let resource = "resource:ssh-proxy:local".to_owned();
|
||||||
let capability = "ssh_proxy.connect".to_owned();
|
let capability = "ssh_proxy.connect".to_owned();
|
||||||
let explanation = geth_auth::explain_auth_ops(
|
let explanation = explain_peer_or_bearer(
|
||||||
&load_auth_ops_for_resource(&store, &resource)?,
|
&store,
|
||||||
PrincipalId::new(peer_card.node_id.to_string()),
|
peer_card.node_id.as_str(),
|
||||||
ResourceId::new(resource),
|
&resource,
|
||||||
Capability::new(capability),
|
&capability,
|
||||||
);
|
&nonce,
|
||||||
|
bearer_proof.as_ref(),
|
||||||
|
)?;
|
||||||
let connection = if explanation.allowed {
|
let connection = if explanation.allowed {
|
||||||
Some(SshProxyConnection {
|
Some(SshProxyConnection {
|
||||||
target_node: NodeId::new(node.node_id.clone()),
|
target_node: NodeId::new(node.node_id.clone()),
|
||||||
|
|
@ -2346,6 +2570,7 @@ async fn handle_iroh_control_connection(
|
||||||
name,
|
name,
|
||||||
since_ms,
|
since_ms,
|
||||||
nonce,
|
nonce,
|
||||||
|
bearer_proof,
|
||||||
} => {
|
} => {
|
||||||
geth_document::validate_document_name(&name)
|
geth_document::validate_document_name(&name)
|
||||||
.map_err(|_| NodeError::InvalidDocumentName(name.clone()))?;
|
.map_err(|_| NodeError::InvalidDocumentName(name.clone()))?;
|
||||||
|
|
@ -2365,12 +2590,14 @@ async fn handle_iroh_control_connection(
|
||||||
if let Some(document) = store.get_document_resource_by_name(&name)? {
|
if let Some(document) = store.get_document_resource_by_name(&name)? {
|
||||||
let capability = "document.read".to_owned();
|
let capability = "document.read".to_owned();
|
||||||
let high_water_ms = geth_store::now_ms();
|
let high_water_ms = geth_store::now_ms();
|
||||||
let explanation = geth_auth::explain_auth_ops(
|
let explanation = explain_peer_or_bearer(
|
||||||
&load_auth_ops_for_resource(&store, &document.resource_id)?,
|
&store,
|
||||||
PrincipalId::new(peer_card.node_id.to_string()),
|
peer_card.node_id.as_str(),
|
||||||
ResourceId::new(document.resource_id.clone()),
|
&document.resource_id,
|
||||||
Capability::new(capability),
|
&capability,
|
||||||
);
|
&nonce,
|
||||||
|
bearer_proof.as_ref(),
|
||||||
|
)?;
|
||||||
let state = if explanation.allowed && document.updated_at_ms >= since_ms {
|
let state = if explanation.allowed && document.updated_at_ms >= since_ms {
|
||||||
Some(document_state_from_stored(&document))
|
Some(document_state_from_stored(&document))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2402,6 +2629,7 @@ async fn handle_iroh_control_connection(
|
||||||
after_db_version,
|
after_db_version,
|
||||||
limit,
|
limit,
|
||||||
nonce,
|
nonce,
|
||||||
|
bearer_proof,
|
||||||
} => {
|
} => {
|
||||||
geth_db::validate_db_name(&name).map_err(|_| NodeError::InvalidDbName(name.clone()))?;
|
geth_db::validate_db_name(&name).map_err(|_| NodeError::InvalidDbName(name.clone()))?;
|
||||||
peer_card.validate_candidate()?;
|
peer_card.validate_candidate()?;
|
||||||
|
|
@ -2419,12 +2647,14 @@ async fn handle_iroh_control_connection(
|
||||||
})?;
|
})?;
|
||||||
if let Some(db) = store.get_db_resource_by_name(&name)? {
|
if let Some(db) = store.get_db_resource_by_name(&name)? {
|
||||||
let capability = "db.sync".to_owned();
|
let capability = "db.sync".to_owned();
|
||||||
let explanation = geth_auth::explain_auth_ops(
|
let explanation = explain_peer_or_bearer(
|
||||||
&load_auth_ops_for_resource(&store, &db.resource_id)?,
|
&store,
|
||||||
PrincipalId::new(peer_card.node_id.to_string()),
|
peer_card.node_id.as_str(),
|
||||||
ResourceId::new(db.resource_id.clone()),
|
&db.resource_id,
|
||||||
Capability::new(capability),
|
&capability,
|
||||||
);
|
&nonce,
|
||||||
|
bearer_proof.as_ref(),
|
||||||
|
)?;
|
||||||
let sync_result: Result<_, String> = if explanation.allowed {
|
let sync_result: Result<_, String> = if explanation.allowed {
|
||||||
let path = Path::new(&db.path);
|
let path = Path::new(&db.path);
|
||||||
match geth_db::extract_crsqlite_changes(path, after_db_version, limit) {
|
match geth_db::extract_crsqlite_changes(path, after_db_version, limit) {
|
||||||
|
|
@ -3731,6 +3961,7 @@ pub fn handle_request(
|
||||||
topic,
|
topic,
|
||||||
message,
|
message,
|
||||||
node: None,
|
node: None,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
geth_pubsub::validate_topic(&topic)?;
|
geth_pubsub::validate_topic(&topic)?;
|
||||||
geth_pubsub::validate_message(&message)?;
|
geth_pubsub::validate_message(&message)?;
|
||||||
|
|
@ -3738,7 +3969,9 @@ pub fn handle_request(
|
||||||
Ok(ControlResponse::PubsubPublished { message })
|
Ok(ControlResponse::PubsubPublished { message })
|
||||||
}
|
}
|
||||||
ControlRequest::PubsubPub { node: Some(_), .. } => Err(NodeError::IrohEndpointUnavailable),
|
ControlRequest::PubsubPub { node: Some(_), .. } => Err(NodeError::IrohEndpointUnavailable),
|
||||||
ControlRequest::PubsubSub { topic, node: None } => {
|
ControlRequest::PubsubSub {
|
||||||
|
topic, node: None, ..
|
||||||
|
} => {
|
||||||
geth_pubsub::validate_topic(&topic)?;
|
geth_pubsub::validate_topic(&topic)?;
|
||||||
let messages = pubsub_messages_for_topic(node, &topic)?;
|
let messages = pubsub_messages_for_topic(node, &topic)?;
|
||||||
Ok(ControlResponse::PubsubMessages {
|
Ok(ControlResponse::PubsubMessages {
|
||||||
|
|
@ -3764,7 +3997,9 @@ pub fn handle_request(
|
||||||
runtime.listeners.insert(name, listener.clone());
|
runtime.listeners.insert(name, listener.clone());
|
||||||
Ok(ControlResponse::PipeListening { listener })
|
Ok(ControlResponse::PipeListening { listener })
|
||||||
}
|
}
|
||||||
ControlRequest::PipeConnect { target, node: None } => {
|
ControlRequest::PipeConnect {
|
||||||
|
target, node: None, ..
|
||||||
|
} => {
|
||||||
geth_pipe::validate_pipe_name(&target)?;
|
geth_pipe::validate_pipe_name(&target)?;
|
||||||
let connection = record_pipe_connection(
|
let connection = record_pipe_connection(
|
||||||
node,
|
node,
|
||||||
|
|
@ -4899,6 +5134,7 @@ mod tests {
|
||||||
ControlRequest::CasFetch {
|
ControlRequest::CasFetch {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
hash: right_blob.hash.clone(),
|
hash: right_blob.hash.clone(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -4927,6 +5163,7 @@ mod tests {
|
||||||
ControlRequest::KvSync {
|
ControlRequest::KvSync {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
name: "prefs".to_owned(),
|
name: "prefs".to_owned(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -4951,6 +5188,7 @@ mod tests {
|
||||||
topic: "presence/test".to_owned(),
|
topic: "presence/test".to_owned(),
|
||||||
message: "hello".to_owned(),
|
message: "hello".to_owned(),
|
||||||
node: Some(right_card.node_id.to_string()),
|
node: Some(right_card.node_id.to_string()),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -4974,6 +5212,7 @@ mod tests {
|
||||||
ControlRequest::PubsubSub {
|
ControlRequest::PubsubSub {
|
||||||
topic: "presence/test".to_owned(),
|
topic: "presence/test".to_owned(),
|
||||||
node: Some(right_card.node_id.to_string()),
|
node: Some(right_card.node_id.to_string()),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -4997,6 +5236,7 @@ mod tests {
|
||||||
ControlRequest::PipeConnect {
|
ControlRequest::PipeConnect {
|
||||||
target: "inbox".to_owned(),
|
target: "inbox".to_owned(),
|
||||||
node: Some(right_card.node_id.to_string()),
|
node: Some(right_card.node_id.to_string()),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5019,6 +5259,7 @@ mod tests {
|
||||||
&left,
|
&left,
|
||||||
ControlRequest::SshProxyConnect {
|
ControlRequest::SshProxyConnect {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5042,6 +5283,7 @@ mod tests {
|
||||||
ControlRequest::DocumentSync {
|
ControlRequest::DocumentSync {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
name: "notes".to_owned(),
|
name: "notes".to_owned(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5066,6 +5308,7 @@ mod tests {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
name: "notes".to_owned(),
|
name: "notes".to_owned(),
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5088,6 +5331,45 @@ mod tests {
|
||||||
other => panic!("unexpected denied DB sync response: {other:?}"),
|
other => panic!("unexpected denied DB sync response: {other:?}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let bearer_secret = match handle_request(
|
||||||
|
&right,
|
||||||
|
ControlRequest::SecretBearerCreate {
|
||||||
|
resource: "resource:cas:local".to_owned(),
|
||||||
|
capabilities: vec!["cas.fetch".to_owned()],
|
||||||
|
expires_at_ms: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("create remote CAS bearer access")
|
||||||
|
{
|
||||||
|
ControlResponse::SecretBearerCreated { access } => access.secret.to_string(),
|
||||||
|
other => panic!("unexpected bearer create response: {other:?}"),
|
||||||
|
};
|
||||||
|
let bearer_fetch = handle_request_async(
|
||||||
|
&left,
|
||||||
|
ControlRequest::CasFetch {
|
||||||
|
node: right_card.node_id.to_string(),
|
||||||
|
hash: right_blob.hash.clone(),
|
||||||
|
bearer_secret: Some(bearer_secret),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("bearer cas fetch");
|
||||||
|
match bearer_fetch {
|
||||||
|
ControlResponse::CasFetched {
|
||||||
|
allowed,
|
||||||
|
hash,
|
||||||
|
size_bytes,
|
||||||
|
reason,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
assert!(allowed);
|
||||||
|
assert_eq!(hash, right_blob.hash);
|
||||||
|
assert_eq!(size_bytes, right_blob.size_bytes);
|
||||||
|
assert!(reason.contains("bearer proof"));
|
||||||
|
}
|
||||||
|
other => panic!("unexpected bearer CAS fetch response: {other:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
handle_request(
|
handle_request(
|
||||||
&right,
|
&right,
|
||||||
ControlRequest::AuthGrant {
|
ControlRequest::AuthGrant {
|
||||||
|
|
@ -5198,6 +5480,7 @@ mod tests {
|
||||||
ControlRequest::CasFetch {
|
ControlRequest::CasFetch {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
hash: right_blob.hash.clone(),
|
hash: right_blob.hash.clone(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5237,6 +5520,7 @@ mod tests {
|
||||||
ControlRequest::KvSync {
|
ControlRequest::KvSync {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
name: "prefs".to_owned(),
|
name: "prefs".to_owned(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5277,6 +5561,7 @@ mod tests {
|
||||||
topic: "presence/test".to_owned(),
|
topic: "presence/test".to_owned(),
|
||||||
message: "hello".to_owned(),
|
message: "hello".to_owned(),
|
||||||
node: Some(right_card.node_id.to_string()),
|
node: Some(right_card.node_id.to_string()),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5302,6 +5587,7 @@ mod tests {
|
||||||
ControlRequest::PubsubSub {
|
ControlRequest::PubsubSub {
|
||||||
topic: "presence/test".to_owned(),
|
topic: "presence/test".to_owned(),
|
||||||
node: None,
|
node: None,
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("right pubsub sub after remote publish");
|
.expect("right pubsub sub after remote publish");
|
||||||
|
|
@ -5318,6 +5604,7 @@ mod tests {
|
||||||
ControlRequest::PubsubSub {
|
ControlRequest::PubsubSub {
|
||||||
topic: "presence/test".to_owned(),
|
topic: "presence/test".to_owned(),
|
||||||
node: Some(right_card.node_id.to_string()),
|
node: Some(right_card.node_id.to_string()),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5344,6 +5631,7 @@ mod tests {
|
||||||
ControlRequest::PipeConnect {
|
ControlRequest::PipeConnect {
|
||||||
target: "inbox".to_owned(),
|
target: "inbox".to_owned(),
|
||||||
node: Some(right_card.node_id.to_string()),
|
node: Some(right_card.node_id.to_string()),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5368,6 +5656,7 @@ mod tests {
|
||||||
&left,
|
&left,
|
||||||
ControlRequest::SshProxyConnect {
|
ControlRequest::SshProxyConnect {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5398,6 +5687,7 @@ mod tests {
|
||||||
ControlRequest::DocumentSync {
|
ControlRequest::DocumentSync {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
name: "notes".to_owned(),
|
name: "notes".to_owned(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5437,6 +5727,7 @@ mod tests {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
name: "notes".to_owned(),
|
name: "notes".to_owned(),
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5472,6 +5763,7 @@ mod tests {
|
||||||
&left,
|
&left,
|
||||||
ControlRequest::SshCertSync {
|
ControlRequest::SshCertSync {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5517,6 +5809,7 @@ mod tests {
|
||||||
&left,
|
&left,
|
||||||
ControlRequest::SshCertSync {
|
ControlRequest::SshCertSync {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
@ -5552,6 +5845,7 @@ mod tests {
|
||||||
&left,
|
&left,
|
||||||
ControlRequest::SshRevocationSync {
|
ControlRequest::SshRevocationSync {
|
||||||
node: right_card.node_id.to_string(),
|
node: right_card.node_id.to_string(),
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -1368,6 +1368,7 @@ fn pubsub_pub_sub_uses_lossy_in_memory_runtime() {
|
||||||
topic: "presence/laptop".to_owned(),
|
topic: "presence/laptop".to_owned(),
|
||||||
message: "online".to_owned(),
|
message: "online".to_owned(),
|
||||||
node: None,
|
node: None,
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("publish");
|
.expect("publish");
|
||||||
|
|
@ -1384,6 +1385,7 @@ fn pubsub_pub_sub_uses_lossy_in_memory_runtime() {
|
||||||
geth_control::ControlRequest::PubsubSub {
|
geth_control::ControlRequest::PubsubSub {
|
||||||
topic: "presence/laptop".to_owned(),
|
topic: "presence/laptop".to_owned(),
|
||||||
node: None,
|
node: None,
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("subscribe snapshot");
|
.expect("subscribe snapshot");
|
||||||
|
|
@ -1407,6 +1409,7 @@ fn pubsub_pub_sub_uses_lossy_in_memory_runtime() {
|
||||||
geth_control::ControlRequest::PubsubSub {
|
geth_control::ControlRequest::PubsubSub {
|
||||||
topic: "presence/laptop".to_owned(),
|
topic: "presence/laptop".to_owned(),
|
||||||
node: None,
|
node: None,
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("subscribe reopened snapshot");
|
.expect("subscribe reopened snapshot");
|
||||||
|
|
@ -1424,6 +1427,7 @@ fn pubsub_pub_sub_uses_lossy_in_memory_runtime() {
|
||||||
topic: "presence/laptop".to_owned(),
|
topic: "presence/laptop".to_owned(),
|
||||||
message: String::new(),
|
message: String::new(),
|
||||||
node: None,
|
node: None,
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
|
|
@ -1457,6 +1461,7 @@ fn pipe_listen_connect_uses_local_runtime_registry() {
|
||||||
geth_control::ControlRequest::PipeConnect {
|
geth_control::ControlRequest::PipeConnect {
|
||||||
target: "inbox".to_owned(),
|
target: "inbox".to_owned(),
|
||||||
node: None,
|
node: None,
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("connect pipe");
|
.expect("connect pipe");
|
||||||
|
|
@ -1479,6 +1484,7 @@ fn pipe_listen_connect_uses_local_runtime_registry() {
|
||||||
geth_control::ControlRequest::PipeConnect {
|
geth_control::ControlRequest::PipeConnect {
|
||||||
target: "inbox".to_owned(),
|
target: "inbox".to_owned(),
|
||||||
node: None,
|
node: None,
|
||||||
|
bearer_secret: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("connect after reopen");
|
.expect("connect after reopen");
|
||||||
|
|
|
||||||
|
|
@ -271,8 +271,11 @@ as resource-scoped auth operations and rejects trust-mutation capabilities such
|
||||||
as `auth.delegate`, `auth.revoke`, and `node.enroll`. Bearer challenge/proof
|
as `auth.delegate`, `auth.revoke`, and `node.enroll`. Bearer challenge/proof
|
||||||
commands derive deterministic BLAKE3 keyed responses from the bearer secret,
|
commands derive deterministic BLAKE3 keyed responses from the bearer secret,
|
||||||
resource, nonce, and requested capabilities, then verify them against active
|
resource, nonce, and requested capabilities, then verify them against active
|
||||||
resource-scoped bearer grants. The daemon does not yet store payload key
|
resource-scoped bearer grants. Remote resource operations can carry optional
|
||||||
material, encrypt resource data, or distribute key envelopes.
|
bearer proofs over the protected Iroh control path; a valid proof authorizes
|
||||||
|
only the requested resource capability and does not create node trust. The daemon
|
||||||
|
does not yet store payload key material, encrypt resource data, distribute key
|
||||||
|
envelopes, or separate public bearer ids from private bearer tokens.
|
||||||
|
|
||||||
## Multi-User Direction
|
## Multi-User Direction
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,8 +223,12 @@ resource-scoped capability decisions.
|
||||||
- `[x]` `geth secret bearer challenge/prove/verify` exercises
|
- `[x]` `geth secret bearer challenge/prove/verify` exercises
|
||||||
resource-scoped bearer challenge-response proofs.
|
resource-scoped bearer challenge-response proofs.
|
||||||
- `[x]` Tests verify valid bearer proofs and capability-scoped proof denial.
|
- `[x]` Tests verify valid bearer proofs and capability-scoped proof denial.
|
||||||
- `[ ]` Future completion wires bearer proof verification into remote module
|
- `[x]` Remote module authorization paths accept optional bearer proofs for
|
||||||
authorization paths.
|
the requested resource capability without granting node identity.
|
||||||
|
- `[x]` Tests verify remote CAS fetch succeeds through a bearer proof before
|
||||||
|
the caller has a node grant.
|
||||||
|
- `[ ]` Future completion avoids sending bearer secret identifiers as proof
|
||||||
|
material by separating public bearer ids from private bearer tokens.
|
||||||
|
|
||||||
- `[~]` SSH certificate and revocation lifecycle.
|
- `[~]` SSH certificate and revocation lifecycle.
|
||||||
Acceptance criteria:
|
Acceptance criteria:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue