oslo.privsep-2.1.1/0000775000175000017500000000000013643050371014202 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/.zuul.yaml0000664000175000017500000000041313643050273016142 0ustar zuulzuul00000000000000- project: templates: - check-requirements - lib-forward-testing-python3 - openstack-lower-constraints-jobs - openstack-python3-ussuri-jobs - periodic-stable-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 oslo.privsep-2.1.1/requirements.txt0000664000175000017500000000067613643050273017500 0ustar zuulzuul00000000000000# 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>=3.36.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 cffi>=1.7.0 # MIT eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT greenlet>=0.4.14 # MIT msgpack>=0.6.0 # Apache-2.0 oslo.privsep-2.1.1/setup.py0000664000175000017500000000127113643050273015716 0ustar zuulzuul00000000000000# 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. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) oslo.privsep-2.1.1/babel.cfg0000664000175000017500000000002113643050273015722 0ustar zuulzuul00000000000000[python: **.py] oslo.privsep-2.1.1/LICENSE0000664000175000017500000002363713643050273015223 0ustar zuulzuul00000000000000 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. oslo.privsep-2.1.1/.coveragerc0000664000175000017500000000015713643050273016327 0ustar zuulzuul00000000000000[run] branch = True source = privsep omit = privsep/tests/*,privsep/openstack/* [report] ignore-errors = True oslo.privsep-2.1.1/HACKING.rst0000664000175000017500000000025113643050273015777 0ustar zuulzuul00000000000000oslo.privsep Style Commandments ====================================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ oslo.privsep-2.1.1/oslo.privsep.egg-info/0000775000175000017500000000000013643050371020337 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo.privsep.egg-info/top_level.txt0000664000175000017500000000001513643050371023065 0ustar zuulzuul00000000000000oslo_privsep oslo.privsep-2.1.1/oslo.privsep.egg-info/SOURCES.txt0000664000175000017500000000405113643050371022223 0ustar zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg lower-constraints.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/contributor/contributing.rst doc/source/contributor/history.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/reference/index.rst doc/source/user/index.rst oslo.privsep.egg-info/PKG-INFO oslo.privsep.egg-info/SOURCES.txt oslo.privsep.egg-info/dependency_links.txt oslo.privsep.egg-info/entry_points.txt oslo.privsep.egg-info/not-zip-safe oslo.privsep.egg-info/pbr.json oslo.privsep.egg-info/requires.txt oslo.privsep.egg-info/top_level.txt oslo_privsep/__init__.py oslo_privsep/_i18n.py oslo_privsep/capabilities.py oslo_privsep/comm.py oslo_privsep/daemon.py oslo_privsep/priv_context.py oslo_privsep/version.py oslo_privsep/functional/__init__.py oslo_privsep/functional/test_daemon.py oslo_privsep/locale/de/LC_MESSAGES/oslo_privsep.po oslo_privsep/locale/en_GB/LC_MESSAGES/oslo_privsep.po oslo_privsep/tests/__init__.py oslo_privsep/tests/fixture.py oslo_privsep/tests/test_capabilities.py oslo_privsep/tests/test_comm.py oslo_privsep/tests/test_daemon.py oslo_privsep/tests/test_priv_context.py oslo_privsep/tests/testctx.py releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml releasenotes/notes/add_thread_pool_size-a54e6f27ab019f96.yaml releasenotes/notes/auto-restart-client-channel-619545294557bf2b.yaml releasenotes/notes/drop-python27-support-6da3028c1cf099eb.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.pooslo.privsep-2.1.1/oslo.privsep.egg-info/not-zip-safe0000664000175000017500000000000113643050371022565 0ustar zuulzuul00000000000000 oslo.privsep-2.1.1/oslo.privsep.egg-info/PKG-INFO0000664000175000017500000000470013643050371021435 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: oslo.privsep Version: 2.1.1 Summary: OpenStack library for privilege separation Home-page: https://docs.openstack.org/oslo.privsep/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/oslo.privsep.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============ oslo.privsep ============ .. image:: https://img.shields.io/pypi/v/oslo.privsep.svg :target: https://pypi.org/project/oslo.privsep/ :alt: Latest Version 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: https://docs.openstack.org/oslo.privsep/latest/ * Source: https://opendev.org/openstack/oslo.privsep * Bugs: https://bugs.launchpad.net/oslo.privsep * Release Notes: https://docs.openstack.org/releasenotes/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 Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 oslo.privsep-2.1.1/oslo.privsep.egg-info/entry_points.txt0000664000175000017500000000021413643050371023632 0ustar zuulzuul00000000000000[console_scripts] privsep-helper = oslo_privsep.daemon:helper_main [oslo.config.opts] oslo.privsep = oslo_privsep.priv_context:_list_opts oslo.privsep-2.1.1/oslo.privsep.egg-info/dependency_links.txt0000664000175000017500000000000113643050371024405 0ustar zuulzuul00000000000000 oslo.privsep-2.1.1/oslo.privsep.egg-info/requires.txt0000664000175000017500000000023013643050371022732 0ustar zuulzuul00000000000000oslo.log>=3.36.0 oslo.i18n>=3.15.3 oslo.config>=5.2.0 oslo.utils>=3.33.0 cffi>=1.7.0 eventlet!=0.18.3,!=0.20.1,>=0.18.2 greenlet>=0.4.14 msgpack>=0.6.0 oslo.privsep-2.1.1/oslo.privsep.egg-info/pbr.json0000664000175000017500000000005613643050371022016 0ustar zuulzuul00000000000000{"git_version": "5f49e8f", "is_release": true}oslo.privsep-2.1.1/PKG-INFO0000664000175000017500000000470013643050371015300 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: oslo.privsep Version: 2.1.1 Summary: OpenStack library for privilege separation Home-page: https://docs.openstack.org/oslo.privsep/latest/ Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/oslo.privsep.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============ oslo.privsep ============ .. image:: https://img.shields.io/pypi/v/oslo.privsep.svg :target: https://pypi.org/project/oslo.privsep/ :alt: Latest Version 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: https://docs.openstack.org/oslo.privsep/latest/ * Source: https://opendev.org/openstack/oslo.privsep * Bugs: https://bugs.launchpad.net/oslo.privsep * Release Notes: https://docs.openstack.org/releasenotes/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 Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 oslo.privsep-2.1.1/releasenotes/0000775000175000017500000000000013643050371016673 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/0000775000175000017500000000000013643050371020173 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/conf.py0000664000175000017500000002104113643050273021471 0ustar zuulzuul00000000000000# -*- 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. # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/oslo.privsep' bug_project = 'oslo.privsep' bug_tag = '' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. copyright = u'2016, oslo.privsep Developers' # Release notes do not need a version in the title, they span # multiple versions. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # 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 # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'oslo.privsepReleaseNotesDoc' # -- Options for LaTeX output --------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'oslo.privsepReleaseNotes.tex', u'oslo.privsep Release Notes Documentation', u'oslo.privsep Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'oslo.privsepReleaseNotes', u'oslo.privsep Release Notes Documentation', [u'oslo.privsep Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'oslo.privsepReleaseNotes', u'oslo.privsep Release Notes Documentation', u'oslo.privsep Developers', 'oslo.privsepReleaseNotes', 'OpenStack library for privilege separation.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] oslo.privsep-2.1.1/releasenotes/source/train.rst0000664000175000017500000000017613643050273022047 0ustar zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train oslo.privsep-2.1.1/releasenotes/source/rocky.rst0000664000175000017500000000022113643050273022050 0ustar zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky oslo.privsep-2.1.1/releasenotes/source/index.rst0000664000175000017500000000031513643050273022034 0ustar zuulzuul00000000000000============================ oslo.privsep Release Notes ============================ .. toctree:: :maxdepth: 1 unreleased train stein rocky queens pike ocata newton oslo.privsep-2.1.1/releasenotes/source/locale/0000775000175000017500000000000013643050371021432 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/locale/en_GB/0000775000175000017500000000000013643050371022404 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000013643050371024171 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000664000175000017500000000224613643050273027227 0ustar zuulzuul00000000000000# Andi Chandler , 2017. #zanata # Andi Chandler , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.privsep Release Notes\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-02-08 23:11+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-06 11:25+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "1.13.0" msgstr "1.13.0" msgid "Newton Series Release Notes" msgstr "Newton Series Release Notes" msgid "Ocata Series Release Notes" msgstr "Ocata Series Release Notes" msgid "Other Notes" msgstr "Other Notes" msgid "Pike Series Release Notes" msgstr "Pike Series Release Notes" msgid "Queens Series Release Notes" msgstr "Queens Series Release Notes" msgid "Switch to reno for managing release notes." msgstr "Switch to Reno for managing release notes." msgid "Unreleased Release Notes" msgstr "Unreleased Release Notes" msgid "oslo.privsep Release Notes" msgstr "oslo.privsep Release Notes" oslo.privsep-2.1.1/releasenotes/source/locale/fr/0000775000175000017500000000000013643050371022041 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175000017500000000000013643050371023626 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000664000175000017500000000171313643050273026662 0ustar zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.privsep Release Notes 1.14.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-10-23 20:38+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 06:04+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.7.3\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "1.13.0" msgstr "1.13.0" msgid "Newton Series Release Notes" msgstr "Note de release pour Newton" msgid "Other Notes" msgstr "Autres notes" msgid "Switch to reno for managing release notes." msgstr "Commence à utiliser reno pour la gestion des notes de release" msgid "Unreleased Release Notes" msgstr "Note de release pour les changements non déployées" msgid "oslo.privsep Release Notes" msgstr "Note de release pour oslo.privsep" oslo.privsep-2.1.1/releasenotes/source/ocata.rst0000664000175000017500000000023013643050273022010 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata oslo.privsep-2.1.1/releasenotes/source/newton.rst0000664000175000017500000000021613643050273022237 0ustar zuulzuul00000000000000============================= Newton Series Release Notes ============================= .. release-notes:: :branch: origin/stable/newton oslo.privsep-2.1.1/releasenotes/source/_static/0000775000175000017500000000000013643050371021621 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/_static/.placeholder0000664000175000017500000000000013643050273024073 0ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/pike.rst0000664000175000017500000000021713643050273021656 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike oslo.privsep-2.1.1/releasenotes/source/queens.rst0000664000175000017500000000022313643050273022223 0ustar zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens oslo.privsep-2.1.1/releasenotes/source/stein.rst0000664000175000017500000000022113643050273022043 0ustar zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein oslo.privsep-2.1.1/releasenotes/source/_templates/0000775000175000017500000000000013643050371022330 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013643050273024602 0ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/source/unreleased.rst0000664000175000017500000000014413643050273023054 0ustar zuulzuul00000000000000========================== Unreleased Release Notes ========================== .. release-notes:: oslo.privsep-2.1.1/releasenotes/notes/0000775000175000017500000000000013643050371020023 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/releasenotes/notes/drop-python27-support-6da3028c1cf099eb.yaml0000664000175000017500000000017713643050273027356 0ustar zuulzuul00000000000000--- upgrade: - | Support for Python 2.7 has been dropped. The minimum version of Python now supported is Python 3.6. oslo.privsep-2.1.1/releasenotes/notes/auto-restart-client-channel-619545294557bf2b.yaml0000664000175000017500000000136013643050273030233 0ustar zuulzuul00000000000000--- fixes: - | When the privsep helper dies, the client side PrivContext now restarts the client channel and the helper so that privileged commands can continue to be processed. See `bug 1715374`_ for details. In conjunction with the fix for `bug 1794708`_ in oslo.service, the nova-compute service now behaves correctly when it receives ``SIGHUP``. .. note:: This only works for the ``ROOTWRAP`` method of starting the daemon. With the ``FORK`` method we've dropped privileges and no longer have the ability to restart the daemon in privileged mode. .. _`bug 1715374`: https://bugs.launchpad.net/nova/+bug/1715374 .. _`bug 1794708`: https://bugs.launchpad.net/oslo.service/+bug/1794708 oslo.privsep-2.1.1/releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml0000664000175000017500000000007113643050273024705 0ustar zuulzuul00000000000000--- other: - Switch to reno for managing release notes.oslo.privsep-2.1.1/releasenotes/notes/add_thread_pool_size-a54e6f27ab019f96.yaml0000664000175000017500000000040413643050273027273 0ustar zuulzuul00000000000000--- features: - | Privsep now uses multithreading to allow concurrency in executing privileged commands. The number of concurrent threads defaults to the available CPU cores, but can be adjusted by the new ``thread_pool_size`` config option. oslo.privsep-2.1.1/lower-constraints.txt0000664000175000017500000000177113643050273020447 0ustar zuulzuul00000000000000alabaster==0.7.10 appdirs==1.3.0 Babel==2.3.4 bandit==1.1.0 cffi==1.7.0 debtcollector==1.2.0 docutils==0.11 dulwich==0.15.0 eventlet==0.18.2 extras==1.0.0 fixtures==3.0.0 gitdb==0.6.4 GitPython==1.0.1 greenlet==0.4.14 imagesize==0.7.1 iso8601==0.1.11 Jinja2==2.10 keystoneauth1==3.4.0 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.2.1 mox3==0.20.0 msgpack==0.6.0 netaddr==0.7.18 netifaces==0.10.4 openstackdocstheme==1.20.0 os-client-config==1.28.0 oslo.config==5.2.0 oslo.context==2.19.2 oslo.i18n==3.15.3 oslo.log==3.36.0 oslo.serialization==2.24.0 oslo.utils==3.33.0 oslotest==3.2.0 pbr==2.0.0 pycparser==2.18 Pygments==2.2.0 pyinotify==0.9.6 pyparsing==2.1.0 python-dateutil==2.5.3 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.13 reno==2.5.0 requests==2.14.2 requestsexceptions==1.2.0 rfc3986==0.3.1 six==1.10.0 smmap==0.9.0 snowballstemmer==1.2.1 Sphinx==1.8.0 sphinxcontrib-websupport==1.0.1 stestr==2.0.0 stevedore==1.20.0 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 wrapt==1.7.0 oslo.privsep-2.1.1/setup.cfg0000664000175000017500000000244013643050371016023 0ustar zuulzuul00000000000000[metadata] name = oslo.privsep summary = OpenStack library for privilege separation description-file = README.rst author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/oslo.privsep/latest/ python-requires = >=3.6 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 :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython [files] packages = oslo_privsep [entry_points] console_scripts = privsep-helper = oslo_privsep.daemon:helper_main oslo.config.opts = oslo.privsep = oslo_privsep.priv_context:_list_opts [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 [egg_info] tag_build = tag_date = 0 oslo.privsep-2.1.1/AUTHORS0000664000175000017500000000363613643050371015262 0ustar zuulzuul00000000000000Alexander Tsamutali Andreas Jaeger Angus Lees Ben Nemec Bogdan Teleaga ChangBo Guo(gcb) Chuck Short Claudiu Belu Corey Bryant Cédric Jeanneret Darragh O'Reilly Davanum Srinivas Dirk Mueller Doug Hellmann Eric Brown Eric Fried Eric Harney Flavio Percoco Hervé Beraud Hongbin Lu Javier Pena John Garbutt Joshua Harlow Joshua Harlow Kirill Bespalov OpenStack Release Bot Pavlo Shchelokovskyy Rodolfo Alonso Hernandez Sam Wan Sean McGinnis Stephen Finucane Swapnil Kulkarni (coolsvap) Thierry Carrez TommyLike Tony Breeds Vieri <15050873171@163.com> Vu Cong Tuan Walter A. Boring IV Zhihai Song ZhijunWei ZhongShengping avnish caoyuan jacky06 liangcui loooosy melissaml pengyuesheng ricolin shupeng <15050873171@163.com> sonu.kumar wangqi xgwang5843 oslo.privsep-2.1.1/test-requirements.txt0000664000175000017500000000061713643050273020450 0ustar zuulzuul00000000000000# 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>=3.0,<3.1.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD stestr>=2.0.0 # Apache-2.0 # Bandit security code scanner bandit>=1.1.0,<1.6.0 # Apache-2.0 oslo.privsep-2.1.1/oslo_privsep/0000775000175000017500000000000013643050371016726 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo_privsep/capabilities.py0000664000175000017500000001227713643050273021743 0ustar zuulzuul00000000000000# 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 enum import os import platform import sys import cffi class Capabilities(enum.IntEnum): # Generated with: # awk '/^#define CAP_[A-Z_]+[ \t]+[0-9]+/ {print $2,"=",$3}' \ # include/uapi/linux/capability.h # From the 4.11.11 kernel and the kernel git SHA:235b84fc862 # Will need to be refreshed as new capabilites are added to the kernel CAP_CHOWN = 0 CAP_DAC_OVERRIDE = 1 CAP_DAC_READ_SEARCH = 2 CAP_FOWNER = 3 CAP_FSETID = 4 CAP_KILL = 5 CAP_SETGID = 6 CAP_SETUID = 7 CAP_SETPCAP = 8 CAP_LINUX_IMMUTABLE = 9 CAP_NET_BIND_SERVICE = 10 CAP_NET_BROADCAST = 11 CAP_NET_ADMIN = 12 CAP_NET_RAW = 13 CAP_IPC_LOCK = 14 CAP_IPC_OWNER = 15 CAP_SYS_MODULE = 16 CAP_SYS_RAWIO = 17 CAP_SYS_CHROOT = 18 CAP_SYS_PTRACE = 19 CAP_SYS_PACCT = 20 CAP_SYS_ADMIN = 21 CAP_SYS_BOOT = 22 CAP_SYS_NICE = 23 CAP_SYS_RESOURCE = 24 CAP_SYS_TIME = 25 CAP_SYS_TTY_CONFIG = 26 CAP_MKNOD = 27 CAP_LEASE = 28 CAP_AUDIT_WRITE = 29 CAP_AUDIT_CONTROL = 30 CAP_SETFCAP = 31 CAP_MAC_OVERRIDE = 32 CAP_MAC_ADMIN = 33 CAP_SYSLOG = 34 CAP_WAKE_ALARM = 35 CAP_BLOCK_SUSPEND = 36 CAP_AUDIT_READ = 37 CAPS_BYNAME = {} CAPS_BYVALUE = {} module = sys.modules[__name__] # Convenience dicts for human readable values # module attributes for backwards compat/convenience for c in Capabilities: CAPS_BYNAME[c.name] = c.value CAPS_BYVALUE[c.value] = c.name setattr(module, c.name, c.value) CDEF = ''' /* Edited highlights from `echo '#include ' | 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 ' | gcc -E -` */ #define PR_GET_KEEPCAPS 7 #define PR_SET_KEEPCAPS 8 int prctl (int __option, ...); ''' ffi = cffi.FFI() ffi.cdef(CDEF) if platform.system() == 'Linux': # mock.patching crt.* directly seems to upset cffi. Use an # indirection point here for easier testing. crt = ffi.dlopen(None) _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)), ) oslo.privsep-2.1.1/oslo_privsep/_i18n.py0000664000175000017500000000200713643050273020216 0ustar zuulzuul00000000000000# 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 https://docs.openstack.org/oslo.i18n/latest/user/index.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 oslo.privsep-2.1.1/oslo_privsep/version.py0000664000175000017500000000126413643050273020771 0ustar zuulzuul00000000000000# Copyright 2016 OpenStack Foundation # # 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 pbr.version version_info = pbr.version.VersionInfo('oslo.privsep') oslo.privsep-2.1.1/oslo_privsep/locale/0000775000175000017500000000000013643050371020165 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo_privsep/locale/en_GB/0000775000175000017500000000000013643050371021137 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo_privsep/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000013643050371022724 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo_privsep/locale/en_GB/LC_MESSAGES/oslo_privsep.po0000664000175000017500000000435513643050273026020 0ustar zuulzuul00000000000000# Andi Chandler , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.privsep VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-08 23:11+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 \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.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." oslo.privsep-2.1.1/oslo_privsep/locale/de/0000775000175000017500000000000013643050371020555 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo_privsep/locale/de/LC_MESSAGES/0000775000175000017500000000000013643050371022342 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo_privsep/locale/de/LC_MESSAGES/oslo_privsep.po0000664000175000017500000000330413643050273025427 0ustar zuulzuul00000000000000# Andreas Jaeger , 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 \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." oslo.privsep-2.1.1/oslo_privsep/tests/0000775000175000017500000000000013643050371020070 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo_privsep/tests/test_priv_context.py0000664000175000017500000001534313643050273024234 0ustar zuulzuul00000000000000# 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 tempfile from unittest 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' _, temp_path = tempfile.mkstemp() cmd = testctx.context.helper_command(temp_path) expected = [ 'foo', '--bar', '--privsep_context', testctx.context.pypath, '--privsep_sock_path', temp_path, ] self.assertEqual(expected, cmd) def test_helper_command_default(self): self.privsep_conf.config_file = ['/bar.conf'] _, temp_path = tempfile.mkstemp() cmd = testctx.context.helper_command(temp_path) expected = [ 'sudo', 'privsep-helper', '--config-file', '/bar.conf', # --config-dir arg should be skipped '--privsep_context', testctx.context.pypath, '--privsep_sock_path', temp_path, ] 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'] _, temp_path = tempfile.mkstemp() cmd = testctx.context.helper_command(temp_path) expected = [ 'sudo', 'privsep-helper', '--config-file', '/bar.conf', '--config-file', '/baz.conf', '--config-dir', '/foo.d', '--privsep_context', testctx.context.pypath, '--privsep_sock_path', temp_path, ] 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']) def test_start_acquires_lock(self): context = priv_context.PrivContext('test', capabilities=[]) context.channel = "something not None" context.start_lock = mock.Mock() context.start_lock.__enter__ = mock.Mock() context.start_lock.__exit__ = mock.Mock() self.assertFalse(context.start_lock.__enter__.called) context.start() self.assertTrue(context.start_lock.__enter__.called) @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.assertRaisesRegex( 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!') oslo.privsep-2.1.1/oslo_privsep/tests/test_capabilities.py0000664000175000017500000000652513643050273024143 0ustar zuulzuul00000000000000# 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. from unittest 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()) oslo.privsep-2.1.1/oslo_privsep/tests/test_comm.py0000664000175000017500000000522013643050273022434 0ustar zuulzuul00000000000000# 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\udc82' 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) oslo.privsep-2.1.1/oslo_privsep/tests/fixture.py0000664000175000017500000000342413643050273022134 0ustar zuulzuul00000000000000# 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) oslo.privsep-2.1.1/oslo_privsep/tests/test_daemon.py0000664000175000017500000001615613643050273022756 0ustar zuulzuul00000000000000# 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 copy import fixtures import functools import logging as pylogging import platform import sys import time from unittest import mock from oslo_log import formatters from oslo_log import log as logging from oslotest import base import six import testtools from oslo_privsep import capabilities from oslo_privsep import comm from oslo_privsep import daemon from oslo_privsep.tests import testctx LOG = logging.getLogger(__name__) def undecorated(): pass class TestException(Exception): pass @testctx.context.entrypoint def logme(level, msg, exc_info=False): # We want to make sure we log everything from the priv side for # the purposes of this test, so force loglevel. LOG.logger.setLevel(logging.DEBUG) if exc_info: try: raise TestException('with arg') except TestException: LOG.log(level, msg, exc_info=True) else: LOG.log(level, msg) class LogRecorder(pylogging.Formatter): def __init__(self, logs, *args, **kwargs): if sys.version_info >= (3, 8): kwargs['validate'] = False super(LogRecorder, self).__init__(*args, **kwargs) self.logs = logs def format(self, record): self.logs.append(copy.deepcopy(record)) return super(LogRecorder, self).format(record) @testtools.skipIf(platform.system() != 'Linux', 'works only on Linux platform.') class LogTest(testctx.TestContextTestCase): def setUp(self): super(LogTest, self).setUp() def test_priv_loglevel(self): logger = self.useFixture(fixtures.FakeLogger( level=logging.INFO)) # These write to the log on the priv side 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 # logger.output is the resulting log on the unpriv side. # This should have been filtered based on (unpriv) loglevel. self.assertNotIn(u'test@DEBUG', logger.output) self.assertIn(u'test@WARN', logger.output) def test_record_data(self): logs = [] self.useFixture(fixtures.FakeLogger( level=logging.INFO, format='dummy', # fixtures.FakeLogger accepts only a formatter # class/function, not an instance :( formatter=functools.partial(LogRecorder, logs))) logme(logging.WARN, u'test with exc', exc_info=True) time.sleep(0.1) # Hack to give logging thread a chance to run self.assertEqual(1, len(logs)) record = logs[0] self.assertIn(u'test with exc', record.getMessage()) self.assertIsNone(record.exc_info) self.assertIn(u'TestException: with arg', record.exc_text) self.assertEqual('PrivContext(cfg_section=privsep)', record.processName) self.assertIn(u'test_daemon.py', record.exc_text) self.assertEqual(logging.WARN, record.levelno) self.assertEqual('logme', record.funcName) def test_format_record(self): logs = [] self.useFixture(fixtures.FakeLogger( level=logging.INFO, format='dummy', # fixtures.FakeLogger accepts only a formatter # class/function, not an instance :( formatter=functools.partial(LogRecorder, logs))) logme(logging.WARN, u'test with exc', exc_info=True) time.sleep(0.1) # Hack to give logging thread a chance to run self.assertEqual(1, len(logs)) record = logs[0] # Verify the log record can be formatted by ContextFormatter fake_config = mock.Mock( logging_default_format_string="NOCTXT: %(message)s") formatter = formatters.ContextFormatter(config=fake_config) formatter.format(record) @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.thread_pool_size = 10 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.assertRaisesRegex( NameError, 'undecorated not exported', testctx.context._wrap, undecorated) class ClientChannelTestCase(base.BaseTestCase): DICT = { 'string_1': ('tuple_1', six.b('tuple_2')), six.b('byte_1'): ['list_1', 'list_2'], } EXPECTED = { 'string_1': ('tuple_1', six.b('tuple_2')), 'byte_1': ['list_1', 'list_2'], } def setUp(self): super(ClientChannelTestCase, self).setUp() with mock.patch.object(comm.ClientChannel, '__init__'), \ mock.patch.object(daemon._ClientChannel, 'exchange_ping'): self.client_channel = daemon._ClientChannel(mock.ANY) def test_out_of_band_log_message(self): message = [daemon.Message.LOG, self.DICT] with mock.patch.object(pylogging, 'makeLogRecord') as mock_make_log, \ mock.patch.object(daemon.LOG, 'isEnabledFor', return_value=False): self.client_channel.out_of_band(message) mock_make_log.assert_called_once_with(self.EXPECTED) def test_out_of_band_not_log_message(self): with mock.patch.object(daemon.LOG, 'warning') as mock_warning: self.client_channel.out_of_band([daemon.Message.PING]) mock_warning.assert_called_once() oslo.privsep-2.1.1/oslo_privsep/tests/__init__.py0000664000175000017500000000000013643050273022170 0ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo_privsep/tests/testctx.py0000664000175000017500000000275413643050273022151 0ustar zuulzuul00000000000000# 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) oslo.privsep-2.1.1/oslo_privsep/daemon.py0000664000175000017500000004436513643050273020560 0ustar zuulzuul00000000000000# 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). """ from concurrent import futures import enum import errno import io import logging as pylogging import os import platform import socket import subprocess import sys import tempfile import threading import eventlet from oslo_config import cfg from oslo_log import log as logging from oslo_utils import encodeutils from oslo_utils import importutils import six from oslo_privsep._i18n import _ from oslo_privsep import capabilities from oslo_privsep import comm if platform.system() == 'Linux': import fcntl import grp import pwd 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 LOG = 6 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 PrivsepLogHandler(pylogging.Handler): def __init__(self, channel, processName=None): super(PrivsepLogHandler, self).__init__() self.channel = channel self.processName = processName def emit(self, record): # Vaguely based on pylogging.handlers.SocketHandler.makePickle if self.processName: record.processName = self.processName data = dict(record.__dict__) if record.exc_info: if not record.exc_text: fmt = self.formatter or pylogging.Formatter() data['exc_text'] = fmt.formatException(record.exc_info) data['exc_info'] = None # drop traceback in favor of exc_text # serialise msg now so we can drop (potentially unserialisable) args data['msg'] = record.getMessage() data['args'] = () self.channel.send((None, (Message.LOG, data))) 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('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 out_of_band(self, msg): if msg[0] == Message.LOG: # (LOG, LogRecord __dict__) message = {encodeutils.safe_decode(k): v for k, v in msg[1].items()} record = pylogging.makeLogRecord(message) if LOG.isEnabledFor(record.levelno): LOG.logger.handle(record) else: LOG.warning('Ignoring unexpected OOB message from privileged ' 'process: %r', msg) 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() if os.fork() == 0: # child channel = comm.ServerChannel(sock_b) sock_a.close() # Replace root logger early (to capture any errors during setup) replace_logging(PrivsepLogHandler(channel, processName=str(context))) Daemon(channel, 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('Running privsep helper: %s', cmd) proc = subprocess.Popen(cmd, shell=False, stderr=_fd_logger()) if proc.wait() != 0: msg = ('privsep helper command exited non-zero (%s)' % proc.returncode) LOG.critical(msg) raise FailedToDropPrivileges(msg) LOG.info('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) self.thread_pool = futures.ThreadPoolExecutor( context.conf.thread_pool_size) self.communication_error = None 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('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' fc = [capabilities.CAPS_BYVALUE.get(c, str(c)) for c in capset] fc.sort() return '|'.join(fc) eff, prm, inh = capabilities.get_caps() LOG.info( '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, msgid, cmd, *args): """Executes the requested command in an execution thread. This executes a call within a thread executor and returns the results of the execution. :param msgid: The message identifier. :param cmd: The `Message` type indicating the command type. :param args: The function, args, and kwargs if a Message.CALL type. :return: A tuple of the return status, optional call output, and optional error information. """ if cmd == Message.PING: return (Message.PONG.value,) try: if cmd != Message.CALL: raise ProtocolError(_('Unknown privsep cmd: %s') % cmd) # Extract the callable and arguments 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) 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__) return (Message.ERR.value, cls_name, e.args) def _create_done_callback(self, msgid): """Creates a future callback to receive command execution results. :param msgid: The message identifier. :return: A future reply callback. """ channel = self.channel def _call_back(result): """Future execution callback. :param result: The `future` execution and its results. """ try: reply = result.result() LOG.debug('privsep: reply[%(msgid)s]: %(reply)s', {'msgid': msgid, 'reply': reply}) channel.send((msgid, reply)) except IOError: self.communication_error = sys.exc_info() 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: channel.send((msgid, reply)) except IOError: self.communication_error = sys.exc_info() return _call_back def loop(self): """Main body of daemon request loop""" LOG.info('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: error = self.communication_error if error: if error[1].errno == errno.EPIPE: # Write stream closed, exit loop break six.reraise(*error) # Submit the command for execution future = self.thread_pool.submit(self._process_cmd, msgid, *msg) future.add_done_callback(self._create_done_callback(msgid)) 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') # note replace_logging call below 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('--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. # Channel is set up now, so move to in-band logging replace_logging(PrivsepLogHandler(channel)) LOG.info('privsep daemon starting') 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() oslo.privsep-2.1.1/oslo_privsep/priv_context.py0000664000175000017500000002372113643050273022032 0ustar zuulzuul00000000000000# 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 copy import enum import functools import logging import multiprocessing import shlex import sys import threading from oslo_config import cfg from oslo_config import types from oslo_utils import importutils from oslo_privsep._i18n import _ from oslo_privsep import capabilities from oslo_privsep import daemon 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.IntOpt('thread_pool_size', min=1, help=_("The number of threads available for privsep to " "concurrently run processes. Defaults to the number of " "CPU cores in the system."), default=multiprocessing.cpu_count(), sample_default='multiprocessing.cpu_count()'), 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'] def _list_opts(): """Returns a list of oslo.config options available in the library. The returned list includes all oslo.config options which may be registered at runtime by the library. Each element of the list is a tuple. The first element is the name of the group under which the list of elements in the second element will be registered. A group name of None corresponds to the [DEFAULT] group in config files. The purpose of this is to allow tools like the Oslo sample config file generator to discover the options exposed to users by this library. :returns: a list of (group_name, opts) tuples """ # This is the default group name, but that can be overridden by the caller group = cfg.OptGroup('privsep', title='oslo.privsep options', help='Configuration options for the oslo.privsep ' 'daemon. Note that this group name can be ' 'changed by the consuming service. Check the ' 'service\'s docs to see if this is the case.' ) return [(group, copy.deepcopy(OPTS))] @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 self.start_lock = threading.Lock() 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. if self.pypath is None: raise AssertionError('helper_command requires priv_context ' 'pypath to be specified') if importutils.import_class(self.pypath) is not self: raise AssertionError('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: for cfg_dir in cfg.CONF.config_dir: cmd.extend(['--config-dir', cfg_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( "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.""" if not func.__module__.startswith(self.prefix): raise AssertionError('%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. if getattr(func, _ENTRYPOINT_ATTR, None) is not None: raise AssertionError('%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 not None and not self.channel.running: LOG.warning("RESTARTING PrivContext for %s", name) self.stop() 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): with self.start_lock: if self.channel is not None: LOG.warning('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 oslo.privsep-2.1.1/oslo_privsep/__init__.py0000664000175000017500000000000013643050273021026 0ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo_privsep/functional/0000775000175000017500000000000013643050371021070 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/oslo_privsep/functional/test_daemon.py0000664000175000017500000000364713643050273023757 0ustar zuulzuul00000000000000# Copyright 2019 Red Hat, 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 time import unittest from oslo_config import fixture as config_fixture from oslotest import base from oslo_privsep import priv_context test_context = priv_context.PrivContext( __name__, cfg_section='privsep', pypath=__name__ + '.test_context', capabilities=[], ) @test_context.entrypoint def sleep(): # We don't want the daemon to be able to handle these calls too fast. time.sleep(.001) @test_context.entrypoint def one(): return 1 @test_context.entrypoint def logs(): logging.warning('foo') class TestDaemon(base.BaseTestCase): def setUp(self): super(TestDaemon, self).setUp() venv_path = os.environ['VIRTUAL_ENV'] self.cfg_fixture = self.useFixture(config_fixture.Config()) self.cfg_fixture.config( group='privsep', helper_command='sudo -E %s/bin/privsep-helper' % venv_path) priv_context.init() def test_concurrency(self): # Throw a large number of simultaneous requests at the daemon to make # sure it can can handle them. for i in range(1000): sleep() # Make sure the daemon is still working self.assertEqual(1, one()) def test_logging(self): logs() self.assertIn('foo', self.log_fixture.logger.output) if __name__ == '__main__': unittest.main() oslo.privsep-2.1.1/oslo_privsep/functional/__init__.py0000664000175000017500000000032613643050273023203 0ustar zuulzuul00000000000000import os.path def load_tests(loader, tests, pattern): this_dir = os.path.dirname(__file__) new_tests = loader.discover(start_dir=this_dir, pattern=pattern) tests.addTests(new_tests) return tests oslo.privsep-2.1.1/oslo_privsep/comm.py0000664000175000017500000001373013643050273020240 0ustar zuulzuul00000000000000# 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, unicode_errors='surrogateescape') 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, raw=False, strict_map_key=False, unicode_errors='surrogateescape') def __iter__(self): return self def __next__(self): while True: try: return next(self.unpacker) except StopIteration: try: buf = self.readsock.recv(4096) if not buf: raise self.unpacker.feed(buf) except socket.timeout: pass 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.running = False 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""" with self.lock: self.running = True for msg in reader: msgid, data = msg if msgid is None: self.out_of_band(data) else: with self.lock: if msgid not in self.outstanding_msgs: raise AssertionError("msgid should in " "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) self.running = False def out_of_band(self, msg): """Received OOB message. Subclasses might want to override this.""" pass def send_recv(self, msg): myid = _get_thread_ident() future = Future(self.lock) with self.lock: if myid in self.outstanding_msgs: raise AssertionError("myid shoudn't be in 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) oslo.privsep-2.1.1/.stestr.conf0000664000175000017500000000010613643050273016451 0ustar zuulzuul00000000000000[DEFAULT] test_path=${OS_TEST_PATH:-./oslo_privsep/tests} top_path=./ oslo.privsep-2.1.1/README.rst0000664000175000017500000000245113643050273015674 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/oslo.privsep.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ============ oslo.privsep ============ .. image:: https://img.shields.io/pypi/v/oslo.privsep.svg :target: https://pypi.org/project/oslo.privsep/ :alt: Latest Version 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: https://docs.openstack.org/oslo.privsep/latest/ * Source: https://opendev.org/openstack/oslo.privsep * Bugs: https://bugs.launchpad.net/oslo.privsep * Release Notes: https://docs.openstack.org/releasenotes/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 oslo.privsep-2.1.1/ChangeLog0000664000175000017500000001726513643050371015767 0ustar zuulzuul00000000000000CHANGES ======= 2.1.1 ----- * Update hacking for Python3 * Use unittest.mock instead of third party mock 2.1.0 ----- * Make compatible with msgpack 1.0.0 * Bring sanity to lower-constraints * Disable logger validation during unit testing * Add lock around channel creation * trivial: Cleanup Sphinx config file, setup.cfg * tox: Add missing 'deps' for releasenotes testenv 2.0.0 ----- * remove outdated header * [ussuri][goal] Drop python 2.7 support and testing 1.34.0 ------ * tox: Trivial cleanup * Add functional tests * Bump the openstackdocstheme extension to 1.20 * tox: Keeping going with docs * Switch to Ussuri jobs * Update the constraints url * Update master for stable/train 1.33.3 ------ * Reno for SIGHUP fix 1.33.2 ------ * Self-resetting PrivContext * Add Python 3 Train unit tests * Move doc related modules to doc/requirements.txt 1.33.1 ------ * Pass correct arguments to six.reraise * Cap Bandit below 1.6.0 and update Sphinx requirement * Replace git.openstack.org URLs with opendev.org URLs 1.33.0 ------ * OpenDev Migration Patch * Add more usage documentation * Convert dict keys received in \_ClientChannel from byte to str * Update master for stable/stein * Add sample\_default for thread\_pool\_size Opt 1.32.1 ------ * add python 3.7 unit test job * Update hacking version 1.32.0 ------ 1.31.1 ------ * Expose privsep options for config-generator 1.31.0 ------ * Use template for lower-constraints * Set unicode\_errors handler to 'surrogateescape' in msgpack * Add futures as a requirement for Python 2 * Update mailinglist from dev to discuss * Use threads to process target function * Clean up .gitignore references to personal tools * Don't quote {posargs} in tox.ini 1.30.1 ------ * Replace assertRaisesRegexp with assertRaisesRegex * Avoids calling ffi.dlopen(None) on Windows * add lib-forward-testing-python3 test job * add python 3.6 unit test job * Remove PyPI downloads * import zuul job settings from project-config * Add that 'Release Notes' in README * Update reno for stable/rocky * Switch to stestr * fix tox python3 overrides * Added example blogposts * Trivial: Update pypi url to new url 1.29.0 ------ * set default python to python3 * fix lower constraints and uncap eventlet * Skip unit tests in bandit scan * add lower-constraints job * Updated from global requirements 1.28.0 ------ * Updated from global requirements * Imported Translations from Zanata * Update links in README * Imported Translations from Zanata * Update reno for stable/queens * Updated from global requirements * Updated from global requirements * Updated from global requirements * msgpack-python has been renamed to msgpack 1.26.0 ------ * Updated from global requirements 1.25.0 ------ * Expose caps values/names as int enum * add bandit to pep8 job 1.24.0 ------ * Remove -U from pip install * Avoid tox\_install.sh for constraints support * Updated from global requirements * Remove setting of version/release from releasenotes * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Updated from global requirements * Updated from global requirements 1.23.0 ------ * Updated from global requirements * Imported Translations from Zanata * Updated from global requirements * Update reno for stable/pike * Updated from global requirements * Update capabilities from current kernel source 1.22.0 ------ * Updated from global requirements * Update URLs in documents according to document migration * add sphinx instructions to build API reference docs * switch from oslosphinx to openstackdocstheme * rearrange existing documentation to follow the new standard layout 1.21.1 ------ * Enable some off-by-default checks 1.21.0 ------ * Updated from global requirements * Updated from global requirements * Remove pbr warnerrors in favor of sphinx check * Updated from global requirements * Using assertIsNone(xxx) instead of assertEqual(None, xxx) * Updated from global requirements * Updated from global requirements * Updated from global requirements 1.20.0 ------ * Updated from global requirements * Add test to verify log record can be formatted * Updated from global requirements 1.19.0 ------ * Remove log translations 1.18.0 ------ * Use iterable object for 'args' in log record * Updated from global requirements 1.17.0 ------ * Updated from global requirements * [Fix gate]Update test requirement * Updated from global requirements * pbr.version.VersionInfo needs package name (oslo.xyz and not oslo\_xyz) * Update reno for stable/ocata * Remove references to Python 3.4 1.16.0 ------ * Add Constraints support * Show team and repo badges on README 1.15.0 ------ * Updated from global requirements * Don't use deprecated method logger.warn * Updated from global requirements * Imported Translations from Zanata * Updated from global requirements 1.14.0 ------ * Enable release notes translation * Updated from global requirements * Updated from global requirements * modify the home-page info with the developer documentation * Ignore timeout error when receiving message from sockect * Update reno for stable/newton * Deal with CONF.config\_dir correctly * Preserve all LogRecord fields from privileged side 1.13.0 ------ * Report underlying integer for unknown capabilities * Updated from global requirements 1.12.0 ------ * Add Python 3.5 classifier and venv * Fixes unit tests on Windows * More sophisticated logging on privileged side 1.11.0 ------ * Updated from global requirements * Use default value for undefined caps in fmt\_caps 1.10.0 ------ * Updated from global requirements * Add reno for release notes management * Updated from global requirements * Updated from global requirements 1.9.0 ----- * Provide way to "initialise" oslo.privsep * PrivContext: Sets client\_mode to False on Windows * Imported Translations from Zanata 1.8.0 ----- * Updated from global requirements * Drop python3.3 support in classifier 1.7.0 ----- 1.6.0 ----- * Imported Translations from Zanata * Remove unused py27 socketpair/makefile workaround * Remove direct dependency on babel * Updated from global requirements 1.5.0 ----- * Updated from global requirements * Updated from global requirements * Switch to msgpack for serialization * Updated from global requirements 1.3.0 ----- * Updated from global requirements 1.2.0 ----- * Updated from global requirements * fdopen: Use better "is using eventlet" test * Ensure fdopen uses greenio object under eventlet 1.1.0 ----- * UnprivilegedPrivsepFixture: Clear capabilities config * Change name of privsep\_helper to match code 1.0.0 ----- * Ignore --config-dir when value is None * Add version and download badges to README * Update translation setup * Updated from global requirements * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Updated from global requirements * Update/make better the README.rst long description 0.3.0 ----- * Improve \`helper\_command' config default * Updated from global requirements * Replace deprecated LOG.warn with LOG.warning * Updated from global requirements * Use logging intead of oslo\_log * Remove unused file openstack-common.conf * remove python 2.6 trove classifier 0.2.0 ----- * Updated from global requirements * Updated from global requirements * Removes MANIFEST.in as it is not needed explicitely by PBR * Remove python 2.6 and tox.ini cleanup * Don't fail badly on windows 0.1.0 ----- * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Add support for Linux capabilities * Enable translations * Initial basic privsep functionality * Tell git to ignore /.eggs dir too * Updated from global requirements * oslo.i18n boilerplate * Initial cookiecutter project * Added .gitreview oslo.privsep-2.1.1/tox.ini0000664000175000017500000000364513643050273015526 0ustar zuulzuul00000000000000[tox] minversion = 3.1.1 envlist = py37,pypy,pep8 ignore_basepython_conflict = true [testenv] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run --slowest {posargs} [testenv:pep8] deps = -r{toxinidir}/test-requirements.txt commands = flake8 # Run security linter bandit -r oslo_privsep -x tests -n5 --skip B404,B603 [testenv:venv] commands = {posargs} [testenv:docs] whitelist_externals = rm deps = {[testenv]deps} -r{toxinidir}/doc/requirements.txt commands = rm -rf doc/build doc/source/reference/api sphinx-build -W --keep-going -b html doc/source doc/build/html [testenv:cover] setenv = PYTHON=coverage run --source $project --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [flake8] # E123, E125 skipped as they are invalid PEP-8. # [H106] Don’t put vim configuration in source files # [H203] Use assertIs(Not)None to check for None # [W504] line break after binary operator show-source = True ignore = E123,E125,W504 builtins = _ exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build enable-extensions = H106,H203 [hacking] import_exceptions = oslo_privsep._i18n [testenv:releasenotes] whitelist_externals = rm deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = rm -rf releasenotes/build sphinx-build -a -E -W -d releasenotes/build/doctrees --keep-going -b html releasenotes/source releasenotes/build/html [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt [testenv:functional] basepython = python3 setenv = OS_TEST_PATH=./oslo_privsep/functional OS_LOG_CAPTURE=1 oslo.privsep-2.1.1/.mailmap0000664000175000017500000000013113643050273015617 0ustar zuulzuul00000000000000# Format is: # # oslo.privsep-2.1.1/CONTRIBUTING.rst0000664000175000017500000000122013643050273016637 0ustar zuulzuul00000000000000If 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 oslo.privsep-2.1.1/doc/0000775000175000017500000000000013643050371014747 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/doc/requirements.txt0000664000175000017500000000052313643050273020234 0ustar zuulzuul00000000000000# 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. openstackdocstheme>=1.20.0 # Apache-2.0 sphinx>=1.8.0,!=2.1.0 # BSD reno>=2.5.0 # Apache-2.0 sphinxcontrib-apidoc>=0.2.0 # BSD oslo.privsep-2.1.1/doc/source/0000775000175000017500000000000013643050371016247 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/doc/source/reference/0000775000175000017500000000000013643050371020205 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/doc/source/reference/index.rst0000664000175000017500000000007713643050273022053 0ustar zuulzuul00000000000000===== API ===== .. toctree:: :maxdepth: 2 api/modules oslo.privsep-2.1.1/doc/source/conf.py0000775000175000017500000000371613643050273017561 0ustar zuulzuul00000000000000# -*- 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. # -- 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', 'sphinxcontrib.apidoc', 'openstackdocstheme', ] # openstackdocstheme options repository_name = 'openstack/oslo.privsep' bug_project = 'oslo.privsep' bug_tag = '' # 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 = 'openstackdocs' # -- sphinxcontrib.apidoc configuration -------------------------------------- apidoc_module_dir = '../../oslo_privsep' apidoc_output_dir = 'reference/api' apidoc_excluded_paths = [ 'tests', ] oslo.privsep-2.1.1/doc/source/index.rst0000664000175000017500000000147413643050273020117 0ustar zuulzuul00000000000000============== oslo.privsep ============== oslo.privsep is an OpenStack library for privilege separation. It 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. .. _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 Contents ======== .. toctree:: :maxdepth: 2 install/index user/index contributor/index reference/index Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` oslo.privsep-2.1.1/doc/source/install/0000775000175000017500000000000013643050371017715 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/doc/source/install/index.rst0000664000175000017500000000014313643050273021555 0ustar zuulzuul00000000000000============== Installation ============== At the command line:: $ pip install oslo.privsep oslo.privsep-2.1.1/doc/source/contributor/0000775000175000017500000000000013643050371020621 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/doc/source/contributor/index.rst0000664000175000017500000000016513643050273022465 0ustar zuulzuul00000000000000=================== Contributor Guide =================== .. toctree:: :maxdepth: 2 contributing history oslo.privsep-2.1.1/doc/source/contributor/history.rst0000664000175000017500000000004013643050273023047 0ustar zuulzuul00000000000000.. include:: ../../../ChangeLog oslo.privsep-2.1.1/doc/source/contributor/contributing.rst0000664000175000017500000000012413643050273024060 0ustar zuulzuul00000000000000============== Contributing ============== .. include:: ../../../CONTRIBUTING.rst oslo.privsep-2.1.1/doc/source/user/0000775000175000017500000000000013643050371017225 5ustar zuulzuul00000000000000oslo.privsep-2.1.1/doc/source/user/index.rst0000664000175000017500000001042213643050273021066 0ustar zuulzuul00000000000000======= Usage ======= oslo.privsep lets you define in your code specific functions that will run in predefined privilege contexts. This lets you run functions with more (or less) privileges than the rest of the code. Privsep functions live in a specific ``privsep`` submodule (for example, ``nova.privsep`` for nova). Defining a context ================== Contexts are defined in the ``privsep/__init__.py`` file. For example, this defines a sys_admin_pctxt with ``CAP_CHOWN``, ``CAP_DAC_OVERRIDE``, ``CAP_DAC_READ_SEARCH``, ``CAP_FOWNER``, ``CAP_NET_ADMIN``, and ``CAP_SYS_ADMIN`` rights (equivalent to ``sudo`` rights):: from oslo_privsep import capabilities from oslo_privsep import priv_context sys_admin_pctxt = priv_context.PrivContext( 'nova', cfg_section='nova_sys_admin', pypath=__name__ + '.sys_admin_pctxt', capabilities=[capabilities.CAP_CHOWN, capabilities.CAP_DAC_OVERRIDE, capabilities.CAP_DAC_READ_SEARCH, capabilities.CAP_FOWNER, capabilities.CAP_NET_ADMIN, capabilities.CAP_SYS_ADMIN], ) Defining a privileged function ============================== Functions are defined in files under the ``privsep/`` subdirectory, for example in a ``privsep/motd.py`` file for functions touching the MOTD file. They make use of a decorator pointing to the context we defined above:: import nova.privsep @nova.privsep.sys_admin_pctxt.entrypoint def update_motd(message): with open('/etc/motd', 'w') as f: f.write(message) Privileged functions must be as simple, specialized and narrow as possible, so as to prevent further escalation. In this example, ``update_motd(message)`` is narrow: it only allows the service to overwrite the MOTD file. If a more generic ``update_file(filename, content)`` was created, it could be used to overwrite any file in the filesystem, allowing easy escalation to root rights. That would defeat the whole purpose of oslo.privsep. Using a privileged function =========================== To use the privileged function in the regular code, you can just call it:: import nova.privsep.motd ... nova.privsep.motd.update_motd('This node is currently idle') It is better to import the complete path (``import nova.privsep.motd``) rather than the motd name (``from nova.privsep import motd``) so that it is easier to spot that the function runs in a different privileged context. For more details, you can read the following blog post: * `How to make a privileged call with oslo privsep`_ .. _How to make a privileged call with oslo privsep: https://www.madebymikal.com/how-to-make-a-privileged-call-with-oslo-privsep/ Converting from rootwrap to privsep =================================== oslo.rootwrap is a precursor of oslo.privsep to allow code to run commands under sudo if they match a predefined filter. For example, you could define a filter that would allow you to run chmod as root using the following filter:: chmod: CommandFilter, chmod, root Beyond the bad performance of calling full commands in order to accomplish simple tasks, rootwrap also led to bad security: it was difficult to filter commands in a way that would not easily allow privilege escalation. Replacing rootwrap filters with privsep functions is easy. The chmod filter above can be replaced with a function that calls ``os.chmod()``. However a straight 1:1 filter:function replacement generally results in functions that are still too broad for good security. It is better to replace each chmod rootwrap *call* with a narrow privsep function that will limit it to specific files. Sometimes it is necessary to refactor the calling code: the rootwrap design discouraged the creation of new filters and therefore often resulted in the creation of overly-broad calling functions. As an example, this `patch series`_ is work-in-progress to transition Nova from rootwrap to privsep. For more details, you can read the following blog post: * `Adding oslo privsep to a new project, a worked example`_ .. _patch series: https://review.openstack.org/#/q/project:openstack/nova+branch:master+topic:my-own-personal-alternative-universe .. _Adding oslo privsep to a new project, a worked example: https://www.madebymikal.com/adding-oslo-privsep-to-a-new-project-a-worked-example/