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 |
|
3475.1.1
by j at oil21
fix contrib/bzr_access |
43 |
Right now only one section is supported [/], defining the permissions for the
|
44 |
repository. The options in those sections are user names or group references
|
|
45 |
(group name with a leading '@'), the corresponding values are the
|
|
46 |
permissions: 'rw', 'r' and '' (without the quotes)
|
|
47 |
for read-write, read-only and no access, respectively.
|
|
3099.4.4
by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST |
48 |
|
49 |
Sample bzr_access.conf::
|
|
50 |
||
51 |
[groups]
|
|
52 |
admins = alpha
|
|
3112.1.4
by Balint Aradi
Changing example to show comma separated user names. |
53 |
devels = beta, gamma, delta
|
3099.4.4
by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST |
54 |
|
3475.1.1
by j at oil21
fix contrib/bzr_access |
55 |
[/]
|
3099.4.4
by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST |
56 |
@admins = rw
|
57 |
@devels = r
|
|
58 |
||
3099.4.5
by John Arbash Meinel
A bit more documentation cleanup |
59 |
This allows you to set up a single SSH user, and customize the access based on
|
60 |
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 |
61 |
|
3099.4.5
by John Arbash Meinel
A bit more documentation cleanup |
62 |
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 |
63 |
"""
|
64 |
||
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
65 |
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 |
66 |
import os |
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
67 |
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 |
68 |
import subprocess |
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
69 |
import sys |
70 |
||
71 |
CONFIG_FILE = "bzr_access.conf" |
|
72 |
SCRIPT_NAME = os.path.basename(sys.argv[0]) |
|
73 |
||
74 |
# Permission constants
|
|
75 |
PERM_DENIED = 0 |
|
76 |
PERM_READ = 1 |
|
77 |
PERM_READWRITE = 2 |
|
78 |
PERM_DICT = { "r": PERM_READ, "rw": PERM_READWRITE } |
|
79 |
||
80 |
# Exit codes
|
|
81 |
EXIT_BAD_NR_ARG = 1 |
|
82 |
EXIT_BZR_NOEXEC = 2 |
|
83 |
EXIT_REPO_NOREAD = 3 |
|
84 |
EXIT_BADENV = 4 |
|
85 |
EXIT_BADDIR = 5 |
|
86 |
EXIT_NOCONF = 6 |
|
87 |
EXIT_NOACCESS = 7 |
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
88 |
EXIT_BADUSERNAME = 8 |
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
89 |
|
90 |
# pattern for the bzr command passed to ssh
|
|
91 |
PAT_SSH_COMMAND = re.compile(r"""^bzr\s+ |
|
92 |
serve\s+
|
|
93 |
--inet\s+
|
|
94 |
--directory=(?P<dir>\S+)\s+
|
|
95 |
--allow-writes\s*$""", re.VERBOSE) |
|
96 |
||
3099.4.4
by John Arbash Meinel
Change initial comment into the docstring, and update snippets to be proper ReST |
97 |
# 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 |
98 |
BZR_OPTIONS = ['serve', '--inet', '--directory'] |
99 |
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 |
100 |
|
101 |
||
102 |
||
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
103 |
def error(msg, exit_code): |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
104 |
"""Prints error message to stdout and exits with given error code."""
|
105 |
||
106 |
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. |
107 |
sys.exit(exit_code) |
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
108 |
|
109 |
||
110 |
||
111 |
class AccessManager(object): |
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
112 |
"""Manages the permissions, can be queried for a specific user and path."""
|
113 |
||
114 |
def __init__(self, fp): |
|
115 |
""":param fp: File like object, containing the configuration options.
|
|
116 |
"""
|
|
3099.4.7
by John Arbash Meinel
Add a comment about using configobj instead of ConfigParser |
117 |
# 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. |
118 |
self.config = ConfigParser.ConfigParser() |
119 |
self.config.readfp(fp) |
|
120 |
self.groups = {} |
|
121 |
if self.config.has_section("groups"): |
|
122 |
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. |
123 |
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 |
124 |
|
125 |
||
3475.1.1
by j at oil21
fix contrib/bzr_access |
126 |
def permission(self, user): |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
127 |
"""Determines the permission for a given user and a given path
|
128 |
:param user: user to look for.
|
|
129 |
:return: permission.
|
|
130 |
"""
|
|
3475.1.1
by j at oil21
fix contrib/bzr_access |
131 |
configSection = "/" |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
132 |
perm = PERM_DENIED |
3475.1.1
by j at oil21
fix contrib/bzr_access |
133 |
pathFound = self.config.has_section(configSection) |
134 |
if (pathFound): |
|
135 |
options = reversed(self.config.options(configSection)) |
|
136 |
for option in options: |
|
137 |
value = PERM_DICT.get(self.config.get(configSection, option), |
|
138 |
PERM_DENIED) |
|
139 |
if self._is_relevant(option, user): |
|
140 |
perm = value |
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
141 |
return perm |
3475.1.1
by j at oil21
fix contrib/bzr_access |
142 |
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
143 |
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
144 |
def _is_relevant(self, option, user): |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
145 |
"""Decides if a certain option is relevant for a given user.
|
146 |
|
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
147 |
An option is relevant if it is identical with the user or with a
|
148 |
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. |
149 |
|
150 |
:param option: Option to check.
|
|
151 |
:param user: User
|
|
152 |
:return: True if option is relevant for the user, False otherwise.
|
|
153 |
"""
|
|
154 |
if option.startswith("@"): |
|
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
155 |
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. |
156 |
else: |
157 |
result = (option == user) |
|
158 |
return result |
|
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
159 |
|
160 |
||
161 |
||
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
162 |
def get_directory(command): |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
163 |
"""Extracts the directory name from the command pass to ssh.
|
164 |
:param command: command to parse.
|
|
165 |
:return: Directory name or empty string, if directory was not found or if it
|
|
166 |
does not start with '/'.
|
|
167 |
"""
|
|
168 |
match = PAT_SSH_COMMAND.match(command) |
|
169 |
if not match: |
|
170 |
return "" |
|
171 |
directory = match.group("dir") |
|
172 |
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 |
173 |
|
174 |
||
175 |
||
176 |
############################################################################
|
|
177 |
# Main program
|
|
178 |
############################################################################
|
|
179 |
def main(): |
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
180 |
# Read arguments
|
181 |
if len(sys.argv) != 4: |
|
182 |
error("Invalid number or arguments.", EXIT_BAD_NR_ARG) |
|
183 |
(bzrExec, repoRoot, user) = sys.argv[1:4] |
|
184 |
||
185 |
# Sanity checks
|
|
186 |
if not os.access(bzrExec, os.X_OK): |
|
187 |
error("bzr is not executable.", EXIT_BZR_NOEXEC) |
|
188 |
if not os.access(repoRoot, os.R_OK): |
|
189 |
error("Path to repository not readable.", EXIT_REPO_NOREAD) |
|
190 |
||
191 |
# Extract the repository path from the command passed to ssh.
|
|
192 |
if not os.environ.has_key("SSH_ORIGINAL_COMMAND"): |
|
193 |
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. |
194 |
directory = get_directory(os.environ["SSH_ORIGINAL_COMMAND"]) |
3112.1.5
by John Arbash Meinel
a small bit of cleanup. |
195 |
if len(directory) == 0: |
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
196 |
error("Bad directory name.", EXIT_BADDIR) |
3112.1.3
by Balint Aradi
Tiny changes for more readable code and better compliance with bzr style. |
197 |
|
198 |
# Control user name
|
|
199 |
if not user.isalnum(): |
|
200 |
error("Invalid user name", EXIT_BADUSERNAME) |
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
201 |
|
202 |
# Read in config file.
|
|
203 |
try: |
|
204 |
fp = open(os.path.join(repoRoot, CONFIG_FILE), "r") |
|
205 |
try: |
|
206 |
accessMan = AccessManager(fp) |
|
207 |
finally: |
|
208 |
fp.close() |
|
209 |
except IOError: |
|
210 |
error("Can't read config file.", EXIT_NOCONF) |
|
211 |
||
212 |
# Determine permission and execute bzr with appropriate options
|
|
3475.1.1
by j at oil21
fix contrib/bzr_access |
213 |
perm = accessMan.permission(user) |
214 |
command = [bzrExec] + BZR_OPTIONS + [repoRoot] |
|
3099.4.3
by John Arbash Meinel
Change the indentation to 4 spaces according to Bazaar style guidelines. |
215 |
if perm == PERM_READ: |
216 |
# Nothing extra needed for readonly operations
|
|
217 |
pass
|
|
218 |
elif perm == PERM_READWRITE: |
|
219 |
# Add the write flags
|
|
220 |
command.extend(BZR_READWRITE_FLAGS) |
|
221 |
else: |
|
222 |
error("Access denied.", EXIT_NOACCESS) |
|
223 |
return subprocess.call(command) |
|
3099.4.1
by Bálint Aradi
Add a bzr_access script for allowing custom access control over bzr+ssh |
224 |
|
225 |
||
226 |
if __name__ == "__main__": |
|
227 |
main() |
|
228 |
||
229 |
||
230 |
### Local Variables:
|
|
231 |
### mode:python
|
|
232 |
### End:
|