From 9f768a74117415111ea4ee67027935a072feb986 Mon Sep 17 00:00:00 2001 From: Tim Hinrichs Date: Fri, 18 Oct 2013 11:27:04 -0700 Subject: [PATCH] Expanded private_network script Renamed 'projection' to 'simulation' and expanded private_public_network demo script to include simulation. Issue: # Change-Id: I3cae526d37425bb53e91034d1af75199c1d83050 --- examples/private_public_network.action | 3 + examples/private_public_network.script | 134 +++++++++++++++++++------ src/policy/runtime.py | 52 +++++----- src/policy/tests/test_runtime.py | 6 +- 4 files changed, 132 insertions(+), 63 deletions(-) diff --git a/examples/private_public_network.action b/examples/private_public_network.action index 2e058d3b9..24e3626d9 100644 --- a/examples/private_public_network.action +++ b/examples/private_public_network.action @@ -1,4 +1,7 @@ +// connect_network action +action("connect_network") +nova:network+(vm, network) :- connect_network(vm, network) // disconnect_network action action("disconnect_network") diff --git a/examples/private_public_network.script b/examples/private_public_network.script index e3981c367..992a617e0 100644 --- a/examples/private_public_network.script +++ b/examples/private_public_network.script @@ -1,13 +1,16 @@ Script for a demo. -0) Example policy +********************************************************************* +** Monitoring: Classification and Data sources +********************************************************************* +1) Classification Policy + +Informally: "all vms must be attached to public networks or to private networks owned by someone in the same group as the vm owner" -1) Draws on disparate data sources - -Schema: +Cloud services at our disposal: nova:virtual_machine(vm) nova:network(vm, network) nova:owner(vm, owner) @@ -16,8 +19,7 @@ neutron:owner(network, owner) cms:group(user, group) -2) Policy - +Formal policy: error(vm) :- nova:virtual_machine(vm), nova:network(vm, network), not neutron:public_network(network), neutron:owner(network, netowner), nova:owner(vm, vmowner), not same_group(netowner, vmowner) @@ -34,7 +36,7 @@ python ------------------------------------------------- -3) Are there any violations? Not yet. +2) Are there any violations? Not yet. --- Commands ------------------------------------ >>> print r.select("error(x)") @@ -42,36 +44,36 @@ python ------------------------------------------------- -4) Change some data to create an error: remove "tim" from group "congress" - +3) Change some data to create an error: remove "tim" from group "congress" +OR make this change in ActiveDirectory. --- Commands ------------------------------------ >>> r.delete('cms:group("tim", "congress")') ------------------------------------------------- -5) Check for violations +4) Check for violations --- Commands ------------------------------------ >>> print r.select("error(x)") -error(vm1) +error("vm1") ------------------------------------------------- -6) Explain the violation: +5) Explain the violation: --- Commands ------------------------------------ >>> print r.explain('error("vm1")') -error(vm1) - nova:virtual_machine(vm1) - nova:network(vm1, net_private) - not neutron:public_network(net_private) - neutron:owner(net_private, martin) - nova:owner(vm1, tim) - not same_group(martin, tim) +error("vm1") + nova:virtual_machine("vm1") + nova:network("vm1", "net_private") + not neutron:public_network("net_private") + neutron:owner("net_private", "martin") + nova:owner("vm1", "tim") + not same_group("martin", "tim") ------------------------------------------------- -7) Insert new rules: "Error if vm without a network" +6) Insert new policy fragment: "Error if vm without a network" --- Commands ------------------------------------ >>> r.insert('error(vm) :- nova:virtual_machine(vm), not is_some_network(vm)' @@ -79,25 +81,43 @@ error(vm1) ------------------------------------------------- -8) Check for violations +7) Check for violations --- Commands ------------------------------------ >>> print r.select("error(x)") -error(vm1) error(vm3) +error("vm1") error("vm3") ------------------------------------------------- -9) Explain the new violation +8) Explain the new violation --- Commands ------------------------------------ >>> print r.explain('error("vm3")') -error(vm3) - nova:virtual_machine(vm3) - not is_some_network(vm3) +error("vm3") + nova:virtual_machine("vm3") + not is_some_network("vm3") ------------------------------------------------- -10) Install Action theory +********************************************************************* +** Enforcement: Action Policy and Operations Policy +********************************************************************* + + +9) To help with enforcement, Congress needs to know what actions are available to it. Codify these in the Action policy. + +Informal policy: + +connect_network(vm, network) +disconnect_network(vm, network) +delete_vm(vm) +make_public(network) + +Formal policy: + +// connect_network action +action("connect_network") +nova:network+(vm, network) :- connect_network(vm, network) // disconnect_network action action("disconnect_network") @@ -118,13 +138,61 @@ neutron:public_network+(network) :- make_public(network) ------------------------------------------------- -11) Ask for remediations: actions that when executed will fix a violation +10) Simulate actions and query resulting state (without actually changing state) --- Commands ------------------------------------ -print r.remediate('error("vm1")') -nova:virtual_machine-(vm1) :- delete_vm(vm1) -nova:network-(vm1, net_private) :- disconnect_network(vm1, net_private) -neutron:public_network+(net_private) :- make_public(net_private) -nova:owner-(vm1, tim) :- delete_vm(vm1) +>>> print r.simulate('error(x)', 'connect_network("vm3", "net_public")') +error("vm1") ------------------------------------------------- + +11) Ask for remediations: action sequence that when executed will fix a violation. (Caveat: multiple reasons for a violation--fixing a single reason.) + +--- Commands ------------------------------------ +>>> print r.remediate('error("vm1")') +nova:virtual_machine-("vm1") :- delete_vm("vm1") +nova:network-("vm1", "net_private") :- disconnect_network("vm1", "net_private") +neutron:public_network+("net_private") :- make_public("net_private") +nova:owner-("vm1", "tim") :- delete_vm("vm1") +------------------------------------------------- + + +********************************************************************* +** Future Enforcement: Operations Policy +********************************************************************* + +12) Execute an action-sequence: disconnect offending private network + +r.execute('action1 action2 action3') + +13) Dictate conditions under which actions should automatically be executed: Operations policy. + +Informal policy: +every time a VM has an error and that VM is connected to a private network not owned by someone in the same group as the VM owner, then execute 'disconnect_network'. + +Formal policy: + +disconnect_network(vm1, net_private) :- + error(vm1) + nova:virtual_machine(vm1) + nova:network(vm1, net_private) + not neutron:public_network(net_private) + neutron:owner(net_private, martin) + nova:owner(vm1, tim) + not same_group(martin, tim) + +r.load_file("../../examples/private_public_network.operations", target=r.OPERATION_POLICY) + + +14) Create an error that the operations policy should automatically correct. + +r.execute('connect_network("vm1", "net_private")') +print r.select("error(x)") +error("vm3") + + +15) Show log for auditing to see that we actually did connect the network and then disconnect it. Log should include explanation for why action was executed. + + + + diff --git a/src/policy/runtime.py b/src/policy/runtime.py index 64f73ef80..bf397f1ba 100644 --- a/src/policy/runtime.py +++ b/src/policy/runtime.py @@ -1336,16 +1336,16 @@ class Runtime (object): else: return self.remediate_obj(formula) - def project(self, query, sequence): - """ Event handler for projection: the computation of a query given an + def simulate(self, query, sequence): + """ Event handler for simulation: the computation of a query given an action sequence. [Eventually, we will want to support a sequence of {action, rule insert/delete, atom insert/delete}. Holding off only because no syntactic way of differentiating those within the language. May need modals/function constants.] """ if isinstance(query, basestring) and isinstance(sequence, basestring): - return self.project_string(query, sequence) + return self.simulate_string(query, sequence) else: - return self.project_obj(query, sequence) + return self.simulate_obj(query, sequence) # Maybe implement one day # def select_if(self, query, temporary_data): @@ -1470,30 +1470,42 @@ class Runtime (object): results.append(abduction) return results - # project - def project_string(self, query, sequence): + # simulate + def simulate_string(self, query, sequence): query = compile.parse1(query) sequence = compile.parse(sequence) - result = self.project_obj(query, sequence) + result = self.simulate_obj(query, sequence) return compile.formulas_to_string(result) - def project_obj(self, query, sequence): + def simulate_obj(self, query, sequence): assert (isinstance(query, compile.Rule) or isinstance(query, compile.Atom)), "Query must be formula" # Each action is represented as a rule with the actual action # in the head and its supporting data (e.g. options) in the body assert all(isinstance(x, compile.Rule) or isinstance(x, compile.Atom) for x in sequence), "Sequence must be an iterable of Rules" + # apply SEQUENCE + undo = self.project(sequence) + # query the resulting state + result = self.theory[self.CLASSIFY_THEORY].select(query) + self.log(query.tablename(), "Result of {} is {}".format( + str(query), iterstr(result))) + # rollback the changes + self.project(undo) + return result + + def project(self, sequence): + """ Apply the list of updates SEQUENCE to the classification theory. + Return an update sequence that will undo the projection. """ actth = self.theory[self.ACTION_THEORY] - clsth = self.theory[self.CLASSIFY_THEORY] # apply changes to the state newth = NonrecursiveRuleTheory() newth.tracer.trace('*') actth.includes.append(newth) actions = self.get_actions() - self.log(query.tablename(), "Actions: " + str(actions)) - change_sequence = [] # a list of lists of updates + self.log(None, "Actions: " + str(actions)) + undos = [] # a list of updates that will undo SEQUENCE for formula in sequence: if formula.is_atom(): tablename = formula.table @@ -1511,25 +1523,11 @@ class Runtime (object): updates = self.resolve_conflicts(updates) else: updates = [formula] - # apply and remember each update-set - changes = [] for update in updates: undo = self.update_classifier(update) if undo is not None: - changes.append(undo) - change_sequence.append(changes) - - # query the resulting state - result = clsth.select(query) - self.log(query.tablename(), "Result of {} is {}".format( - str(query), iterstr(result))) - # rollback the changes: in the reverse order we applied them in - self.log(query.tablename(), "* Rolling back") - actth.includes.remove(newth) - for changes in reversed(change_sequence): - for undo in reversed(changes): - self.update_classifier(undo) - return result + undos.append(undo) + return reversed(undos) def update_classifier(self, delta): """ Takes an atom/rule DELTA with update head table diff --git a/src/policy/tests/test_runtime.py b/src/policy/tests/test_runtime.py index 34dc79c19..7928b4da3 100644 --- a/src/policy/tests/test_runtime.py +++ b/src/policy/tests/test_runtime.py @@ -993,8 +993,8 @@ class TestRuntime(unittest.TestCase): 'p-(1) :- a(1) q-(1) :- b(1)', 'Monadic, two conditions, two actions') - def test_projection(self): - """ Test projection: the computation of a query given a sequence of + def test_simulate(self): + """ Test simulate: the computation of a query given a sequence of actions. """ def create(action_code, class_code): run = self.prep_runtime() @@ -1004,7 +1004,7 @@ class TestRuntime(unittest.TestCase): run.insert(class_code, target=clsth) return run def check(run, action_sequence, query, correct, original_db, msg): - actual = run.project(query, action_sequence) + actual = run.simulate(query, action_sequence) self.check_equal(actual, correct, msg) self.check(run, original_db, msg)