~hazmat/pyjuju/security-policy-with-topology

« back to all changes in this revision

Viewing changes to ensemble/state/securityrules.py.THIS

  • Committer: kapil.thangavelu at canonical
  • Date: 2012-03-31 03:34:10 UTC
  • mfrom: (316.1.11 states-with-principals)
  • Revision ID: kapil.thangavelu@canonical.com-20120331033410-mj2znr90wnm0j88b
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"""
2
 
Security rules to determine ACL by node path.
3
 
 
4
 
"""
5
 
 
6
 
from twisted.internet.defer import inlineCallbacks, returnValue
7
 
 
8
 
from ensemble.state.auth import make_ace
9
 
from ensemble.state.topology import InternalTopology
10
 
 
11
 
 
12
 
def formula_rule(policy, path):
13
 
    """An ACL policy rule for nodes under the '/formulas' path.
14
 
 
15
 
    These nodes are read only by any connected user, and are
16
 
    writable only by the admin cli (which creates them).
17
 
 
18
 
    Path rule::
19
 
 
20
 
      /formulas
21
 
         /formula-id (all:admin, r:everyone)
22
 
    """
23
 
    if not path.startswith("/formulas"):
24
 
        return
25
 
    return [make_ace("anyone", "world", read=True)]
26
 
 
27
 
 
28
 
@inlineCallbacks
29
 
def machine_rule(policy, path):
30
 
    """An ACL policy rule for nodes under the '/machines' path.
31
 
 
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.
37
 
    """
38
 
 
39
 
    if not path.startswith("/machines"):
40
 
        return
41
 
 
42
 
    agent_token = yield policy.get_token("ensemble-provider")
43
 
 
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)])
52
 
 
53
 
 
54
 
@inlineCallbacks
55
 
def _get_service_data(policy, relation_id):
56
 
    """Discover service information for a relation
57
 
 
58
 
    :param policy: A :class:SecurityPolicy instance
59
 
    :param relation_id: The internal relation id
60
 
 
61
 
    Returns a list of dictionaries containing service relation data
62
 
    and a the service token.
63
 
    """
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)
68
 
    results = []
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)
75
 
    returnValue(results)
76
 
 
77
 
 
78
 
@inlineCallbacks
79
 
def 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
 
    if path == "/relations":
86
 
        return
87
 
 
88
 
    parts = filter(None, path.split("/"))[1:]
89
 
 
90
 
    # Extract relation-id
91
 
    relation_id = parts.pop(0)
92
 
 
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]
96
 
 
97
 
    # The relation itself /relations/relation-id
98
 
    if not parts:
99
 
        returnValue(map(lambda x: make_ace(x, read=True), service_tokens))
100
 
 
101
 
    container = parts.pop(0)
102
 
 
103
 
    # If its just the container /relations/relation-id/(settings|role)
104
 
    # then allow both services to read from it
105
 
    if not parts:
106
 
        if container in ("settings", "peer"):
107
 
            returnValue(
108
 
                map(lambda x: make_ace(x, create=True, read=True),
109
 
                    service_tokens))
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()
115
 
            returnValue(
116
 
                [make_ace(matched_service["token"], read=True, create=True),
117
 
                 make_ace(related_service["token"], read=True)])
118
 
    else:
119
 
        # Allow read access to settings and presence nodes
120
 
        returnValue(
121
 
            map(lambda x: make_ace(x, read=True), service_tokens))
122
 
 
123
 
 
124
 
@inlineCallbacks
125
 
def service_rule(policy, path):
126
 
    """An ACL rule for the /services hierarchy."""
127
 
 
128
 
    if not path.startswith("/services"):
129
 
        return
130
 
 
131
 
    if path == "/services":
132
 
        return
133
 
 
134
 
    parts = filter(None, path.split("/"))[1:]
135
 
    service_id = parts.pop(0)
136
 
 
137
 
    if not parts:
138
 
        return
139
 
 
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)])
150
 
 
151
 
 
152
 
@inlineCallbacks
153
 
def unit_rule(policy, path):
154
 
    """An ACL generator rule for the '/units' subtree."""
155
 
    if not path.startswith("/units"):
156
 
        return
157
 
 
158
 
    if path == "/units":
159
 
        return
160
 
 
161
 
    parts = filter(None, path.split("/"))[1:]
162
 
    unit_id = parts.pop(0)
163
 
 
164
 
    topology_data, stat = yield policy.client.get("/topology")
165
 
    topology = InternalTopology()
166
 
    topology.parse(topology_data)
167
 
 
168
 
    unit_name = topology.get_service_unit_name_from_id(unit_id)
169
 
    unit_token = yield policy.get_token(unit_name)
170
 
 
171
 
    if not parts:
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)])
178
 
 
179
 
    segment = parts.pop(0)
180
 
 
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)])
188
 
 
189
 
 
190
 
@inlineCallbacks
191
 
def global_rule(policy, path):
192
 
    """An ACL for top-level nodes like '/topology' and '/environment'
193
 
    """
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)])
204
 
 
205
 
 
206
 
DEFAULT_RULES = [
207
 
    global_rule,
208
 
    formula_rule,
209
 
    relation_rule,
210
 
    machine_rule,
211
 
    service_rule,
212
 
    unit_rule
213
 
    ]
214
 
 
215
 
 
216
 
def get_default_rules():
217
 
    return []