Merge lp:~jmwilson/duplicity/filecaps into lp:~duplicity-team/duplicity/0.7-series
- filecaps
- Merge into 0.7-series
Status: | Work in progress |
---|---|
Proposed branch: | lp:~jmwilson/duplicity/filecaps |
Merge into: | lp:~duplicity-team/duplicity/0.7-series |
Diff against target: |
309 lines (+140/-28) 7 files modified
bin/duplicity (+3/-7) debian/control (+3/-0) debian/duplicity.docs (+2/-0) debian/duplicity.postinst (+6/-0) debian/rules (+6/-4) duplicity/selection.py (+7/-13) setup.py (+113/-4) |
To merge this branch: | bzr merge lp:~jmwilson/duplicity/filecaps |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
duplicity-team | Pending | ||
Review via email: mp+266773@code.launchpad.net |
This proposal supersedes a proposal from 2015-04-27.
Commit message
Description of the change
Continuing the earlier discussion on using file capabilities, this will use cx_Freeze to create a binary executable for duplicity, and if the setcap command is available during package installation, then it will set inherited capabilities cap_chown,
I've been running these changes to do full system backups on my machine for weeks without running as root, and I also used it to restore my home directory in an emergency. I installed libpam-cap, and then added a line "cap_dac_
Most of the changes are in setup.py to run cx_Freeze as part of the build step. I'll highlight some of the code changes to duplicity with their rationale:
bin/duplicity: Use execvpe instead of execve since argv[0] is not guaranteed to be a full path. It seems the python interpreter makes argv[0] a full path, at least on some systems. The setuid(geteuid()) incantation doesn't really accomplish anything if geteuid() == 0.
duplicity/
Kenneth Loafman (kenneth-loafman) wrote : Posted in a previous version of this proposal | # |
edso (ed.so) wrote : Posted in a previous version of this proposal | # |
looks like capng is difficult to retrieve. if so i would hesitate to make duplicity depend on it by default.
does it work with older python? we officially support python 2.6+ still.
..ede/duply.net
On 27.04.2015 15:15, Kenneth Loafman wrote:
> If I understand this correctly, then new files will be created by
> 'duplicity', but older files owned by root will not be deletable.
> Correct? I'm thinking a one-time 'chown -R duplicity: ~/cache/
> would fix that problem.
>
>
> On Sun, Apr 26, 2015 at 7:31 PM, James Wilson <email address hidden> wrote:
>
>> James Wilson has proposed merging lp:~jmwilson/duplicity/capabilities into
>> lp:duplicity.
>>
>> Requested reviews:
>> duplicity-team (duplicity-team)
>>
>> For more details, see:
>> https:/
>>
>> Proposal is to add an unprivileged "duplicity" user during installation
>> that is used to limit the capabilities when duplicity is run as root. To
>> manage capabilities, I'm using the python bindings for libcap-ng (which
>> needs its own updating since at least the current vivid package is empty
>> for some reason, but is correct when built from source).
>>
>> I've been interested in using duplicity for system-wide backups, but one
>> thing that is troubling is that it must be run as root. As an interpreted
>> program that communicates on the network, this exposes the host to possible
>> bugs in python or any other packages imported in duplicity.
>>
>> First, examining the code in bin/duplicity:
>> # if python is run setuid, it's only partway set,
>> # so make sure to run with euid/egid of root
>> if os.geteuid() == 0:
>> # make sure uid/gid match euid/egid
>> os.setuid(
>> os.setgid(
>>
>> I'm not sure what this is for; if you're root (i.e., os.geteuid() == 0)
>> then there's no need to switch the real uid. When the machination of
>> "setuid(geteuid())" is used it is typically when the ruid=0 and we want to
>> irrevocably drop privileges to a euid!=0 normal user. That's not the case
>> here, so this doesn't help or harm us.
>>
>> Then there's the issue of running duplicity as root. Since it's an
>> interpreted program, the normal ways of expanding privileges (SUID
>> executable or setcap on the script) are unavailable, and the other options
>> are to run it as root, or put SUID or file capabilities on the python
>> interpreter.
>>
>> The only reason to run duplicity as root is get read access to the whole
>> file system. We can safely drop all capabilities other than
>> CAP_DAC_
>> back if we do execve, so we could also change the bounding set or lock the
>> securebits to prevent the kernel from re-granting privileges on execve.
>> Nonetheless, we're still root, and lots of important files are owned by and
>> writable to root, so the best choice is to change uid to an unprivileged
>> user who maintains only the ability to read the whole file system.
>>
>> It's hard to test the change directly, since it doesn't change the output
>> or actions of duplicity. The following python script mimics what t...
Michael Terry (mterry) wrote : Posted in a previous version of this proposal | # |
Also, this should fallback gracefully if the 'duplicity' user doesn't exist.
Kenneth Loafman (kenneth-loafman) wrote : Posted in a previous version of this proposal | # |
I cannot find python-capng for Ubuntu 14.04, Python 2.7, so I'm guessing
it's a new critter.
On Mon, Apr 27, 2015 at 8:33 AM, edso <email address hidden> wrote:
> looks like capng is difficult to retrieve. if so i would hesitate to make
> duplicity depend on it by default.
>
> does it work with older python? we officially support python 2.6+ still.
>
> ..ede/duply.net
>
> On 27.04.2015 15:15, Kenneth Loafman wrote:
> > If I understand this correctly, then new files will be created by
> > 'duplicity', but older files owned by root will not be deletable.
> > Correct? I'm thinking a one-time 'chown -R duplicity:
> ~/cache/
> > would fix that problem.
> >
> >
> > On Sun, Apr 26, 2015 at 7:31 PM, James Wilson <email address hidden> wrote:
> >
> >> James Wilson has proposed merging lp:~jmwilson/duplicity/capabilities
> into
> >> lp:duplicity.
> >>
> >> Requested reviews:
> >> duplicity-team (duplicity-team)
> >>
> >> For more details, see:
> >>
> https:/
> >>
> >> Proposal is to add an unprivileged "duplicity" user during installation
> >> that is used to limit the capabilities when duplicity is run as root. To
> >> manage capabilities, I'm using the python bindings for libcap-ng (which
> >> needs its own updating since at least the current vivid package is empty
> >> for some reason, but is correct when built from source).
> >>
> >> I've been interested in using duplicity for system-wide backups, but one
> >> thing that is troubling is that it must be run as root. As an
> interpreted
> >> program that communicates on the network, this exposes the host to
> possible
> >> bugs in python or any other packages imported in duplicity.
> >>
> >> First, examining the code in bin/duplicity:
> >> # if python is run setuid, it's only partway set,
> >> # so make sure to run with euid/egid of root
> >> if os.geteuid() == 0:
> >> # make sure uid/gid match euid/egid
> >> os.setuid(
> >> os.setgid(
> >>
> >> I'm not sure what this is for; if you're root (i.e., os.geteuid() == 0)
> >> then there's no need to switch the real uid. When the machination of
> >> "setuid(geteuid())" is used it is typically when the ruid=0 and we want
> to
> >> irrevocably drop privileges to a euid!=0 normal user. That's not the
> case
> >> here, so this doesn't help or harm us.
> >>
> >> Then there's the issue of running duplicity as root. Since it's an
> >> interpreted program, the normal ways of expanding privileges (SUID
> >> executable or setcap on the script) are unavailable, and the other
> options
> >> are to run it as root, or put SUID or file capabilities on the python
> >> interpreter.
> >>
> >> The only reason to run duplicity as root is get read access to the whole
> >> file system. We can safely drop all capabilities other than
> >> CAP_DAC_
> >> back if we do execve, so we could also change the bounding set or lock
> the
> >> securebits to prevent the kernel from re-granting privileges on execve.
> >> Nonetheless, we're still root, and lots of important files are owned by
...
James Wilson (jmwilson) wrote : Posted in a previous version of this proposal | # |
Oops, the package is python-cap-ng. The problem is that the distro version is missing the goods. I think they forgot to include a dependency in their control file, since if I grab the source it builds fine and I didn't have to do anything. It's tracked in https:/
Kenneth Loafman (kenneth-loafman) wrote : Posted in a previous version of this proposal | # |
I marked this as a "Work in Progress" because I don't want to forget it, but it sounds like it would be a support nightmare without distro support or pip support. Perhaps someone will put together a PPA with the appropriate packaging, but for now, we'll just have to wait.
James Wilson (jmwilson) wrote : Posted in a previous version of this proposal | # |
Hey, I wanted to resume this discussion. Since April a couple of things have changed: 1) I helped the maintainer of libcap-ng to fix its build issues as of 0.7.4-3 that is available in sid and stretch, and 2) I found another way to limit root capabilities using file capabilities that has a smaller footprint (it doesn't create a new system user).
The catch for #2 is that file capabilities need a real binary executable, because the kernel ignores file capabilities set on interpreted programs. I found a utility called cx_Freeze that uses a minimal binary loader to start up the python environment and run a bundled script. Then we can set inherited file capabilities on /usr/bin/duplicity in the post-install script. The inherited capabilities cannot be harnessed unless the executing user also has the same capabilities in their inherited set, which the system administrator can take care of by granting inherited capabilities on the backup user. The easiest way to do this is using pam_cap.so. One possible issue is that cx_Freeze is available as an ubuntu package (cx-freeze) but it is not in Debian.
I've been running a custom version of duplicity with this setup for several weeks. It's a cleaner solution since it doesn't require a specific duplicity user to exist. The tradeoff is that system-wide backups require more setup since the system administrator will need to set up the inherited user capabilities. It's worth adding some documentation on how to do all of this.
Are either of these directions something you'd like to consider now?
Kenneth Loafman (kenneth-loafman) wrote : Posted in a previous version of this proposal | # |
I've run cx-freeze as well and I like it (gsutil on Freebsd). From the
standpoint of isolation it would give us the ability to supply all versions
of all needed modules as well as the Python version itself (for those that
refuse to upgrade). The cost is that the package becomes huge, but it's
just tradeoff, our support time vs a bit of their storage space.
What we might consider doing is both a frozen and a non-frozen version.
The users still on Python 2.4 (gack!) could upgrade to the latest and
greatest by using the frozen version, while the more advanced could upgrade
normally. I know it works on all Linux distros and Freebsd.
As to file capabilities, that will depend on what kernel version they are
running and whether it's baked into the filesystem. I suppose the code
could check for availability, use if possible and operate as normal if not.
On Fri, Jun 5, 2015 at 12:27 PM, James Wilson <email address hidden> wrote:
> Hey, I wanted to resume this discussion. Since April a couple of things
> have changed: 1) I helped the maintainer of libcap-ng to fix its build
> issues as of 0.7.4-3 that is available in sid and stretch, and 2) I found
> another way to limit root capabilities using file capabilities that has a
> smaller footprint (it doesn't create a new system user).
>
> The catch for #2 is that file capabilities need a real binary executable,
> because the kernel ignores file capabilities set on interpreted programs. I
> found a utility called cx_Freeze that uses a minimal binary loader to start
> up the python environment and run a bundled script. Then we can set
> inherited file capabilities on /usr/bin/duplicity in the post-install
> script. The inherited capabilities cannot be harnessed unless the executing
> user also has the same capabilities in their inherited set, which the
> system administrator can take care of by granting inherited capabilities on
> the backup user. The easiest way to do this is using pam_cap.so. One
> possible issue is that cx_Freeze is available as an ubuntu package
> (cx-freeze) but it is not in Debian.
>
> I've been running a custom version of duplicity with this setup for
> several weeks. It's a cleaner solution since it doesn't require a specific
> duplicity user to exist. The tradeoff is that system-wide backups require
> more setup since the system administrator will need to set up the inherited
> user capabilities. It's worth adding some documentation on how to do all of
> this.
>
> Are either of these directions something you'd like to consider now?
> --
> https:/
> You are subscribed to branch lp:duplicity.
>
Aaron Whitehouse (aaron-whitehouse) : | # |
Unmerged revisions
- 1114. By James Wilson
-
Use file capabilities on frozen executable
Preview Diff
1 | === modified file 'bin/duplicity' |
2 | --- bin/duplicity 2015-05-08 12:28:47 +0000 |
3 | +++ bin/duplicity 2015-08-03 18:32:04 +0000 |
4 | @@ -1302,7 +1302,7 @@ |
5 | log.Notice(_("RESTART: The first volume failed to upload before termination.\n" |
6 | " Restart is impossible...starting backup from beginning.")) |
7 | self.last_backup.delete() |
8 | - os.execve(sys.argv[0], sys.argv, os.environ) |
9 | + os.execvpe(sys.argv[0], sys.argv, os.environ) |
10 | elif mf_len - self.start_vol > 0: |
11 | # upload of N vols failed, fix manifest and restart |
12 | log.Notice(_("RESTART: Volumes %d to %d failed to upload before termination.\n" |
13 | @@ -1317,7 +1317,7 @@ |
14 | " backup then restart the backup from the beginning.") % |
15 | (mf_len, self.start_vol)) |
16 | self.last_backup.delete() |
17 | - os.execve(sys.argv[0], sys.argv, os.environ) |
18 | + os.execvpe(sys.argv[0], sys.argv, os.environ) |
19 | |
20 | def setLastSaved(self, mf): |
21 | vi = mf.volume_info_dict[self.start_vol] |
22 | @@ -1341,12 +1341,8 @@ |
23 | See https://bugs.launchpad.net/duplicity/+bug/931175 |
24 | """), log.ErrorCode.pythonoptimize_set) |
25 | |
26 | - # if python is run setuid, it's only partway set, |
27 | - # so make sure to run with euid/egid of root |
28 | if os.geteuid() == 0: |
29 | - # make sure uid/gid match euid/egid |
30 | - os.setuid(os.geteuid()) |
31 | - os.setgid(os.getegid()) |
32 | + log.Warn(_("Running duplicity as root is not recommend.")) |
33 | |
34 | # set the current time strings (make it available for command line processing) |
35 | dup_time.setcurtime() |
36 | |
37 | === modified file 'debian/control' |
38 | --- debian/control 2014-10-27 14:15:52 +0000 |
39 | +++ debian/control 2015-08-03 18:32:04 +0000 |
40 | @@ -15,6 +15,7 @@ |
41 | python-pexpect, |
42 | rdiff, |
43 | rsync, |
44 | + cx-freeze, |
45 | Homepage: https://launchpad.net/duplicity |
46 | Standards-Version: 3.9.5 |
47 | X-Python-Version: >= 2.6 |
48 | @@ -27,9 +28,11 @@ |
49 | gnupg, |
50 | python-lockfile, |
51 | python-pexpect, |
52 | +Recommends: libcap2-bin, |
53 | Suggests: ncftp, |
54 | python-boto, |
55 | python-paramiko, |
56 | + libpam-cap, |
57 | Description: encrypted bandwidth-efficient backup |
58 | Duplicity backs directories by producing encrypted tar-format volumes |
59 | and uploading them to a remote or local file server. Because duplicity |
60 | |
61 | === added file 'debian/duplicity.docs' |
62 | --- debian/duplicity.docs 1970-01-01 00:00:00 +0000 |
63 | +++ debian/duplicity.docs 2015-08-03 18:32:04 +0000 |
64 | @@ -0,0 +1,2 @@ |
65 | +README |
66 | +README-LOG |
67 | |
68 | === added file 'debian/duplicity.postinst' |
69 | --- debian/duplicity.postinst 1970-01-01 00:00:00 +0000 |
70 | +++ debian/duplicity.postinst 2015-08-03 18:32:04 +0000 |
71 | @@ -0,0 +1,6 @@ |
72 | +#!/bin/sh -e |
73 | + |
74 | +PROGRAM=/usr/bin/duplicity |
75 | + |
76 | +[ -e "${PROGRAM}" -a -x "${PROGRAM}" ] || exit |
77 | +which setcap >/dev/null && setcap -q cap_chown,cap_dac_read_search,cap_fowner=ei "${PROGRAM}" |
78 | |
79 | === modified file 'debian/rules' |
80 | --- debian/rules 2014-10-31 20:33:27 +0000 |
81 | +++ debian/rules 2015-08-03 18:32:04 +0000 |
82 | @@ -12,14 +12,16 @@ |
83 | |
84 | override_dh_auto_install: |
85 | dh_auto_install |
86 | - |
87 | + |
88 | # Debian installs docs itself in /usr/share/doc/duplicity/ |
89 | rm -r debian/duplicity/usr/share/doc/duplicity-* |
90 | - |
91 | + |
92 | # Modify upstream's version string into the right version |
93 | find debian/duplicity -name "*\$$version*" | xargs rename "s/\\\$$version/$(UPSTREAM_VERSION)/g" |
94 | find debian/duplicity -name "*_version*" | xargs rename "s/_version/$(UPSTREAM_VERSION)/g" |
95 | grep -Rl "\$$version" debian/duplicity | xargs sed -i "s/\$$version/$(UPSTREAM_VERSION)/g" |
96 | |
97 | -override_dh_installdocs: |
98 | - dh_installdocs README README-LOG |
99 | +override_dh_strip: |
100 | + # Don't strip the executable since it will remove the attached zip |
101 | + # archive, and the base image from cx_Freeze is already stripped. |
102 | + dh_strip --exclude=/usr/bin/duplicity |
103 | |
104 | === modified file 'duplicity/selection.py' |
105 | --- duplicity/selection.py 2015-07-31 08:22:31 +0000 |
106 | +++ duplicity/selection.py 2015-08-03 18:32:04 +0000 |
107 | @@ -151,16 +151,7 @@ |
108 | for filename in robust.listpath(path): |
109 | new_path = robust.check_common_error( |
110 | error_handler, Path.append, (path, filename)) |
111 | - # make sure file is read accessible |
112 | - if (new_path and new_path.type in ["reg", "dir"] |
113 | - and not os.access(new_path.name, os.R_OK)): |
114 | - log.Warn(_("Error accessing possibly locked file %s") % util.ufn(new_path.name), |
115 | - log.WarningCode.cannot_read, |
116 | - util.escape(new_path.name)) |
117 | - if diffdir.stats: |
118 | - diffdir.stats.Errors += 1 |
119 | - new_path = None |
120 | - elif new_path: |
121 | + if new_path: |
122 | s = self.Select(new_path) |
123 | if s == 1: |
124 | yield (new_path, 0) |
125 | @@ -447,9 +438,12 @@ |
126 | |
127 | def exclude_sel_func(path): |
128 | # do not follow symbolic links when checking for file existence! |
129 | - if path.isdir() and path.append(filename).exists(): |
130 | - return 0 |
131 | - else: |
132 | + try: |
133 | + if path.isdir() and path.append(filename).exists(): |
134 | + return 0 |
135 | + else: |
136 | + return None |
137 | + except OSError: |
138 | return None |
139 | |
140 | if include == 0: |
141 | |
142 | === modified file 'setup.py' |
143 | --- setup.py 2015-02-01 17:37:37 +0000 |
144 | +++ setup.py 2015-08-03 18:32:04 +0000 |
145 | @@ -22,10 +22,15 @@ |
146 | |
147 | import sys |
148 | import os |
149 | -from setuptools import setup, Extension |
150 | +import cx_Freeze |
151 | +from setuptools import setup, Command, Extension |
152 | +from setuptools import Distribution as _Distribution |
153 | from setuptools.command.test import test |
154 | from setuptools.command.install import install |
155 | from setuptools.command.sdist import sdist |
156 | +from distutils.command.clean import clean |
157 | +import distutils.dir_util |
158 | +import distutils.log |
159 | |
160 | version_string = "$version" |
161 | |
162 | @@ -77,6 +82,9 @@ |
163 | # And make sure our scripts are ready |
164 | build_scripts_cmd = self.get_finalized_command("build_scripts") |
165 | build_scripts_cmd.run() |
166 | + # And make sure our executables are ready |
167 | + build_exe_cmd = self.get_finalized_command("build_exe") |
168 | + build_exe_cmd.run() |
169 | |
170 | # make symlinks for test data |
171 | if build_cmd.build_lib != top_dir: |
172 | @@ -88,14 +96,45 @@ |
173 | except Exception: |
174 | pass |
175 | |
176 | - os.environ['PATH'] = "%s:%s" % ( |
177 | + os.environ['PATH'] = "%s:%s:%s" % ( |
178 | + os.path.abspath(build_exe_cmd.build_exe), |
179 | os.path.abspath(build_scripts_cmd.build_dir), |
180 | os.environ.get('PATH')) |
181 | |
182 | test.run(self) |
183 | |
184 | |
185 | +class Distribution(_Distribution): |
186 | + def __init__(self, attrs): |
187 | + self.executables = [] |
188 | + _Distribution.__init__(self, attrs) |
189 | + |
190 | + |
191 | class InstallCommand(install): |
192 | + user_options = install.user_options + [ |
193 | + ('install-exe=', None, 'installation directory for executables'), |
194 | + ] |
195 | + |
196 | + def expand_dirs(self): |
197 | + install.expand_dirs(self) |
198 | + self._expand_attrs(['install_exe']) |
199 | + |
200 | + def get_sub_commands(self): |
201 | + subCommands = install.get_sub_commands(self) |
202 | + if self.distribution.executables: |
203 | + subCommands.append("install_exe") |
204 | + return [s for s in subCommands if s != "install_egg_info"] |
205 | + |
206 | + def initialize_options(self): |
207 | + install.initialize_options(self) |
208 | + self.install_exe = None |
209 | + |
210 | + def finalize_options(self): |
211 | + install.finalize_options(self) |
212 | + self.convert_paths('exe') |
213 | + if self.root is not None: |
214 | + self.change_roots('exe') |
215 | + |
216 | def run(self): |
217 | # Normally, install will call build(). But we want to delete the |
218 | # testing dir between building and installing. So we manually build |
219 | @@ -110,6 +149,67 @@ |
220 | |
221 | install.run(self) |
222 | |
223 | + def select_scheme(self, name): |
224 | + install.select_scheme(self, name) |
225 | + if self.install_exe is None: |
226 | + self.install_exe = self.install_scripts |
227 | + |
228 | + |
229 | +class InstallExeCommand(Command): |
230 | + description = "install executables built from Python scripts" |
231 | + user_options = [ |
232 | + ('install-dir=', 'd', 'directory to install executables to'), |
233 | + ('build-dir=', 'b', 'build directory (where to install from)'), |
234 | + ('force', 'f', 'force installation (overwrite existing files)'), |
235 | + ('skip-build', None, 'skip the build steps'), |
236 | + ] |
237 | + |
238 | + def initialize_options(self): |
239 | + self.install_dir = None |
240 | + self.force = 0 |
241 | + self.build_dir = None |
242 | + self.skip_build = None |
243 | + |
244 | + def finalize_options(self): |
245 | + self.set_undefined_options('build', ('build_exe', 'build_dir')) |
246 | + self.set_undefined_options('install', |
247 | + ('install_exe', 'install_dir')) |
248 | + self.set_undefined_options('install', |
249 | + ('force', 'force'), |
250 | + ('skip_build', 'skip_build')) |
251 | + |
252 | + def run(self): |
253 | + if not self.skip_build: |
254 | + self.run_command('build_exe') |
255 | + self.outfiles = self.copy_tree(self.build_dir, self.install_dir) |
256 | + |
257 | + def get_inputs(self): |
258 | + return self.distribution.executables or [] |
259 | + |
260 | + def get_outputs(self): |
261 | + return self.outfiles or [] |
262 | + |
263 | + |
264 | +class CleanCommand(clean): |
265 | + user_options = clean.user_options + [ |
266 | + ('build-exe=', None, 'build directory for executables'), |
267 | + ] |
268 | + |
269 | + def initialize_options(self): |
270 | + clean.initialize_options(self) |
271 | + self.build_exe = None |
272 | + |
273 | + def finalize_options(self): |
274 | + clean.finalize_options(self) |
275 | + self.set_undefined_options('build_exe', ('build_exe', 'build_exe')) |
276 | + |
277 | + def run(self): |
278 | + if os.path.exists(self.build_exe): |
279 | + distutils.dir_util.remove_tree(self.build_exe, dry_run=self.dry_run) |
280 | + else: |
281 | + distutils.log.warn("'%s' does not exist -- can't clean it", self.build_exe) |
282 | + clean.run(self) |
283 | + |
284 | |
285 | # TODO: move logic from dist/makedist inline |
286 | class SDistCommand(sdist): |
287 | @@ -145,11 +245,20 @@ |
288 | include_dirs=incdir_list, |
289 | library_dirs=libdir_list, |
290 | libraries=["rsync"])], |
291 | - scripts=['bin/rdiffdir', 'bin/duplicity'], |
292 | + scripts=['bin/rdiffdir'], |
293 | data_files=data_files, |
294 | tests_require=['lockfile', 'mock', 'pexpect'], |
295 | test_suite='testing', |
296 | cmdclass={'test': TestCommand, |
297 | 'install': InstallCommand, |
298 | - 'sdist': SDistCommand}, |
299 | + 'install_exe': InstallExeCommand, |
300 | + 'sdist': SDistCommand, |
301 | + 'build': cx_Freeze.build, |
302 | + 'build_exe': cx_Freeze.build_exe, |
303 | + 'clean': CleanCommand}, |
304 | + distclass=Distribution, |
305 | + executables=[cx_Freeze.Executable('bin/duplicity')], |
306 | + options={"build_exe": {"copy_dependent_files": False, |
307 | + "create_shared_zip": False, |
308 | + "append_script_to_exe": True}}, |
309 | ) |
If I understand this correctly, then new files will be created by duplicity/ *'
'duplicity', but older files owned by root will not be deletable.
Correct? I'm thinking a one-time 'chown -R duplicity: ~/cache/
would fix that problem.
On Sun, Apr 26, 2015 at 7:31 PM, James Wilson <email address hidden> wrote:
> James Wilson has proposed merging lp:~jmwilson/duplicity/capabilities into /code.launchpad .net/~jmwilson/ duplicity/ capabilities/ +merge/ 257488 os.geteuid( )) os.getegid( )) READ_SEARCH. Since we're still root, we'll get all capabilities "nobody" )
> lp:duplicity.
>
> Requested reviews:
> duplicity-team (duplicity-team)
>
> For more details, see:
> https:/
>
> Proposal is to add an unprivileged "duplicity" user during installation
> that is used to limit the capabilities when duplicity is run as root. To
> manage capabilities, I'm using the python bindings for libcap-ng (which
> needs its own updating since at least the current vivid package is empty
> for some reason, but is correct when built from source).
>
> I've been interested in using duplicity for system-wide backups, but one
> thing that is troubling is that it must be run as root. As an interpreted
> program that communicates on the network, this exposes the host to possible
> bugs in python or any other packages imported in duplicity.
>
> First, examining the code in bin/duplicity:
> # if python is run setuid, it's only partway set,
> # so make sure to run with euid/egid of root
> if os.geteuid() == 0:
> # make sure uid/gid match euid/egid
> os.setuid(
> os.setgid(
>
> I'm not sure what this is for; if you're root (i.e., os.geteuid() == 0)
> then there's no need to switch the real uid. When the machination of
> "setuid(geteuid())" is used it is typically when the ruid=0 and we want to
> irrevocably drop privileges to a euid!=0 normal user. That's not the case
> here, so this doesn't help or harm us.
>
> Then there's the issue of running duplicity as root. Since it's an
> interpreted program, the normal ways of expanding privileges (SUID
> executable or setcap on the script) are unavailable, and the other options
> are to run it as root, or put SUID or file capabilities on the python
> interpreter.
>
> The only reason to run duplicity as root is get read access to the whole
> file system. We can safely drop all capabilities other than
> CAP_DAC_
> back if we do execve, so we could also change the bounding set or lock the
> securebits to prevent the kernel from re-granting privileges on execve.
> Nonetheless, we're still root, and lots of important files are owned by and
> writable to root, so the best choice is to change uid to an unprivileged
> user who maintains only the ability to read the whole file system.
>
> It's hard to test the change directly, since it doesn't change the output
> or actions of duplicity. The following python script mimics what the code
> does and demonstrates the reduction of capabilities:
>
> #!/usr/bin/env python
>
> from __future__ import print_function
> from capng import *
> import fcntl
> import os
> import pwd
> import signal
> import sys
>
> if os.geteuid() != 0:
> sys.exit()
>
> user = pwd.getpwnam(
> capng_clear...