osloprivsep(1)

OSLOPRIVSEP(1) oslo.privsep OSLOPRIVSEP(1)

NAME

osloprivsep - oslo.privsep 3.1.0

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.

CONTENTS

Installation

At the command line:

$ pip install oslo.privsep


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 context with timeout

It is possible to initialize PrivContext with timeout:

from oslo_privsep import capabilities
from oslo_privsep import priv_context
dhcp_release_cmd = priv_context.PrivContext(

__name__,
cfg_section='privsep_dhcp_release',
pypath=__name__ + '.dhcp_release_cmd',
capabilities=[caps.CAP_SYS_ADMIN,
caps.CAP_NET_ADMIN],
timeout=5 )


PrivsepTimeout is raised if timeout is reached.

WARNING:

The daemon (the root process) task won't stop when timeout is reached. That means we'll have less available threads if the related thread never finishes.


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.

Defining a privileged function with timeout

It is possible to use entrypoint_with_timeout decorator:

from oslo_privsep import daemon
from neutron import privileged
@privileged.default.entrypoint_with_timeout(timeout=5)
def get_link_devices(namespace, **kwargs):

try:
with get_iproute(namespace) as ip:
return make_serializable(ip.get_links(**kwargs))
except OSError as e:
if e.errno == errno.ENOENT:
raise NetworkNamespaceNotFound(netns_name=namespace)
raise
except daemon.FailedToDropPrivileges:
raise
except daemon.PrivsepTimeout:
raise


PrivsepTimeout is raised if timeout is reached.

WARNING:

The daemon (the root process) task won't stop when timeout is reached. That means we'll have less available threads if the related thread never finishes.


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

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

Contributor Guide

Contributing

If you would like to contribute to the development of oslo's libraries, first you must take a look to this page:

https://specs.openstack.org/openstack/oslo-specs/specs/policy/contributing.html


If you would like to contribute to the development of OpenStack, you must follow the steps in this page:

http://docs.openstack.org/infra/manual/developers.html


If you already have a good understanding of how the system works and your OpenStack accounts are set up, you can skip to the development workflow section of this documentation to learn how changes to OpenStack should be submitted for review via the Gerrit tool:

http://docs.openstack.org/infra/manual/developers.html#development-workflow


Pull requests submitted through GitHub will be ignored.

Bugs should be filed on Launchpad, not GitHub:

https://bugs.launchpad.net/oslo.privsep


CHANGES

3.1.0

  • Setup logging without fixing evenlet logging
  • Add Python3 antelope unit tests
  • Update master for stable/zed

3.0.1

Remove logic for Python < 3.8

3.0.0

Drop python3.6/3.7 support in testing runtime

2.8.0

  • Remove unnecessary unicode prefixes
  • Add note explaining max_buffer_size value
  • Fix formatting of release list
  • Add Python3 zed unit tests
  • Update master for stable/yoga

2.7.0

  • Remove six
  • Bump max_buffer_size for Deserializer
  • Add Python3 yoga unit tests
  • Update master for stable/xena

2.6.2

2.6.1

Add except path with exception debug to send_recv

2.6.0

  • Add timeout to PrivContext and entrypoint_with_timeout decorator
  • Changed minversion in tox to 3.18.0
  • Upgrade the pre-commit-hooks version
  • setup.cfg: Replace dashes with underscores
  • Allow finer grained log levels
  • Add Python3 xena unit tests
  • Update master for stable/wallaby
  • Fix requirements issues
  • Use TOX_CONSTRAINTS_FILE
  • Use py3 as the default runtime for tox

2.5.0

  • Add Python3 wallaby unit tests
  • Update master for stable/victoria
  • Adding pre-commit

2.4.0

  • [goal] Migrate testing to ubuntu focal
  • Bump bandit version

2.3.0

Undo the eventlet monkey patch for the privileged daemon

2.2.1

  • Replace assertItemsEqual with assertCountEqual
  • Fix pygments style

2.2.0

  • Fix hacking min version to 3.0.1
  • Switch to newer openstackdocstheme and reno versions
  • Remove the unused coding style modules
  • Remove translation sections from setup.cfg
  • Align contributing doc with oslo's policy
  • Bump default tox env from py37 to py38
  • Add py38 package metadata
  • Add release notes links to doc index
  • Add Python3 victoria unit tests
  • Update master for stable/ussuri

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

API

oslo_privsep

oslo_privsep package

Subpackages

oslo_privsep.functional package

Submodules

oslo_privsep.functional.test_daemon module

class oslo_privsep.functional.test_daemon.TestDaemon(*args, **kwds)
Bases: oslotest.base.BaseTestCase
setUp()
Hook method for setting up the test fixture before exercising it.

test_concurrency()

test_context_with_timeout()

test_context_with_timeout_pass()

test_entrypoint_with_timeout()

test_entrypoint_with_timeout_pass()

test_logging()


oslo_privsep.functional.test_daemon.sleep_with_timeout(long_timeout=0.04)

Module contents

oslo_privsep.functional.load_tests(loader, tests, pattern)

Submodules

oslo_privsep.capabilities module

class oslo_privsep.capabilities.Capabilities(value)
Bases: enum.IntEnum

An enumeration.

CAP_AUDIT_CONTROL = 30

CAP_AUDIT_READ = 37

CAP_AUDIT_WRITE = 29

CAP_BLOCK_SUSPEND = 36

CAP_CHOWN = 0

CAP_DAC_OVERRIDE = 1


CAP_FOWNER = 3

CAP_FSETID = 4

CAP_IPC_LOCK = 14

CAP_IPC_OWNER = 15

CAP_KILL = 5

CAP_LEASE = 28

CAP_LINUX_IMMUTABLE = 9

CAP_MAC_ADMIN = 33

CAP_MAC_OVERRIDE = 32

CAP_MKNOD = 27

CAP_NET_ADMIN = 12

CAP_NET_BIND_SERVICE = 10

CAP_NET_BROADCAST = 11

CAP_NET_RAW = 13

CAP_SETFCAP = 31

CAP_SETGID = 6

CAP_SETPCAP = 8

CAP_SETUID = 7

CAP_SYSLOG = 34

CAP_SYS_ADMIN = 21

CAP_SYS_BOOT = 22

CAP_SYS_CHROOT = 18

CAP_SYS_MODULE = 16

CAP_SYS_NICE = 23

CAP_SYS_PACCT = 20

CAP_SYS_PTRACE = 19

CAP_SYS_RAWIO = 17

CAP_SYS_RESOURCE = 24

CAP_SYS_TIME = 25

CAP_SYS_TTY_CONFIG = 26

CAP_WAKE_ALARM = 35


oslo_privsep.capabilities.drop_all_caps_except(effective, permitted, inheritable)
Set (effective, permitted, inheritable) to provided list of caps

oslo_privsep.capabilities.get_caps()
Return (effective, permitted, inheritable) as lists of caps

oslo_privsep.capabilities.set_keepcaps(enable)
Set/unset thread's "keep capabilities" flag - see prctl(2)

oslo_privsep.comm module

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.

class oslo_privsep.comm.ClientChannel(sock)
Bases: object
close()

out_of_band(msg)
Received OOB message. Subclasses might want to override this.

send_recv(msg, timeout=None)


class oslo_privsep.comm.Deserializer(readsock)
Bases: object

class oslo_privsep.comm.Future(lock, timeout=None)
Bases: object

A very simple object to track the return of a function call

result()
Must already be holding lock used in constructor

set_exception(exc)
Must already be holding lock used in constructor

set_result(data)
Must already be holding lock used in constructor


class oslo_privsep.comm.Message(value)
Bases: enum.IntEnum

Types of messages sent across the communication channel

CALL = 3

ERR = 5

LOG = 6

PING = 1

PONG = 2

RET = 4


exception oslo_privsep.comm.PrivsepTimeout
Bases: Exception

class oslo_privsep.comm.Serializer(writesock)
Bases: object
close()

send(msg)


class oslo_privsep.comm.ServerChannel(sock)
Bases: object

Server-side twin to ClientChannel

send(msg)


oslo_privsep.daemon module

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).

class oslo_privsep.daemon.Daemon(channel, context)
Bases: object

NB: This doesn't fork() - do that yourself before calling run()

loop()
Main body of daemon request loop

run()
Run request loop. Sets up environment, then calls loop()


exception oslo_privsep.daemon.FailedToDropPrivileges
Bases: Exception

class oslo_privsep.daemon.ForkingClientChannel(context)
Bases: oslo_privsep.daemon._ClientChannel

class oslo_privsep.daemon.PrivsepLogHandler(channel, processName=None)
Bases: logging.Handler
emit(record)
Do whatever it takes to actually log the specified logging record.

This version is intended to be implemented by subclasses and so raises a NotImplementedError.



exception oslo_privsep.daemon.ProtocolError
Bases: Exception

class oslo_privsep.daemon.RootwrapClientChannel(context)
Bases: oslo_privsep.daemon._ClientChannel

class oslo_privsep.daemon.StdioFd(value)
Bases: enum.IntEnum

An enumeration.

STDERR = 2

STDIN = 0

STDOUT = 1


oslo_privsep.daemon.fdopen(fd, *args, **kwargs)

oslo_privsep.daemon.helper_main()
Start privileged process, serving requests over a Unix socket.

oslo_privsep.daemon.replace_logging(handler, log_root=None)

oslo_privsep.daemon.set_cloexec(fd)

oslo_privsep.daemon.setgid(group_id_or_name)

oslo_privsep.daemon.setuid(user_id_or_name)

oslo_privsep.daemon.un_monkey_patch()

oslo_privsep.priv_context module

oslo_privsep.priv_context.CapNameOrInt(value)

class oslo_privsep.priv_context.Method(value)
Bases: enum.Enum

An enumeration.

FORK = 1

ROOTWRAP = 2


class oslo_privsep.priv_context.PrivContext(prefix, cfg_section='privsep', pypath=None, capabilities=None, logger_name='oslo_privsep.daemon', timeout=None)
Bases: object
property conf
Return the oslo.config section object as lazily as possible.

entrypoint(func)
This is intended to be used as a decorator.

entrypoint_with_timeout(timeout)
This is intended to be used as a decorator with timeout.

helper_command(sockpath)

is_entrypoint(func)

set_client_mode(enabled)

start(method=<Method.ROOTWRAP: 2>)

stop()


oslo_privsep.priv_context.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".

Parameters
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.


oslo_privsep.version module

Module contents

RELEASE NOTES

Read also the oslo.privsep Release Notes.

INDICES AND TABLES

  • genindex
  • modindex
  • search

AUTHOR

unknown

COPYRIGHT

2023, OpenStack Foundation

December 29, 2023 3.1.0