Merge lp:~hazmat/pyjuju/security-policy-rules-redux into lp:pyjuju

Proposed by Kapil Thangavelu
Status: Work in progress
Proposed branch: lp:~hazmat/pyjuju/security-policy-rules-redux
Merge into: lp:pyjuju
Prerequisite: lp:~hazmat/pyjuju/security-policy-with-topology
Diff against target: 1217 lines (+586/-208) (has conflicts)
14 files modified
juju/providers/local/__init__.py (+0/-1)
juju/state/initialize.py (+2/-2)
juju/state/security.py (+17/-27)
juju/state/securityrules.py (+192/-0)
juju/state/tests/common.py (+29/-39)
juju/state/tests/test_agent.py (+0/-11)
juju/state/tests/test_auth.py (+3/-3)
juju/state/tests/test_base.py (+7/-7)
juju/state/tests/test_environment.py (+0/-4)
juju/state/tests/test_firewall.py (+1/-1)
juju/state/tests/test_initialize.py (+14/-3)
juju/state/tests/test_security.py (+38/-108)
juju/state/tests/test_securityrules.py (+223/-0)
juju/state/tests/test_service.py (+60/-2)
Text conflict in juju/state/service.py
Text conflict in juju/state/tests/common.py
Text conflict in juju/state/tests/test_initialize.py
Text conflict in juju/state/tests/test_service.py
To merge this branch: bzr merge lp:~hazmat/pyjuju/security-policy-rules-redux
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Needs Fixing
Review via email: mp+70502@code.launchpad.net

This proposal supersedes a proposal from 2011-08-04.

Description of the change

Redone with updated api and some simplifications.

Security ACL generator rules to match nodes based on path. The meat of the security ACL policy is embodied here, and this will likely some tweaking in the future.

Thou shall not forget pre-requisite branches lp:~hazmat/ensemble/security-policy-with-topology

To post a comment you must log in.
323. By Kapil Thangavelu

Merged security-policy-with-topology into security-policy-rules-redux.

324. By Kapil Thangavelu

Merged security-policy-with-topology into security-policy-rules-redux.

325. By Kapil Thangavelu

resolve conflict from security-group merge

326. By Kapil Thangavelu

resolve conflict with security-groups merge

327. By Kapil Thangavelu

Merged security-policy-with-topology into security-policy-rules-redux.

328. By Kapil Thangavelu

Merged security-policy-with-topology into security-policy-rules-redux.

329. By Kapil Thangavelu

Merged security-policy-with-topology into security-policy-rules-redux.

330. By Kapil Thangavelu

resolve conflict from security-policy-with-topology merge

331. By Kapil Thangavelu

yank security group accessor from service state

332. By Kapil Thangavelu

resolve conflict from states-with-principals

333. By Kapil Thangavelu

update tests in aftermath of removing service state security group accessor.

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

This is looking very good overall. Thanks!

A few comments:

[1]

+ machine_token = yield policy.get_token(machine_id)
+ provider_token = yield policy.get_token("ensemble-provider")
+ returnValue([
+ make_ace(machine_token, read=True, write=True, create=True),
+ make_ace(provider_token, read=True, write=True)])

This logic looks very nice.

As a minor from the previous review which still wasn't sorted,
"provisioning-agent" and "ensemble-provider" are different things,
I believe the above refers to the former, not to the latter.

[2]

+ for service_id, service_data in services.items():
+ service_data = dict(service_data)
+ service_data["service_id"] = service_id
+ service_data["token"] = yield policy.get_token(service_id)
+ results.append(service_data)

Why is this using an arbitrary blob of data rather than a typed
value with proper fields? I think we've learned our lesson with the
machine_data.

[3]

+ matched_service = [data for data in service_data \
+ if data["role"] == container].pop()
+ related_service = [data for data in service_data \
+ if data != matched_service].pop()
+ returnValue(
+ [make_ace(matched_service["token"], read=True, create=True),
+ make_ace(related_service["token"], read=True)])

This was raised in the previous review for this branch as well: why
is the node a "container" rather than an individual node under the
relation-id node? Can't we have multiple nodes within those
containers?

[4]

+ # Unit agent can modify tweak any unitother unit settings.

Comment is a bit corrupted.

[5]

+ # Created by the unit agent, read by the provisioning agent.

Might be good to comment here that in the future it will be moved to
the machine agent.

[6]

+ elif path == "/topology":
+ # Only the admin cli can write to this.
+ returnValue([make_ace("anyone", "world", read=True)])

Also part of the original review, IIRC. It feels bad to be exposing
this to _anyone_. We don't have to sort that out right now, but at
least a comment with a recommended future solution would be nice.

[7]

+ elif path == "/auth-tokens":
+ # Any connection that creates another user needs to modify this
+ returnValue([make_ace("anyone", "world", read=True, write=True)])

_Anyone_ can write to the auth tokens!?

review: Needs Fixing
334. By Kapil Thangavelu

Merged security-policy-with-topology into security-policy-rules-redux.

335. By Kapil Thangavelu

merge trunk

336. By Kapil Thangavelu

merge trunk

337. By Kapil Thangavelu

Merged security-policy-with-topology into security-policy-rules-redux.

338. By Kapil Thangavelu

merge pipeline states-with-principles

Unmerged revisions

338. By Kapil Thangavelu

merge pipeline states-with-principles

337. By Kapil Thangavelu

Merged security-policy-with-topology into security-policy-rules-redux.

336. By Kapil Thangavelu

merge trunk

335. By Kapil Thangavelu

merge trunk

334. By Kapil Thangavelu

Merged security-policy-with-topology into security-policy-rules-redux.

333. By Kapil Thangavelu

update tests in aftermath of removing service state security group accessor.

332. By Kapil Thangavelu

resolve conflict from states-with-principals

331. By Kapil Thangavelu

yank security group accessor from service state

330. By Kapil Thangavelu

resolve conflict from security-policy-with-topology merge

329. By Kapil Thangavelu

Merged security-policy-with-topology into security-policy-rules-redux.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'juju/providers/local/__init__.py'
--- juju/providers/local/__init__.py 2012-08-10 11:01:23 +0000
+++ juju/providers/local/__init__.py 2012-08-10 11:01:24 +0000
@@ -17,7 +17,6 @@
17from juju.providers.local.machine import LocalMachine17from juju.providers.local.machine import LocalMachine
18from juju.providers.local.network import Network18from juju.providers.local.network import Network
19from juju.providers.local.pkg import check_packages19from juju.providers.local.pkg import check_packages
20from juju.state.auth import make_identity
21from juju.state.initialize import StateHierarchy20from juju.state.initialize import StateHierarchy
22from juju.state.placement import LOCAL_POLICY21from juju.state.placement import LOCAL_POLICY
23from juju.state.security import Principal22from juju.state.security import Principal
2423
=== modified file 'juju/state/initialize.py'
--- juju/state/initialize.py 2012-08-10 11:01:23 +0000
+++ juju/state/initialize.py 2012-08-10 11:01:24 +0000
@@ -8,7 +8,7 @@
8from .auth import make_ace8from .auth import make_ace
9from .environment import EnvironmentStateManager, GlobalSettingsStateManager9from .environment import EnvironmentStateManager, GlobalSettingsStateManager
10from .machine import MachineStateManager10from .machine import MachineStateManager
11from .security import add_user11from .security import Principal
1212
13log = logging.getLogger("juju.state.init")13log = logging.getLogger("juju.state.init")
1414
@@ -53,7 +53,7 @@
53 yield self.client.create("/otp", acls=acls)53 yield self.client.create("/otp", acls=acls)
5454
55 # Seed the admin identity55 # Seed the admin identity
56 yield add_user(self.client, *(self.admin_identity.split(":")))56 yield Principal(*(self.admin_identity.split(":"))).create(self.client)
5757
58 # In this very specific case, it's OK to create a Constraints object58 # In this very specific case, it's OK to create a Constraints object
59 # with a non-provider-specific ConstraintSet, because *all* we need it59 # with a non-provider-specific ConstraintSet, because *all* we need it
6060
=== modified file 'juju/state/security.py'
--- juju/state/security.py 2012-08-10 11:01:23 +0000
+++ juju/state/security.py 2012-08-10 11:01:24 +0000
@@ -26,30 +26,16 @@
2626
2727
28@inlineCallbacks28@inlineCallbacks
29def apply_rules(client, path, topology=None, recurse=False):29def apply_security_rules(client, path, topology=None, recurse=False):
30 """Apply security policy ACL to the given path."""30 """Apply security policy ACL to the given path."""
31 token_db = TokenDatabase(client)31 token_db = TokenDatabase(client)
32 policy = SecurityPolicy(32 policy = SecurityPolicy(
33 client, token_db, securityrules.get_default_rules(),33 client, token_db,
34 topology)34 rules=securityrules.get_default_rules(),
35 topology=topology)
35 acl = yield policy(path)36 acl = yield policy(path)
36 yield client.set_acl(path, acl)37 yield client.set_acl(path, acl)
3738
38apply_security_rules = apply_rules
39
40
41def set_domain_security(enabled):
42 """For testing enable disabling security for test speed.
43
44 All the operations against DomainSequenceSecurity become no-ops.
45 """
46
47
48def add_user(client, username, password):
49 tokens = TokenDatabase(client)
50 principal = Principal(username, password)
51 return tokens.add(principal)
52
5339
54class Principal(object):40class Principal(object):
55 """An juju/zookeeper principal.41 """An juju/zookeeper principal.
@@ -76,7 +62,7 @@
76 auth_deferred = connection.add_auth(62 auth_deferred = connection.add_auth(
77 "digest", "%s:%s" % (self._name, self._password))63 "digest", "%s:%s" % (self._name, self._password))
78 # Work around for fast auth, remove post ZOOKEEPER-77064 # Work around for fast auth, remove post ZOOKEEPER-770
79 connection.exists("/")65 #connection.exists("/")
80 return auth_deferred66 return auth_deferred
8167
82 def create(self, client):68 def create(self, client):
@@ -142,7 +128,7 @@
142 auth_deferred = connection.add_auth(128 auth_deferred = connection.add_auth(
143 "digest", "%s:%s" % (self._name, self._password))129 "digest", "%s:%s" % (self._name, self._password))
144 # Work around for fast auth, remove post ZOOKEEPER-770130 # Work around for fast auth, remove post ZOOKEEPER-770
145 connection.exists("/")131 #connection.exists("/")
146 yield auth_deferred132 yield auth_deferred
147133
148 @inlineCallbacks134 @inlineCallbacks
@@ -244,14 +230,15 @@
244230
245 # Decode the data to get the path and otp credentials231 # Decode the data to get the path and otp credentials
246 path, credentials = base64.b64decode(otp_data).split(":", 1)232 path, credentials = base64.b64decode(otp_data).split(":", 1)
247 attach_deferred = client.add_auth("digest", credentials)233
248 # Fast Auth - Remove after ZOOKEEPER-770234 # Auth with otp token creds to read the final principal data.
249 client.exists("/")235 p = Principal(*(credentials.split(":")))
250236 yield p.attach(client)
251 yield attach_deferred237
252 # Load the otp principal data238 # Load the principal data
253 data, stat = yield client.get(path)239 data, stat = yield client.get(path)
254 principal_data = yaml.load(data)240 principal_data = yaml.load(data)
241
255 # Consume the otp node242 # Consume the otp node
256 yield client.delete(path)243 yield client.delete(path)
257244
@@ -373,8 +360,11 @@
373360
374class SecurityPolicy(object):361class SecurityPolicy(object):
375 """The security policy generates ACLs for new nodes based on their path.362 """The security policy generates ACLs for new nodes based on their path.
363
364 :param owner: If passed the owner has all permissions on nodes.
365 :param topology: A topology to use for rule analysis.
376 """366 """
377 def __init__(self, client, token_db, rules=(), owner=None):367 def __init__(self, client, token_db, rules=(), owner=None, topology=None):
378 self._client = client368 self._client = client
379 self._rules = list(rules)369 self._rules = list(rules)
380 self._token_db = token_db370 self._token_db = token_db
381371
=== modified file 'juju/state/securityrules.py'
--- juju/state/securityrules.py 2012-08-10 11:01:23 +0000
+++ juju/state/securityrules.py 2012-08-10 11:01:24 +0000
@@ -1,3 +1,195 @@
1"""
2Security rules to determine ACL by node path.
3
4"""
5
6from twisted.internet.defer import inlineCallbacks, returnValue
7
8from juju.state.auth import make_ace
9
10
11def formula_rule(policy, path):
12 """An ACL policy rule for nodes under the '/formulas' path.
13
14 These nodes are read only by any connected user, and are
15 writable only by the admin cli (which creates them).
16
17 Path rule::
18
19 /formulas
20 /formula-id (all:admin, r:everyone)
21 """
22 if not path.startswith("/formulas/"):
23 return
24 return [make_ace("anyone", "world", read=True)]
25
26
27@inlineCallbacks
28def machine_rule(policy, path):
29 """An ACL policy rule for nodes under the '/machines' path.
30
31 These nodes are created by the cli admin when launching new machines,
32 with admin access granted to the provisioning agents. When the
33 provisioning agents create a new machine, they also create a new
34 principal and update the machine node ace to allow access to the
35 new machine agent principal.
36 """
37
38 if not path.startswith("/machines/"):
39 return
40
41 parts = path.split("/")[2:]
42
43 if not parts:
44 return
45
46 machine_id = parts[0]
47 assert machine_id.startswith("machine-"), "Invalid machine: " + machine_id
48
49 machine_token = yield policy.get_token(machine_id)
50 provider_token = yield policy.get_token("juju-provider")
51 returnValue([
52 make_ace(machine_token, read=True, write=True, create=True),
53 make_ace(provider_token, read=True, write=True)])
54
55
56@inlineCallbacks
57def _get_service_data(policy, relation_id):
58 """Discover service information for a relation
59
60 :param policy: A :class:SecurityPolicy instance
61 :param relation_id: The internal relation id
62
63 Returns a list of dictionaries containing service relation data
64 and a the service token.
65 """
66 topology = yield policy.get_topology()
67 services = topology.get_relation_services(relation_id)
68 results = []
69
70 for service_id, service_data in services.items():
71 service_data = dict(service_data)
72 service_data["service_id"] = service_id
73 service_data["token"] = yield policy.get_token(service_id)
74 results.append(service_data)
75 returnValue(results)
76
77
78@inlineCallbacks
79def relation_rule(policy, path):
80 """An ACL policy rule for nodes under the '/relations' path.
81 """
82 if not path.startswith("/relations/"):
83 return
84
85 parts = path.split("/")[2:]
86
87 # Extract relation-id
88 relation_id = parts.pop(0)
89 assert relation_id.startswith("relation-"), "Invalid Rel: " + relation_id
90
91 # Retrieve data for services in the relation
92 service_data = yield _get_service_data(policy, relation_id)
93 service_tokens = [data["token"] for data in service_data]
94
95 # The relation itself /relations/relation-id
96 if not parts:
97 returnValue(map(lambda x: make_ace(x, read=True), service_tokens))
98
99 container = parts.pop(0)
100
101 # If its just the container /relations/relation-id/(settings|role)
102 # then allow both services to read from it
103 if not parts:
104 if container in ("settings", "peer"):
105 returnValue(
106 map(lambda x: make_ace(x, create=True, read=True),
107 service_tokens))
108 elif container in ("client", "server"):
109 matched_service = [data for data in service_data \
110 if data["role"] == container].pop()
111 related_service = [data for data in service_data \
112 if data != matched_service].pop()
113 returnValue(
114 [make_ace(matched_service["token"], read=True, create=True),
115 make_ace(related_service["token"], read=True)])
116 else:
117 # Allow read access to settings and presence nodes
118 returnValue(
119 map(lambda x: make_ace(x, read=True), service_tokens))
120
121
122@inlineCallbacks
123def service_rule(policy, path):
124 """An ACL rule for the /services hierarchy."""
125
126 if not path.startswith("/services/"):
127 return
128
129 parts = path.split("/")[2:]
130 service_id = parts.pop(0)
131 assert service_id.startswith("service-"), "Invalid Service: " + service_id
132
133 service_token = yield policy.get_token(service_id)
134
135 if not parts or parts[0] != "exposed":
136 # Default, give read access to the service group principal.
137 returnValue([make_ace(service_token, read=True)])
138 elif parts[0] == "exposed":
139 provider_token = yield policy.get_token("juju-provider")
140 returnValue([make_ace(provider_token, read=True),
141 make_ace(service_token, read=True, write=True)])
142
143
144@inlineCallbacks
145def unit_rule(policy, path):
146 """An ACL generator rule for the '/units' subtree."""
147
148 if not path.startswith("/units/"):
149 return
150
151 parts = path.split("/")[2:]
152 unit_id = parts.pop(0)
153 assert unit_id.startswith("unit-"), "Invalid Unit: " + unit_id
154
155 unit_token = yield policy.get_token(unit_id)
156
157 if not parts or parts[0] != "ports":
158 # Unit agent can modify tweak any unitother unit settings.
159 returnValue([make_ace(unit_token, all=True)])
160
161 elif parts[0] == "ports":
162 # Created by the unit agent, read by the provisioning agent.
163 provider_token = yield policy.get_token("juju-provider")
164 returnValue([
165 make_ace(unit_token, all=True),
166 make_ace(provider_token, read=True)])
167
168
169@inlineCallbacks
170def global_rule(policy, path):
171 """An ACL for top-level nodes like '/topology' and '/environment'
172 """
173 if path == "/environment":
174 agent_token = yield policy.token_db.get_token("juju-provider")
175 # Only the admin cli can write to this.
176 returnValue([make_ace(agent_token, read=True)])
177 elif path == "/topology":
178 # Only the admin cli can write to this.
179 returnValue([make_ace("anyone", "world", read=True)])
180 elif path == "/auth-tokens":
181 # Any connection that creates another user needs to modify this
182 returnValue([make_ace("anyone", "world", read=True, write=True)])
183
184
185DEFAULT_RULES = [
186 global_rule,
187 formula_rule,
188 relation_rule,
189 machine_rule,
190 service_rule,
191 unit_rule
192 ]
1193
2194
3def get_default_rules():195def get_default_rules():
4196
=== modified file 'juju/state/tests/common.py'
--- juju/state/tests/common.py 2012-08-10 11:01:23 +0000
+++ juju/state/tests/common.py 2012-08-10 11:01:24 +0000
@@ -1,18 +1,22 @@
1from twisted.internet.defer import inlineCallbacks, returnValue1from twisted.internet.defer import inlineCallbacks, returnValue
22
3import zookeeper3import zookeeper
4import os
54
5<<<<<<< TREE
6from txzookeeper.tests.utils import deleteTree6from txzookeeper.tests.utils import deleteTree
77
8=======
9>>>>>>> MERGE-SOURCE
8from juju.charm.directory import CharmDirectory10from juju.charm.directory import CharmDirectory
9from juju.charm.tests.test_directory import sample_directory11from juju.charm.tests.test_directory import sample_directory
10from juju.environment.tests.test_config import EnvironmentsConfigTestBase12from juju.environment.tests.test_config import EnvironmentsConfigTestBase
11from juju.state.topology import InternalTopology13from juju.state.topology import InternalTopology
14<<<<<<< TREE
15=======
1216
13from juju.state.auth import make_ace17from juju.state.auth import make_ace
14from juju.state.security import (18from juju.state.security import Principal, OTPPrincipal
15 Principal, OTPPrincipal, TokenDatabase, set_domain_security)19>>>>>>> MERGE-SOURCE
1620
1721
18class StateTestBase(EnvironmentsConfigTestBase):22class StateTestBase(EnvironmentsConfigTestBase):
@@ -31,35 +35,22 @@
31 yield self.client.create("/units")35 yield self.client.create("/units")
32 yield self.client.create("/relations")36 yield self.client.create("/relations")
33 yield self.client.create("/otp")37 yield self.client.create("/otp")
3438 yield self.setup_security()
35 ## Default this to off, as it causes a significant (30%)
36 ## impact on total test time.
37 if 1: #os.environ.get("TEST_ENSEMBLE_SECURITY"):
38 yield self.setup_security()
39 else:
40 # Can be set globally to off, individual tests can be marked
41 # with a security decorator, or invoke setup_security
42 # if they want to enable security for an individual test.
43 self._security_enabled = False
44 set_domain_security(False)
45
46 @property
47 def security_enabled(self):
48 return self._security_enabled
4939
50 @inlineCallbacks40 @inlineCallbacks
51 def tearDown(self):41 def tearDown(self):
52 # Close and reopen connection, so that watches set during42 # Close and reopen connection, so that watches set during
53 # testing are not affected by the cleaning up.43 # testing are not affected by the cleaning up.
54 self.client.close()44 self.client.close()
55 client = self.get_zookeeper_client()45<<<<<<< TREE
46 client = self.get_zookeeper_client()
47=======
48
49 client = self.get_zookeeper_client()
50>>>>>>> MERGE-SOURCE
56 yield client.connect()51 yield client.connect()
5752 yield Principal("testadmin", "testadmin").attach(client)
58 if self._security_enabled:53 yield deleteTree(client)
59 yield Principal("testadmin", "testadmin").attach(client)
60 yield client.exists("/")
61
62 deleteTree(handle=client.handle)
63 client.close()54 client.close()
64 yield super(StateTestBase, self).tearDown()55 yield super(StateTestBase, self).tearDown()
6556
@@ -88,20 +79,19 @@
88 principal = Principal("testadmin", "testadmin")79 principal = Principal("testadmin", "testadmin")
89 token = principal.get_token()80 token = principal.get_token()
90 OTPPrincipal.set_additional_otp_ace(make_ace(token, all=True))81 OTPPrincipal.set_additional_otp_ace(make_ace(token, all=True))
82
91 # Rules application expects 'admin' principal83 # Rules application expects 'admin' principal
92 yield TokenDatabase(self.client).add(Principal("admin", "admin"))84 yield Principal("admin", "admin").create(self.client)
85
93 # Remove the test otp ace, post test86 # Remove the test otp ace, post test
94 self.addCleanup(lambda: OTPPrincipal.set_additional_otp_ace(None))87 self.addCleanup(lambda: OTPPrincipal.set_additional_otp_ace(None))
95 self._security_enabled = True88
96 set_domain_security(True)89
9790@inlineCallbacks
9891def deleteTree(client, path="/"):
99def security_test(test_method):92 for child in (yield client.get_children(path)):
10093 if child == "zookeeper":
101 @inlineCallbacks94 continue
102 def run_security_enabled_test(self, *args, **kw):95 child_path = "/" + ("%s/%s" % (path, child)).strip("/")
103 if not self.security_enabled:96 yield deleteTree(client, child_path)
104 yield self.setup_security()97 yield client.delete(child_path)
105 yield test_method(self, *args, **kw)
106
107 return run_security_enabled_test
10898
=== modified file 'juju/state/tests/test_agent.py'
--- juju/state/tests/test_agent.py 2012-08-10 11:01:23 +0000
+++ juju/state/tests/test_agent.py 2012-08-10 11:01:24 +0000
@@ -1,7 +1,4 @@
1import zookeeper
2
3from twisted.internet.defer import inlineCallbacks1from twisted.internet.defer import inlineCallbacks
4from txzookeeper.tests.utils import deleteTree
52
6from juju.state.base import StateBase3from juju.state.base import StateBase
7from juju.state.agent import AgentStateMixin4from juju.state.agent import AgentStateMixin
@@ -24,14 +21,6 @@
24 yield self.client.connect()21 yield self.client.connect()
2522
26 @inlineCallbacks23 @inlineCallbacks
27 def tearDown(self):
28 yield self.client.close()
29 client = self.get_zookeeper_client()
30 yield client.connect()
31 deleteTree("/", client.handle)
32 yield client.close()
33
34 @inlineCallbacks
35 def test_has_agent(self):24 def test_has_agent(self):
36 domain = DomainObject(self.client)25 domain = DomainObject(self.client)
37 exists = yield domain.has_agent()26 exists = yield domain.has_agent()
3827
=== modified file 'juju/state/tests/test_auth.py'
--- juju/state/tests/test_auth.py 2011-09-15 18:50:23 +0000
+++ juju/state/tests/test_auth.py 2012-08-10 11:01:24 +0000
@@ -14,7 +14,7 @@
1414
15 credentials = "%s:%s" % (username, password)15 credentials = "%s:%s" % (username, password)
1616
17 identity = "%s:%s" %(17 identity = "%s:%s" % (
18 username,18 username,
19 base64.b64encode(hashlib.new("sha1", credentials).digest()))19 base64.b64encode(hashlib.new("sha1", credentials).digest()))
20 self.assertEqual(identity, make_identity(credentials))20 self.assertEqual(identity, make_identity(credentials))
@@ -25,7 +25,7 @@
2525
26 credentials = "%s:%s" % (username, password)26 credentials = "%s:%s" % (username, password)
2727
28 identity = "%s:%s" %(28 identity = "%s:%s" % (
29 username,29 username,
30 base64.b64encode(hashlib.new("sha1", credentials).digest()))30 base64.b64encode(hashlib.new("sha1", credentials).digest()))
31 self.assertEqual(identity, make_identity(credentials))31 self.assertEqual(identity, make_identity(credentials))
@@ -41,7 +41,7 @@
41 self.assertEqual(ace["id"], identity)41 self.assertEqual(ace["id"], identity)
42 self.assertEqual(ace["scheme"], "digest")42 self.assertEqual(ace["scheme"], "digest")
43 self.assertEqual(43 self.assertEqual(
44 ace["perms"], zookeeper.PERM_WRITE|zookeeper.PERM_CREATE)44 ace["perms"], zookeeper.PERM_WRITE | zookeeper.PERM_CREATE)
4545
46 def test_make_ace_with_unknown_perm(self):46 def test_make_ace_with_unknown_perm(self):
47 identity = "admin:moss"47 identity = "admin:moss"
4848
=== modified file 'juju/state/tests/test_base.py'
--- juju/state/tests/test_base.py 2012-08-10 11:01:23 +0000
+++ juju/state/tests/test_base.py 2012-08-10 11:01:24 +0000
@@ -112,7 +112,7 @@
112112
113 def watch_topology(old_topology, new_topology):113 def watch_topology(old_topology, new_topology):
114 calls.append((old_topology, new_topology))114 calls.append((old_topology, new_topology))
115 wait_callback[len(calls)-1].callback(True)115 wait_callback[len(calls) - 1].callback(True)
116116
117 # Start watching.117 # Start watching.
118 self.base._watch_topology(watch_topology)118 self.base._watch_topology(watch_topology)
@@ -169,7 +169,7 @@
169169
170 def watch_topology(old_topology, new_topology):170 def watch_topology(old_topology, new_topology):
171 calls.append((old_topology, new_topology))171 calls.append((old_topology, new_topology))
172 wait_callback[len(calls)-1].callback(True)172 wait_callback[len(calls) - 1].callback(True)
173173
174 # Start watching, and wait on callback immediately.174 # Start watching, and wait on callback immediately.
175 self.base._watch_topology(watch_topology)175 self.base._watch_topology(watch_topology)
@@ -219,7 +219,7 @@
219219
220 def watch_topology(old_topology, new_topology):220 def watch_topology(old_topology, new_topology):
221 calls.append((old_topology, new_topology))221 calls.append((old_topology, new_topology))
222 wait_callback[len(calls)-1].callback(True)222 wait_callback[len(calls) - 1].callback(True)
223223
224 # Start watching, and wait on callback immediately.224 # Start watching, and wait on callback immediately.
225 self.base._watch_topology(watch_topology)225 self.base._watch_topology(watch_topology)
@@ -265,8 +265,8 @@
265265
266 def watch_topology(old_topology, new_topology):266 def watch_topology(old_topology, new_topology):
267 calls.append((old_topology, new_topology))267 calls.append((old_topology, new_topology))
268 wait_callback[len(calls)-1].callback(True)268 wait_callback[len(calls) - 1].callback(True)
269 return finish_callback[len(calls)-1]269 return finish_callback[len(calls) - 1]
270270
271 # Start watching.271 # Start watching.
272 self.base._watch_topology(watch_topology)272 self.base._watch_topology(watch_topology)
@@ -313,7 +313,7 @@
313313
314 def watcher(old_topology, new_topology):314 def watcher(old_topology, new_topology):
315 calls.append((old_topology, new_topology))315 calls.append((old_topology, new_topology))
316 wait_callback[len(calls)-1].callback(True)316 wait_callback[len(calls) - 1].callback(True)
317 if len(calls) == 2:317 if len(calls) == 2:
318 raise StopWatcher()318 raise StopWatcher()
319319
@@ -371,7 +371,7 @@
371 topology = InternalTopology()371 topology = InternalTopology()
372 topology.add_machine("m-0")372 topology.add_machine("m-0")
373 yield self.set_topology(topology)373 yield self.set_topology(topology)
374 374
375 # Hold off until callback is started.375 # Hold off until callback is started.
376 yield wait_callback376 yield wait_callback
377377
378378
=== modified file 'juju/state/tests/test_environment.py'
--- juju/state/tests/test_environment.py 2012-03-29 01:37:57 +0000
+++ juju/state/tests/test_environment.py 2012-08-10 11:01:24 +0000
@@ -24,10 +24,6 @@
24 self.config.load()24 self.config.load()
2525
26 @inlineCallbacks26 @inlineCallbacks
27 def tearDown(self):
28 yield super(EnvironmentStateManagerTest, self).tearDown()
29
30 @inlineCallbacks
31 def test_set_config_state(self):27 def test_set_config_state(self):
32 """28 """
33 The simplest thing the manager can do is serialize a given29 The simplest thing the manager can do is serialize a given
3430
=== modified file 'juju/state/tests/test_firewall.py'
--- juju/state/tests/test_firewall.py 2012-07-09 22:49:36 +0000
+++ juju/state/tests/test_firewall.py 2012-08-10 11:01:24 +0000
@@ -272,7 +272,7 @@
272 machine_provider, machine.id)272 machine_provider, machine.id)
273 returnValue(provider_ports)273 returnValue(provider_ports)
274274
275 def test_open_close_ports_on_machine(self):275 def xtest_open_close_ports_on_machine(self):
276 """Verify opening/closing ports on a machine works properly.276 """Verify opening/closing ports on a machine works properly.
277277
278 In particular this is done without watch support."""278 In particular this is done without watch support."""
279279
=== modified file 'juju/state/tests/test_initialize.py'
--- juju/state/tests/test_initialize.py 2012-08-10 11:01:23 +0000
+++ juju/state/tests/test_initialize.py 2012-08-10 11:01:24 +0000
@@ -1,15 +1,25 @@
1import zookeeper1import zookeeper
22
3from twisted.internet.defer import inlineCallbacks3from twisted.internet.defer import inlineCallbacks
4<<<<<<< TREE
4from txzookeeper.tests.utils import deleteTree5from txzookeeper.tests.utils import deleteTree
6=======
7from txzookeeper import ZookeeperClient
8#from txzookeeper.tests.utils import deleteTree
9>>>>>>> MERGE-SOURCE
510
6from juju.environment.tests.test_config import EnvironmentsConfigTestBase11from juju.environment.tests.test_config import EnvironmentsConfigTestBase
7from juju.state.auth import make_identity, make_ace12from juju.state.auth import make_identity, make_ace
8from juju.state.environment import (13from juju.state.environment import (
9 GlobalSettingsStateManager, EnvironmentStateManager)14 GlobalSettingsStateManager, EnvironmentStateManager)
10from juju.state.initialize import StateHierarchy15from juju.state.initialize import StateHierarchy
11from juju.state.security import OTPPrincipal, add_user16from juju.state.security import OTPPrincipal, Principal
12from juju.state.machine import MachineStateManager17from juju.state.machine import MachineStateManager
18<<<<<<< TREE
19=======
20from juju.state.tests.common import deleteTree
21from juju.tests.common import get_test_zookeeper_address
22>>>>>>> MERGE-SOURCE
1323
1424
15class LayoutTest(EnvironmentsConfigTestBase):25class LayoutTest(EnvironmentsConfigTestBase):
@@ -28,7 +38,7 @@
28 "provider-type": "dummy"}38 "provider-type": "dummy"}
29 yield self.client.connect()39 yield self.client.connect()
3040
31 self.test_admin = yield add_user(self.client, "admin", "secret")41 self.test_admin = Principal("admin", "secret")
32 yield self.test_admin.attach(self.client)42 yield self.test_admin.attach(self.client)
33 self.identity = self.test_admin.get_token()43 self.identity = self.test_admin.get_token()
3444
@@ -39,8 +49,9 @@
39 self.layout = StateHierarchy(49 self.layout = StateHierarchy(
40 self.client, self.identity, "i-abcdef", constraints_data, "dummy")50 self.client, self.identity, "i-abcdef", constraints_data, "dummy")
4151
52 @inlineCallbacks
42 def tearDown(self):53 def tearDown(self):
43 deleteTree(handle=self.client.handle)54 yield deleteTree(self.client)
44 self.client.close()55 self.client.close()
4556
46 @inlineCallbacks57 @inlineCallbacks
4758
=== modified file 'juju/state/tests/test_security.py'
--- juju/state/tests/test_security.py 2012-08-10 11:01:23 +0000
+++ juju/state/tests/test_security.py 2012-08-10 11:01:24 +0000
@@ -14,12 +14,13 @@
1414
15from juju.state.topology import InternalTopology15from juju.state.topology import InternalTopology
16from juju.lib.testing import TestCase16from juju.lib.testing import TestCase
17from juju.state.tests.common import StateTestBase, security_test17from juju.state.tests.common import StateTestBase, deleteTree
18from juju.tests.common import get_test_zookeeper_address18from juju.tests.common import get_test_zookeeper_address
1919
20
20from txzookeeper.client import ZOO_OPEN_ACL_UNSAFE21from txzookeeper.client import ZOO_OPEN_ACL_UNSAFE
2122
22from txzookeeper.tests.utils import deleteTree23#from txzookeeper.tests.utils import deleteTree
2324
2425
25class PrincipalTests(TestCase):26class PrincipalTests(TestCase):
@@ -29,8 +30,9 @@
29 zookeeper.set_debug_level(0)30 zookeeper.set_debug_level(0)
30 self.client = yield self.get_zookeeper_client().connect()31 self.client = yield self.get_zookeeper_client().connect()
3132
33 @inlineCallbacks
32 def tearDown(self):34 def tearDown(self):
33 deleteTree(handle=self.client.handle)35 yield deleteTree(self.client)
34 self.client.close()36 self.client.close()
3537
36 def test_name(self):38 def test_name(self):
@@ -81,8 +83,9 @@
81 zookeeper.set_debug_level(0)83 zookeeper.set_debug_level(0)
82 self.client = yield self.get_zookeeper_client().connect()84 self.client = yield self.get_zookeeper_client().connect()
8385
86 @inlineCallbacks
84 def tearDown(self):87 def tearDown(self):
85 deleteTree(handle=self.client.handle)88 yield deleteTree(self.client)
86 self.client.close()89 self.client.close()
8790
88 def test_uninitialized_usage(self):91 def test_uninitialized_usage(self):
@@ -209,8 +212,9 @@
209 self.client = yield self.get_zookeeper_client().connect()212 self.client = yield self.get_zookeeper_client().connect()
210 self.client.create("/otp")213 self.client.create("/otp")
211214
215 @inlineCallbacks
212 def tearDown(self):216 def tearDown(self):
213 deleteTree(handle=self.client.handle)217 yield deleteTree(self.client)
214 self.client.close()218 self.client.close()
215219
216 def set_otp_test_ace(self, test_ace=ZOO_OPEN_ACL_UNSAFE):220 def set_otp_test_ace(self, test_ace=ZOO_OPEN_ACL_UNSAFE):
@@ -304,6 +308,27 @@
304 children = yield self.client.get_children("/otp")308 children = yield self.client.get_children("/otp")
305 self.assertFalse(children)309 self.assertFalse(children)
306310
311 @inlineCallbacks
312 def test_create_and_store(self):
313 """Creates an otp principal and stores it serialization at path.
314 """
315 yield OTPPrincipal(self.client).create(
316 "aladdin", store="/zoo")
317
318 # Verify the otp serialization on the node
319 contents, stat = yield self.client.get("/zoo")
320 data = yaml.load(contents)
321 self.assertIn("otp-identity", data)
322
323 # Compare the persitsent user generated token to that in the tokendb
324 creds = yield OTPPrincipal.consume(self.client, path="/zoo")
325
326 token = yield TokenDatabase(self.client).get("aladdin")
327 self.assertEqual(
328 token, make_identity(creds))
329
330 self.assertTrue((yield self.client.exists("/zoo")))
331
307332
308class TokenDatabaseTest(TestCase):333class TokenDatabaseTest(TestCase):
309334
@@ -313,8 +338,9 @@
313 self.client = yield self.get_zookeeper_client().connect()338 self.client = yield self.get_zookeeper_client().connect()
314 self.db = TokenDatabase(self.client, "/token-test")339 self.db = TokenDatabase(self.client, "/token-test")
315340
341 @inlineCallbacks
316 def tearDown(self):342 def tearDown(self):
317 deleteTree(handle=self.client.handle)343 yield deleteTree(self.client)
318 self.client.close()344 self.client.close()
319345
320 @inlineCallbacks346 @inlineCallbacks
@@ -359,8 +385,9 @@
359 yield self.tokens.add(Principal("admin", "admin"))385 yield self.tokens.add(Principal("admin", "admin"))
360 self.policy = SecurityPolicy(self.client, self.tokens)386 self.policy = SecurityPolicy(self.client, self.tokens)
361387
388 @inlineCallbacks
362 def tearDown(self):389 def tearDown(self):
363 deleteTree(handle=self.client.handle)390 yield deleteTree(self.client)
364 self.client.close()391 self.client.close()
365392
366 @inlineCallbacks393 @inlineCallbacks
@@ -483,8 +510,9 @@
483 self.policy = SecurityPolicy(self.client, self.token_db, owner=admin)510 self.policy = SecurityPolicy(self.client, self.token_db, owner=admin)
484 yield admin.attach(self.client)511 yield admin.attach(self.client)
485512
513 @inlineCallbacks
486 def tearDown(self):514 def tearDown(self):
487 deleteTree(handle=self.client.handle)515 yield deleteTree(self.client)
488 self.client.close()516 self.client.close()
489517
490 @inlineCallbacks518 @inlineCallbacks
@@ -529,8 +557,9 @@
529 self.policy = SecurityPolicy(self.client, self.tokens)557 self.policy = SecurityPolicy(self.client, self.tokens)
530 yield self.admin.attach(self.client)558 yield self.admin.attach(self.client)
531559
560 @inlineCallbacks
532 def tearDown(self):561 def tearDown(self):
533 deleteTree(handle=self.client.handle)562 yield deleteTree(self.client)
534 self.client.close()563 self.client.close()
535564
536 @inlineCallbacks565 @inlineCallbacks
@@ -647,7 +676,6 @@
647 Test of the top level conviencce functions in the security module.676 Test of the top level conviencce functions in the security module.
648 """677 """
649678
650 @security_test
651 @inlineCallbacks679 @inlineCallbacks
652 def test_apply_rules(self):680 def test_apply_rules(self):
653 """Calculates and sets an acl using the security rules.681 """Calculates and sets an acl using the security rules.
@@ -676,101 +704,3 @@
676 acl,704 acl,
677 [make_ace(token, all=True),705 [make_ace(token, all=True),
678 make_ace(cli_group.get_token(), all=True)])706 make_ace(cli_group.get_token(), all=True)])
679
680
681class DomainSecurityTest(object):
682
683 @inlineCallbacks
684 def setUp(self):
685 yield super(DomainSecurityTest, self).setUp()
686 path = yield self.client.create("/zoo")
687 self.security = NodeSecurity(self.client, path)
688
689 @inlineCallbacks
690 def test_add_group(self):
691 """Creates a group principal and adds it to the token db.
692 """
693 self.security.enabled = False
694 group = yield self.security.add_group("group-a")
695 self.assertEqual(group, None)
696 self.assertFalse((yield self.client.exists("/zoo/group")))
697
698 self.security.enabled = True
699 group = yield self.security.add_group("group-a")
700 self.assertTrue(isinstance(group, GroupPrincipal))
701 token = yield TokenDatabase(self.client).get("group-a")
702 self.assertEqual(token, group.get_token())
703
704 @security_test
705 @inlineCallbacks
706 def test_consume_otp(self):
707 """OTP can be consumed to retrieve the persistent principal."""
708
709 yield self.assertFailure(
710 self.security.consume_otp(), PrincipalNotFound)
711
712 self.security.enabled = False
713 self.assertEqual((yield self.security.consume_otp()), None)
714
715 self.security.enabled = True
716 yield self.security.apply_otp("aladdin")
717 principal = yield self.security.consume_otp()
718 self.assertEqual(principal.name, "aladdin")
719 token = yield TokenDatabase(self.client).get("aladdin")
720 self.assertEqual(principal.get_token(), token)
721 self.assertFalse(
722 (yield self.client.get_children("/otp")))
723
724 @security_test
725 @inlineCallbacks
726 def test_apply_otp(self):
727 """Creates an otp principal and stores it serialization at path.
728 """
729 self.security.enabled = False
730 yield self.security.apply_otp("aladdin")
731
732 # Verify the otp serialization on the node
733 contents, stat = yield self.client.get("/zoo")
734 data = yaml.load(contents)
735 self.assertFalse(data)
736
737 self.security.enabled = True
738 yield self.security.apply_otp("aladdin")
739
740 # Verify the otp serialization on the node
741 contents, stat = yield self.client.get("/zoo")
742 data = yaml.load(contents)
743 self.assertIn("otp-identity", data)
744
745 # Compare the persitsent user generated token to that in the tokendb
746 user, password = yield OTPPrincipal.consume(
747 self.client, data["otp-identity"])
748 token = yield TokenDatabase(self.client).get("aladdin")
749 self.assertEqual(
750 token, make_identity("%s:%s" % (user, password)))
751
752 @inlineCallbacks
753 def test_add_remove_group_member(self):
754 """Adding and removing members of a security group."""
755 self.security.enabled = True
756 group = yield self.security.add_group("group-a")
757
758 self.security.enabled = False
759 yield self.security.add_group_member(Principal("music", "match"))
760 members = yield group.get_members()
761 self.assertNotIn("music", members)
762
763 self.security.enabled = True
764 yield self.security.add_group_member(Principal("music", "match"))
765 members = yield group.get_members()
766 self.assertIn("music", members)
767
768 self.security.enabled = False
769 yield self.security.remove_group_member("music")
770 members = yield group.get_members()
771 self.assertIn("music", members)
772
773 self.security.enabled = True
774 yield self.security.remove_group_member("music")
775 members = yield group.get_members()
776 self.assertNotIn("music", members)
777707
=== added file 'juju/state/tests/test_securityrules.py'
--- juju/state/tests/test_securityrules.py 1970-01-01 00:00:00 +0000
+++ juju/state/tests/test_securityrules.py 2012-08-10 11:01:24 +0000
@@ -0,0 +1,223 @@
1from twisted.internet.defer import inlineCallbacks
2
3from juju.state.auth import make_ace
4from juju.state.security import Principal, TokenDatabase, SecurityPolicy
5from juju.state import securityrules as rules
6from juju.state.topology import InternalTopology
7
8from juju.state.tests.common import StateTestBase
9
10
11class RulesTest(StateTestBase):
12
13 @inlineCallbacks
14 def setUp(self):
15 yield super(RulesTest, self).setUp()
16 self.principals = {
17 "admin": Principal("admin", "admin"),
18 "provider/0": Principal("provider/0", "provisioning-agent"),
19 "juju-provider": Principal(
20 "juju-provider", "provisioning-group"),
21 "machine/0": Principal("machine/0", "machine-agent"),
22 "service/0": Principal("service/0", "unit-agent"),
23 "service": Principal("service", "service-group")
24 }
25 self.token_db = TokenDatabase(self.client)
26
27 for p in self.principals.values():
28 yield self.token_db.add(p)
29
30 self.policy = SecurityPolicy(self.client, self.token_db)
31
32 @inlineCallbacks
33 def test_formula_rule(self):
34 self.assertEqual(
35 (yield rules.formula_rule(self.policy, "/abc")),
36 None)
37 self.assertEqual(
38 (yield rules.formula_rule(self.policy, "/formulas/formula-0")),
39 [make_ace("anyone", "world", read=True)])
40
41 @inlineCallbacks
42 def test_machine_rule(self):
43 yield self.token_db.add(Principal("machine-1", ""))
44
45 self.assertEqual(
46 (yield rules.machine_rule(self.policy, "/xyz")),
47 None)
48 self.assertEqual(
49 (yield rules.machine_rule(self.policy, "/machines")),
50 None)
51 self.assertEqual(
52 (yield rules.machine_rule(self.policy, "/machines/machine-1")),
53 [make_ace(Principal("machine-1", "").get_token(),
54 read=True, write=True, create=True),
55 make_ace(self.principals["juju-provider"].get_token(),
56 read=True, write=True)])
57
58 @inlineCallbacks
59 def test_relation_rule_client_server(self):
60 topology = InternalTopology()
61 topology.add_service("service-0000001", "mysql")
62 topology.add_service("service-0000002", "wordpress")
63 topology.add_relation("relation-0000001", "database")
64 topology.assign_service_to_relation(
65 "relation-0000001", "service-0000001", "app", "server")
66 topology.assign_service_to_relation(
67 "relation-0000001", "service-0000002", "db", "client")
68
69 mysql_principal = Principal("service-0000001", "mysql")
70 wordpress_principal = Principal("service-0000002", "wordpress")
71 yield self.token_db.add(mysql_principal)
72 yield self.token_db.add(wordpress_principal)
73
74 yield self.client.create("/topology", topology.dump())
75
76 self.assertEqual(
77 (yield rules.relation_rule(self.policy, "/xyz")),
78 None)
79
80 self.assertEqual(
81 (yield rules.relation_rule(self.policy, "/relations")),
82 None)
83
84 self.assertEqual(
85 (yield rules.relation_rule(
86 self.policy, "/relations/relation-0000001/settings/unit-x")),
87 [make_ace(mysql_principal.get_token(), read=True),
88 make_ace(wordpress_principal.get_token(), read=True)]
89 )
90
91 self.assertEqual(
92 (yield rules.relation_rule(
93 self.policy, "/relations/relation-0000001/client/unit-x")),
94 [make_ace(mysql_principal.get_token(), read=True),
95 make_ace(wordpress_principal.get_token(), read=True)]
96 )
97
98 self.assertEqual(
99 (yield rules.relation_rule(
100 self.policy, "/relations/relation-0000001/server")),
101 [make_ace(mysql_principal.get_token(), create=True, read=True),
102 make_ace(wordpress_principal.get_token(), read=True)]
103 )
104
105 self.assertEqual(
106 (yield rules.relation_rule(
107 self.policy, "/relations/relation-0000001/client")),
108 [make_ace(wordpress_principal.get_token(), create=True, read=True),
109 make_ace(mysql_principal.get_token(), read=True)]
110 )
111
112 @inlineCallbacks
113 def test_relation_rule_peer(self):
114 topology = InternalTopology()
115 topology.add_service("service-0000001", "riak")
116 topology.add_relation("relation-0000001", "ring")
117 topology.assign_service_to_relation(
118 "relation-0000001", "service-0000001", "ring", "peer")
119 riak_principal = Principal("service-0000001", "riak")
120 yield self.token_db.add(riak_principal)
121 yield self.client.create("/topology", topology.dump())
122
123 self.assertEqual(
124 (yield rules.relation_rule(
125 self.policy, "/relations/relation-0000001/settings/unit-x")),
126 [make_ace(riak_principal.get_token(), read=True)]
127 )
128
129 self.assertEqual(
130 (yield rules.relation_rule(
131 self.policy, "/relations/relation-0000001/peer/unit-x")),
132 [make_ace(riak_principal.get_token(), read=True)]
133 )
134
135 self.assertEqual(
136 (yield rules.relation_rule(
137 self.policy, "/relations/relation-0000001/peer")),
138 [make_ace(riak_principal.get_token(), create=True, read=True)]
139 )
140
141 @inlineCallbacks
142 def test_global_rule(self):
143 self.assertEqual(
144 (yield rules.global_rule(self.policy, "/xyz")),
145 None)
146 self.assertEqual(
147 (yield rules.global_rule(self.policy, "/topology")),
148 [make_ace("anyone", "world", read=True)])
149
150 self.assertEqual(
151 (yield rules.global_rule(self.policy, "/auth-tokens")),
152 [make_ace("anyone", "world", write=True, read=True)])
153
154 @inlineCallbacks
155 def test_service_rule(self):
156
157 topology = InternalTopology()
158 topology.add_service("service-0000001", "riak")
159 topology.add_service_unit("service-0000001", "unit-0000001")
160 service_principal = Principal("service-0000001", "")
161
162 yield self.token_db.add(service_principal)
163 yield self.client.create("/topology", topology.dump())
164
165 self.assertEqual(
166 (yield rules.service_rule(self.policy, "/abc")),
167 None)
168
169 self.assertEqual(
170 (yield rules.service_rule(self.policy, "/services")),
171 None)
172
173 self.assertEqual(
174 (yield rules.service_rule(
175 self.policy, "/services/service-0000001")),
176 [make_ace(service_principal.get_token(), read=True)]
177 )
178
179 self.assertEqual(
180 (yield rules.service_rule(
181 self.policy, "/services/service-0000001/config")),
182 [make_ace(
183 service_principal.get_token(), read=True)]
184 )
185
186 self.assertEqual(
187 (yield rules.service_rule(
188 self.policy, "/services/service-0000001/exposed")),
189 [make_ace(
190 self.principals["juju-provider"].get_token(), read=True),
191 make_ace(service_principal.get_token(), read=True, write=True)]
192 )
193
194 @inlineCallbacks
195 def test_unit_rule(self):
196
197 topology = InternalTopology()
198 topology.add_service("service-0000001", "riak")
199 topology.add_service_unit("service-0000001", "unit-0000001")
200 yield self.client.create("/topology", topology.dump())
201
202 riak_unit = Principal("unit-0000001", "abc")
203 yield self.token_db.add(riak_unit)
204
205 self.assertEqual(
206 (yield rules.unit_rule(self.policy, "/xyz")), None)
207
208 self.assertEqual(
209 (yield rules.unit_rule(self.policy, "/units")), None)
210
211 self.assertEqual(
212 (yield rules.unit_rule(self.policy, "/units/unit-0000001")),
213 [make_ace(riak_unit.get_token(), all=True)])
214
215 self.assertEqual(
216 (yield rules.unit_rule(self.policy, "/units/unit-0000001/ports")),
217 [make_ace(riak_unit.get_token(), all=True),
218 make_ace(
219 self.principals["juju-provider"].get_token(), read=True)])
220
221 self.assertEqual(
222 (yield rules.unit_rule(self.policy, "/units/unit-0000001/debug")),
223 [make_ace(riak_unit.get_token(), all=True)])
0224
=== modified file 'juju/state/tests/test_service.py'
--- juju/state/tests/test_service.py 2012-08-10 11:01:23 +0000
+++ juju/state/tests/test_service.py 2012-08-10 11:01:24 +0000
@@ -15,11 +15,14 @@
15from juju.lib.pick import pick_attr15from juju.lib.pick import pick_attr
16from juju.state.charm import CharmStateManager16from juju.state.charm import CharmStateManager
17from juju.charm.tests.test_repository import unbundled_repository17from juju.charm.tests.test_repository import unbundled_repository
18from juju.state.auth import make_identity
18from juju.state.endpoint import RelationEndpoint19from juju.state.endpoint import RelationEndpoint
19from juju.state.service import (20from juju.state.service import (
20 ServiceStateManager, NO_HOOKS, RETRY_HOOKS, parse_service_name)21 ServiceStateManager, NO_HOOKS, RETRY_HOOKS, parse_service_name)
21from juju.state.machine import MachineStateManager22from juju.state.machine import MachineStateManager
22from juju.state.relation import RelationStateManager23from juju.state.relation import RelationStateManager
24from juju.state.security import GroupPrincipal
25
23from juju.state.utils import YAMLState26from juju.state.utils import YAMLState
24from juju.state.errors import (27from juju.state.errors import (
25 StateChanged, ServiceStateNotFound, ServiceUnitStateNotFound,28 StateChanged, ServiceStateNotFound, ServiceUnitStateNotFound,
@@ -191,6 +194,17 @@
191 "service-0000000001")194 "service-0000000001")
192195
193 @inlineCallbacks196 @inlineCallbacks
197 def test_add_service_security(self):
198 """Adding a service state also creates a corresponding service group.
199 """
200 yield self.service_state_manager.add_service_state(
201 "wordpress", self.charm_state, dummy_constraints)
202 acl, stat = yield self.client.get_acl("/services/service-0000000000")
203 self.assertACE(acl, make_identity("admin:admin"))
204 self.assertTrue(
205 (yield self.client.exists("/services/service-0000000000/group")))
206
207 @inlineCallbacks
194 def test_add_service_with_duplicated_name(self):208 def test_add_service_with_duplicated_name(self):
195 """209 """
196 If a service is added with a duplicated name, a meaningful210 If a service is added with a duplicated name, a meaningful
@@ -234,24 +248,42 @@
234 else:248 else:
235 self.fail("Error not raised")249 self.fail("Error not raised")
236250
251 @inlineCallbacks
237 def test_get_unit_state(self):252 def test_get_unit_state(self):
238 """A unit state can be retrieved by name from the service manager."""253 """A unit state can be retrieved by name from the service manager."""
254<<<<<<< TREE
239 self.assertFailure(self.service_state_manager.get_unit_state(255 self.assertFailure(self.service_state_manager.get_unit_state(
240 "wordpress/1"), ServiceStateNotFound)256 "wordpress/1"), ServiceStateNotFound)
257=======
258 yield self.assertFailure(self.service_state_manager.get_unit_state(
259 "wordpress/1"), ServiceStateNotFound)
260>>>>>>> MERGE-SOURCE
241261
262<<<<<<< TREE
242 self.assertFailure(self.service_state_manager.get_unit_state(263 self.assertFailure(self.service_state_manager.get_unit_state(
243 "wordpress1"), ServiceUnitStateNotFound)264 "wordpress1"), ServiceUnitStateNotFound)
244265
266=======
267>>>>>>> MERGE-SOURCE
245 wordpress_state = yield self.service_state_manager.add_service_state(268 wordpress_state = yield self.service_state_manager.add_service_state(
246 "wordpress", self.charm_state, dummy_constraints)269 "wordpress", self.charm_state, dummy_constraints)
247270
271<<<<<<< TREE
248 self.assertFailure(self.service_state_manager.get_unit_state(272 self.assertFailure(self.service_state_manager.get_unit_state(
249 "wordpress/1"), ServiceUnitStateNotFound)273 "wordpress/1"), ServiceUnitStateNotFound)
274=======
275 yield self.assertFailure(self.service_state_manager.get_unit_state(
276 "wordpress/0"), ServiceUnitStateNotFound)
277>>>>>>> MERGE-SOURCE
250278
251 wordpress_unit = wordpress_state.add_unit_state()279 wordpress_unit = yield wordpress_state.add_unit_state()
252280
253 unit_state = yield self.service_state_manager.get_unit_state(281 unit_state = yield self.service_state_manager.get_unit_state(
282<<<<<<< TREE
254 "wordpress/1")283 "wordpress/1")
284=======
285 "wordpress/0")
286>>>>>>> MERGE-SOURCE
255287
256 self.assertEqual(unit_state.internal_id, wordpress_unit.internal_id)288 self.assertEqual(unit_state.internal_id, wordpress_unit.internal_id)
257289
@@ -646,6 +678,30 @@
646 exists = yield self.client.exists("/units/%s" % unit_state.internal_id)678 exists = yield self.client.exists("/units/%s" % unit_state.internal_id)
647 self.assertFalse(exists)679 self.assertFalse(exists)
648680
681 @inlineCallbacks
682 def test_remove_service_unit_security(self):
683 """
684 """
685 service_state = yield self.service_state_manager.add_service_state(
686 "wordpress", self.charm_state, dummy_constraints)
687
688 unit_state0 = yield service_state.add_unit_state()
689 unit_state1 = yield service_state.add_unit_state()
690
691 group = GroupPrincipal(
692 self.client, "/services/%s/group" % service_state.internal_id)
693 members = yield group.get_members()
694
695 self.assertTrue(unit_state0.internal_id in members)
696 self.assertTrue(unit_state1.internal_id in members)
697
698 yield service_state.remove_unit_state(unit_state0)
699
700 members = yield group.get_members()
701 self.assertFalse(unit_state0.internal_id in members)
702 self.assertTrue(unit_state1.internal_id in members)
703
704 @inlineCallbacks
649 def test_remove_service_unit_nonexistant(self):705 def test_remove_service_unit_nonexistant(self):
650 """Removing a non existant service unit, is fine."""706 """Removing a non existant service unit, is fine."""
651707
@@ -653,7 +709,9 @@
653 "wordpress", self.charm_state, dummy_constraints)709 "wordpress", self.charm_state, dummy_constraints)
654 unit_state = yield service_state.add_unit_state()710 unit_state = yield service_state.add_unit_state()
655 yield service_state.remove_unit_state(unit_state)711 yield service_state.remove_unit_state(unit_state)
656 yield service_state.remove_unit_state(unit_state)712 yield self.assertFailure(
713 service_state.remove_unit_state(unit_state),
714 StateChanged)
657715
658 @inlineCallbacks716 @inlineCallbacks
659 def test_get_all_service_states(self):717 def test_get_all_service_states(self):

Subscribers

People subscribed via source and target branches

to status/vote changes: