10637.3.7
by Guilherme Salgado
merge devel |
1 |
#!/usr/bin/python -S
|
8687.15.9
by Karl Fogel
Add the copyright header block to more files (everything under database/). |
2 |
#
|
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
3 |
# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
|
8687.15.9
by Karl Fogel
Add the copyright header block to more files (everything under database/). |
4 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
5 |
|
6 |
__metaclass__ = type |
|
7 |
||
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
8 |
import _pythonpath |
9 |
||
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
10 |
from collections import defaultdict |
11 |
from ConfigParser import SafeConfigParser |
|
7658.6.3
by Stuart Bishop
Review feedback from allenap |
12 |
from itertools import chain |
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
13 |
from optparse import OptionParser |
7370.2.2
by Barry Warsaw
pick lint |
14 |
import os |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
15 |
import sys |
16 |
||
17 |
import psycopg2 |
|
7370.2.2
by Barry Warsaw
pick lint |
18 |
|
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
19 |
from fti import quote_identifier |
14606.3.4
by William Grant
Replace canonical.database usage everywhere, and format-imports. |
20 |
from lp.services.database.sqlbase import connect |
21 |
from lp.services.scripts import ( |
|
22 |
db_options, |
|
23 |
logger, |
|
24 |
logger_options, |
|
25 |
)
|
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
26 |
import replication.helpers |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
27 |
|
7675.396.2
by Stuart Bishop
Blacklist read permissions on some tables to the read group |
28 |
# The 'read' group does not get given select permission on the following
|
29 |
# tables. This is to stop the ro user being given access to secrurity
|
|
30 |
# sensitive information that interactive sessions don't need.
|
|
7675.1220.15
by William Grant
Set up the read role outside the loop, so we don't do it len(users) times per object. |
31 |
SECURE_TABLES = set(( |
7675.396.2
by Stuart Bishop
Blacklist read permissions on some tables to the read group |
32 |
'public.accountpassword', |
7675.1220.15
by William Grant
Set up the read role outside the loop, so we don't do it len(users) times per object. |
33 |
'public.accountpassword_id_seq', |
7675.395.31
by Stuart Bishop
More secure tables |
34 |
'public.oauthnonce', |
7675.1220.15
by William Grant
Set up the read role outside the loop, so we don't do it len(users) times per object. |
35 |
'public.oauthnonce_id_seq', |
7675.395.31
by Stuart Bishop
More secure tables |
36 |
'public.openidnonce', |
7675.1220.15
by William Grant
Set up the read role outside the loop, so we don't do it len(users) times per object. |
37 |
'public.openidnonce_id_seq', |
7675.395.31
by Stuart Bishop
More secure tables |
38 |
'public.openidconsumernonce', |
7675.1220.15
by William Grant
Set up the read role outside the loop, so we don't do it len(users) times per object. |
39 |
'public.openidconsumernonce_id_seq', |
40 |
))
|
|
7675.396.2
by Stuart Bishop
Blacklist read permissions on some tables to the read group |
41 |
|
7675.1220.2
by William Grant
Parse postgres ACLs into dicts. |
42 |
POSTGRES_ACL_MAP = { |
43 |
'r': 'SELECT', |
|
44 |
'w': 'UPDATE', |
|
45 |
'a': 'INSERT', |
|
46 |
'd': 'DELETE', |
|
47 |
'D': 'TRUNCATE', |
|
48 |
'x': 'REFERENCES', |
|
49 |
't': 'TRIGGER', |
|
50 |
'X': 'EXECUTE', |
|
51 |
'U': 'USAGE', |
|
52 |
'C': 'CREATE', |
|
53 |
'c': 'CONNECT', |
|
54 |
'T': 'TEMPORARY', |
|
55 |
}
|
|
56 |
||
57 |
||
7675.1220.12
by William Grant
Replace unquote_identifier and bits of parse_postgres_acl with _split_postgres_aclitem, which handles quoted identifiers properly. |
58 |
def _split_postgres_aclitem(aclitem): |
59 |
"""Split a PostgreSQL aclitem textual representation.
|
|
60 |
||
61 |
Returns the (grantee, privs, grantor), unquoted and separated.
|
|
62 |
"""
|
|
63 |
components = {'grantee': '', 'privs': '', 'grantor': ''} |
|
64 |
current_component = 'grantee' |
|
65 |
inside_quoted = False |
|
66 |
maybe_finished_quoted = False |
|
67 |
for char in aclitem: |
|
7675.1220.25
by William Grant
Invert _split_postgres_aclitem a bit. |
68 |
if inside_quoted: |
69 |
if maybe_finished_quoted: |
|
70 |
maybe_finished_quoted = False |
|
71 |
if char == '"': |
|
72 |
components[current_component] += '"' |
|
73 |
continue
|
|
74 |
else: |
|
75 |
inside_quoted = False |
|
76 |
elif char == '"': |
|
77 |
maybe_finished_quoted = True |
|
78 |
continue
|
|
79 |
# inside_quoted may have just been made False, so no else block
|
|
80 |
# for you.
|
|
81 |
if not inside_quoted: |
|
82 |
if char == '"': |
|
7675.1220.12
by William Grant
Replace unquote_identifier and bits of parse_postgres_acl with _split_postgres_aclitem, which handles quoted identifiers properly. |
83 |
inside_quoted = True |
7675.1220.25
by William Grant
Invert _split_postgres_aclitem a bit. |
84 |
continue
|
85 |
elif char == '=': |
|
86 |
current_component = 'privs' |
|
87 |
continue
|
|
88 |
elif char == '/': |
|
89 |
current_component = 'grantor' |
|
90 |
continue
|
|
91 |
components[current_component] += char |
|
7675.1220.12
by William Grant
Replace unquote_identifier and bits of parse_postgres_acl with _split_postgres_aclitem, which handles quoted identifiers properly. |
92 |
return components['grantee'], components['privs'], components['grantor'] |
7675.1220.5
by William Grant
Fix parse_postgrs_acl to unquote names, and handle missing ACLs. |
93 |
|
94 |
||
7675.1220.2
by William Grant
Parse postgres ACLs into dicts. |
95 |
def parse_postgres_acl(acl): |
7675.1220.3
by William Grant
Docstring. |
96 |
"""Parse a PostgreSQL object ACL into a dict with permission names.
|
97 |
||
98 |
The dict is of the form {user: {permission: grant option}}.
|
|
99 |
"""
|
|
7675.1220.2
by William Grant
Parse postgres ACLs into dicts. |
100 |
parsed = {} |
7675.1220.5
by William Grant
Fix parse_postgrs_acl to unquote names, and handle missing ACLs. |
101 |
if acl is None: |
102 |
return parsed |
|
7675.1220.2
by William Grant
Parse postgres ACLs into dicts. |
103 |
for entry in acl: |
7675.1220.12
by William Grant
Replace unquote_identifier and bits of parse_postgres_acl with _split_postgres_aclitem, which handles quoted identifiers properly. |
104 |
grantee, privs, grantor = _split_postgres_aclitem(entry) |
105 |
if grantee == '': |
|
106 |
grantee = 'public' |
|
107 |
parsed_privs = [] |
|
108 |
for priv in privs: |
|
109 |
if priv == '*': |
|
110 |
parsed_privs[-1] = (parsed_privs[-1][0], True) |
|
7675.1220.6
by William Grant
Fix grant option handling in parse_postgres_acl. |
111 |
continue
|
7675.1220.12
by William Grant
Replace unquote_identifier and bits of parse_postgres_acl with _split_postgres_aclitem, which handles quoted identifiers properly. |
112 |
parsed_privs.append((POSTGRES_ACL_MAP[priv], False)) |
113 |
parsed[grantee] = dict(parsed_privs) |
|
7675.1220.2
by William Grant
Parse postgres ACLs into dicts. |
114 |
return parsed |
115 |
||
7675.396.2
by Stuart Bishop
Blacklist read permissions on some tables to the read group |
116 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
117 |
class DbObject(object): |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
118 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
119 |
def __init__( |
7675.1220.1
by William Grant
Grab the ACL for relations and functions. |
120 |
self, schema, name, type_, owner, acl, arguments=None, language=None): |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
121 |
self.schema = schema |
122 |
self.name = name |
|
123 |
self.type = type_ |
|
124 |
self.owner = owner |
|
7675.1220.1
by William Grant
Grab the ACL for relations and functions. |
125 |
self.acl = acl |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
126 |
self.arguments = arguments |
127 |
self.language = language |
|
128 |
||
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
129 |
def __eq__(self, other): |
130 |
return self.schema == other.schema and self.name == other.name |
|
131 |
||
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
132 |
@property
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
133 |
def fullname(self): |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
134 |
fn = "%s.%s" % (self.schema, self.name) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
135 |
if self.type == 'function': |
136 |
fn = "%s(%s)" % (fn, self.arguments) |
|
137 |
return fn |
|
138 |
||
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
139 |
@property
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
140 |
def seqname(self): |
141 |
if self.type != 'table': |
|
142 |
return '' |
|
143 |
return "%s.%s" % (self.schema, self.name + '_id_seq') |
|
144 |
||
145 |
||
146 |
class DbSchema(dict): |
|
13465.2.20
by Stuart Bishop
delint |
147 |
groups = None # List of groups defined in the db |
148 |
users = None # List of users defined in the db |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
149 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
150 |
def __init__(self, con): |
7370.2.2
by Barry Warsaw
pick lint |
151 |
super(DbSchema, self).__init__() |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
152 |
cur = con.cursor() |
153 |
cur.execute(''' |
|
7370.2.1
by Barry Warsaw
Fixes exposed by testing on staging, along with more unittests and whitespace |
154 |
SELECT
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
155 |
n.nspname as "Schema",
|
156 |
c.relname as "Name",
|
|
157 |
CASE c.relkind
|
|
158 |
WHEN 'r' THEN 'table'
|
|
159 |
WHEN 'v' THEN 'view'
|
|
160 |
WHEN 'i' THEN 'index'
|
|
161 |
WHEN 'S' THEN 'sequence'
|
|
162 |
WHEN 's' THEN 'special'
|
|
163 |
END as "Type",
|
|
7675.1220.1
by William Grant
Grab the ACL for relations and functions. |
164 |
u.usename as "Owner",
|
7675.1220.2
by William Grant
Parse postgres ACLs into dicts. |
165 |
c.relacl::text[] as "ACL"
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
166 |
FROM pg_catalog.pg_class c
|
167 |
LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
|
|
168 |
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
169 |
WHERE c.relkind IN ('r','v','S','')
|
|
170 |
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
|
171 |
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
172 |
ORDER BY 1,2
|
|
173 |
''') |
|
7675.1220.1
by William Grant
Grab the ACL for relations and functions. |
174 |
for schema, name, type_, owner, acl in cur.fetchall(): |
7370.2.2
by Barry Warsaw
pick lint |
175 |
key = '%s.%s' % (schema, name) |
7675.1220.2
by William Grant
Parse postgres ACLs into dicts. |
176 |
self[key] = DbObject( |
177 |
schema, name, type_, owner, parse_postgres_acl(acl)) |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
178 |
|
179 |
cur.execute(r""" |
|
180 |
SELECT
|
|
181 |
n.nspname as "schema",
|
|
182 |
p.proname as "name",
|
|
183 |
pg_catalog.oidvectortypes(p.proargtypes) as "Argument types",
|
|
184 |
u.usename as "owner",
|
|
7675.1220.1
by William Grant
Grab the ACL for relations and functions. |
185 |
p.proacl::text[] as "acl",
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
186 |
l.lanname as "language"
|
187 |
FROM pg_catalog.pg_proc p
|
|
188 |
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
|
189 |
LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang
|
|
190 |
LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner
|
|
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
191 |
LEFT JOIN pg_catalog.pg_type r ON r.oid = p.prorettype
|
192 |
WHERE
|
|
193 |
r.typname NOT IN ('trigger', 'language_handler')
|
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
194 |
AND pg_catalog.pg_function_is_visible(p.oid)
|
195 |
AND n.nspname <> 'pg_catalog'
|
|
196 |
""") |
|
7675.1220.1
by William Grant
Grab the ACL for relations and functions. |
197 |
for schema, name, arguments, owner, acl, language in cur.fetchall(): |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
198 |
self['%s.%s(%s)' % (schema, name, arguments)] = DbObject( |
7675.1220.2
by William Grant
Parse postgres ACLs into dicts. |
199 |
schema, name, 'function', owner, parse_postgres_acl(acl), |
200 |
arguments, language) |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
201 |
# Pull a list of groups
|
202 |
cur.execute("SELECT groname FROM pg_group") |
|
203 |
self.groups = [r[0] for r in cur.fetchall()] |
|
204 |
||
205 |
# Pull a list of users
|
|
206 |
cur.execute("SELECT usename FROM pg_user") |
|
207 |
self.users = [r[0] for r in cur.fetchall()] |
|
208 |
||
7658.6.3
by Stuart Bishop
Review feedback from allenap |
209 |
@property
|
210 |
def principals(self): |
|
211 |
return chain(self.groups, self.users) |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
212 |
|
213 |
||
214 |
class CursorWrapper(object): |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
215 |
|
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
216 |
def __init__(self, cursor): |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
217 |
self.__dict__['_cursor'] = cursor |
218 |
||
219 |
def execute(self, cmd, params=None): |
|
1318
by Canonical.com Patch Queue Manager
Install psycopg type converters so that psycopg automatically returns Unicode strings and datetime objects, and allow test suites to pass with new psycopgda |
220 |
cmd = cmd.encode('utf8') |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
221 |
if params is None: |
13465.2.27
by Stuart Bishop
Lower the volumn of security.py DEBUG output, making full-update.py -v readable |
222 |
log.debug3('%s' % (cmd, )) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
223 |
return self.__dict__['_cursor'].execute(cmd) |
224 |
else: |
|
13465.2.27
by Stuart Bishop
Lower the volumn of security.py DEBUG output, making full-update.py -v readable |
225 |
log.debug3('%s [%r]' % (cmd, params)) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
226 |
return self.__dict__['_cursor'].execute(cmd, params) |
227 |
||
228 |
def __getattr__(self, key): |
|
229 |
return getattr(self.__dict__['_cursor'], key) |
|
230 |
||
231 |
def __setattr__(self, key, value): |
|
232 |
return setattr(self.__dict__['_cursor'], key, value) |
|
233 |
||
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
234 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
235 |
CONFIG_DEFAULTS = { |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
236 |
'groups': '', |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
237 |
}
|
238 |
||
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
239 |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
240 |
def main(options): |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
241 |
# Load the config file
|
242 |
config = SafeConfigParser(CONFIG_DEFAULTS) |
|
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
243 |
configfile_name = os.path.join(os.path.dirname(__file__), 'security.cfg') |
244 |
config.read([configfile_name]) |
|
245 |
||
13879.1.3
by William Grant
Drop now-obsolete connect(user) args. |
246 |
con = connect() |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
247 |
|
248 |
if options.cluster: |
|
249 |
nodes = replication.helpers.get_nodes(con, 1) |
|
250 |
if nodes: |
|
251 |
# If we have a replicated environment, reset permissions on all
|
|
252 |
# Nodes.
|
|
253 |
con.close() |
|
254 |
for node in nodes: |
|
255 |
log.info("Resetting permissions on %s (%s)" % ( |
|
256 |
node.nickname, node.connection_string)) |
|
257 |
reset_permissions( |
|
258 |
psycopg2.connect(node.connection_string), config, options) |
|
13465.2.15
by Stuart Bishop
Invoke security.py in-process to save startup overhead |
259 |
return 0 |
7675.395.197
by Stuart Bishop
Full database update script for fast deployments |
260 |
log.warning("--cluster requested, but not a Slony-I cluster.") |
261 |
log.info("Resetting permissions on single database") |
|
262 |
reset_permissions(con, config, options) |
|
13465.2.15
by Stuart Bishop
Invoke security.py in-process to save startup overhead |
263 |
return 0 |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
264 |
|
265 |
||
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
266 |
def list_identifiers(identifiers): |
267 |
"""List all of `identifiers` as SQL, quoted and separated by commas.
|
|
268 |
||
269 |
:param identifiers: A sequence of SQL identifiers.
|
|
270 |
:return: A comma-separated SQL string consisting of all identifiers
|
|
271 |
passed in. Each will be quoted for use in SQL.
|
|
272 |
"""
|
|
273 |
return ', '.join([ |
|
274 |
quote_identifier(identifier) for identifier in identifiers]) |
|
275 |
||
276 |
||
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
277 |
class PermissionGatherer: |
278 |
"""Gather permissions for bulk granting or revocation.
|
|
279 |
||
280 |
Processing such statements in bulk (with multiple users, tables,
|
|
281 |
or permissions in one statement) is faster than issuing very large
|
|
282 |
numbers of individual statements.
|
|
283 |
"""
|
|
284 |
||
285 |
def __init__(self, entity_keyword): |
|
286 |
"""Gather for SQL entities of one kind (TABLE, FUNCTION, SEQUENCE).
|
|
287 |
||
288 |
:param entity_keyword: The SQL keyword for the kind of entity
|
|
289 |
that permissions will be gathered for.
|
|
290 |
"""
|
|
291 |
self.entity_keyword = entity_keyword |
|
292 |
self.permissions = defaultdict(dict) |
|
293 |
||
7675.1220.4
by William Grant
groups and users are now roles, so the GROUP option to GRANT can be removed. |
294 |
def add(self, permission, entity, principal): |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
295 |
"""Add a permission.
|
296 |
||
297 |
Add all privileges you want to grant or revoke first, then use
|
|
298 |
`grant` or `revoke` to process them in bulk.
|
|
299 |
||
300 |
:param permission: A permission: SELECT, INSERT, EXECUTE, etc.
|
|
301 |
:param entity: Table, function, or sequence on which to grant
|
|
302 |
or revoke a privilege.
|
|
303 |
:param principal: User or group to which the privilege should
|
|
304 |
apply.
|
|
305 |
"""
|
|
7675.1220.11
by William Grant
Group GRANTs/REVOKEs by (permission, principal) rather than (permission, entity); there are many more entities than there are principals, plus we always grant ALL to admin so should optimise for that case. |
306 |
self.permissions[permission].setdefault(principal, set()).add(entity) |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
307 |
|
308 |
def tabulate(self): |
|
309 |
"""Group privileges into single-statement work items.
|
|
310 |
||
311 |
Each entry returned by this method represents a batch of
|
|
312 |
privileges that can be granted or revoked in a single SQL
|
|
313 |
statement.
|
|
314 |
||
315 |
:return: A sequence of tuples of strings: permission(s) to
|
|
316 |
grant/revoke, entity or entities to act on, and principal(s)
|
|
317 |
to grant or revoke for. Each is a string.
|
|
318 |
"""
|
|
319 |
result = [] |
|
320 |
for permission, parties in self.permissions.iteritems(): |
|
7675.1220.11
by William Grant
Group GRANTs/REVOKEs by (permission, principal) rather than (permission, entity); there are many more entities than there are principals, plus we always grant ALL to admin so should optimise for that case. |
321 |
for principal, entities in parties.iteritems(): |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
322 |
result.append( |
7675.1220.11
by William Grant
Group GRANTs/REVOKEs by (permission, principal) rather than (permission, entity); there are many more entities than there are principals, plus we always grant ALL to admin so should optimise for that case. |
323 |
(permission, ", ".join(entities), principal)) |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
324 |
return result |
325 |
||
326 |
def countPermissions(self): |
|
327 |
"""Count the number of different permissions."""
|
|
328 |
return len(self.permissions) |
|
329 |
||
330 |
def countEntities(self): |
|
331 |
"""Count the number of different entities."""
|
|
7675.1220.11
by William Grant
Group GRANTs/REVOKEs by (permission, principal) rather than (permission, entity); there are many more entities than there are principals, plus we always grant ALL to admin so should optimise for that case. |
332 |
entities = set() |
333 |
for entities_and_entities in self.permissions.itervalues(): |
|
334 |
for extra_entities in entities_and_entities.itervalues(): |
|
335 |
entities.update(extra_entities) |
|
336 |
return len(entities) |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
337 |
|
338 |
def countPrincipals(self): |
|
339 |
"""Count the number of different principals."""
|
|
7675.1220.11
by William Grant
Group GRANTs/REVOKEs by (permission, principal) rather than (permission, entity); there are many more entities than there are principals, plus we always grant ALL to admin so should optimise for that case. |
340 |
return len(set(sum([ |
341 |
principals.keys() |
|
342 |
for principals in self.permissions.itervalues()], []))) |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
343 |
|
344 |
def grant(self, cur): |
|
345 |
"""Grant all gathered permissions.
|
|
346 |
||
347 |
:param cur: A cursor to operate on.
|
|
348 |
"""
|
|
349 |
log.debug( |
|
350 |
"Granting %d permission(s) on %d %s(s) for %d user(s)/group(s).", |
|
351 |
self.countPermissions(), |
|
352 |
self.countEntities(), |
|
353 |
self.entity_keyword, |
|
354 |
self.countPrincipals()) |
|
355 |
grant_count = 0 |
|
356 |
for permissions, entities, principals in self.tabulate(): |
|
357 |
grant = "GRANT %s ON %s %s TO %s" % ( |
|
358 |
permissions, self.entity_keyword, entities, principals) |
|
359 |
log.debug2(grant) |
|
360 |
cur.execute(grant) |
|
361 |
grant_count += 1 |
|
362 |
log.debug("Issued %d GRANT statement(s).", grant_count) |
|
363 |
||
364 |
def revoke(self, cur): |
|
365 |
"""Revoke all gathered permissions.
|
|
366 |
||
367 |
:param cur: A cursor to operate on.
|
|
368 |
"""
|
|
369 |
log.debug( |
|
370 |
"Revoking %d permission(s) on %d %s(s) for %d user(s)/group(s).", |
|
371 |
self.countPermissions(), |
|
372 |
self.countEntities(), |
|
373 |
self.entity_keyword, |
|
374 |
self.countPrincipals()) |
|
375 |
revoke_count = 0 |
|
376 |
for permissions, entities, principals in self.tabulate(): |
|
377 |
revoke = "REVOKE %s ON %s %s FROM %s" % ( |
|
378 |
permissions, self.entity_keyword, entities, principals) |
|
379 |
log.debug2(revoke) |
|
380 |
cur.execute(revoke) |
|
381 |
revoke_count += 1 |
|
382 |
log.debug("Issued %d REVOKE statement(s).", revoke_count) |
|
383 |
||
384 |
||
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
385 |
def alter_permissions(cur, which, revoke=False): |
386 |
"""Efficiently apply a set of permission changes.
|
|
387 |
||
388 |
:param cur: a database cursor
|
|
389 |
:param which: an iterable of (object, role, permissions)
|
|
390 |
:param revoke: whether to revoke or grant permissions
|
|
391 |
"""
|
|
392 |
gatherers = { |
|
393 |
'table': PermissionGatherer("TABLE"), |
|
394 |
'function': PermissionGatherer("FUNCTION"), |
|
395 |
'sequence': PermissionGatherer("SEQUENCE"), |
|
396 |
}
|
|
397 |
||
398 |
for obj, role, perms in which: |
|
399 |
gatherers.get(obj.type, gatherers['table']).add( |
|
400 |
', '.join(perms), obj.fullname, quote_identifier(role)) |
|
401 |
||
402 |
for gatherer in gatherers.values(): |
|
403 |
if revoke: |
|
404 |
gatherer.revoke(cur) |
|
405 |
else: |
|
406 |
gatherer.grant(cur) |
|
407 |
||
408 |
||
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
409 |
def reset_permissions(con, config, options): |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
410 |
schema = DbSchema(con) |
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
411 |
all_users = list_identifiers(schema.users) |
412 |
||
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
413 |
cur = CursorWrapper(con.cursor()) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
414 |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
415 |
# Add our two automatically maintained groups
|
1812
by Canonical.com Patch Queue Manager
[trivial] Create admin and read groups automatically |
416 |
for group in ['read', 'admin']: |
417 |
if group in schema.principals: |
|
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
418 |
log.debug("Removing managed users from %s role" % group) |
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
419 |
cur.execute("ALTER GROUP %s DROP USER %s" % ( |
420 |
quote_identifier(group), all_users)) |
|
1812
by Canonical.com Patch Queue Manager
[trivial] Create admin and read groups automatically |
421 |
else: |
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
422 |
log.debug("Creating %s role" % group) |
1812
by Canonical.com Patch Queue Manager
[trivial] Create admin and read groups automatically |
423 |
cur.execute("CREATE GROUP %s" % quote_identifier(group)) |
424 |
schema.groups.append(group) |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
425 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
426 |
# Create all required groups and users.
|
427 |
for section_name in config.sections(): |
|
428 |
if section_name.lower() == 'public': |
|
429 |
continue
|
|
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
430 |
|
7658.6.3
by Stuart Bishop
Review feedback from allenap |
431 |
assert not section_name.endswith('_ro'), ( |
432 |
'_ro namespace is reserved (%s)' % repr(section_name)) |
|
433 |
||
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
434 |
type_ = config.get(section_name, 'type') |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
435 |
assert type_ in ['user', 'group'], 'Unknown type %s' % type_ |
436 |
||
437 |
role_options = [ |
|
438 |
'NOCREATEDB', 'NOCREATEROLE', 'NOCREATEUSER', 'INHERIT'] |
|
7658.5.1
by Stuart Bishop
Typo stopping db users from logging in |
439 |
if type_ == 'user': |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
440 |
role_options.append('LOGIN') |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
441 |
else: |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
442 |
role_options.append('NOLOGIN') |
443 |
||
444 |
for username in [section_name, '%s_ro' % section_name]: |
|
445 |
if username in schema.principals: |
|
446 |
if type_ == 'group': |
|
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
447 |
if options.revoke: |
13465.2.27
by Stuart Bishop
Lower the volumn of security.py DEBUG output, making full-update.py -v readable |
448 |
log.debug2("Revoking membership of %s role", username) |
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
449 |
cur.execute("REVOKE %s FROM %s" % ( |
450 |
quote_identifier(username), all_users)) |
|
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
451 |
else: |
452 |
# Note - we don't drop the user because it might own
|
|
453 |
# objects in other databases. We need to ensure they are
|
|
454 |
# not superusers though!
|
|
13465.2.27
by Stuart Bishop
Lower the volumn of security.py DEBUG output, making full-update.py -v readable |
455 |
log.debug2("Resetting role options of %s role.", username) |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
456 |
cur.execute( |
457 |
"ALTER ROLE %s WITH %s" % ( |
|
458 |
quote_identifier(username), |
|
459 |
' '.join(role_options))) |
|
460 |
else: |
|
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
461 |
log.debug("Creating %s role.", username) |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
462 |
cur.execute( |
463 |
"CREATE ROLE %s WITH %s" |
|
464 |
% (quote_identifier(username), ' '.join(role_options))) |
|
465 |
schema.groups.append(username) |
|
466 |
||
7658.5.2
by Stuart Bishop
Set default_transaction_read_only on database roles |
467 |
# Set default read-only mode for our roles.
|
468 |
cur.execute( |
|
469 |
'ALTER ROLE %s SET default_transaction_read_only TO FALSE' |
|
470 |
% quote_identifier(section_name)) |
|
471 |
cur.execute( |
|
472 |
'ALTER ROLE %s SET default_transaction_read_only TO TRUE' |
|
473 |
% quote_identifier('%s_ro' % section_name)) |
|
474 |
||
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
475 |
# Add users to groups
|
476 |
for user in config.sections(): |
|
477 |
if config.get(user, 'type') != 'user': |
|
478 |
continue
|
|
479 |
groups = [ |
|
480 |
g.strip() for g in config.get(user, 'groups', '').split(',') |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
481 |
if g.strip()] |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
482 |
# Read-Only users get added to Read-Only groups.
|
483 |
if user.endswith('_ro'): |
|
484 |
groups = ['%s_ro' % group for group in groups] |
|
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
485 |
if groups: |
13465.2.27
by Stuart Bishop
Lower the volumn of security.py DEBUG output, making full-update.py -v readable |
486 |
log.debug2("Adding %s to %s roles", user, ', '.join(groups)) |
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
487 |
for group in groups: |
488 |
cur.execute(r"""ALTER GROUP %s ADD USER %s""" % ( |
|
489 |
quote_identifier(group), quote_identifier(user))) |
|
490 |
else: |
|
13465.2.27
by Stuart Bishop
Lower the volumn of security.py DEBUG output, making full-update.py -v readable |
491 |
log.debug2("%s not in any roles", user) |
7370.2.1
by Barry Warsaw
Fixes exposed by testing on staging, along with more unittests and whitespace |
492 |
|
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
493 |
if options.revoke: |
13465.2.19
by Stuart Bishop
security.py --no-revoke should not attempt ownership changes |
494 |
# Change ownership of all objects to OWNER.
|
495 |
# We skip this in --no-revoke mode as ownership changes may
|
|
496 |
# block on a live system.
|
|
497 |
for obj in schema.values(): |
|
498 |
if obj.type in ("function", "sequence"): |
|
13465.2.20
by Stuart Bishop
delint |
499 |
pass # Can't change ownership of functions or sequences |
13465.2.19
by Stuart Bishop
security.py --no-revoke should not attempt ownership changes |
500 |
else: |
501 |
if obj.owner != options.owner: |
|
502 |
log.info("Resetting ownership of %s", obj.fullname) |
|
503 |
cur.execute("ALTER TABLE %s OWNER TO %s" % ( |
|
504 |
obj.fullname, quote_identifier(options.owner))) |
|
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
505 |
else: |
13465.2.19
by Stuart Bishop
security.py --no-revoke should not attempt ownership changes |
506 |
log.info("Not resetting ownership of database objects") |
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
507 |
|
7675.1220.24
by William Grant
Controlled roles are now known as managed roles. |
508 |
managed_roles = set(['read', 'admin']) |
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
509 |
for section_name in config.sections(): |
7675.1220.24
by William Grant
Controlled roles are now known as managed roles. |
510 |
managed_roles.add(section_name) |
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
511 |
if section_name != 'public': |
7675.1220.24
by William Grant
Controlled roles are now known as managed roles. |
512 |
managed_roles.add(section_name + "_ro") |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
513 |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
514 |
# Set of all tables we have granted permissions on. After we have assigned
|
515 |
# permissions, we can use this to determine what tables have been
|
|
516 |
# forgotten about.
|
|
10293.3.1
by Max Bowsher
Remove use of the deprecated sets module. |
517 |
found = set() |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
518 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
519 |
# Set permissions as per config file
|
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
520 |
desired_permissions = defaultdict(lambda: defaultdict(set)) |
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
521 |
|
7675.1220.14
by William Grant
Rip slow logging and dict key existence checks out of the permission generator loop. |
522 |
valid_objs = set(schema.iterkeys()) |
523 |
||
7675.1220.18
by William Grant
Rename controlled_objs to readable_objs. |
524 |
# Any object with permissions granted is accessible to the 'read'
|
525 |
# role. Some (eg. the lp_* replicated tables and internal or trigger
|
|
526 |
# functions) aren't readable.
|
|
7675.1220.19
by William Grant
Don't just control permissions on controlled_objs; deal with controlled_roles on all objects in the DB. This tweaking the admin handling to grant ALL on granted_objs, and revoke ALL from everything else that has admin in the ACL. |
527 |
granted_objs = set() |
7675.1220.18
by William Grant
Rename controlled_objs to readable_objs. |
528 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
529 |
for username in config.sections(): |
7675.1220.14
by William Grant
Rip slow logging and dict key existence checks out of the permission generator loop. |
530 |
who = username |
531 |
if username == 'public': |
|
532 |
who_ro = who |
|
533 |
else: |
|
534 |
who_ro = '%s_ro' % username |
|
535 |
||
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
536 |
for obj_name, perm in config.items(username): |
537 |
if '.' not in obj_name: |
|
538 |
continue
|
|
7675.1220.14
by William Grant
Rip slow logging and dict key existence checks out of the permission generator loop. |
539 |
if obj_name not in valid_objs: |
2837.1.3
by Stuart Bishop
Make 'unknown object' be a warning instead of a failure to ease |
540 |
log.warn('Bad object name %r', obj_name) |
541 |
continue
|
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
542 |
obj = schema[obj_name] |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
543 |
|
544 |
found.add(obj) |
|
545 |
||
546 |
perm = perm.strip() |
|
547 |
if not perm: |
|
548 |
# No perm means no rights. We can't grant no rights, so skip.
|
|
549 |
continue
|
|
550 |
||
7675.1220.19
by William Grant
Don't just control permissions on controlled_objs; deal with controlled_roles on all objects in the DB. This tweaking the admin handling to grant ALL on granted_objs, and revoke ALL from everything else that has admin in the ACL. |
551 |
granted_objs.add(obj) |
7675.1220.8
by William Grant
Granting ALL on everything to admin is more easily done at the end of the loop. |
552 |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
553 |
if obj.type == 'function': |
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
554 |
desired_permissions[obj][who].update(perm.split(', ')) |
555 |
if who_ro: |
|
556 |
desired_permissions[obj][who_ro].add("EXECUTE") |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
557 |
else: |
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
558 |
desired_permissions[obj][who].update(perm.split(', ')) |
559 |
if who_ro: |
|
560 |
desired_permissions[obj][who_ro].add("SELECT") |
|
7675.1220.14
by William Grant
Rip slow logging and dict key existence checks out of the permission generator loop. |
561 |
if obj.seqname in valid_objs: |
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
562 |
seq = schema[obj.seqname] |
7675.1220.19
by William Grant
Don't just control permissions on controlled_objs; deal with controlled_roles on all objects in the DB. This tweaking the admin handling to grant ALL on granted_objs, and revoke ALL from everything else that has admin in the ACL. |
563 |
granted_objs.add(seq) |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
564 |
if 'INSERT' in perm: |
565 |
seqperm = 'USAGE' |
|
566 |
elif 'SELECT' in perm: |
|
567 |
seqperm = 'SELECT' |
|
7675.1220.17
by William Grant
Fix an existing bug where if neither SELECT nor INSERT was granted on a table, the permission for the previous sequence would be granted to the current one. Only affects launchpad_main's buildfarmjob and packagebuild privileges, where it holds only DELETE. |
568 |
else: |
569 |
seqperm = None |
|
570 |
if seqperm: |
|
571 |
desired_permissions[seq][who].add(seqperm) |
|
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
572 |
desired_permissions[seq][who_ro].add("SELECT") |
573 |
||
7675.1220.21
by William Grant
Comments. |
574 |
# read gets read access to all non-secure objects that we've granted
|
575 |
# anybody access to.
|
|
7675.1220.19
by William Grant
Don't just control permissions on controlled_objs; deal with controlled_roles on all objects in the DB. This tweaking the admin handling to grant ALL on granted_objs, and revoke ALL from everything else that has admin in the ACL. |
576 |
for obj in granted_objs: |
7675.1220.15
by William Grant
Set up the read role outside the loop, so we don't do it len(users) times per object. |
577 |
if obj.type == 'function': |
578 |
desired_permissions[obj]['read'].add("EXECUTE") |
|
579 |
else: |
|
7675.1220.22
by William Grant
functions can't be secure. |
580 |
if obj.fullname not in SECURE_TABLES: |
7675.1220.15
by William Grant
Set up the read role outside the loop, so we don't do it len(users) times per object. |
581 |
desired_permissions[obj]['read'].add("SELECT") |
582 |
||
7675.1220.20
by William Grant
Integrate public schema (ts2) handling into the main thing. |
583 |
# Set permissions on public schemas
|
584 |
public_schemas = [ |
|
585 |
s.strip() for s in config.get('DEFAULT', 'public_schemas').split(',') |
|
586 |
if s.strip()] |
|
587 |
log.debug("Granting access to %d public schemas", len(public_schemas)) |
|
588 |
for schema_name in public_schemas: |
|
589 |
cur.execute("GRANT USAGE ON SCHEMA %s TO PUBLIC" % ( |
|
590 |
quote_identifier(schema_name), |
|
591 |
))
|
|
592 |
for obj in schema.values(): |
|
593 |
if obj.schema not in public_schemas: |
|
594 |
continue
|
|
595 |
found.add(obj) |
|
596 |
if obj.type == 'function': |
|
597 |
desired_permissions[obj]['public'].add('EXECUTE') |
|
598 |
else: |
|
599 |
desired_permissions[obj]['public'].add('SELECT') |
|
600 |
||
7675.1220.21
by William Grant
Comments. |
601 |
# For every object in the DB, ensure that the privileges held by our
|
7675.1220.24
by William Grant
Controlled roles are now known as managed roles. |
602 |
# managed roles match our expectations. If not, store the delta
|
7675.1220.21
by William Grant
Comments. |
603 |
# to be applied later.
|
604 |
# Also grants/revokes access by the admin role, which isn't a
|
|
7675.1220.24
by William Grant
Controlled roles are now known as managed roles. |
605 |
# traditionally managed role.
|
7675.1220.23
by William Grant
Log unmanaged roles that hold privileges on managed objects. |
606 |
unmanaged_roles = set() |
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
607 |
required_grants = [] |
608 |
required_revokes = [] |
|
7675.1220.19
by William Grant
Don't just control permissions on controlled_objs; deal with controlled_roles on all objects in the DB. This tweaking the admin handling to grant ALL on granted_objs, and revoke ALL from everything else that has admin in the ACL. |
609 |
for obj in schema.values(): |
7675.1220.21
by William Grant
Comments. |
610 |
# We only care about roles that are in either the desired or
|
7675.1220.24
by William Grant
Controlled roles are now known as managed roles. |
611 |
# existing ACL, and are also our managed roles. But skip admin,
|
612 |
# because it's done at the end.
|
|
7675.1220.13
by William Grant
Optimise delta generation from ~250ms to 15ms. |
613 |
interesting_roles = set(desired_permissions[obj]).union(obj.acl) |
7675.1220.24
by William Grant
Controlled roles are now known as managed roles. |
614 |
unmanaged_roles.update(interesting_roles.difference(managed_roles)) |
615 |
for role in managed_roles.intersection(interesting_roles): |
|
7675.1220.23
by William Grant
Log unmanaged roles that hold privileges on managed objects. |
616 |
if role == 'admin': |
617 |
continue
|
|
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
618 |
new = desired_permissions[obj][role] |
7675.1220.13
by William Grant
Optimise delta generation from ~250ms to 15ms. |
619 |
old_privs = obj.acl.get(role, {}) |
620 |
old = set(old_privs) |
|
621 |
if any(old_privs.itervalues()): |
|
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
622 |
log.warning("%s has grant option on %s", role, obj.fullname) |
7675.1220.13
by William Grant
Optimise delta generation from ~250ms to 15ms. |
623 |
if new == old: |
624 |
continue
|
|
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
625 |
missing = new.difference(old) |
626 |
extra = old.difference(new) |
|
627 |
if missing: |
|
628 |
required_grants.append((obj, role, missing)) |
|
629 |
if extra: |
|
630 |
required_revokes.append((obj, role, extra)) |
|
7675.1220.19
by William Grant
Don't just control permissions on controlled_objs; deal with controlled_roles on all objects in the DB. This tweaking the admin handling to grant ALL on granted_objs, and revoke ALL from everything else that has admin in the ACL. |
631 |
|
632 |
# admin get all privileges on anything with privileges granted
|
|
633 |
# in security.cfg. We don't have a mapping from ALL to real
|
|
634 |
# privileges for each object type, so we just grant or revoke ALL
|
|
635 |
# each time.
|
|
636 |
if obj in granted_objs: |
|
637 |
required_grants.append((obj, "admin", ("ALL",))) |
|
638 |
else: |
|
639 |
if "admin" in obj.acl: |
|
640 |
required_revokes.append((obj, "admin", ("ALL",))) |
|
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
641 |
|
7675.1220.23
by William Grant
Log unmanaged roles that hold privileges on managed objects. |
642 |
log.debug("Unmanaged roles on managed objects: %r", list(unmanaged_roles)) |
643 |
||
7675.1220.7
by William Grant
First stab at permission diffing. Works for everything except revocation of admin privs. |
644 |
alter_permissions(cur, required_grants) |
645 |
if options.revoke: |
|
646 |
alter_permissions(cur, required_revokes, revoke=True) |
|
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
647 |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
648 |
# Raise an error if we have database objects lying around that have not
|
649 |
# had permissions assigned.
|
|
10293.3.1
by Max Bowsher
Remove use of the deprecated sets module. |
650 |
forgotten = set() |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
651 |
for obj in schema.values(): |
652 |
if obj not in found: |
|
653 |
forgotten.add(obj) |
|
654 |
forgotten = [obj.fullname for obj in forgotten |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
655 |
if obj.type in ['table', 'function', 'view']] |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
656 |
if forgotten: |
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
657 |
log.warn('No permissions specified for %r', forgotten) |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
658 |
|
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
659 |
if options.dryrun: |
660 |
log.info("Dry run - rolling back changes") |
|
661 |
con.rollback() |
|
662 |
else: |
|
663 |
log.debug("Committing changes") |
|
664 |
con.commit() |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
665 |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
666 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
667 |
if __name__ == '__main__': |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
668 |
parser = OptionParser() |
669 |
parser.add_option( |
|
7675.395.152
by Stuart Bishop
Add --no-revoke to security.py for use live, and --dry-run and improve logging for shits and giggles. |
670 |
"-n", "--dry-run", dest="dryrun", default=False, |
671 |
action="store_true", help="Don't commit any changes") |
|
672 |
parser.add_option( |
|
673 |
"--revoke", dest="revoke", default=True, action="store_true", |
|
674 |
help="Revoke privileges as well as add them") |
|
675 |
parser.add_option( |
|
676 |
"--no-revoke", dest="revoke", default=True, action="store_false", |
|
677 |
help="Do not revoke any privileges. Just add.") |
|
678 |
parser.add_option( |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
679 |
"-o", "--owner", dest="owner", default="postgres", |
680 |
help="Owner of PostgreSQL objects") |
|
681 |
parser.add_option( |
|
682 |
"-c", "--cluster", dest="cluster", default=False, |
|
683 |
action="store_true", |
|
684 |
help="Rebuild permissions on all nodes in the Slony-I cluster.") |
|
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
685 |
db_options(parser) |
686 |
logger_options(parser) |
|
687 |
||
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
688 |
(options, args) = parser.parse_args() |
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
689 |
|
690 |
log = logger(options) |
|
691 |
||
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
692 |
sys.exit(main(options)) |