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 |
||
7370.2.2
by Barry Warsaw
pick lint |
8 |
# pylint: disable-msg=W0403
|
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
9 |
import _pythonpath |
10 |
||
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. |
11 |
from collections import defaultdict |
12 |
from ConfigParser import SafeConfigParser |
|
7658.6.3
by Stuart Bishop
Review feedback from allenap |
13 |
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. |
14 |
from optparse import OptionParser |
7370.2.2
by Barry Warsaw
pick lint |
15 |
import os |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
16 |
import sys |
17 |
||
18 |
import psycopg2 |
|
7370.2.2
by Barry Warsaw
pick lint |
19 |
|
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
20 |
from canonical.database.sqlbase import connect |
21 |
from canonical.launchpad.scripts import logger_options, logger, db_options |
|
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. |
22 |
from fti import quote_identifier |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
23 |
import replication.helpers |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
24 |
|
7370.2.2
by Barry Warsaw
pick lint |
25 |
|
7675.396.2
by Stuart Bishop
Blacklist read permissions on some tables to the read group |
26 |
# The 'read' group does not get given select permission on the following
|
27 |
# tables. This is to stop the ro user being given access to secrurity
|
|
28 |
# sensitive information that interactive sessions don't need.
|
|
29 |
SECURE_TABLES = [ |
|
30 |
'public.accountpassword', |
|
7675.395.31
by Stuart Bishop
More secure tables |
31 |
'public.oauthnonce', |
32 |
'public.openidnonce', |
|
33 |
'public.openidconsumernonce', |
|
7675.396.2
by Stuart Bishop
Blacklist read permissions on some tables to the read group |
34 |
]
|
35 |
||
36 |
||
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
37 |
class DbObject(object): |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
38 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
39 |
def __init__( |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
40 |
self, schema, name, type_, owner, arguments=None, language=None): |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
41 |
self.schema = schema |
42 |
self.name = name |
|
43 |
self.type = type_ |
|
44 |
self.owner = owner |
|
45 |
self.arguments = arguments |
|
46 |
self.language = language |
|
47 |
||
1339
by Canonical.com Patch Queue Manager
Add generated database diagrams (initial cut) |
48 |
def __eq__(self, other): |
49 |
return self.schema == other.schema and self.name == other.name |
|
50 |
||
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
51 |
@property
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
52 |
def fullname(self): |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
53 |
fn = "%s.%s" % (self.schema, self.name) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
54 |
if self.type == 'function': |
55 |
fn = "%s(%s)" % (fn, self.arguments) |
|
56 |
return fn |
|
57 |
||
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
58 |
@property
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
59 |
def seqname(self): |
60 |
if self.type != 'table': |
|
61 |
return '' |
|
62 |
return "%s.%s" % (self.schema, self.name + '_id_seq') |
|
63 |
||
64 |
||
65 |
class DbSchema(dict): |
|
13465.2.20
by Stuart Bishop
delint |
66 |
groups = None # List of groups defined in the db |
67 |
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. |
68 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
69 |
def __init__(self, con): |
7370.2.2
by Barry Warsaw
pick lint |
70 |
super(DbSchema, self).__init__() |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
71 |
cur = con.cursor() |
72 |
cur.execute(''' |
|
7370.2.1
by Barry Warsaw
Fixes exposed by testing on staging, along with more unittests and whitespace |
73 |
SELECT
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
74 |
n.nspname as "Schema",
|
75 |
c.relname as "Name",
|
|
76 |
CASE c.relkind
|
|
77 |
WHEN 'r' THEN 'table'
|
|
78 |
WHEN 'v' THEN 'view'
|
|
79 |
WHEN 'i' THEN 'index'
|
|
80 |
WHEN 'S' THEN 'sequence'
|
|
81 |
WHEN 's' THEN 'special'
|
|
82 |
END as "Type",
|
|
83 |
u.usename as "Owner"
|
|
84 |
FROM pg_catalog.pg_class c
|
|
85 |
LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner
|
|
86 |
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
87 |
WHERE c.relkind IN ('r','v','S','')
|
|
88 |
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
|
|
89 |
AND pg_catalog.pg_table_is_visible(c.oid)
|
|
90 |
ORDER BY 1,2
|
|
91 |
''') |
|
92 |
for schema, name, type_, owner in cur.fetchall(): |
|
7370.2.2
by Barry Warsaw
pick lint |
93 |
key = '%s.%s' % (schema, name) |
94 |
self[key] = DbObject(schema, name, type_, owner) |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
95 |
|
96 |
cur.execute(r""" |
|
97 |
SELECT
|
|
98 |
n.nspname as "schema",
|
|
99 |
p.proname as "name",
|
|
100 |
pg_catalog.oidvectortypes(p.proargtypes) as "Argument types",
|
|
101 |
u.usename as "owner",
|
|
102 |
l.lanname as "language"
|
|
103 |
FROM pg_catalog.pg_proc p
|
|
104 |
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
|
105 |
LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang
|
|
106 |
LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner
|
|
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
107 |
LEFT JOIN pg_catalog.pg_type r ON r.oid = p.prorettype
|
108 |
WHERE
|
|
109 |
r.typname NOT IN ('trigger', 'language_handler')
|
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
110 |
AND pg_catalog.pg_function_is_visible(p.oid)
|
111 |
AND n.nspname <> 'pg_catalog'
|
|
112 |
""") |
|
113 |
for schema, name, arguments, owner, language in cur.fetchall(): |
|
114 |
self['%s.%s(%s)' % (schema, name, arguments)] = DbObject( |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
115 |
schema, name, 'function', owner, arguments, language) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
116 |
# Pull a list of groups
|
117 |
cur.execute("SELECT groname FROM pg_group") |
|
118 |
self.groups = [r[0] for r in cur.fetchall()] |
|
119 |
||
120 |
# Pull a list of users
|
|
121 |
cur.execute("SELECT usename FROM pg_user") |
|
122 |
self.users = [r[0] for r in cur.fetchall()] |
|
123 |
||
7658.6.3
by Stuart Bishop
Review feedback from allenap |
124 |
@property
|
125 |
def principals(self): |
|
126 |
return chain(self.groups, self.users) |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
127 |
|
128 |
||
129 |
class CursorWrapper(object): |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
130 |
|
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
131 |
def __init__(self, cursor): |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
132 |
self.__dict__['_cursor'] = cursor |
133 |
||
134 |
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 |
135 |
cmd = cmd.encode('utf8') |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
136 |
if params is None: |
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. |
137 |
log.debug2('%s' % (cmd, )) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
138 |
return self.__dict__['_cursor'].execute(cmd) |
139 |
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. |
140 |
log.debug2('%s [%r]' % (cmd, params)) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
141 |
return self.__dict__['_cursor'].execute(cmd, params) |
142 |
||
143 |
def __getattr__(self, key): |
|
144 |
return getattr(self.__dict__['_cursor'], key) |
|
145 |
||
146 |
def __setattr__(self, key, value): |
|
147 |
return setattr(self.__dict__['_cursor'], key, value) |
|
148 |
||
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
149 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
150 |
CONFIG_DEFAULTS = { |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
151 |
'groups': '', |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
152 |
}
|
153 |
||
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
154 |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
155 |
def main(options): |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
156 |
# Load the config file
|
157 |
config = SafeConfigParser(CONFIG_DEFAULTS) |
|
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
158 |
configfile_name = os.path.join(os.path.dirname(__file__), 'security.cfg') |
159 |
config.read([configfile_name]) |
|
160 |
||
161 |
con = connect(options.dbuser) |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
162 |
|
163 |
if options.cluster: |
|
164 |
nodes = replication.helpers.get_nodes(con, 1) |
|
165 |
if nodes: |
|
166 |
# If we have a replicated environment, reset permissions on all
|
|
167 |
# Nodes.
|
|
168 |
con.close() |
|
169 |
for node in nodes: |
|
170 |
log.info("Resetting permissions on %s (%s)" % ( |
|
171 |
node.nickname, node.connection_string)) |
|
172 |
reset_permissions( |
|
173 |
psycopg2.connect(node.connection_string), config, options) |
|
13465.2.15
by Stuart Bishop
Invoke security.py in-process to save startup overhead |
174 |
return 0 |
7675.395.197
by Stuart Bishop
Full database update script for fast deployments |
175 |
log.warning("--cluster requested, but not a Slony-I cluster.") |
176 |
log.info("Resetting permissions on single database") |
|
177 |
reset_permissions(con, config, options) |
|
13465.2.15
by Stuart Bishop
Invoke security.py in-process to save startup overhead |
178 |
return 0 |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
179 |
|
180 |
||
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
181 |
def list_identifiers(identifiers): |
182 |
"""List all of `identifiers` as SQL, quoted and separated by commas.
|
|
183 |
||
184 |
:param identifiers: A sequence of SQL identifiers.
|
|
185 |
:return: A comma-separated SQL string consisting of all identifiers
|
|
186 |
passed in. Each will be quoted for use in SQL.
|
|
187 |
"""
|
|
188 |
return ', '.join([ |
|
189 |
quote_identifier(identifier) for identifier in identifiers]) |
|
190 |
||
191 |
||
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
192 |
class PermissionGatherer: |
193 |
"""Gather permissions for bulk granting or revocation.
|
|
194 |
||
195 |
Processing such statements in bulk (with multiple users, tables,
|
|
196 |
or permissions in one statement) is faster than issuing very large
|
|
197 |
numbers of individual statements.
|
|
198 |
"""
|
|
199 |
||
200 |
def __init__(self, entity_keyword): |
|
201 |
"""Gather for SQL entities of one kind (TABLE, FUNCTION, SEQUENCE).
|
|
202 |
||
203 |
:param entity_keyword: The SQL keyword for the kind of entity
|
|
204 |
that permissions will be gathered for.
|
|
205 |
"""
|
|
206 |
self.entity_keyword = entity_keyword |
|
207 |
self.permissions = defaultdict(dict) |
|
208 |
||
209 |
def add(self, permission, entity, principal, is_group=False): |
|
210 |
"""Add a permission.
|
|
211 |
||
212 |
Add all privileges you want to grant or revoke first, then use
|
|
213 |
`grant` or `revoke` to process them in bulk.
|
|
214 |
||
215 |
:param permission: A permission: SELECT, INSERT, EXECUTE, etc.
|
|
216 |
:param entity: Table, function, or sequence on which to grant
|
|
217 |
or revoke a privilege.
|
|
218 |
:param principal: User or group to which the privilege should
|
|
219 |
apply.
|
|
220 |
:param is_group: Is `principal` a group?
|
|
221 |
"""
|
|
222 |
if is_group: |
|
223 |
full_principal = "GROUP " + principal |
|
224 |
else: |
|
225 |
full_principal = principal |
|
226 |
self.permissions[permission].setdefault(entity, set()).add( |
|
227 |
full_principal) |
|
228 |
||
229 |
def tabulate(self): |
|
230 |
"""Group privileges into single-statement work items.
|
|
231 |
||
232 |
Each entry returned by this method represents a batch of
|
|
233 |
privileges that can be granted or revoked in a single SQL
|
|
234 |
statement.
|
|
235 |
||
236 |
:return: A sequence of tuples of strings: permission(s) to
|
|
237 |
grant/revoke, entity or entities to act on, and principal(s)
|
|
238 |
to grant or revoke for. Each is a string.
|
|
239 |
"""
|
|
240 |
result = [] |
|
241 |
for permission, parties in self.permissions.iteritems(): |
|
242 |
for entity, principals in parties.iteritems(): |
|
243 |
result.append( |
|
244 |
(permission, entity, ", ".join(principals))) |
|
245 |
return result |
|
246 |
||
247 |
def countPermissions(self): |
|
248 |
"""Count the number of different permissions."""
|
|
249 |
return len(self.permissions) |
|
250 |
||
251 |
def countEntities(self): |
|
252 |
"""Count the number of different entities."""
|
|
253 |
return len(set(sum([ |
|
254 |
entities.keys() |
|
255 |
for entities in self.permissions.itervalues()], []))) |
|
256 |
||
257 |
def countPrincipals(self): |
|
258 |
"""Count the number of different principals."""
|
|
259 |
principals = set() |
|
260 |
for entities_and_principals in self.permissions.itervalues(): |
|
261 |
for extra_principals in entities_and_principals.itervalues(): |
|
262 |
principals.update(extra_principals) |
|
263 |
return len(principals) |
|
264 |
||
265 |
def grant(self, cur): |
|
266 |
"""Grant all gathered permissions.
|
|
267 |
||
268 |
:param cur: A cursor to operate on.
|
|
269 |
"""
|
|
270 |
log.debug( |
|
271 |
"Granting %d permission(s) on %d %s(s) for %d user(s)/group(s).", |
|
272 |
self.countPermissions(), |
|
273 |
self.countEntities(), |
|
274 |
self.entity_keyword, |
|
275 |
self.countPrincipals()) |
|
276 |
grant_count = 0 |
|
277 |
for permissions, entities, principals in self.tabulate(): |
|
278 |
grant = "GRANT %s ON %s %s TO %s" % ( |
|
279 |
permissions, self.entity_keyword, entities, principals) |
|
280 |
log.debug2(grant) |
|
281 |
cur.execute(grant) |
|
282 |
grant_count += 1 |
|
283 |
log.debug("Issued %d GRANT statement(s).", grant_count) |
|
284 |
||
285 |
def revoke(self, cur): |
|
286 |
"""Revoke all gathered permissions.
|
|
287 |
||
288 |
:param cur: A cursor to operate on.
|
|
289 |
"""
|
|
290 |
log.debug( |
|
291 |
"Revoking %d permission(s) on %d %s(s) for %d user(s)/group(s).", |
|
292 |
self.countPermissions(), |
|
293 |
self.countEntities(), |
|
294 |
self.entity_keyword, |
|
295 |
self.countPrincipals()) |
|
296 |
revoke_count = 0 |
|
297 |
for permissions, entities, principals in self.tabulate(): |
|
298 |
revoke = "REVOKE %s ON %s %s FROM %s" % ( |
|
299 |
permissions, self.entity_keyword, entities, principals) |
|
300 |
log.debug2(revoke) |
|
301 |
cur.execute(revoke) |
|
302 |
revoke_count += 1 |
|
303 |
log.debug("Issued %d REVOKE statement(s).", revoke_count) |
|
304 |
||
305 |
||
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
306 |
def reset_permissions(con, config, options): |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
307 |
schema = DbSchema(con) |
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
308 |
all_users = list_identifiers(schema.users) |
309 |
||
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
310 |
cur = CursorWrapper(con.cursor()) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
311 |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
312 |
# Add our two automatically maintained groups
|
1812
by Canonical.com Patch Queue Manager
[trivial] Create admin and read groups automatically |
313 |
for group in ['read', 'admin']: |
314 |
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. |
315 |
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. |
316 |
cur.execute("ALTER GROUP %s DROP USER %s" % ( |
317 |
quote_identifier(group), all_users)) |
|
1812
by Canonical.com Patch Queue Manager
[trivial] Create admin and read groups automatically |
318 |
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. |
319 |
log.debug("Creating %s role" % group) |
1812
by Canonical.com Patch Queue Manager
[trivial] Create admin and read groups automatically |
320 |
cur.execute("CREATE GROUP %s" % quote_identifier(group)) |
321 |
schema.groups.append(group) |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
322 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
323 |
# Create all required groups and users.
|
324 |
for section_name in config.sections(): |
|
325 |
if section_name.lower() == 'public': |
|
326 |
continue
|
|
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
327 |
|
7658.6.3
by Stuart Bishop
Review feedback from allenap |
328 |
assert not section_name.endswith('_ro'), ( |
329 |
'_ro namespace is reserved (%s)' % repr(section_name)) |
|
330 |
||
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
331 |
type_ = config.get(section_name, 'type') |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
332 |
assert type_ in ['user', 'group'], 'Unknown type %s' % type_ |
333 |
||
334 |
role_options = [ |
|
335 |
'NOCREATEDB', 'NOCREATEROLE', 'NOCREATEUSER', 'INHERIT'] |
|
7658.5.1
by Stuart Bishop
Typo stopping db users from logging in |
336 |
if type_ == 'user': |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
337 |
role_options.append('LOGIN') |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
338 |
else: |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
339 |
role_options.append('NOLOGIN') |
340 |
||
341 |
for username in [section_name, '%s_ro' % section_name]: |
|
342 |
if username in schema.principals: |
|
343 |
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. |
344 |
if options.revoke: |
345 |
log.debug("Revoking membership of %s role", username) |
|
346 |
cur.execute("REVOKE %s FROM %s" % ( |
|
347 |
quote_identifier(username), all_users)) |
|
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
348 |
else: |
349 |
# Note - we don't drop the user because it might own
|
|
350 |
# objects in other databases. We need to ensure they are
|
|
351 |
# not superusers though!
|
|
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. |
352 |
log.debug("Resetting role options of %s role.", username) |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
353 |
cur.execute( |
354 |
"ALTER ROLE %s WITH %s" % ( |
|
355 |
quote_identifier(username), |
|
356 |
' '.join(role_options))) |
|
357 |
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. |
358 |
log.debug("Creating %s role.", username) |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
359 |
cur.execute( |
360 |
"CREATE ROLE %s WITH %s" |
|
361 |
% (quote_identifier(username), ' '.join(role_options))) |
|
362 |
schema.groups.append(username) |
|
363 |
||
7658.5.2
by Stuart Bishop
Set default_transaction_read_only on database roles |
364 |
# Set default read-only mode for our roles.
|
365 |
cur.execute( |
|
366 |
'ALTER ROLE %s SET default_transaction_read_only TO FALSE' |
|
367 |
% quote_identifier(section_name)) |
|
368 |
cur.execute( |
|
369 |
'ALTER ROLE %s SET default_transaction_read_only TO TRUE' |
|
370 |
% quote_identifier('%s_ro' % section_name)) |
|
371 |
||
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
372 |
# Add users to groups
|
373 |
for user in config.sections(): |
|
374 |
if config.get(user, 'type') != 'user': |
|
375 |
continue
|
|
376 |
groups = [ |
|
377 |
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. |
378 |
if g.strip()] |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
379 |
# Read-Only users get added to Read-Only groups.
|
380 |
if user.endswith('_ro'): |
|
381 |
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. |
382 |
if groups: |
383 |
log.debug("Adding %s to %s roles", user, ', '.join(groups)) |
|
384 |
for group in groups: |
|
385 |
cur.execute(r"""ALTER GROUP %s ADD USER %s""" % ( |
|
386 |
quote_identifier(group), quote_identifier(user))) |
|
387 |
else: |
|
388 |
log.debug("%s not in any roles", user) |
|
7370.2.1
by Barry Warsaw
Fixes exposed by testing on staging, along with more unittests and whitespace |
389 |
|
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. |
390 |
if options.revoke: |
13465.2.19
by Stuart Bishop
security.py --no-revoke should not attempt ownership changes |
391 |
# Change ownership of all objects to OWNER.
|
392 |
# We skip this in --no-revoke mode as ownership changes may
|
|
393 |
# block on a live system.
|
|
394 |
for obj in schema.values(): |
|
395 |
if obj.type in ("function", "sequence"): |
|
13465.2.20
by Stuart Bishop
delint |
396 |
pass # Can't change ownership of functions or sequences |
13465.2.19
by Stuart Bishop
security.py --no-revoke should not attempt ownership changes |
397 |
else: |
398 |
if obj.owner != options.owner: |
|
399 |
log.info("Resetting ownership of %s", obj.fullname) |
|
400 |
cur.execute("ALTER TABLE %s OWNER TO %s" % ( |
|
401 |
obj.fullname, quote_identifier(options.owner))) |
|
402 |
||
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
403 |
# Revoke all privs from known groups. Don't revoke anything for
|
404 |
# users or groups not defined in our security.cfg.
|
|
405 |
table_revocations = PermissionGatherer("TABLE") |
|
406 |
function_revocations = PermissionGatherer("FUNCTION") |
|
407 |
sequence_revocations = PermissionGatherer("SEQUENCE") |
|
408 |
||
409 |
# Gather all revocations.
|
|
410 |
for section_name in config.sections(): |
|
411 |
role = quote_identifier(section_name) |
|
412 |
if section_name == 'public': |
|
413 |
ro_role = None |
|
414 |
else: |
|
415 |
ro_role = quote_identifier(section_name + "_ro") |
|
416 |
||
417 |
for obj in schema.values(): |
|
418 |
if obj.type == 'function': |
|
419 |
gatherer = function_revocations |
|
420 |
else: |
|
421 |
gatherer = table_revocations |
|
422 |
||
423 |
gatherer.add("ALL", obj.fullname, role) |
|
424 |
||
425 |
if obj.seqname in schema: |
|
426 |
sequence_revocations.add("ALL", obj.seqname, role) |
|
427 |
if ro_role is not None: |
|
428 |
sequence_revocations.add("ALL", obj.seqname, ro_role) |
|
429 |
||
430 |
table_revocations.revoke(cur) |
|
431 |
function_revocations.revoke(cur) |
|
432 |
sequence_revocations.revoke(cur) |
|
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. |
433 |
else: |
13465.2.19
by Stuart Bishop
security.py --no-revoke should not attempt ownership changes |
434 |
log.info("Not resetting ownership of database objects") |
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. |
435 |
log.info("Not revoking permissions on database objects") |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
436 |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
437 |
# Set of all tables we have granted permissions on. After we have assigned
|
438 |
# permissions, we can use this to determine what tables have been
|
|
439 |
# forgotten about.
|
|
10293.3.1
by Max Bowsher
Remove use of the deprecated sets module. |
440 |
found = set() |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
441 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
442 |
# Set permissions as per config file
|
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
443 |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
444 |
table_permissions = PermissionGatherer("TABLE") |
445 |
function_permissions = PermissionGatherer("FUNCTION") |
|
446 |
sequence_permissions = PermissionGatherer("SEQUENCE") |
|
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
447 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
448 |
for username in config.sections(): |
449 |
for obj_name, perm in config.items(username): |
|
450 |
if '.' not in obj_name: |
|
451 |
continue
|
|
2837.1.3
by Stuart Bishop
Make 'unknown object' be a warning instead of a failure to ease |
452 |
if obj_name not in schema.keys(): |
453 |
log.warn('Bad object name %r', obj_name) |
|
454 |
continue
|
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
455 |
obj = schema[obj_name] |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
456 |
|
457 |
found.add(obj) |
|
458 |
||
459 |
perm = perm.strip() |
|
460 |
if not perm: |
|
461 |
# No perm means no rights. We can't grant no rights, so skip.
|
|
462 |
continue
|
|
463 |
||
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
464 |
who = quote_identifier(username) |
465 |
if username == 'public': |
|
466 |
who_ro = who |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
467 |
else: |
7658.3.16
by Stuart Bishop
Reapply backed out db changes |
468 |
who_ro = quote_identifier('%s_ro' % username) |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
469 |
|
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. |
470 |
log.debug( |
471 |
"Granting %s on %s to %s", perm, obj.fullname, who) |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
472 |
if obj.type == 'function': |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
473 |
function_permissions.add(perm, obj.fullname, who) |
474 |
function_permissions.add("EXECUTE", obj.fullname, who_ro) |
|
475 |
function_permissions.add( |
|
476 |
"EXECUTE", obj.fullname, "read", is_group=True) |
|
477 |
function_permissions.add( |
|
478 |
"ALL", obj.fullname, "admin", is_group=True) |
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
479 |
else: |
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
480 |
table_permissions.add( |
481 |
"ALL", obj.fullname, "admin", is_group=True) |
|
482 |
table_permissions.add(perm, obj.fullname, who) |
|
483 |
table_permissions.add("SELECT", obj.fullname, who_ro) |
|
484 |
is_secure = (obj.fullname in SECURE_TABLES) |
|
485 |
if not is_secure: |
|
486 |
table_permissions.add( |
|
487 |
"SELECT", obj.fullname, "read", is_group=True) |
|
488 |
if obj.seqname in schema: |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
489 |
if 'INSERT' in perm: |
490 |
seqperm = 'USAGE' |
|
491 |
elif 'SELECT' in perm: |
|
492 |
seqperm = 'SELECT' |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
493 |
sequence_permissions.add(seqperm, obj.seqname, who) |
494 |
if not is_secure: |
|
495 |
sequence_permissions.add( |
|
496 |
"SELECT", obj.seqname, "read", is_group=True) |
|
497 |
sequence_permissions.add("SELECT", obj.seqname, who_ro) |
|
498 |
sequence_permissions.add( |
|
499 |
"ALL", obj.seqname, "admin", is_group=True) |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
500 |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
501 |
function_permissions.grant(cur) |
502 |
table_permissions.grant(cur) |
|
503 |
sequence_permissions.grant(cur) |
|
7675.895.1
by Jeroen Vermeulen
Speed up security.py by an order of magnitude by batching permission changes. |
504 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
505 |
# Set permissions on public schemas
|
506 |
public_schemas = [ |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
507 |
s.strip() for s in config.get('DEFAULT', 'public_schemas').split(',') |
508 |
if s.strip()] |
|
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. |
509 |
log.debug("Granting access to %d public schemas", len(public_schemas)) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
510 |
for schema_name in public_schemas: |
511 |
cur.execute("GRANT USAGE ON SCHEMA %s TO PUBLIC" % ( |
|
512 |
quote_identifier(schema_name), |
|
513 |
))
|
|
514 |
for obj in schema.values(): |
|
515 |
if obj.schema not in public_schemas: |
|
516 |
continue
|
|
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
517 |
found.add(obj) |
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
518 |
if obj.type == 'function': |
7370.2.2
by Barry Warsaw
pick lint |
519 |
cur.execute('GRANT EXECUTE ON FUNCTION %s TO PUBLIC' % |
520 |
obj.fullname) |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
521 |
else: |
522 |
cur.execute('GRANT SELECT ON TABLE %s TO PUBLIC' % obj.fullname) |
|
523 |
||
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
524 |
# Raise an error if we have database objects lying around that have not
|
525 |
# had permissions assigned.
|
|
10293.3.1
by Max Bowsher
Remove use of the deprecated sets module. |
526 |
forgotten = set() |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
527 |
for obj in schema.values(): |
528 |
if obj not in found: |
|
529 |
forgotten.add(obj) |
|
530 |
forgotten = [obj.fullname for obj in forgotten |
|
7675.1072.1
by Jeroen Vermeulen
security.py cleanup and speedup, ported over from devel branch. |
531 |
if obj.type in ['table', 'function', 'view']] |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
532 |
if forgotten: |
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
533 |
log.warn('No permissions specified for %r', forgotten) |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
534 |
|
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. |
535 |
if options.dryrun: |
536 |
log.info("Dry run - rolling back changes") |
|
537 |
con.rollback() |
|
538 |
else: |
|
539 |
log.debug("Committing changes") |
|
540 |
con.commit() |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
541 |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
542 |
|
1297
by Canonical.com Patch Queue Manager
Merge in database security branch |
543 |
if __name__ == '__main__': |
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
544 |
parser = OptionParser() |
545 |
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. |
546 |
"-n", "--dry-run", dest="dryrun", default=False, |
547 |
action="store_true", help="Don't commit any changes") |
|
548 |
parser.add_option( |
|
549 |
"--revoke", dest="revoke", default=True, action="store_true", |
|
550 |
help="Revoke privileges as well as add them") |
|
551 |
parser.add_option( |
|
552 |
"--no-revoke", dest="revoke", default=True, action="store_false", |
|
553 |
help="Do not revoke any privileges. Just add.") |
|
554 |
parser.add_option( |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
555 |
"-o", "--owner", dest="owner", default="postgres", |
556 |
help="Owner of PostgreSQL objects") |
|
557 |
parser.add_option( |
|
558 |
"-c", "--cluster", dest="cluster", default=False, |
|
559 |
action="store_true", |
|
560 |
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 |
561 |
db_options(parser) |
562 |
logger_options(parser) |
|
563 |
||
1520
by Canonical.com Patch Queue Manager
Review and fix database security update code |
564 |
(options, args) = parser.parse_args() |
1831
by Canonical.com Patch Queue Manager
New config machinery, database helpers and oddsnsods required for staging |
565 |
|
566 |
log = logger(options) |
|
567 |
||
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
568 |
sys.exit(main(options)) |