Retire repo

This repo was created by accident, use deb-python-oslo.privsep
instead.

Needed-By: I1ac1a06931c8b6dd7c2e73620a0302c29e605f03
Change-Id: I81894aea69b9d09b0977039623c26781093a397a
This commit is contained in:
Andreas Jaeger 2017-04-17 19:38:50 +02:00
parent 4982b93c4e
commit a0a0be8d0e
42 changed files with 13 additions and 2463 deletions

View File

@ -1,7 +0,0 @@
[run]
branch = True
source = privsep
omit = privsep/tests/*,privsep/openstack/*
[report]
ignore-errors = True

52
.gitignore vendored
View File

@ -1,52 +0,0 @@
*.py[cod]
# C extensions
*.so
# Packages
*.egg
*.egg-info
dist
build
eggs
/.eggs/
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
nosetests.xml
.testrepository
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Complexity
output/*.html
output/*/index.html
# Sphinx
doc/build
# pbr generates these
AUTHORS
ChangeLog
# Editors
*~
.*.swp

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/oslo.privsep.git

View File

@ -1,3 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

View File

@ -1,7 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
${PYTHON:-python} -m subunit.run discover -t ./ ./oslo_privsep $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -1,17 +0,0 @@
If you would like to contribute to the development of OpenStack, you must
follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
If you already have a good understanding of how the system works and your
OpenStack accounts are set up, you can skip to the development workflow
section of this documentation to learn how changes to OpenStack should be
submitted for review via the Gerrit tool:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/oslo.privsep

View File

@ -1,4 +0,0 @@
oslo.privsep Style Commandments
======================================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

176
LICENSE
View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,29 +0,0 @@
============
oslo.privsep
============
.. image:: https://img.shields.io/pypi/v/oslo.privsep.svg
:target: https://pypi.python.org/pypi/oslo.privsep/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/dm/oslo.privsep.svg
:target: https://pypi.python.org/pypi/oslo.privsep/
:alt: Downloads
OpenStack library for privilege separation
This library helps applications perform actions which require more or
less privileges than they were started with in a safe, easy to code
and easy to use manner. For more information on why this is generally
a good idea please read over the `principle of least privilege`_ and
the `specification`_ which created this library.
* Free software: Apache license
* Documentation: http://docs.openstack.org/developer/oslo.privsep
* Source: http://git.openstack.org/cgit/openstack/oslo.privsep
* Bugs: http://bugs.launchpad.net/oslo.privsep
.. _principle of least privilege: https://en.wikipedia.org/wiki/\
Principle_of_least_privilege
.. _specification: https://specs.openstack.org/openstack/\
oslo-specs/specs/liberty/privsep.html

13
README.txt Normal file
View File

@ -0,0 +1,13 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
Use instead the project deb-python-oslo.privsep at
http://git.openstack.org/cgit/openstack/deb-python-oslo.privsep .
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@ -1,2 +0,0 @@
[python: **.py]

View File

@ -1,12 +0,0 @@
=====
API
=====
.. Use autodoc directives to describe the *public* modules and classes
in the library.
If the modules are completely unrelated, create an api subdirectory
and use a separate file for each (see oslo.utils).
If there is only one submodule, a single api.rst file like this
sufficient (see oslo.i18n).

View File

@ -1,75 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
#'sphinx.ext.intersphinx',
'oslosphinx'
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'oslo.privsep'
copyright = u'2014, OpenStack Foundation'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
# html_theme = '_theme'
# html_static_path = ['static']
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index',
'%s.tex' % project,
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'),
]
# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}

View File

@ -1,5 +0,0 @@
==============
Contributing
==============
.. include:: ../../CONTRIBUTING.rst

View File

@ -1 +0,0 @@
.. include:: ../../ChangeLog

View File

@ -1,25 +0,0 @@
==============
oslo.privsep
==============
OpenStack library for privilege separation
Contents
========
.. toctree::
:maxdepth: 2
installation
api
usage
contributing
history
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -1,7 +0,0 @@
==============
Installation
==============
At the command line::
$ pip install oslo.privsep

View File

@ -1,7 +0,0 @@
=======
Usage
=======
To use oslo.privsep in a project::
import oslo_privsep

View File

@ -1,41 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""oslo.i18n integration module.
See http://docs.openstack.org/developer/oslo.i18n/usage.html
"""
import oslo_i18n
_translators = oslo_i18n.TranslatorFactory(domain='oslo_privsep')
# The primary translation function using the well-known name "_"
_ = _translators.primary
# The contextual translation function using the name "_C"
_C = _translators.contextual_form
# The plural translation function using the name "_P"
_P = _translators.plural_form
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical

View File

@ -1,151 +0,0 @@
# Copyright 2015 Rackspace Hosting
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import platform
import cffi
# Expand as necessary
CAP_CHOWN = 0
CAP_DAC_OVERRIDE = 1
CAP_FOWNER = 3
CAP_KILL = 5
CAP_SETPCAP = 8
CAP_NET_BIND_SERVICE = 10
CAP_NET_BROADCAST = 11
CAP_NET_ADMIN = 12
CAP_NET_RAW = 13
CAP_SYS_ADMIN = 21
# Convenience dicts for human readable values
CAPS_BYNAME = {}
CAPS_BYVALUE = {}
for k, v in globals().copy().items():
if k.startswith('CAP_'):
CAPS_BYNAME[k] = v
CAPS_BYVALUE[v] = k
CDEF = '''
/* Edited highlights from `echo '#include <sys/capability.h>' | gcc -E -` */
#define _LINUX_CAPABILITY_VERSION_2 0x20071026
#define _LINUX_CAPABILITY_U32S_2 2
typedef unsigned int __u32;
typedef struct __user_cap_header_struct {
__u32 version;
int pid;
} *cap_user_header_t;
typedef struct __user_cap_data_struct {
__u32 effective;
__u32 permitted;
__u32 inheritable;
} *cap_user_data_t;
int capset(cap_user_header_t header, const cap_user_data_t data);
int capget(cap_user_header_t header, cap_user_data_t data);
/* Edited highlights from `echo '#include <sys/prctl.h>' | gcc -E -` */
#define PR_GET_KEEPCAPS 7
#define PR_SET_KEEPCAPS 8
int prctl (int __option, ...);
'''
ffi = cffi.FFI()
crt = ffi.dlopen(None)
ffi.cdef(CDEF)
if platform.system() == 'Linux':
# mock.patching crt.* directly seems to upset cffi. Use an
# indirection point here for easier testing.
_prctl = crt.prctl
_capget = crt.capget
_capset = crt.capset
else:
_prctl = None
_capget = None
_capset = None
def set_keepcaps(enable):
"""Set/unset thread's "keep capabilities" flag - see prctl(2)"""
ret = _prctl(crt.PR_SET_KEEPCAPS,
ffi.cast('unsigned long', bool(enable)))
if ret != 0:
errno = ffi.errno
raise OSError(errno, os.strerror(errno))
def drop_all_caps_except(effective, permitted, inheritable):
"""Set (effective, permitted, inheritable) to provided list of caps"""
eff = _caps_to_mask(effective)
prm = _caps_to_mask(permitted)
inh = _caps_to_mask(inheritable)
header = ffi.new('cap_user_header_t',
{'version': crt._LINUX_CAPABILITY_VERSION_2,
'pid': 0})
data = ffi.new('struct __user_cap_data_struct[2]')
data[0].effective = eff & 0xffffffff
data[1].effective = eff >> 32
data[0].permitted = prm & 0xffffffff
data[1].permitted = prm >> 32
data[0].inheritable = inh & 0xffffffff
data[1].inheritable = inh >> 32
ret = _capset(header, data)
if ret != 0:
errno = ffi.errno
raise OSError(errno, os.strerror(errno))
def _mask_to_caps(mask):
"""Convert bitmask to list of set bit offsets"""
return [i for i in range(64) if (1 << i) & mask]
def _caps_to_mask(caps):
"""Convert list of bit offsets to bitmask"""
mask = 0
for cap in caps:
mask |= 1 << cap
return mask
def get_caps():
"""Return (effective, permitted, inheritable) as lists of caps"""
header = ffi.new('cap_user_header_t',
{'version': crt._LINUX_CAPABILITY_VERSION_2,
'pid': 0})
data = ffi.new('struct __user_cap_data_struct[2]')
ret = _capget(header, data)
if ret != 0:
errno = ffi.errno
raise OSError(errno, os.strerror(errno))
return (
_mask_to_caps(data[0].effective |
(data[1].effective << 32)),
_mask_to_caps(data[0].permitted |
(data[1].permitted << 32)),
_mask_to_caps(data[0].inheritable |
(data[1].inheritable << 32)),
)

View File

@ -1,183 +0,0 @@
# Copyright 2015 Rackspace Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Serialization/Deserialization for privsep.
The wire format is a stream of msgpack objects encoding primitive
python datatypes. Msgpack 'raw' is assumed to be a valid utf8 string
(msgpack 2.0 'bin' type is used for bytes). Python lists are
converted to tuples during serialization/deserialization.
"""
import logging
import socket
import threading
import msgpack
import six
from oslo_privsep._i18n import _
LOG = logging.getLogger(__name__)
try:
import greenlet
def _get_thread_ident():
# This returns something sensible, even if the current thread
# isn't a greenthread
return id(greenlet.getcurrent())
except ImportError:
def _get_thread_ident():
return threading.current_thread().ident
class Serializer(object):
def __init__(self, writesock):
self.writesock = writesock
def send(self, msg):
buf = msgpack.packb(msg, use_bin_type=True)
self.writesock.sendall(buf)
def close(self):
# Hilarious. `socket._socketobject.close()` doesn't actually
# call `self._sock.close()`. Oh well, we really wanted a half
# close anyway.
self.writesock.shutdown(socket.SHUT_WR)
class Deserializer(six.Iterator):
def __init__(self, readsock):
self.readsock = readsock
self.unpacker = msgpack.Unpacker(use_list=False, encoding='utf-8')
def __iter__(self):
return self
def __next__(self):
while True:
try:
return next(self.unpacker)
except StopIteration:
buf = self.readsock.recv(4096)
if not buf:
raise
self.unpacker.feed(buf)
class Future(object):
"""A very simple object to track the return of a function call"""
def __init__(self, lock):
self.condvar = threading.Condition(lock)
self.error = None
self.data = None
def set_result(self, data):
"""Must already be holding lock used in constructor"""
self.data = data
self.condvar.notify()
def set_exception(self, exc):
"""Must already be holding lock used in constructor"""
self.error = exc
self.condvar.notify()
def result(self):
"""Must already be holding lock used in constructor"""
self.condvar.wait()
if self.error is not None:
raise self.error
return self.data
class ClientChannel(object):
def __init__(self, sock):
self.writer = Serializer(sock)
self.lock = threading.Lock()
self.reader_thread = threading.Thread(
name='privsep_reader',
target=self._reader_main,
args=(Deserializer(sock),),
)
self.reader_thread.daemon = True
self.outstanding_msgs = {}
self.reader_thread.start()
def _reader_main(self, reader):
"""This thread owns and demuxes the read channel"""
for msg in reader:
msgid, data = msg
with self.lock:
assert msgid in self.outstanding_msgs
self.outstanding_msgs[msgid].set_result(data)
# EOF. Perhaps the privileged process exited?
# Send an IOError to any oustanding waiting readers. Assuming
# the write direction is also closed, any new writes should
# get an immediate similar error.
LOG.debug('EOF on privsep read channel')
exc = IOError(_('Premature eof waiting for privileged process'))
with self.lock:
for mbox in self.outstanding_msgs.values():
mbox.set_exception(exc)
def send_recv(self, msg):
myid = _get_thread_ident()
future = Future(self.lock)
with self.lock:
assert myid not in self.outstanding_msgs
self.outstanding_msgs[myid] = future
try:
self.writer.send((myid, msg))
reply = future.result()
finally:
del self.outstanding_msgs[myid]
return reply
def close(self):
with self.lock:
self.writer.close()
self.reader_thread.join()
class ServerChannel(six.Iterator):
"""Server-side twin to ClientChannel"""
def __init__(self, sock):
self.rlock = threading.Lock()
self.reader_iter = iter(Deserializer(sock))
self.wlock = threading.Lock()
self.writer = Serializer(sock)
def __iter__(self):
return self
def __next__(self):
with self.rlock:
return next(self.reader_iter)
def send(self, msg):
with self.wlock:
self.writer.send(msg)

View File

@ -1,478 +0,0 @@
# Copyright 2015 Rackspace Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
'''Privilege separation ("privsep") daemon.
To ease transition this supports 2 alternative methods of starting the
daemon, all resulting in a helper process running with elevated
privileges and open socket(s) to the original process:
1. Start via fork()
Assumes process currently has all required privileges and is about
to drop them (perhaps by setuid to an unprivileged user). If the
the initial environment is secure and `PrivContext.start(Method.FORK)`
is called early in `main()`, then this is the most secure and
simplest. In particular, if the initial process is already running
as non-root (but with sufficient capabilities, via eg suitable
systemd service files), then no part needs to involve uid=0 or
sudo.
2. Start via sudo/rootwrap
This starts the privsep helper on first use via sudo and rootwrap,
and communicates via a temporary Unix socket passed on the command
line. The communication channel is briefly exposed in the
filesystem, but is protected with file permissions and connecting
to it only grants access to the unprivileged process. Requires a
suitable entry in sudoers or rootwrap.conf filters.
The privsep daemon exits when the communication channel is closed,
(which usually occurs when the unprivileged process exits).
'''
import enum
import errno
import io
import logging as pylogging
import os
import platform
import socket
import subprocess
import sys
import tempfile
import threading
if platform.system() == 'Linux':
import fcntl
import grp
import pwd
import eventlet
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
from oslo_privsep import capabilities
from oslo_privsep import comm
from oslo_privsep._i18n import _, _LE, _LI
LOG = logging.getLogger(__name__)
@enum.unique
class StdioFd(enum.IntEnum):
# NOTE(gus): We can't use sys.std*.fileno() here. sys.std*
# objects may be random file-like objects that may not match the
# true system std* fds - and indeed may not even have a file
# descriptor at all (eg: test fixtures that monkey patch
# fixtures.StringStream onto sys.stdout). Below we always want
# the _real_ well-known 0,1,2 Unix fds during os.dup2
# manipulation.
STDIN = 0
STDOUT = 1
STDERR = 2
@enum.unique
class Message(enum.IntEnum):
"""Types of messages sent across the communication channel"""
PING = 1
PONG = 2
CALL = 3
RET = 4
ERR = 5
class FailedToDropPrivileges(Exception):
pass
class ProtocolError(Exception):
pass
def set_cloexec(fd):
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
if (flags & fcntl.FD_CLOEXEC) == 0:
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(fd, fcntl.F_SETFD, flags)
def setuid(user_id_or_name):
try:
new_uid = int(user_id_or_name)
except (TypeError, ValueError):
new_uid = pwd.getpwnam(user_id_or_name).pw_uid
if new_uid != 0:
try:
os.setuid(new_uid)
except OSError:
msg = _('Failed to set uid %s') % new_uid
LOG.critical(msg)
raise FailedToDropPrivileges(msg)
def setgid(group_id_or_name):
try:
new_gid = int(group_id_or_name)
except (TypeError, ValueError):
new_gid = grp.getgrnam(group_id_or_name).gr_gid
if new_gid != 0:
try:
os.setgid(new_gid)
except OSError:
msg = _('Failed to set gid %s') % new_gid
LOG.critical(msg)
raise FailedToDropPrivileges(msg)
class _ClientChannel(comm.ClientChannel):
"""Our protocol, layered on the basic primitives in comm.ClientChannel"""
def __init__(self, sock):
super(_ClientChannel, self).__init__(sock)
self.exchange_ping()
def exchange_ping(self):
try:
# exchange "ready" messages
reply = self.send_recv((Message.PING.value,))
success = reply[0] == Message.PONG
except Exception as e:
LOG.exception(
_LE('Error while sending initial PING to privsep: %s'), e)
success = False
if not success:
msg = _('Privsep daemon failed to start')
LOG.critical(msg)
raise FailedToDropPrivileges(msg)
def remote_call(self, name, args, kwargs):
result = self.send_recv((Message.CALL.value, name, args, kwargs))
if result[0] == Message.RET:
# (RET, return value)
return result[1]
elif result[0] == Message.ERR:
# (ERR, exc_type, args)
#
# TODO(gus): see what can be done to preserve traceback
# (without leaking local values)
exc_type = importutils.import_class(result[1])
raise exc_type(*result[2])
else:
raise ProtocolError(_('Unexpected response: %r') % result)
def fdopen(fd, *args, **kwargs):
# NOTE(gus): We can't just use os.fdopen() here and allow the
# regular (optional) monkey_patching to do its thing. Turns out
# that regular file objects (as returned by os.fdopen) on python2
# are broken in lots of ways regarding blocking behaviour. We
# *need* the newer io.* objects on py2 (doesn't matter on py3,
# since the old file code has been replaced with io.*)
if eventlet.patcher.is_monkey_patched('socket'):
return eventlet.greenio.GreenPipe(fd, *args, **kwargs)
else:
return io.open(fd, *args, **kwargs)
def _fd_logger(level=logging.WARN):
"""Helper that returns a file object that is asynchronously logged"""
read_fd, write_fd = os.pipe()
read_end = fdopen(read_fd, 'r', 1)
write_end = fdopen(write_fd, 'w', 1)
def logger(f):
for line in f:
LOG.log(level, 'privsep log: %s', line.rstrip())
t = threading.Thread(
name='fd_logger',
target=logger, args=(read_end,)
)
t.daemon = True
t.start()
return write_end
def replace_logging(handler, log_root=None):
if log_root is None:
log_root = logging.getLogger(None).logger # root logger
for h in log_root.handlers:
log_root.removeHandler(h)
log_root.addHandler(handler)
class ForkingClientChannel(_ClientChannel):
def __init__(self, context):
"""Start privsep daemon using fork()
Assumes we already have required privileges.
"""
sock_a, sock_b = socket.socketpair()
for s in (sock_a, sock_b):
s.setblocking(True)
# Important that these sockets don't get leaked
set_cloexec(s)
# Try to prevent any buffered output from being written by both
# parent and child.
for f in (sys.stdout, sys.stderr):
f.flush()
log_fd = _fd_logger()
if os.fork() == 0:
# child
# replace root logger early (to capture any errors below)
replace_logging(pylogging.StreamHandler(log_fd))
sock_a.close()
Daemon(comm.ServerChannel(sock_b), context=context).run()
LOG.debug('privsep daemon exiting')
os._exit(0)
# parent
sock_b.close()
super(ForkingClientChannel, self).__init__(sock_a)
class RootwrapClientChannel(_ClientChannel):
def __init__(self, context):
"""Start privsep daemon using exec()
Uses sudo/rootwrap to gain privileges.
"""
listen_sock = socket.socket(socket.AF_UNIX)
# Note we listen() on the unprivileged side, and connect to it
# from the privileged process. This means there is no exposed
# attack point on the privileged side.
# NB: Permissions on sockets are not checked on some (BSD) Unices
# so create socket in a private directory for safety. Privsep
# daemon will (initially) be running as root, so will still be
# able to connect to sock path.
tmpdir = tempfile.mkdtemp() # NB: created with 0700 perms
try:
sockpath = os.path.join(tmpdir, 'privsep.sock')
listen_sock.bind(sockpath)
listen_sock.listen(1)
cmd = context.helper_command(sockpath)
LOG.info(_LI('Running privsep helper: %s'), cmd)
proc = subprocess.Popen(cmd, shell=False, stderr=_fd_logger())
if proc.wait() != 0:
msg = (_LE('privsep helper command exited non-zero (%s)') %
proc.returncode)
LOG.critical(msg)
raise FailedToDropPrivileges(msg)
LOG.info(_LI('Spawned new privsep daemon via rootwrap'))
sock, _addr = listen_sock.accept()
LOG.debug('Accepted privsep connection to %s', sockpath)
finally:
# Don't need listen_sock anymore, so clean up.
listen_sock.close()
try:
os.unlink(sockpath)
except OSError as e:
if e.errno != errno.ENOENT:
raise
os.rmdir(tmpdir)
super(RootwrapClientChannel, self).__init__(sock)
class Daemon(object):
"""NB: This doesn't fork() - do that yourself before calling run()"""
def __init__(self, channel, context):
self.channel = channel
self.context = context
self.user = context.conf.user
self.group = context.conf.group
self.caps = set(context.conf.capabilities)
def run(self):
"""Run request loop. Sets up environment, then calls loop()"""
os.chdir("/")
os.umask(0)
self._drop_privs()
self._close_stdio()
self.loop()
def _close_stdio(self):
with open(os.devnull, 'w+') as devnull:
os.dup2(devnull.fileno(), StdioFd.STDIN)
os.dup2(devnull.fileno(), StdioFd.STDOUT)
# stderr is left untouched
def _drop_privs(self):
try:
# Keep current capabilities across setuid away from root.
capabilities.set_keepcaps(True)
if self.group is not None:
try:
os.setgroups([])
except OSError:
msg = _('Failed to remove supplemental groups')
LOG.critical(msg)
raise FailedToDropPrivileges(msg)
if self.user is not None:
setuid(self.user)
if self.group is not None:
setgid(self.group)
finally:
capabilities.set_keepcaps(False)
LOG.info(_LI('privsep process running with uid/gid: %(uid)s/%(gid)s'),
{'uid': os.getuid(), 'gid': os.getgid()})
capabilities.drop_all_caps_except(self.caps, self.caps, [])
def fmt_caps(capset):
if not capset:
return 'none'
return '|'.join(sorted(capabilities.CAPS_BYVALUE[c]
for c in capset))
eff, prm, inh = capabilities.get_caps()
LOG.info(
_LI('privsep process running with capabilities '
'(eff/prm/inh): %(eff)s/%(prm)s/%(inh)s'),
{
'eff': fmt_caps(eff),
'prm': fmt_caps(prm),
'inh': fmt_caps(inh),
})
def _process_cmd(self, cmd, *args):
if cmd == Message.PING:
return (Message.PONG.value,)
elif cmd == Message.CALL:
name, f_args, f_kwargs = args
func = importutils.import_class(name)
if not self.context.is_entrypoint(func):
msg = _('Invalid privsep function: %s not exported') % name
raise NameError(msg)
ret = func(*f_args, **f_kwargs)
return (Message.RET.value, ret)
raise ProtocolError(_('Unknown privsep cmd: %s') % cmd)
def loop(self):
"""Main body of daemon request loop"""
LOG.info(_LI('privsep daemon running as pid %s'), os.getpid())
# We *are* this context now - any calls through it should be
# executed locally.
self.context.set_client_mode(False)
for msgid, msg in self.channel:
LOG.debug('privsep: request[%(msgid)s]: %(req)s',
{'msgid': msgid, 'req': msg})
try:
reply = self._process_cmd(*msg)
except Exception as e:
LOG.debug(
'privsep: Exception during request[%(msgid)s]: %(err)s',
{'msgid': msgid, 'err': e}, exc_info=True)
cls = e.__class__
cls_name = '%s.%s' % (cls.__module__, cls.__name__)
reply = (Message.ERR.value, cls_name, e.args)
try:
LOG.debug('privsep: reply[%(msgid)s]: %(reply)s',
{'msgid': msgid, 'reply': reply})
self.channel.send((msgid, reply))
except IOError as e:
if e.errno == errno.EPIPE:
# Write stream closed, exit loop
break
raise
LOG.debug('Socket closed, shutting down privsep daemon')
def helper_main():
"""Start privileged process, serving requests over a Unix socket."""
cfg.CONF.register_cli_opts([
cfg.StrOpt('privsep_context', required=True),
cfg.StrOpt('privsep_sock_path', required=True),
])
logging.register_options(cfg.CONF)
cfg.CONF(args=sys.argv[1:], project='privsep')
logging.setup(cfg.CONF, 'privsep')
# We always log to stderr. Replace the root logger we just set up.
replace_logging(pylogging.StreamHandler(sys.stderr))
LOG.info(_LI('privsep daemon starting'))
context = importutils.import_class(cfg.CONF.privsep_context)
from oslo_privsep import priv_context # Avoid circular import
if not isinstance(context, priv_context.PrivContext):
LOG.fatal(_LE('--privsep_context must be the (python) name of a '
'PrivContext object'))
sock = socket.socket(socket.AF_UNIX)
sock.connect(cfg.CONF.privsep_sock_path)
set_cloexec(sock)
channel = comm.ServerChannel(sock)
# Channel is set up, so fork off daemon "in the background" and exit
if os.fork() != 0:
# parent
return
# child
# Note we don't move into a new process group/session like a
# regular daemon might, since we _want_ to remain associated with
# the originating (unprivileged) process.
try:
Daemon(channel, context).run()
except Exception as e:
LOG.exception(e)
sys.exit(str(e))
LOG.debug('privsep daemon exiting')
sys.exit(0)
if __name__ == '__main__':
helper_main()

View File

@ -1,18 +0,0 @@
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: oslo.privsep 1.5.1.dev2\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-04-19 13:52+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2015-11-13 04:56+0000\n"
"Last-Translator: Andreas Jaeger <jaegerandi@gmail.com>\n"
"Language-Team: German\n"
"Language: de\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "privsep daemon already running"
msgstr "Der Privsep Dämon läuft bereits."

View File

@ -1,53 +0,0 @@
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: oslo.privsep 1.7.1.dev1\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-06-10 16:43+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-06-09 01:20+0000\n"
"Last-Translator: Andreas Jaeger <jaegerandi@gmail.com>\n"
"Language-Team: German\n"
"Language: de\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "Failed to remove supplemental groups"
msgstr "Fehler beim Entfernen zusätzlicher Gruppen"
#, python-format
msgid "Failed to set gid %s"
msgstr "Fehler beim Festlegen von GID %s"
#, python-format
msgid "Failed to set uid %s"
msgstr "Fehler beim Festlegen von Benutzer-ID %s"
msgid "Group that the privsep daemon should run as."
msgstr "Gruppe als die der Privsep Dämon laufen soll."
#, python-format
msgid "Invalid privsep function: %s not exported"
msgstr "Invalide Privsep Funktion: %s ist nicht exportiert."
msgid "List of Linux capabilities retained by the privsep daemon."
msgstr "Liste von Linux Capabilities, die der Privsep Dämon behält."
msgid "Premature eof waiting for privileged process"
msgstr "Vorzeitiges Dateiende beim Warten auf den priviligierten Prozeß"
msgid "Privsep daemon failed to start"
msgstr "Der Privsep Dämon konnte nicht gestartet werden."
#, python-format
msgid "Unexpected response: %r"
msgstr "Unerwartete Antwort: %r"
#, python-format
msgid "Unknown privsep cmd: %s"
msgstr "Unbekanntes Privsep Kommando: %s"
msgid "User that the privsep daemon should run as."
msgstr "User als der der Privsep Dämon laufen soll."

View File

@ -1,26 +0,0 @@
# Andi Chandler <andi@gowling.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: oslo.privsep 1.7.1.dev1\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-06-10 16:43+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-06-09 11:12+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en-GB\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "--privsep_context must be the (python) name of a PrivContext object"
msgstr "--privsep_context must be the (python) name of a PrivContext object"
#, python-format
msgid "Error while sending initial PING to privsep: %s"
msgstr "Error while sending initial PING to privsep: %s"
#, python-format
msgid "privsep helper command exited non-zero (%s)"
msgstr "privsep helper command exited non-zero (%s)"

View File

@ -1,41 +0,0 @@
# Andi Chandler <andi@gowling.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: oslo.privsep 1.7.1.dev1\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-06-10 16:43+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-06-09 11:13+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en-GB\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#, python-format
msgid "Running privsep helper: %s"
msgstr "Running privsep helper: %s"
msgid "Spawned new privsep daemon via rootwrap"
msgstr "Spawned new privsep daemon via rootwrap"
#, python-format
msgid "privsep daemon running as pid %s"
msgstr "privsep daemon running as pid %s"
msgid "privsep daemon starting"
msgstr "privsep daemon starting"
#, python-format
msgid ""
"privsep process running with capabilities (eff/prm/inh): %(eff)s/%(prm)s/"
"%(inh)s"
msgstr ""
"privsep process running with capabilities (eff/prm/inh): %(eff)s/%(prm)s/"
"%(inh)s"
#, python-format
msgid "privsep process running with uid/gid: %(uid)s/%(gid)s"
msgstr "privsep process running with uid/gid: %(uid)s/%(gid)s"

View File

@ -1,18 +0,0 @@
# Andi Chandler <andi@gowling.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: oslo.privsep 1.7.1.dev1\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-06-10 16:43+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-06-09 11:13+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en-GB\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "privsep daemon already running"
msgstr "privsep daemon already running"

View File

@ -1,66 +0,0 @@
# Andi Chandler <andi@gowling.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: oslo.privsep 1.7.1.dev1\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-06-10 16:43+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-06-09 11:12+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (United Kingdom)\n"
"Language: en-GB\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid ""
"Command to invoke to start the privsep daemon if not using the \"fork\" "
"method. If not specified, a default is generated using \"sudo privsep-helper"
"\" and arguments designed to recreate the current configuration. This "
"command must accept suitable --privsep_context and --privsep_sock_path "
"arguments."
msgstr ""
"Command to invoke to start the privsep daemon if not using the \"fork\" "
"method. If not specified, a default is generated using \"sudo privsep-helper"
"\" and arguments designed to recreate the current configuration. This "
"command must accept suitable --privsep_context and --privsep_sock_path "
"arguments."
msgid "Failed to remove supplemental groups"
msgstr "Failed to remove supplemental groups"
#, python-format
msgid "Failed to set gid %s"
msgstr "Failed to set gid %s"
#, python-format
msgid "Failed to set uid %s"
msgstr "Failed to set uid %s"
msgid "Group that the privsep daemon should run as."
msgstr "Group that the privsep daemon should run as."
#, python-format
msgid "Invalid privsep function: %s not exported"
msgstr "Invalid privsep function: %s not exported"
msgid "List of Linux capabilities retained by the privsep daemon."
msgstr "List of Linux capabilities retained by the privsep daemon."
msgid "Premature eof waiting for privileged process"
msgstr "Premature EOF waiting for privileged process"
msgid "Privsep daemon failed to start"
msgstr "Privsep daemon failed to start"
#, python-format
msgid "Unexpected response: %r"
msgstr "Unexpected response: %r"
#, python-format
msgid "Unknown privsep cmd: %s"
msgstr "Unknown privsep cmd: %s"
msgid "User that the privsep daemon should run as."
msgstr "User that the privsep daemon should run as."

View File

@ -1,225 +0,0 @@
# Copyright 2015 Rackspace Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import enum
import functools
import logging
import shlex
import sys
from oslo_config import cfg
from oslo_config import types
from oslo_utils import importutils
from oslo_privsep import capabilities
from oslo_privsep import daemon
from oslo_privsep._i18n import _, _LW, _LE
LOG = logging.getLogger(__name__)
def CapNameOrInt(value):
value = str(value).strip()
try:
return capabilities.CAPS_BYNAME[value]
except KeyError:
return int(value)
OPTS = [
cfg.StrOpt('user',
help=_('User that the privsep daemon should run as.')),
cfg.StrOpt('group',
help=_('Group that the privsep daemon should run as.')),
cfg.Opt('capabilities',
type=types.List(CapNameOrInt), default=[],
help=_('List of Linux capabilities retained by the privsep '
'daemon.')),
cfg.StrOpt('helper_command',
help=_('Command to invoke to start the privsep daemon if '
'not using the "fork" method. '
'If not specified, a default is generated using '
'"sudo privsep-helper" and arguments designed to '
'recreate the current configuration. '
'This command must accept suitable --privsep_context '
'and --privsep_sock_path arguments.')),
]
_ENTRYPOINT_ATTR = 'privsep_entrypoint'
_HELPER_COMMAND_PREFIX = ['sudo']
@enum.unique
class Method(enum.Enum):
FORK = 1
ROOTWRAP = 2
def init(root_helper=None):
"""Initialise oslo.privsep library.
This function should be called at the top of main(), after the
command line is parsed, oslo.config is initialised and logging is
set up, but before calling any privileged entrypoint, changing
user id, forking, or anything else "odd".
:param root_helper: List of command and arguments to prefix
privsep-helper with, in order to run helper as root. Note,
ignored if context's helper_command config option is set.
"""
if root_helper:
global _HELPER_COMMAND_PREFIX
_HELPER_COMMAND_PREFIX = root_helper
class PrivContext(object):
def __init__(self, prefix, cfg_section='privsep', pypath=None,
capabilities=None):
# Note that capabilities=[] means retaining no capabilities
# and leaves even uid=0 with no powers except being able to
# read/write to the filesystem as uid=0. This might be what
# you want, but probably isn't.
#
# There is intentionally no way to say "I want all the
# capabilities."
if capabilities is None:
raise ValueError('capabilities is a required parameter')
self.pypath = pypath
self.prefix = prefix
self.cfg_section = cfg_section
# NOTE(claudiub): oslo.privsep is not currently supported on Windows,
# as it uses Linux-specific functionality (os.fork, socker.AF_UNIX).
# The client_mode should be set to False on Windows.
self.client_mode = sys.platform != 'win32'
self.channel = None
cfg.CONF.register_opts(OPTS, group=cfg_section)
cfg.CONF.set_default('capabilities', group=cfg_section,
default=capabilities)
@property
def conf(self):
"""Return the oslo.config section object as lazily as possible."""
# Need to avoid looking this up before oslo_config has been
# properly initialized.
return cfg.CONF[self.cfg_section]
def __repr__(self):
return 'PrivContext(cfg_section=%s)' % self.cfg_section
def helper_command(self, sockpath):
# We need to be able to reconstruct the context object in the new
# python process we'll get after rootwrap/sudo. This means we
# need to construct the context object and store it somewhere
# globally accessible, and then use that python name to find it
# again in the new python interpreter. Yes, it's all a bit
# clumsy, and none of it is required when using the fork-based
# alternative above.
# These asserts here are just attempts to catch errors earlier.
# TODO(gus): Consider replacing with setuptools entry_points.
assert self.pypath is not None, (
'helper_command requires priv_context '
'pypath to be specified')
assert importutils.import_class(self.pypath) is self, (
'helper_command requires priv_context pypath '
'for context object')
# Note order is important here. Deployments will (hopefully)
# have the exact arguments in sudoers/rootwrap configs and
# reordering args will break configs!
if self.conf.helper_command:
cmd = shlex.split(self.conf.helper_command)
else:
cmd = _HELPER_COMMAND_PREFIX + ['privsep-helper']
try:
for cfg_file in cfg.CONF.config_file:
cmd.extend(['--config-file', cfg_file])
except cfg.NoSuchOptError:
pass
try:
if cfg.CONF.config_dir is not None:
cmd.extend(['--config-dir', cfg.CONF.config_dir])
except cfg.NoSuchOptError:
pass
cmd.extend(
['--privsep_context', self.pypath,
'--privsep_sock_path', sockpath])
return cmd
def set_client_mode(self, enabled):
if enabled and sys.platform == 'win32':
raise RuntimeError(
_LE("Enabling the client_mode is not currently "
"supported on Windows."))
self.client_mode = enabled
def entrypoint(self, func):
"""This is intended to be used as a decorator."""
assert func.__module__.startswith(self.prefix), (
'%r entrypoints must be below "%s"' % (self, self.prefix))
# Right now, we only track a single context in
# _ENTRYPOINT_ATTR. This could easily be expanded into a set,
# but that will increase the memory overhead. Revisit if/when
# someone has a need to associate the same entrypoint with
# multiple contexts.
assert getattr(func, _ENTRYPOINT_ATTR, None) is None, (
'%r is already associated with another PrivContext' % func)
f = functools.partial(self._wrap, func)
setattr(f, _ENTRYPOINT_ATTR, self)
return f
def is_entrypoint(self, func):
return getattr(func, _ENTRYPOINT_ATTR, None) is self
def _wrap(self, func, *args, **kwargs):
if self.client_mode:
name = '%s.%s' % (func.__module__, func.__name__)
if self.channel is None:
self.start()
return self.channel.remote_call(name, args, kwargs)
else:
return func(*args, **kwargs)
def start(self, method=Method.ROOTWRAP):
if self.channel is not None:
LOG.warning(_LW('privsep daemon already running'))
return
if method is Method.ROOTWRAP:
channel = daemon.RootwrapClientChannel(context=self)
elif method is Method.FORK:
channel = daemon.ForkingClientChannel(context=self)
else:
raise ValueError('Unknown method: %s' % method)
self.channel = channel
def stop(self):
if self.channel is not None:
self.channel.close()
self.channel = None

View File

@ -1,55 +0,0 @@
# Copyright 2015 Rackspace Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import logging
import os
import sys
from oslo_config import fixture as cfg_fixture
from oslo_privsep import priv_context
LOG = logging.getLogger(__name__)
class UnprivilegedPrivsepFixture(fixtures.Fixture):
def __init__(self, context):
self.context = context
def setUp(self):
super(UnprivilegedPrivsepFixture, self).setUp()
self.conf = self.useFixture(cfg_fixture.Config()).conf
self.conf.set_override('capabilities', [],
group=self.context.cfg_section)
for k in ('user', 'group'):
self.conf.set_override(
k, None, group=self.context.cfg_section)
orig_pid = os.getpid()
try:
self.context.start(method=priv_context.Method.FORK)
except Exception as e:
# py3 unittest/testtools/something catches fatal
# exceptions from child processes and tries to treat them
# like regular non-fatal test failures. Here we attempt
# to undo that.
if os.getpid() == orig_pid:
raise
LOG.exception(e)
sys.exit(1)
self.addCleanup(self.context.stop)

View File

@ -1,88 +0,0 @@
# Copyright 2015 Rackspace Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslotest import base
from oslo_privsep import capabilities
class TestCapabilities(base.BaseTestCase):
@mock.patch('oslo_privsep.capabilities._prctl')
def test_set_keepcaps_error(self, mock_prctl):
mock_prctl.return_value = -1
self.assertRaises(OSError, capabilities.set_keepcaps, True)
@mock.patch('oslo_privsep.capabilities._prctl')
def test_set_keepcaps(self, mock_prctl):
mock_prctl.return_value = 0
capabilities.set_keepcaps(True)
# Disappointingly, ffi.cast(type, 1) != ffi.cast(type, 1)
# so can't just use assert_called_once_with :-(
self.assertEqual(1, mock_prctl.call_count)
self.assertItemsEqual(
[8, 1], # [PR_SET_KEEPCAPS, true]
[int(x) for x in mock_prctl.call_args[0]])
@mock.patch('oslo_privsep.capabilities._capset')
def test_drop_all_caps_except_error(self, mock_capset):
mock_capset.return_value = -1
self.assertRaises(
OSError, capabilities.drop_all_caps_except, [0], [0], [0])
@mock.patch('oslo_privsep.capabilities._capset')
def test_drop_all_caps_except(self, mock_capset):
mock_capset.return_value = 0
# Somewhat arbitrary bit patterns to exercise _caps_to_mask
capabilities.drop_all_caps_except(
(17, 24, 49), (8, 10, 35, 56), (24, 31, 40))
self.assertEqual(1, mock_capset.call_count)
hdr, data = mock_capset.call_args[0]
self.assertEqual(0x20071026, # _LINUX_CAPABILITY_VERSION_2
hdr.version)
self.assertEqual(0x01020000, data[0].effective)
self.assertEqual(0x00020000, data[1].effective)
self.assertEqual(0x00000500, data[0].permitted)
self.assertEqual(0x01000008, data[1].permitted)
self.assertEqual(0x81000000, data[0].inheritable)
self.assertEqual(0x00000100, data[1].inheritable)
@mock.patch('oslo_privsep.capabilities._capget')
def test_get_caps_error(self, mock_capget):
mock_capget.return_value = -1
self.assertRaises(OSError, capabilities.get_caps)
@mock.patch('oslo_privsep.capabilities._capget')
def test_get_caps(self, mock_capget):
def impl(hdr, data):
# Somewhat arbitrary bit patterns to exercise _mask_to_caps
data[0].effective = 0x01020000
data[1].effective = 0x00020000
data[0].permitted = 0x00000500
data[1].permitted = 0x01000008
data[0].inheritable = 0x81000000
data[1].inheritable = 0x00000100
return 0
mock_capget.side_effect = impl
self.assertItemsEqual(
([17, 24, 49],
[8, 10, 35, 56],
[24, 31, 40]),
capabilities.get_caps())

View File

@ -1,104 +0,0 @@
# Copyright 2015 Rackspace Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
from oslotest import base
from oslo_privsep import comm
class BufSock(object):
def __init__(self):
self.readpos = 0
self.buf = six.BytesIO()
def recv(self, bufsize):
if self.buf.closed:
return b''
self.buf.seek(self.readpos, 0)
data = self.buf.read(bufsize)
self.readpos += len(data)
return data
def sendall(self, data):
self.buf.seek(0, 2)
self.buf.write(data)
def shutdown(self, _flag):
self.buf.close()
class TestSerialization(base.BaseTestCase):
def setUp(self):
super(TestSerialization, self).setUp()
sock = BufSock()
self.input = comm.Serializer(sock)
self.output = iter(comm.Deserializer(sock))
def send(self, data):
self.input.send(data)
return next(self.output)
def assertSendable(self, value):
self.assertEqual(value, self.send(value))
def test_none(self):
self.assertSendable(None)
def test_bool(self):
self.assertSendable(True)
self.assertSendable(False)
def test_int(self):
self.assertSendable(42)
self.assertSendable(-84)
def test_bytes(self):
data = b'\x00\x01\x02\xfd\xfe\xff'
self.assertSendable(data)
def test_unicode(self):
data = u'\u4e09\u9df9'
self.assertSendable(data)
def test_tuple(self):
self.assertSendable((1, 'foo'))
def test_list(self):
# NB! currently lists get converted to tuples by serialization.
self.assertEqual((1, 'foo'), self.send([1, 'foo']))
def test_dict(self):
self.assertSendable(
{
'a': 'b',
1: 2,
None: None,
(1, 2): (3, 4),
}
)
def test_badobj(self):
class UnknownClass(object):
pass
obj = UnknownClass()
self.assertRaises(TypeError, self.send, obj)
def test_eof(self):
self.input.close()
self.assertRaises(StopIteration, next, self.output)

View File

@ -1,106 +0,0 @@
# Copyright 2015 Rackspace Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import logging
import mock
import platform
import time
from oslotest import base
import testtools
from oslo_privsep import capabilities
from oslo_privsep import daemon
from oslo_privsep.tests import testctx
LOG = logging.getLogger(__name__)
def undecorated():
pass
@testctx.context.entrypoint
def logme(level, msg):
LOG.log(level, '%s', msg)
@testtools.skipIf(platform.system() != 'Linux',
'works only on Linux platform.')
class LogTest(testctx.TestContextTestCase):
def setUp(self):
super(LogTest, self).setUp()
self.logger = self.useFixture(fixtures.FakeLogger(
name=None, level=logging.INFO))
def test_priv_log(self):
logme(logging.DEBUG, u'test@DEBUG')
logme(logging.WARN, u'test@WARN')
time.sleep(0.1) # Hack to give logging thread a chance to run
# TODO(gus): Currently severity information is lost and
# everything is logged as INFO. Fixing this probably requires
# writing structured messages to the logging socket.
#
# self.assertNotIn('test@DEBUG', self.logger.output)
self.assertIn(u'test@WARN', self.logger.output)
@testtools.skipIf(platform.system() != 'Linux',
'works only on Linux platform.')
class DaemonTest(base.BaseTestCase):
@mock.patch('os.setuid')
@mock.patch('os.setgid')
@mock.patch('os.setgroups')
@mock.patch('oslo_privsep.capabilities.set_keepcaps')
@mock.patch('oslo_privsep.capabilities.drop_all_caps_except')
def test_drop_privs(self, mock_dropcaps, mock_keepcaps,
mock_setgroups, mock_setgid, mock_setuid):
channel = mock.NonCallableMock()
context = mock.NonCallableMock()
context.conf.user = 42
context.conf.group = 84
context.conf.capabilities = [
capabilities.CAP_SYS_ADMIN, capabilities.CAP_NET_ADMIN]
d = daemon.Daemon(channel, context)
d._drop_privs()
mock_setuid.assert_called_once_with(42)
mock_setgid.assert_called_once_with(84)
mock_setgroups.assert_called_once_with([])
self.assertItemsEqual(
[mock.call(True), mock.call(False)],
mock_keepcaps.mock_calls)
mock_dropcaps.assert_called_once_with(
set((capabilities.CAP_SYS_ADMIN, capabilities.CAP_NET_ADMIN)),
set((capabilities.CAP_SYS_ADMIN, capabilities.CAP_NET_ADMIN)),
[])
@testtools.skipIf(platform.system() != 'Linux',
'works only on Linux platform.')
class WithContextTest(testctx.TestContextTestCase):
def test_unexported(self):
self.assertRaisesRegexp(
NameError, 'undecorated not exported',
testctx.context._wrap, undecorated)

View File

@ -1,191 +0,0 @@
# Copyright 2015 Rackspace Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import os
import pipes
import platform
import sys
import mock
import testtools
from oslo_privsep import daemon
from oslo_privsep import priv_context
from oslo_privsep.tests import testctx
LOG = logging.getLogger(__name__)
@testctx.context.entrypoint
def priv_getpid():
return os.getpid()
@testctx.context.entrypoint
def add1(arg):
return arg + 1
class CustomError(Exception):
def __init__(self, code, msg):
super(CustomError, self).__init__(code, msg)
self.code = code
self.msg = msg
def __str__(self):
return 'Code %s: %s' % (self.code, self.msg)
@testctx.context.entrypoint
def fail(custom=False):
if custom:
raise CustomError(42, 'omg!')
else:
raise RuntimeError("I can't let you do that Dave")
@testtools.skipIf(platform.system() != 'Linux',
'works only on Linux platform.')
class PrivContextTest(testctx.TestContextTestCase):
@mock.patch.object(priv_context, 'sys')
def test_init_windows(self, mock_sys):
mock_sys.platform = 'win32'
context = priv_context.PrivContext('test', capabilities=[])
self.assertFalse(context.client_mode)
@mock.patch.object(priv_context, 'sys')
def test_set_client_mode(self, mock_sys):
context = priv_context.PrivContext('test', capabilities=[])
self.assertTrue(context.client_mode)
context.set_client_mode(False)
self.assertFalse(context.client_mode)
# client_mode should remain to False on win32.
mock_sys.platform = 'win32'
self.assertRaises(RuntimeError, context.set_client_mode, True)
def test_helper_command(self):
self.privsep_conf.privsep.helper_command = 'foo --bar'
cmd = testctx.context.helper_command('/tmp/sockpath')
expected = [
'foo', '--bar',
'--privsep_context', testctx.context.pypath,
'--privsep_sock_path', '/tmp/sockpath',
]
self.assertEqual(expected, cmd)
def test_helper_command_default(self):
self.privsep_conf.config_file = ['/bar.conf']
cmd = testctx.context.helper_command('/tmp/sockpath')
expected = [
'sudo', 'privsep-helper',
'--config-file', '/bar.conf',
# --config-dir arg should be skipped
'--privsep_context', testctx.context.pypath,
'--privsep_sock_path', '/tmp/sockpath',
]
self.assertEqual(expected, cmd)
def test_helper_command_default_dirtoo(self):
self.privsep_conf.config_file = ['/bar.conf', '/baz.conf']
self.privsep_conf.config_dir = '/foo.d'
cmd = testctx.context.helper_command('/tmp/sockpath')
expected = [
'sudo', 'privsep-helper',
'--config-file', '/bar.conf',
'--config-file', '/baz.conf',
'--config-dir', '/foo.d',
'--privsep_context', testctx.context.pypath,
'--privsep_sock_path', '/tmp/sockpath',
]
self.assertEqual(expected, cmd)
def test_init_known_contexts(self):
self.assertEqual(testctx.context.helper_command('/sock')[:2],
['sudo', 'privsep-helper'])
priv_context.init(root_helper=['sudo', 'rootwrap'])
self.assertEqual(testctx.context.helper_command('/sock')[:3],
['sudo', 'rootwrap', 'privsep-helper'])
@testtools.skipIf(platform.system() != 'Linux',
'works only on Linux platform.')
class SeparationTest(testctx.TestContextTestCase):
def test_getpid(self):
# Verify that priv_getpid() was executed in another process.
priv_pid = priv_getpid()
self.assertNotMyPid(priv_pid)
def test_client_mode(self):
self.assertNotMyPid(priv_getpid())
self.addCleanup(testctx.context.set_client_mode, True)
testctx.context.set_client_mode(False)
# priv_getpid() should now run locally (and return our pid)
self.assertEqual(os.getpid(), priv_getpid())
testctx.context.set_client_mode(True)
# priv_getpid() should now run remotely again
self.assertNotMyPid(priv_getpid())
@testtools.skipIf(platform.system() != 'Linux',
'works only on Linux platform.')
class RootwrapTest(testctx.TestContextTestCase):
def setUp(self):
super(RootwrapTest, self).setUp()
testctx.context.stop()
# Generate a command that will run daemon.helper_main without
# requiring it to be properly installed.
cmd = [
'env',
'PYTHON_PATH=%s' % os.path.pathsep.join(sys.path),
sys.executable, daemon.__file__,
]
if LOG.isEnabledFor(logging.DEBUG):
cmd.append('--debug')
self.privsep_conf.set_override(
'helper_command', ' '.join(map(pipes.quote, cmd)),
group=testctx.context.cfg_section)
testctx.context.start(method=priv_context.Method.ROOTWRAP)
def test_getpid(self):
# Verify that priv_getpid() was executed in another process.
priv_pid = priv_getpid()
self.assertNotMyPid(priv_pid)
@testtools.skipIf(platform.system() != 'Linux',
'works only on Linux platform.')
class SerializationTest(testctx.TestContextTestCase):
def test_basic_functionality(self):
self.assertEqual(43, add1(42))
def test_raises_standard(self):
self.assertRaisesRegexp(
RuntimeError, "I can't let you do that Dave", fail)
def test_raises_custom(self):
exc = self.assertRaises(CustomError, fail, custom=True)
self.assertEqual(exc.code, 42)
self.assertEqual(exc.msg, 'omg!')

View File

@ -1,44 +0,0 @@
# Copyright 2015 Rackspace Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from oslotest import base
from oslo_privsep import priv_context
import oslo_privsep.tests
from oslo_privsep.tests import fixture
context = priv_context.PrivContext(
# This context allows entrypoints anywhere below oslo_privsep.tests.
oslo_privsep.tests.__name__,
pypath=__name__ + '.context',
# This is one of the rare cases where we actually want zero powers:
capabilities=[],
)
class TestContextTestCase(base.BaseTestCase):
def setUp(self):
super(TestContextTestCase, self).setUp()
privsep_fixture = self.useFixture(
fixture.UnprivilegedPrivsepFixture(context))
self.privsep_conf = privsep_fixture.conf
def assertNotMyPid(self, pid):
# Verify that `pid` is some positive integer, that isn't our pid
self.assertIsInstance(pid, int)
self.assertTrue(pid > 0)
self.assertNotEqual(os.getpid(), pid)

View File

@ -1,13 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
oslo.log>=1.14.0 # Apache-2.0
oslo.i18n>=2.1.0 # Apache-2.0
oslo.config>=3.12.0 # Apache-2.0
oslo.utils>=3.16.0 # Apache-2.0
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
cffi # MIT
eventlet!=0.18.3,>=0.18.2 # MIT
greenlet>=0.3.2 # MIT
msgpack-python>=0.4.0 # Apache-2.0

View File

@ -1,55 +0,0 @@
[metadata]
name = oslo.privsep
summary = OpenStack library for privilege separation
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://launchpad.net/oslo
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
[files]
packages =
oslo_privsep
[pbr]
warnerrors = true
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
[entry_points]
console_scripts =
privsep-helper = oslo_privsep.daemon:helper_main
[upload_sphinx]
upload-dir = doc/build/html
[compile_catalog]
directory = oslo.privsep/locale
domain = oslo_privsep
[update_catalog]
domain = oslo_privsep
output_dir = oslo_privsep/locale
input_file = oslo_privsep/locale/oslo_privsep.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = oslo_privsep/locale/oslo_privsep.pot
[wheel]
universal = true

View File

@ -1,29 +0,0 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr>=1.8'],
pbr=True)

View File

@ -1,12 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking<0.11,>=0.10.2
oslotest>=1.10.0 # Apache-2.0
mock>=2.0 # BSD
fixtures>=3.0.0 # Apache-2.0/BSD
# These are needed for docs generation
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD

33
tox.ini
View File

@ -1,33 +0,0 @@
[tox]
minversion = 1.6
envlist = py34,py27,pypy,pep8
[testenv]
whitelist_externals =
/bin/sh
deps = -r{toxinidir}/test-requirements.txt
commands = python setup.py testr --slowest --testr-args='{posargs}'
[testenv:pep8]
commands = flake8
[testenv:venv]
commands = {posargs}
[testenv:docs]
commands = python setup.py build_sphinx
[testenv:cover]
commands = python setup.py testr --coverage --testr-args='{posargs}'
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
[hacking]
import_exceptions =
oslo_privsep._i18n