2
Security rules to determine ACL by node path.
6
from twisted.internet.defer import inlineCallbacks, returnValue
8
from ensemble.state.auth import make_ace
9
from ensemble.state.topology import InternalTopology
12
def formula_rule(policy, path):
13
"""An ACL policy rule for nodes under the '/formulas' path.
15
These nodes are read only by any connected user, and are
16
writable only by the admin cli (which creates them).
21
/formula-id (all:admin, r:everyone)
23
if not path.startswith("/formulas"):
25
return [make_ace("anyone", "world", read=True)]
29
def machine_rule(policy, path):
30
"""An ACL policy rule for nodes under the '/machines' path.
32
These nodes are created by the cli admin when launching new machines,
33
with admin access granted to the provisioning agents. When the
34
provisioning agents create a new machine, they also create a new
35
principal and update the machine node ace to allow access to the
36
new machine agent principal.
39
if not path.startswith("/machines"):
42
agent_token = yield policy.get_token("ensemble-provider")
44
# If its a machine state
45
if path.startswith("/machines/machine-"):
46
# The provisioning agent updates the acl with the machine agent.
47
returnValue([make_ace(agent_token, read=True, admin=True)])
48
# If its machine state container
49
elif path == "/machines":
50
# Its readable by the provisioning agent
51
returnValue([make_ace(agent_token, read=True)])
55
def _get_service_data(policy, relation_id):
56
"""Discover service information for a relation
58
:param policy: A :class:SecurityPolicy instance
59
:param relation_id: The internal relation id
61
Returns a list of dictionaries containing service relation data
62
and a the service token.
64
topology_data, stat = yield policy.client.get("/topology")
65
topology = InternalTopology()
66
topology.parse(topology_data)
67
services = topology.get_relation_services(relation_id)
69
for service_id, service_data in services.items():
70
service_data = dict(service_data)
71
service_data["service_id"] = service_id
72
service_name = topology.get_service_name(service_id)
73
service_data["token"] = yield policy.get_token(service_name)
74
results.append(service_data)
79
def relation_rule(policy, path):
80
"""An ACL policy rule for nodes under the '/relations' path.
82
if not path.startswith("/relations"):
85
if path == "/relations":
88
parts = filter(None, path.split("/"))[1:]
91
relation_id = parts.pop(0)
93
# Retrieve data for services in the relation
94
service_data = yield _get_service_data(policy, relation_id)
95
service_tokens = [data["token"] for data in service_data]
97
# The relation itself /relations/relation-id
99
returnValue(map(lambda x: make_ace(x, read=True), service_tokens))
101
container = parts.pop(0)
103
# If its just the container /relations/relation-id/(settings|role)
104
# then allow both services to read from it
106
if container in ("settings", "peer"):
108
map(lambda x: make_ace(x, create=True, read=True),
110
elif container in ("client", "server"):
111
matched_service = [data for data in service_data \
112
if data["role"] == container].pop()
113
related_service = [data for data in service_data \
114
if data != matched_service].pop()
116
[make_ace(matched_service["token"], read=True, create=True),
117
make_ace(related_service["token"], read=True)])
119
# Allow read access to settings and presence nodes
121
map(lambda x: make_ace(x, read=True), service_tokens))
125
def service_rule(policy, path):
126
"""An ACL rule for the /services hierarchy."""
128
if not path.startswith("/services"):
131
if path == "/services":
134
parts = filter(None, path.split("/"))[1:]
135
service_id = parts.pop(0)
140
if parts[0] == "config":
141
topology_data, stat = yield policy.client.get("/topology")
142
topology = InternalTopology()
143
topology.parse(topology_data)
144
service_token = yield policy.get_token(
145
topology.get_service_name(service_id))
146
returnValue([make_ace(service_token, read=True)])
147
elif parts[0] == "exposed":
148
provider_token = yield policy.get_token("ensemble-provider")
149
returnValue([make_ace(provider_token, read=True)])
153
def unit_rule(policy, path):
154
"""An ACL generator rule for the '/units' subtree."""
155
if not path.startswith("/units"):
161
parts = filter(None, path.split("/"))[1:]
162
unit_id = parts.pop(0)
164
topology_data, stat = yield policy.client.get("/topology")
165
topology = InternalTopology()
166
topology.parse(topology_data)
168
unit_name = topology.get_service_unit_name_from_id(unit_id)
169
unit_token = yield policy.get_token(unit_name)
172
# The unit agent needs to write to the node to update the formula post
173
# upgrade. it needs to delete children from the node when clearing
174
# various flags. It needs to create when establishing open ports, or
175
# its agent presence.
176
returnValue([make_ace(
177
unit_token, read=True, write=True, delete=True, create=True)])
179
segment = parts.pop(0)
181
if segment == "ports":
182
# Created by the unit agent, read by the provisioning agent.
183
provider_token = yield policy.get_token("ensemble-provider")
184
returnValue([make_ace(provider_token, read=True)])
185
elif segment in ("upgrade", "debug", "resolved", "relations-resolved"):
186
# Created by the admin, read and deleted by the unit agent.
187
returnValue([make_ace(unit_token, read=True)])
191
def global_rule(policy, path):
192
"""An ACL for top-level nodes like '/topology' and '/environment'
194
if path == "/environment":
195
agent_token = yield policy.token_db.get_token("ensemble-provider")
196
# Only the admin cli can write to this.
197
returnValue([make_ace(agent_token, read=True)])
198
elif path == "/topology":
199
# Only the admin cli can write to this.
200
returnValue([make_ace("auth", "world", read=True)])
201
elif path == "/auth-tokens":
202
# Any connection that creates another user needs to modify this
203
returnValue([make_ace("auth", "world", read=True, write=True)])
216
def get_default_rules():