3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
1 |
#!/usr/bin/env python
|
2 |
###############################################################################
|
|
3 |
#
|
|
4 |
# bzr_access:
|
|
5 |
# Simple access control for shared bazaar repository accessed over ssh.
|
|
6 |
#
|
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
7 |
# Copyright (C) 2007 Balint Aradi
|
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
8 |
#
|
3099.4.6
by John Arbash Meinel
Add GPL license to bzr_access script |
9 |
# This program is free software; you can redistribute it and/or modify
|
10 |
# it under the terms of the GNU General Public License as published by
|
|
11 |
# the Free Software Foundation; either version 2 of the License, or
|
|
12 |
# (at your option) any later version.
|
|
13 |
#
|
|
14 |
# This program is distributed in the hope that it will be useful,
|
|
15 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17 |
# GNU General Public License for more details.
|
|
18 |
#
|
|
19 |
# You should have received a copy of the GNU General Public License
|
|
20 |
# along with this program; if not, write to the Free Software
|
|
21 |
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
22 |
#
|
|
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
23 |
###############################################################################
|
3099.4.4
by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST |
24 |
"""
|
25 |
Invocation: bzr_access <bzr_executable> <repo_collection> <user>
|
|
26 |
||
27 |
The script extracts from the SSH_ORIGINAL_COMMAND environment variable the
|
|
28 |
repository, which bazaar tries to access through the bzr+ssh protocol. The
|
|
29 |
repository is assumed to be relative to <repo_collection>. Based
|
|
30 |
on the configuration file <repo_collection>/bzr_access.conf it determines
|
|
31 |
the access rights (denied, read-only, read-write) for the specified user.
|
|
32 |
If the user has read-only or read-write access a bazaar smart server is
|
|
33 |
started for it in read-only or in read-write mode, rsp., using the specified
|
|
34 |
bzr executable.
|
|
35 |
||
36 |
Config file: INI format, pretty much similar to the authfile of subversion.
|
|
37 |
||
38 |
Groups can be defined in the [groups] section. The options in this block are
|
|
39 |
the names of the groups to be defined, the corresponding values the lists of
|
|
40 |
the users belonging to the given groups. (User names must be separated by
|
|
3112.1.4
by Balint Aradi
Changing example to show comma separated user names. |
41 |
commas.)
|
3099.4.4
by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST |
42 |
|
43 |
All other sections names should be path names (starting with '/'), defining
|
|
44 |
the permissions for the given path. The options in those sections are user
|
|
45 |
names or group references (group name with a leading '@'), the corresponding
|
|
46 |
values are the permissions: 'rw', 'r' and '' (without the quotes) for
|
|
47 |
read-write, read-only and no access, respectively.
|
|
48 |
||
49 |
Only the options in the section with the longest matching name are evaluated.
|
|
50 |
The last relevant option for the user is used.
|
|
51 |
||
52 |
Sample bzr_access.conf::
|
|
53 |
||
54 |
[groups]
|
|
55 |
admins = alpha
|
|
3112.1.4
by Balint Aradi
Changing example to show comma separated user names. |
56 |
devels = beta, gamma, delta
|
3099.4.4
by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST |
57 |
|
58 |
[/test/trunk]
|
|
59 |
@admins = rw
|
|
60 |
@devels = r
|
|
61 |
|
|
62 |
[/test/branches]
|
|
63 |
@admins = rw
|
|
64 |
@devels = rw
|
|
65 |
||
66 |
||
3099.4.5
by John Arbash Meinel
A bit more documentation cleanup |
67 |
This allows you to set up a single SSH user, and customize the access based on
|
68 |
ssh key. Your ``.ssh/authorized_key`` file should look something like this::
|
|
3099.4.4
by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST |
69 |
|
3099.4.5
by John Arbash Meinel
A bit more documentation cleanup |
70 |
command="/path/to/bzr_access /path/to/bzr /path/to/repository <username>",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-<type> <key>
|
3099.4.4
by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST |
71 |
"""
|
72 |
||
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
73 |
import ConfigParser |
3099.4.2
by John Arbash Meinel
Some simple spelling fixes, and switch to using subprocess since that is space-in-filename safe |
74 |
import os |
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
75 |
import re |
3099.4.2
by John Arbash Meinel
Some simple spelling fixes, and switch to using subprocess since that is space-in-filename safe |
76 |
import subprocess |
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
77 |
import sys |
78 |
||
79 |
CONFIG_FILE = "bzr_access.conf" |
|
80 |
SCRIPT_NAME = os.path.basename(sys.argv[0]) |
|
81 |
||
82 |
# Permission constants
|
|
83 |
PERM_DENIED = 0 |
|
84 |
PERM_READ = 1 |
|
85 |
PERM_READWRITE = 2 |
|
86 |
PERM_DICT = { "r": PERM_READ, "rw": PERM_READWRITE } |
|
87 |
||
88 |
# Exit codes
|
|
89 |
EXIT_BAD_NR_ARG = 1 |
|
90 |
EXIT_BZR_NOEXEC = 2 |
|
91 |
EXIT_REPO_NOREAD = 3 |
|
92 |
EXIT_BADENV = 4 |
|
93 |
EXIT_BADDIR = 5 |
|
94 |
EXIT_NOCONF = 6 |
|
95 |
EXIT_NOACCESS = 7 |
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
96 |
EXIT_BADUSERNAME = 8 |
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
97 |
|
98 |
# pattern for the bzr command passed to ssh
|
|
99 |
PAT_SSH_COMMAND = re.compile(r"""^bzr\s+ |
|
100 |
serve\s+
|
|
101 |
--inet\s+
|
|
102 |
--directory=(?P<dir>\S+)\s+
|
|
103 |
--allow-writes\s*$""", re.VERBOSE) |
|
104 |
||
3099.4.4
by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST |
105 |
# Command line for starting bzr
|
3099.4.2
by John Arbash Meinel
Some simple spelling fixes, and switch to using subprocess since that is space-in-filename safe |
106 |
BZR_OPTIONS = ['serve', '--inet', '--directory'] |
107 |
BZR_READWRITE_FLAGS = ['--allow-writes'] |
|
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
108 |
|
109 |
||
110 |
||
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
111 |
def error(msg, exit_code): |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
112 |
"""Prints error message to stdout and exits with given error code."""
|
113 |
||
114 |
print >>sys.stderr, "%s::error: %s" % (SCRIPT_NAME, msg) |
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
115 |
sys.exit(exit_code) |
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
116 |
|
117 |
||
118 |
||
119 |
class AccessManager(object): |
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
120 |
"""Manages the permissions, can be queried for a specific user and path."""
|
121 |
||
122 |
def __init__(self, fp): |
|
123 |
""":param fp: File like object, containing the configuration options.
|
|
124 |
"""
|
|
3099.4.7
by John Arbash Meinel
Add a comment about using configobj instead of ConfigParser |
125 |
# TODO: jam 20071211 Consider switching to bzrlib.util.configobj
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
126 |
self.config = ConfigParser.ConfigParser() |
127 |
self.config.readfp(fp) |
|
128 |
self.groups = {} |
|
129 |
if self.config.has_section("groups"): |
|
130 |
for group, users in self.config.items("groups"): |
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
131 |
self.groups[group] = set([ s.strip() for s in users.split(",")]) |
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
132 |
|
133 |
||
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
134 |
def permission(self, user, path): |
135 |
"""Determines the permission for a given user and a given path
|
|
136 |
:param user: user to look for.
|
|
137 |
:param path: path to look for.
|
|
138 |
:return: permission.
|
|
139 |
"""
|
|
140 |
if not path.startswith("/"): |
|
141 |
return PERM_DENIED |
|
142 |
perm = PERM_DENIED |
|
143 |
pathFound = False |
|
144 |
while not pathFound and path != "/": |
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
145 |
print >>sys.stderr, "DEBUG:", path |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
146 |
pathFound = self.config.has_section(path) |
147 |
if (pathFound): |
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
148 |
options = reversed(self.config.options(path)) |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
149 |
for option in options: |
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
150 |
value = PERM_DICT.get(self.config.get(path, option), |
151 |
PERM_DENIED) |
|
152 |
if self._is_relevant(option, user): |
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
153 |
perm = value |
154 |
else: |
|
155 |
path = os.path.dirname(path) |
|
156 |
return perm |
|
157 |
||
158 |
||
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
159 |
def _is_relevant(self, option, user): |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
160 |
"""Decides if a certain option is relevant for a given user.
|
161 |
|
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
162 |
An option is relevant if it is identical with the user or with a
|
163 |
reference to a group including the user.
|
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
164 |
|
165 |
:param option: Option to check.
|
|
166 |
:param user: User
|
|
167 |
:return: True if option is relevant for the user, False otherwise.
|
|
168 |
"""
|
|
169 |
if option.startswith("@"): |
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
170 |
result = (user in self.groups.get(option[1:], set())) |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
171 |
else: |
172 |
result = (option == user) |
|
173 |
return result |
|
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
174 |
|
175 |
||
176 |
||
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
177 |
def get_directory(command): |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
178 |
"""Extracts the directory name from the command pass to ssh.
|
179 |
:param command: command to parse.
|
|
180 |
:return: Directory name or empty string, if directory was not found or if it
|
|
181 |
does not start with '/'.
|
|
182 |
"""
|
|
183 |
match = PAT_SSH_COMMAND.match(command) |
|
184 |
if not match: |
|
185 |
return "" |
|
186 |
directory = match.group("dir") |
|
187 |
return os.path.normpath(directory) |
|
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
188 |
|
189 |
||
190 |
||
191 |
############################################################################
|
|
192 |
# Main program
|
|
193 |
############################################################################
|
|
194 |
def main(): |
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
195 |
# Read arguments
|
196 |
if len(sys.argv) != 4: |
|
197 |
error("Invalid number or arguments.", EXIT_BAD_NR_ARG) |
|
198 |
(bzrExec, repoRoot, user) = sys.argv[1:4] |
|
199 |
||
200 |
# Sanity checks
|
|
201 |
if not os.access(bzrExec, os.X_OK): |
|
202 |
error("bzr is not executable.", EXIT_BZR_NOEXEC) |
|
203 |
if not os.access(repoRoot, os.R_OK): |
|
204 |
error("Path to repository not readable.", EXIT_REPO_NOREAD) |
|
205 |
||
206 |
# Extract the repository path from the command passed to ssh.
|
|
207 |
if not os.environ.has_key("SSH_ORIGINAL_COMMAND"): |
|
208 |
error("Environment variable SSH_ORIGINAL_COMMAND missing.", EXIT_BADENV) |
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
209 |
directory = get_directory(os.environ["SSH_ORIGINAL_COMMAND"]) |
3112.1.5
by John Arbash Meinel
a small bit of cleanup. |
210 |
if len(directory) == 0: |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
211 |
error("Bad directory name.", EXIT_BADDIR) |
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
212 |
|
213 |
# Control user name
|
|
214 |
if not user.isalnum(): |
|
215 |
error("Invalid user name", EXIT_BADUSERNAME) |
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
216 |
|
217 |
# Read in config file.
|
|
218 |
try: |
|
219 |
fp = open(os.path.join(repoRoot, CONFIG_FILE), "r") |
|
220 |
try: |
|
221 |
accessMan = AccessManager(fp) |
|
222 |
finally: |
|
223 |
fp.close() |
|
224 |
except IOError: |
|
225 |
error("Can't read config file.", EXIT_NOCONF) |
|
226 |
||
227 |
# Determine permission and execute bzr with appropriate options
|
|
228 |
perm = accessMan.permission(user, directory) |
|
229 |
absDir = os.path.join(repoRoot, directory) |
|
230 |
command = [bzrExec] + BZR_OPTIONS + [absDir] |
|
231 |
if perm == PERM_READ: |
|
232 |
# Nothing extra needed for readonly operations
|
|
233 |
pass
|
|
234 |
elif perm == PERM_READWRITE: |
|
235 |
# Add the write flags
|
|
236 |
command.extend(BZR_READWRITE_FLAGS) |
|
237 |
else: |
|
238 |
error("Access denied.", EXIT_NOACCESS) |
|
239 |
return subprocess.call(command) |
|
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
240 |
|
241 |
||
242 |
if __name__ == "__main__": |
|
243 |
main() |
|
244 |
||
245 |
||
246 |
### Local Variables:
|
|
247 |
### mode:python
|
|
248 |
### End:
|