Skip to content

Commit 60025cd

Browse files
committed
runtime-sdk: Add provider-related endorsement constraints
1 parent b04dc19 commit 60025cd

14 files changed

Lines changed: 826 additions & 96 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rofl-scheduler/src/manager.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use oasis_runtime_sdk::{
2424
};
2525
use oasis_runtime_sdk_rofl_market::{
2626
self as market,
27+
policy::{ProviderLabel, LABEL_PROVIDER},
2728
types::{Deployment, Instance, InstanceId, InstanceStatus},
2829
};
2930
use rand::Rng;
@@ -999,6 +1000,13 @@ impl Manager {
9991000
deployment_hash(deployment),
10001001
);
10011002

1003+
let provider_label = ProviderLabel {
1004+
provider: self.cfg.provider_address,
1005+
instance: instance.id,
1006+
};
1007+
let provider_label = BASE64_STANDARD.encode(cbor::to_vec(provider_label));
1008+
labels.insert(LABEL_PROVIDER.to_string(), provider_label);
1009+
10021010
let _ = self
10031011
.env
10041012
.host()

runtime-sdk/modules/rofl-market/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ oasis-runtime-sdk-evm = { path = "../evm" }
1313

1414
# Third party.
1515
anyhow = "1.0"
16+
base64 = "0.22.1"
1617
ethabi = { version = "18.0.0", default-features = false, features = ["std"] }
1718
once_cell = "1.8.0"
1819
rustc-hex = "2.0.1"

runtime-sdk/modules/rofl-market/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod config;
1616
mod error;
1717
mod event;
1818
mod payment;
19+
pub mod policy;
1920
pub mod state;
2021
#[cfg(test)]
2122
mod test;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use std::collections::BTreeMap;
2+
3+
use base64::prelude::*;
4+
5+
use oasis_runtime_sdk::{
6+
core::{
7+
common::crypto::signature::{PublicKey, Signature},
8+
consensus::registry::{EndorsedCapabilityTEE, Node},
9+
host::attestation::{LabelAttestation, ATTEST_LABELS_SIGNATURE_CONTEXT},
10+
},
11+
modules::rofl::{
12+
policy::{AllowedEndorsement, EndorsementPolicyEvaluator},
13+
Error,
14+
},
15+
types::address::Address,
16+
Context,
17+
};
18+
19+
use crate::{state, types::InstanceId};
20+
21+
/// Name of the ROFL app instance metadata key used to store the provider attestation.
22+
pub const METADATA_KEY_POLICY_PROVIDER_ATTESTATION: &str = "net.oasis.policy.provider";
23+
/// Name of the provider label set by the scheduler.
24+
pub const LABEL_PROVIDER: &str = "net.oasis.provider";
25+
26+
/// Value of the `LABEL_PROVIDER` label as set by the scheduler.
27+
#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
28+
pub struct ProviderLabel {
29+
/// Address of the provider.
30+
pub provider: Address,
31+
/// Instance identifier.
32+
pub instance: InstanceId,
33+
}
34+
35+
/// Provider attestation metadata stored in `METADATA_KEY_POLICY_PROVIDER_ATTESTATION` label.
36+
#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
37+
pub struct ProviderAttestation {
38+
/// A CBOR-serialized `LabelAttestation`.
39+
pub label_attestation: Vec<u8>,
40+
/// Signature from endorsing node.
41+
pub signature: Signature,
42+
}
43+
44+
/// An endorsement policy evaluator that supports provider-related constraints via lookups
45+
/// in ROFL market module state.
46+
pub struct ProviderEndorsementPolicyEvaluator;
47+
48+
impl EndorsementPolicyEvaluator for ProviderEndorsementPolicyEvaluator {
49+
fn verify_atom<C: Context>(
50+
_ctx: &C,
51+
policy: &AllowedEndorsement,
52+
ect: &EndorsedCapabilityTEE,
53+
endorsing_node_id: PublicKey,
54+
_endorsing_node: &Option<Node>,
55+
metadata: &BTreeMap<String, String>,
56+
) -> Result<(), Error> {
57+
match policy {
58+
AllowedEndorsement::Provider(address) => {
59+
// Check if the endorsing node is endorsed by the given provider.
60+
let provider = state::get_provider(*address).ok_or(Error::NodeNotAllowed)?;
61+
if !provider.nodes.contains(&endorsing_node_id) {
62+
return Err(Error::NodeNotAllowed);
63+
}
64+
Ok(())
65+
}
66+
AllowedEndorsement::ProviderInstanceAdmin(expected_admin) => {
67+
let pa: ProviderAttestation = cbor::from_slice(
68+
&BASE64_STANDARD
69+
.decode(
70+
metadata
71+
.get(METADATA_KEY_POLICY_PROVIDER_ATTESTATION)
72+
.ok_or(Error::NodeNotAllowed)?,
73+
)
74+
.map_err(|_| Error::NodeNotAllowed)?,
75+
)
76+
.map_err(|_| Error::NodeNotAllowed)?;
77+
78+
// Verify node label attestation.
79+
pa.signature
80+
.verify(
81+
&endorsing_node_id,
82+
ATTEST_LABELS_SIGNATURE_CONTEXT,
83+
&pa.label_attestation,
84+
)
85+
.map_err(|_| Error::NodeNotAllowed)?;
86+
let label_attestation: LabelAttestation =
87+
cbor::from_slice(&pa.label_attestation).map_err(|_| Error::NodeNotAllowed)?;
88+
if label_attestation.rak != ect.capability_tee.rak {
89+
return Err(Error::NodeNotAllowed);
90+
}
91+
92+
// Extract provider label (set by the provider's scheduler).
93+
let provider_label: ProviderLabel = cbor::from_slice(
94+
&BASE64_STANDARD
95+
.decode(
96+
label_attestation
97+
.labels
98+
.get(LABEL_PROVIDER)
99+
.ok_or(Error::NodeNotAllowed)?,
100+
)
101+
.map_err(|_| Error::NodeNotAllowed)?,
102+
)
103+
.map_err(|_| Error::NodeNotAllowed)?;
104+
105+
let provider =
106+
state::get_provider(provider_label.provider).ok_or(Error::NodeNotAllowed)?;
107+
if !provider.nodes.contains(&endorsing_node_id) {
108+
return Err(Error::NodeNotAllowed);
109+
}
110+
111+
let instance =
112+
state::get_instance(provider_label.provider, provider_label.instance)
113+
.ok_or(Error::NodeNotAllowed)?;
114+
if &instance.admin != expected_admin {
115+
return Err(Error::NodeNotAllowed);
116+
}
117+
118+
Ok(())
119+
}
120+
_ => Err(Error::NodeNotAllowed),
121+
}
122+
}
123+
}

runtime-sdk/modules/rofl-market/src/test.rs

Lines changed: 173 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
use std::collections::BTreeMap;
22

3+
use anyhow::Context;
4+
use base64::prelude::*;
35
use oasis_runtime_sdk::{
6+
core::{
7+
common::crypto::signature::SignatureBundle,
8+
consensus::registry::EndorsedCapabilityTEE,
9+
host::attestation::{LabelAttestation, ATTEST_LABELS_SIGNATURE_CONTEXT},
10+
},
11+
crypto::signature::Signer,
412
module,
513
modules::{
614
accounts::{self, API as _},
7-
core, rofl,
15+
core,
16+
rofl::{
17+
self,
18+
policy::{BasicEndorsementPolicyEvaluator, EndorsementPolicyEvaluator},
19+
},
820
},
921
testing::{keys, mock},
1022
types::{
@@ -14,7 +26,7 @@ use oasis_runtime_sdk::{
1426
Runtime, Version,
1527
};
1628

17-
use super::{types, ADDRESS_PROVIDER_STAKE_POOL};
29+
use super::{policy, state, types, ADDRESS_PROVIDER_STAKE_POOL};
1830

1931
type Accounts = accounts::Module;
2032
type Core = core::Module<Config>;
@@ -23,7 +35,9 @@ struct Config;
2335

2436
impl core::Config for Config {}
2537

26-
impl rofl::Config for Config {}
38+
impl rofl::Config for Config {
39+
type EndorsementPolicyEvaluator = BasicEndorsementPolicyEvaluator;
40+
}
2741

2842
impl super::Config for Config {
2943
type Rofl = rofl::Module<Config>;
@@ -1032,3 +1046,159 @@ fn test_instance_accept_timeout() {
10321046
let balance = Accounts::get_balance(keys::charlie::address(), Denomination::NATIVE).unwrap();
10331047
assert_eq!(balance, 1_000_000);
10341048
}
1049+
1050+
#[test]
1051+
fn test_endorsement_policy_evaluator() {
1052+
let mut mock = mock::Mock::default();
1053+
let ctx = mock.create_ctx_for_runtime::<TestRuntime>(true);
1054+
1055+
TestRuntime::migrate(&ctx);
1056+
1057+
// Summary of keys used for this test:
1058+
//
1059+
// alice: provider
1060+
// bob: provider's node and scheduler's RAK
1061+
//
1062+
1063+
// Create the scheduler app.
1064+
let create = rofl::types::Create {
1065+
scheme: rofl::types::IdentifierScheme::CreatorNonce,
1066+
..Default::default()
1067+
};
1068+
1069+
let mut signer_alice = mock::Signer::new(0, keys::alice::sigspec());
1070+
let dispatch_result = signer_alice.call(&ctx, "rofl.Create", create);
1071+
assert!(dispatch_result.result.is_success(), "call should succeed");
1072+
let scheduler_app: rofl::app_id::AppId =
1073+
cbor::from_value(dispatch_result.result.unwrap()).unwrap();
1074+
1075+
// Create a provider.
1076+
let create = types::ProviderCreate {
1077+
scheduler_app,
1078+
nodes: vec![keys::bob::pk_ed25519().into()], // Bob seems like a nice node.
1079+
..Default::default()
1080+
};
1081+
1082+
let dispatch_result = signer_alice.call(&ctx, "roflmarket.ProviderCreate", create.clone());
1083+
assert!(dispatch_result.result.is_success(), "call should succeed");
1084+
1085+
// Create a mock instance of the scheduler app.
1086+
let fake_registration = rofl::types::Registration {
1087+
app: scheduler_app,
1088+
node_id: keys::bob::pk_ed25519().into(), // Bob is a nice approved node.
1089+
rak: keys::bob::pk_ed25519().into(), // Bob is also a nice RAK.
1090+
..Default::default()
1091+
};
1092+
rofl::state::update_registration(fake_registration).unwrap();
1093+
1094+
// Create a new accepted instance directly in state.
1095+
let accepted_instance = types::Instance {
1096+
provider: keys::alice::address(),
1097+
status: types::InstanceStatus::Accepted,
1098+
node_id: Some(keys::bob::pk_ed25519().into()),
1099+
admin: keys::charlie::address(),
1100+
..Default::default()
1101+
};
1102+
state::set_instance(accepted_instance.clone());
1103+
1104+
// Construct a composite endorsement policy evaluator.
1105+
type Evaluator = (
1106+
rofl::policy::BasicEndorsementPolicyEvaluator,
1107+
super::policy::ProviderEndorsementPolicyEvaluator,
1108+
);
1109+
1110+
// Mock an endorsed TEE with attested labels.
1111+
let ect = EndorsedCapabilityTEE {
1112+
node_endorsement: SignatureBundle {
1113+
public_key: keys::bob::pk_ed25519().into(),
1114+
..Default::default()
1115+
},
1116+
..Default::default()
1117+
};
1118+
let provider_label = policy::ProviderLabel {
1119+
provider: keys::alice::address(),
1120+
instance: accepted_instance.id,
1121+
};
1122+
let label_attestation = cbor::to_vec(LabelAttestation {
1123+
labels: BTreeMap::from([(
1124+
policy::LABEL_PROVIDER.to_string(),
1125+
BASE64_STANDARD.encode(cbor::to_vec(provider_label)),
1126+
)]),
1127+
rak: ect.capability_tee.rak,
1128+
});
1129+
let signature = keys::bob::signer()
1130+
.sign(ATTEST_LABELS_SIGNATURE_CONTEXT, &label_attestation)
1131+
.unwrap();
1132+
let provider_attestation = policy::ProviderAttestation {
1133+
label_attestation,
1134+
signature: signature.into(),
1135+
};
1136+
let metadata = BTreeMap::from([(
1137+
policy::METADATA_KEY_POLICY_PROVIDER_ATTESTATION.to_string(),
1138+
BASE64_STANDARD.encode(cbor::to_vec(provider_attestation)),
1139+
)]);
1140+
1141+
let tcs = [
1142+
(
1143+
vec![Box::new(rofl::policy::AllowedEndorsement::Provider(
1144+
keys::alice::address(),
1145+
))],
1146+
true,
1147+
),
1148+
(
1149+
vec![Box::new(rofl::policy::AllowedEndorsement::And(vec![
1150+
Box::new(rofl::policy::AllowedEndorsement::Provider(
1151+
keys::alice::address(),
1152+
)),
1153+
Box::new(rofl::policy::AllowedEndorsement::Node(
1154+
keys::bob::pk_ed25519().into(),
1155+
)),
1156+
]))],
1157+
true,
1158+
),
1159+
(
1160+
vec![Box::new(
1161+
rofl::policy::AllowedEndorsement::ProviderInstanceAdmin(keys::charlie::address()),
1162+
)],
1163+
true,
1164+
),
1165+
(
1166+
vec![Box::new(
1167+
rofl::policy::AllowedEndorsement::ProviderInstanceAdmin(keys::dave::address()),
1168+
)],
1169+
false,
1170+
),
1171+
(
1172+
vec![Box::new(rofl::policy::AllowedEndorsement::Provider(
1173+
keys::bob::address(),
1174+
))],
1175+
false,
1176+
),
1177+
(
1178+
vec![Box::new(rofl::policy::AllowedEndorsement::Provider(
1179+
keys::charlie::address(),
1180+
))],
1181+
false,
1182+
),
1183+
(
1184+
vec![Box::new(rofl::policy::AllowedEndorsement::And(vec![
1185+
Box::new(rofl::policy::AllowedEndorsement::Provider(
1186+
keys::alice::address(),
1187+
)),
1188+
Box::new(rofl::policy::AllowedEndorsement::Node(
1189+
keys::charlie::pk_ed25519().into(),
1190+
)),
1191+
]))],
1192+
false,
1193+
),
1194+
];
1195+
1196+
for (idx, tc) in tcs.iter().enumerate() {
1197+
let result = Evaluator::verify(&ctx, &tc.0, &ect, &metadata);
1198+
if tc.1 {
1199+
result.context(format!("test case {}", idx)).unwrap();
1200+
} else {
1201+
result.unwrap_err();
1202+
}
1203+
}
1204+
}

runtime-sdk/src/crypto/signature/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use digest::{typenum::Unsigned as _, Digest as _};
55
use rand_core::{CryptoRng, RngCore};
66
use thiserror::Error;
77

8-
use crate::core::common::crypto::signature::{PublicKey as CorePublicKey, Signer as CoreSigner};
8+
use crate::core::common::crypto::signature::{
9+
PublicKey as CorePublicKey, Signature as CoreSignature, Signer as CoreSigner,
10+
};
911

1012
pub mod context;
1113
mod digests;
@@ -385,6 +387,12 @@ impl From<Signature> for Vec<u8> {
385387
}
386388
}
387389

390+
impl From<Signature> for CoreSignature {
391+
fn from(s: Signature) -> Self {
392+
s.as_ref().into()
393+
}
394+
}
395+
388396
/// Common trait for memory signers.
389397
pub trait Signer: Send + Sync {
390398
/// Create a new random signer.

0 commit comments

Comments
 (0)