Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

8 changes: 8 additions & 0 deletions rofl-scheduler/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use oasis_runtime_sdk::{
};
use oasis_runtime_sdk_rofl_market::{
self as market,
policy::{ProviderLabel, LABEL_PROVIDER},
types::{Deployment, Instance, InstanceId, InstanceStatus},
};
use rand::Rng;
Expand Down Expand Up @@ -999,6 +1000,13 @@ impl Manager {
deployment_hash(deployment),
);

let provider_label = ProviderLabel {
provider: self.cfg.provider_address,
instance: instance.id,
};
let provider_label = BASE64_STANDARD.encode(cbor::to_vec(provider_label));
labels.insert(LABEL_PROVIDER.to_string(), provider_label);

let _ = self
.env
.host()
Expand Down
1 change: 1 addition & 0 deletions runtime-sdk/modules/rofl-market/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ oasis-runtime-sdk-evm = { path = "../evm" }

# Third party.
anyhow = "1.0"
base64 = "0.22.1"
ethabi = { version = "18.0.0", default-features = false, features = ["std"] }
once_cell = "1.8.0"
rustc-hex = "2.0.1"
Expand Down
1 change: 1 addition & 0 deletions runtime-sdk/modules/rofl-market/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod config;
mod error;
mod event;
mod payment;
pub mod policy;
pub mod state;
#[cfg(test)]
mod test;
Expand Down
123 changes: 123 additions & 0 deletions runtime-sdk/modules/rofl-market/src/policy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use std::collections::BTreeMap;

use base64::prelude::*;

use oasis_runtime_sdk::{
core::{
common::crypto::signature::{PublicKey, Signature},
consensus::registry::{EndorsedCapabilityTEE, Node},
host::attestation::{LabelAttestation, ATTEST_LABELS_SIGNATURE_CONTEXT},
},
modules::rofl::{
policy::{AllowedEndorsement, EndorsementPolicyEvaluator},
Error,
},
types::address::Address,
Context,
};

use crate::{state, types::InstanceId};

/// Name of the ROFL app instance metadata key used to store the provider attestation.
pub const METADATA_KEY_POLICY_PROVIDER_ATTESTATION: &str = "net.oasis.policy.provider";
Comment thread
peternose marked this conversation as resolved.
/// Name of the provider label set by the scheduler.
pub const LABEL_PROVIDER: &str = "net.oasis.provider";

/// Value of the `LABEL_PROVIDER` label as set by the scheduler.
#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
pub struct ProviderLabel {
/// Address of the provider.
pub provider: Address,
/// Instance identifier.
pub instance: InstanceId,
}

/// Provider attestation metadata stored in `METADATA_KEY_POLICY_PROVIDER_ATTESTATION` label.
#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
pub struct ProviderAttestation {
/// A CBOR-serialized `LabelAttestation`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other structs you used name attestation and comment CBOR-serialized label attestation..

pub label_attestation: Vec<u8>,
/// Signature from endorsing node.
pub signature: Signature,
}

/// An endorsement policy evaluator that supports provider-related constraints via lookups
/// in ROFL market module state.
pub struct ProviderEndorsementPolicyEvaluator;

impl EndorsementPolicyEvaluator for ProviderEndorsementPolicyEvaluator {
fn verify_atom<C: Context>(
_ctx: &C,
policy: &AllowedEndorsement,
ect: &EndorsedCapabilityTEE,
endorsing_node_id: PublicKey,
_endorsing_node: &Option<Node>,
metadata: &BTreeMap<String, String>,
) -> Result<(), Error> {
match policy {
AllowedEndorsement::Provider(address) => {
// Check if the endorsing node is endorsed by the given provider.
let provider = state::get_provider(*address).ok_or(Error::NodeNotAllowed)?;
if !provider.nodes.contains(&endorsing_node_id) {
return Err(Error::NodeNotAllowed);
}
Ok(())
}
AllowedEndorsement::ProviderInstanceAdmin(expected_admin) => {
let pa: ProviderAttestation = cbor::from_slice(
&BASE64_STANDARD
.decode(
metadata
.get(METADATA_KEY_POLICY_PROVIDER_ATTESTATION)
.ok_or(Error::NodeNotAllowed)?,
Comment thread
peternose marked this conversation as resolved.
)
.map_err(|_| Error::NodeNotAllowed)?,
)
.map_err(|_| Error::NodeNotAllowed)?;

// Verify node label attestation.
pa.signature
.verify(
&endorsing_node_id,
ATTEST_LABELS_SIGNATURE_CONTEXT,
&pa.label_attestation,
)
.map_err(|_| Error::NodeNotAllowed)?;
let label_attestation: LabelAttestation =
cbor::from_slice(&pa.label_attestation).map_err(|_| Error::NodeNotAllowed)?;
if label_attestation.rak != ect.capability_tee.rak {
return Err(Error::NodeNotAllowed);
}

// Extract provider label (set by the provider's scheduler).
let provider_label: ProviderLabel = cbor::from_slice(
&BASE64_STANDARD
.decode(
label_attestation
.labels
.get(LABEL_PROVIDER)
.ok_or(Error::NodeNotAllowed)?,
)
.map_err(|_| Error::NodeNotAllowed)?,
)
.map_err(|_| Error::NodeNotAllowed)?;

let provider =
state::get_provider(provider_label.provider).ok_or(Error::NodeNotAllowed)?;
if !provider.nodes.contains(&endorsing_node_id) {
return Err(Error::NodeNotAllowed);
}

let instance =
state::get_instance(provider_label.provider, provider_label.instance)
.ok_or(Error::NodeNotAllowed)?;
if &instance.admin != expected_admin {
return Err(Error::NodeNotAllowed);
}

Ok(())
}
_ => Err(Error::NodeNotAllowed),
}
}
}
176 changes: 173 additions & 3 deletions runtime-sdk/modules/rofl-market/src/test.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
use std::collections::BTreeMap;

use anyhow::Context;
use base64::prelude::*;
use oasis_runtime_sdk::{
core::{
common::crypto::signature::SignatureBundle,
consensus::registry::EndorsedCapabilityTEE,
host::attestation::{LabelAttestation, ATTEST_LABELS_SIGNATURE_CONTEXT},
},
crypto::signature::Signer,
module,
modules::{
accounts::{self, API as _},
core, rofl,
core,
rofl::{
self,
policy::{BasicEndorsementPolicyEvaluator, EndorsementPolicyEvaluator},
},
},
testing::{keys, mock},
types::{
Expand All @@ -14,7 +26,7 @@ use oasis_runtime_sdk::{
Runtime, Version,
};

use super::{types, ADDRESS_PROVIDER_STAKE_POOL};
use super::{policy, state, types, ADDRESS_PROVIDER_STAKE_POOL};

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

impl core::Config for Config {}

impl rofl::Config for Config {}
impl rofl::Config for Config {
type EndorsementPolicyEvaluator = BasicEndorsementPolicyEvaluator;
}

impl super::Config for Config {
type Rofl = rofl::Module<Config>;
Expand Down Expand Up @@ -1032,3 +1046,159 @@ fn test_instance_accept_timeout() {
let balance = Accounts::get_balance(keys::charlie::address(), Denomination::NATIVE).unwrap();
assert_eq!(balance, 1_000_000);
}

#[test]
fn test_endorsement_policy_evaluator() {
let mut mock = mock::Mock::default();
let ctx = mock.create_ctx_for_runtime::<TestRuntime>(true);

TestRuntime::migrate(&ctx);

// Summary of keys used for this test:
//
// alice: provider
// bob: provider's node and scheduler's RAK
//

// Create the scheduler app.
let create = rofl::types::Create {
scheme: rofl::types::IdentifierScheme::CreatorNonce,
..Default::default()
};

let mut signer_alice = mock::Signer::new(0, keys::alice::sigspec());
let dispatch_result = signer_alice.call(&ctx, "rofl.Create", create);
assert!(dispatch_result.result.is_success(), "call should succeed");
let scheduler_app: rofl::app_id::AppId =
cbor::from_value(dispatch_result.result.unwrap()).unwrap();

// Create a provider.
let create = types::ProviderCreate {
scheduler_app,
nodes: vec![keys::bob::pk_ed25519().into()], // Bob seems like a nice node.
..Default::default()
};

let dispatch_result = signer_alice.call(&ctx, "roflmarket.ProviderCreate", create.clone());
assert!(dispatch_result.result.is_success(), "call should succeed");

// Create a mock instance of the scheduler app.
let fake_registration = rofl::types::Registration {
app: scheduler_app,
node_id: keys::bob::pk_ed25519().into(), // Bob is a nice approved node.
rak: keys::bob::pk_ed25519().into(), // Bob is also a nice RAK.
..Default::default()
};
rofl::state::update_registration(fake_registration).unwrap();

// Create a new accepted instance directly in state.
let accepted_instance = types::Instance {
provider: keys::alice::address(),
status: types::InstanceStatus::Accepted,
node_id: Some(keys::bob::pk_ed25519().into()),
admin: keys::charlie::address(),
..Default::default()
};
state::set_instance(accepted_instance.clone());

// Construct a composite endorsement policy evaluator.
type Evaluator = (
rofl::policy::BasicEndorsementPolicyEvaluator,
super::policy::ProviderEndorsementPolicyEvaluator,
);

// Mock an endorsed TEE with attested labels.
let ect = EndorsedCapabilityTEE {
node_endorsement: SignatureBundle {
public_key: keys::bob::pk_ed25519().into(),
..Default::default()
},
..Default::default()
};
let provider_label = policy::ProviderLabel {
provider: keys::alice::address(),
instance: accepted_instance.id,
};
let label_attestation = cbor::to_vec(LabelAttestation {
labels: BTreeMap::from([(
policy::LABEL_PROVIDER.to_string(),
BASE64_STANDARD.encode(cbor::to_vec(provider_label)),
)]),
rak: ect.capability_tee.rak,
});
let signature = keys::bob::signer()
.sign(ATTEST_LABELS_SIGNATURE_CONTEXT, &label_attestation)
.unwrap();
let provider_attestation = policy::ProviderAttestation {
label_attestation,
signature: signature.into(),
};
let metadata = BTreeMap::from([(
policy::METADATA_KEY_POLICY_PROVIDER_ATTESTATION.to_string(),
BASE64_STANDARD.encode(cbor::to_vec(provider_attestation)),
)]);

let tcs = [
(
vec![Box::new(rofl::policy::AllowedEndorsement::Provider(
keys::alice::address(),
))],
true,
),
(
vec![Box::new(rofl::policy::AllowedEndorsement::And(vec![
Box::new(rofl::policy::AllowedEndorsement::Provider(
keys::alice::address(),
)),
Box::new(rofl::policy::AllowedEndorsement::Node(
keys::bob::pk_ed25519().into(),
)),
]))],
true,
),
(
vec![Box::new(
rofl::policy::AllowedEndorsement::ProviderInstanceAdmin(keys::charlie::address()),
)],
true,
),
(
vec![Box::new(
rofl::policy::AllowedEndorsement::ProviderInstanceAdmin(keys::dave::address()),
)],
false,
),
(
vec![Box::new(rofl::policy::AllowedEndorsement::Provider(
keys::bob::address(),
))],
false,
),
(
vec![Box::new(rofl::policy::AllowedEndorsement::Provider(
keys::charlie::address(),
))],
false,
),
(
vec![Box::new(rofl::policy::AllowedEndorsement::And(vec![
Box::new(rofl::policy::AllowedEndorsement::Provider(
keys::alice::address(),
)),
Box::new(rofl::policy::AllowedEndorsement::Node(
keys::charlie::pk_ed25519().into(),
)),
]))],
false,
),
];

for (idx, tc) in tcs.iter().enumerate() {
let result = Evaluator::verify(&ctx, &tc.0, &ect, &metadata);
if tc.1 {
result.context(format!("test case {}", idx)).unwrap();
} else {
result.unwrap_err();
}
}
}
10 changes: 9 additions & 1 deletion runtime-sdk/src/crypto/signature/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use digest::{typenum::Unsigned as _, Digest as _};
use rand_core::{CryptoRng, RngCore};
use thiserror::Error;

use crate::core::common::crypto::signature::{PublicKey as CorePublicKey, Signer as CoreSigner};
use crate::core::common::crypto::signature::{
PublicKey as CorePublicKey, Signature as CoreSignature, Signer as CoreSigner,
};

pub mod context;
mod digests;
Expand Down Expand Up @@ -385,6 +387,12 @@ impl From<Signature> for Vec<u8> {
}
}

impl From<Signature> for CoreSignature {
fn from(s: Signature) -> Self {
s.as_ref().into()
}
}

/// Common trait for memory signers.
pub trait Signer: Send + Sync {
/// Create a new random signer.
Expand Down
Loading
Loading