discover

package
v0.33.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2024 License: Apache-2.0 Imports: 27 Imported by: 1

Documentation

Overview

Package discover discovers Linux kernel namespaces of types cgroup, ipc, mount, net, pid, time, user, and uts. This package discovers namespaces not only when processes have joined them, but also when namespaces have been bind-mounted or are only referenced anymore by process file descriptors or within a hierarchy (PID and user only).

Please note that our manual has some nice UML diagrams depicting Linux' namespaces model from 10,000m.

In case of PID and user namespaces, lxkns additionally discovers their hierarchies, except when running on a really ancient kernel before 4.9. Furthermore, for user namespaces the owning user ID and the owned namespaces will be discovered too. Time namespaces require a kernel 5.6 or later.

And finally, lxkns relates namespaces to the “leading” (or “root”) processes joined to them; this relationship is basically derived for convenience from the process tree hierarchy. The kernel itself doesn't define any special relationship between namespaces and processes except for the “attachment” of processes joining namespaces.

The namespace discovery process can be controlled in several aspects, according to the range of discovery of namespace types and places to search namespaces for, according to the needs of API users of the lxkns package.

Discovery

A namespace discovery is just a single call to function discover.Namespaces. It accepts option setters, such as discover.StandardDiscovery, discover.WithMounts, et cetera.

import (
    "github.com/thediveo/lxkns/discover"
)

func main() {
    allns := discover.Namespaces(discover.StandardDiscovery())
    ...
}

Please also have a look at our manual's Discovering Namespaces UML diagrams.

Basics of the lxkns Information Model

Not totally unexpectedly, the lxkns discovery information model at its most basic level comprises ... namespaces. Again, the manual has some nice namespace representation UML diagram.

In the previous code snippet, the information model returned is stored in the “allns” variable for further processing. The result organizes the namespaces found by type. For instance, the following code snippet prints all namespaces, sorted first by type and then by namespace identifier:

// Iterate over all 7 types of Linux-kernel namespaces, then over all
// namespaces of a given type...
for nsidx := range allns.Namespaces {
    for _, ns := range allns.SortedNamespaces(discover.NamespaceTypeIndex(nsidx)) {
        println(ns.Type().Name(), ns.ID().Ino)
    }
}

Because namespaces have no order defined, the discovery results “list” the namespaces in per-type maps, indexed by namespace identifiers. For convenience, SortedNamespaces() returns the namespaces of a specific type in a slice instead of a map, sorted numerically by the namespace identifiers (that is, sorting by inode numbers, ignoring dev IDs at this time).

Technically, these namespace identifiers are tuples consisting of 64bit unsigned inode numbers and (~64bit) device ID numbers, and come from the special “nsfs” namespace filesystem integrated with the Linux kernel. And before someone tries: nope, the nsfs cannot be mounted; and it even does not appear in the kernel's list of namespaces.

Unprivileged Discovery and How To Not Panic

While it is possible to discover namespaces without root privileges, this won't return the full set of namespaces in a Linux host. The reason is that while an unprivileged discovery is allowed to see some basic information about all processes in the system, it is not allowed to query the namespaces such privileged processes are joined too. In addition, an unprivileged discovery may turn up namespaces (for instance, when bind-mounted) for which the identifier is discovered, but further information, such as the parent or child namespaces for PID and user namespaces, is undiscoverable.

Users of the lxkns information model thus must be prepared to handle incomplete information yielded by unprivileged discover.Namespaces calls. In particular, applications must be prepared to handle:

  • more than a single "initial" namespace per type of namespace,
  • PID and user namespaces without a parent namespace,
  • namespaces without owning user namespaces,
  • processes not related to any namespace.

In consequence, always check interface values and pointers for nil values like a pro. You can find many examples in the sources for the "lsuns", "lspidns", and "pidtree" CLI tools (inside the cmd sub-package).

In-Capabilities

It is possible to run full discoveries without being root, when given the discovery process the following effective capabilities:

  • CAP_SYS_PTRACE – no joking here, that's what needed for reading namespace references from /proc/[PID]/ns/*
  • CAP_SYS_CHROOT – for mount namespace switching
  • CAP_SYS_ADMIN – for mount namespace switching
  • CAP_DAC_READ_SEARCH – for reading details of bind-mounted namespaces

Considering that especially CAP_SYS_PTRACE being essential there's probably not much difference to “just be root” in the end, unless you want show off your “capabilities capabilities”.

Namespace Hierarchies

PID and user namespaces form separate and independent namespaces hierarchies. This parent-child hierarchy is exposed through the model.Hierarchy interface of the discovered namespaces.

Please note that lxkns represents namespaces often using the model.Namespace interface when the specific type of namespace doesn't matter. In case of PID and user-type namespaces a model.Namespace can be “converted” into an interface value of type model.Hierarchy using a type assertion, in order to access the particular namespace hierarchy.

// If it's a PID or user namespace, then we can turn a "Namespace"
// into an "Hierarchy" in order to access hierarchy information.
if hns, ok := ns.(model.Hierarchy); ok {
    if hns.Parent() != nil {
        ...
    }
    for _, childns := range hns.Children() {
        ...
    }
}

Ownership

User namespaces play the central role in controlling the access of processes to other namespaces as well as the capabilities process gain when allowed to join user namespaces. A comprehensive discussion of the rules and their ramifications is beyond this package documentation. For starters, please refer to the man page for user_namespaces(7).

The controlling role of user namespaces show up in the discovery information model as owner-owneds relationships: user namespaces own non-user namespaces. And non-user namespaces are owned by user namespaces, the “ownings”. In case you are now scratching your head “why the Gopher” the owned namespaces are referred to as “ownings”: welcome to the wonderful Gopher world of “er”-ers, where interface method naming conventions create wonderful identifier art.

If a namespace interface value represents a user-type namespace, then it can be “converted” into an model.Ownership interface value using a type assertion. This interface discloses which namespaces are owned by a particular user namespace. Please note that this does not include child user namespaces, use [Hierarchy.Children] instead.

// Get the user namespace -owned-> namespaces relationships.
if owns, ok := ns.(model.Ownership); ok {
    for _, ownedns := range owns.Ownings() {
        ...
    }
}

In the opposite direction, the owner of a namespace can be directly queried via the model.Namespace interface (again, only for non-user namespaces):

// Get the namespace -owned by-> user namespace relationship.
ownerns := ns.Owner()

When asking a user namespace for its owner, the parent user namespace is returned in accordance with the Linux ioctl()s for discovering the ownership of namespaces.

Namespaces and Processes

The lxkns discovery information model also relates processes to namespaces, and vice versa. After all, processes are probably the main source for discovering namespaces.

For this reason, the discovery results (in “allns” in case of the above discovery code example) not only list the namespaces found, but also a snapshot of the process tree at discovery time (please relax now, as this is a snapshot of the “tree”, not of all the processes themselves).

// Get the init(1) process representation.
initprocess := allns.Processes[model.PIDType(1)]
for _, childprocess := range initprocess.Children() {
    ...
}

Please note that the process tree information is for convenience; it's not a replacement for the famous gopsutil package in many use cases. However, the process tree information show which namespaces are used by (or “joined by”) which particular processes.

// Show all namespaces joined by a specific process, such as init(1).
for nsidx := model.MountNS; nsidx < model.NamespaceTypesCount; nsidx++ {
    println(initprocess.Namespaces[nsidx].String())
}

It's also possible, given a specific namespace, to find the processes joined to this namespace. However, the lxkns information model optimizes this relationship information on the assumption that in many situations not the list of all processes joined to a namespace is needed, but actually only the so-called “leader” process or processes.

A leader process of namespace X is the process topmost in the process tree hierarchy of processes joined to namespace X. It is perfectly valid for a namespace to have more than one leader process joined to it. An example is a container with its own processes joined to the container namespaces, and an additional “visiting” process also joined to one or several namespaces of this container. The lxkns information then is able to correctly handle and represent such system states.

// Show the leader processes joined to the initial user namespace.
for _, leaders := range initprocess.Namespaces[model.UserNS].Leaders() {
    ...
}

Architecture

Please find more details about the lxkns information model in the architectural section of the lxkns manual.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewPIDMap

func NewPIDMap(result *Result) model.PIDMapper

NewPIDMap returns a new PID map (model.PIDMapper) based on the specified discovery results and further information gathered from the /proc filesystem.

func NewlyProcfsPathIsBetter added in v0.23.0

func NewlyProcfsPathIsBetter(newly, known model.NamespaceRef, processes model.ProcessTable) bool

NewlyProcfsPathIsBetter returns true if the passed reference path for a "newly" found namespace reference is "better" than an already known one for that particular namespace. "Better" here is defined as the newly reference belonging to a process process that is older than the process with the already known namespace reference AND all (newly and known) reference elements except for the first one match each other. In all other cases, NewlyProcfsPathIsBetter returns false; in particular, when one of the namespace references isn't from a process.

func PIDfromPath added in v0.23.0

func PIDfromPath(path string) model.PIDType

PIDfromPath returns the PID embedded in a /proc/$PID/... path, or 0 if the path doesn't contain a /proc/$PID.

func SortChildNamespaces

func SortChildNamespaces(nslist []model.Hierarchy) []model.Hierarchy

SortChildNamespaces returns a sorted copy of a list of hierarchical namespaces. The namespaces are sorted by their namespace ids in ascending order. Please note that the list itself is flat, but this function can only be used on hierarchical namespaces (PID, user).

func SortNamespaces

func SortNamespaces(nslist []model.Namespace) []model.Namespace

SortNamespaces returns a sorted copy of a list of namespaces. The namespaces are sorted by their namespace ids in ascending order.

func SortedNamespaces

func SortedNamespaces(nsmap model.NamespaceMap) []model.Namespace

SortedNamespaces returns the namespaces from a map sorted by their namespace identifiers (inode numbers).

Types

type BindmountedNamespaceInfo

type BindmountedNamespaceInfo struct {
	ID        species.NamespaceID
	Type      species.NamespaceType
	Ref       model.NamespaceRef
	OwnernsID species.NamespaceID
}

BindmountedNamespaceInfo describes a bind-mounted namespace in some (other) mount namespace, including the owning user namespace ID, so we can later correctly set up the ownership relations in the discovery results.

type DiscoverOpts

type DiscoverOpts struct {
	// The types of namespaces discovered: this is an OR'ed combination of Linux
	// kernel namespace constants, such as CLONE_NEWNS, CLONE_NEWNET, et cetera.
	// If zero, defaults to discovering all namespaces.
	NamespaceTypes species.NamespaceType `json:"-"`

	ScanProcs            bool              `json:"from-procs"`      // Scan processes for attached namespaces.
	ScanTasks            bool              `json:"from-tasks"`      // Scan all tasks for attached namespaces.
	ScanFds              bool              `json:"from-fds"`        // Scan open file descriptors for namespaces.
	ScanBindmounts       bool              `json:"from-bindmounts"` // Scan bind-mounts for namespaces.
	DiscoverHierarchy    bool              `json:"with-hierarchy"`  // Discover the hierarchy of PID and user namespaces.
	DiscoverOwnership    bool              `json:"with-ownership"`  // Discover the ownership of non-user namespaces.
	DiscoverFreezerState bool              `json:"with-freezer"`    // Discover the cgroup freezer state of processes.
	DiscoverMounts       bool              `json:"with-mounts"`     // Discover mount point hierarchy with mount paths and visibility.
	Labels               map[string]string `json:"labels"`          // Pass options (in form of labels) to decorators

	Containerizer containerizer.Containerizer `json:"-"` // Discover containers using containerizer.
	// contains filtered or unexported fields
}

DiscoverOpts provides information about the extent of a Linux-kernel namespace discovery.

This information is JSON-marshallable, with the exception of the containerizer.Containerizer interface.

type DiscoveryOption

type DiscoveryOption func(*DiscoverOpts)

DiscoveryOption represents a function able to set a particular discovery option state in DiscoverOpts.

func FromBindmounts

func FromBindmounts() DiscoveryOption

FromBindmounts opts to find bind-mounted namespaces.

func FromFds

func FromFds() DiscoveryOption

FromFds opts to find namespaces from the open file descriptors of processes.

func FromProcs

func FromProcs() DiscoveryOption

FromProcs opts to find namespaces attached to processes.

func FromTasks added in v0.26.0

func FromTasks() DiscoveryOption

FromTasks opts to find namespaces attached to tasks (as opposed to processes). FromTask implies FromProcs.

func NotFromBindmounts

func NotFromBindmounts() DiscoveryOption

NotFromBindmounts opts out from searching for bind-mounted namespaces.

func NotFromFds

func NotFromFds() DiscoveryOption

NotFromFds opts out looking at the open file descriptors of processes when searching for namespaces.

func NotFromProcs

func NotFromProcs() DiscoveryOption

NotFromProcs opts out of looking at processes when searching for namespaces.

func NotFromTasks added in v0.26.0

func NotFromTasks() DiscoveryOption

NotFromTasks opts out of looking at tasks when searching for namespaces. This does not include NotFromProcs, so a full opt-out should specify both.

func SameAs

func SameAs(r *Result) DiscoveryOption

SameAs reuses the discovery options used for a previous discovery.

func WithContainerizer

func WithContainerizer(c containerizer.Containerizer) DiscoveryOption

WithContainerizer opts for discovery of containers related to namespaces, using the specified Containerizer. Depending on your system configuration you might want to additionally use WithPIDMapper in order to support containers in containers.

func WithFullDiscovery

func WithFullDiscovery() DiscoveryOption

WithFullDiscovery opts in to all discovery features that lxkns has to offer. Please note that API users still need to set an optional containerizer.Containerizer explicitly using WithContainerizer.

func WithHierarchy

func WithHierarchy() DiscoveryOption

WithHierarchy opts to query the namespace hierarchy of PID and user namespaces.

func WithLabel

func WithLabel(key, value string) DiscoveryOption

WithLabel adds a key-value pair to the discovery options.

func WithLabels

func WithLabels(labels map[string]string) DiscoveryOption

WithLabels adds a map of key-value pair to the discovery options.

func WithMounts

func WithMounts() DiscoveryOption

WithMounts opts to find mount points and determine their visibility.

func WithNamespaceTypes

func WithNamespaceTypes(t species.NamespaceType) DiscoveryOption

WithNamespaceTypes sets the types of namespaces to discover, where multiple types need to be OR'ed together. Setting 0 will discover all available types.

func WithOwnership

func WithOwnership() DiscoveryOption

WithOwnership opts to find the ownership relations between user namespaces and all other namespaces.

func WithPIDMapper

func WithPIDMapper() DiscoveryOption

WithPIDMapper opts in to discover the PID mapping between PID namespaces. In order to correctly map container PIDs of containers inside another container, API users need to enable this option. It defaults to off due to the additional system load it causes when scanning all processes for their PID namespace-related information and building the full translation map.

func WithStandardDiscovery

func WithStandardDiscovery() DiscoveryOption

WithStandardDiscovery opts for a "standard" discovery, scanning not only processes, but also open file descriptors and bind-mounts, as well as the namespace hierarchy and ownership, and freezer states. All types of namespaces will be discovered. Please note that time namespaces can only be discovered on newer kernels with support for them.

Tasks will not be scanned, except for the task group leader that represents the process.

Please note that mount point discovery (including visibility calculation) is not automatically opted in; it has to be opted in individually.

func WithoutHierarchy

func WithoutHierarchy() DiscoveryOption

WithoutHierarchy opts out of querying the namespace hierarchy of PID and user namespaces.

func WithoutMounts

func WithoutMounts() DiscoveryOption

WithoutMounts opts out of finding mount points and determining their visibility.

func WithoutOwnership

func WithoutOwnership() DiscoveryOption

WithoutOwnership opts out of looking for the ownership relations between user namespaces and all other namespaces.

type NamespacedMountPathMap

type NamespacedMountPathMap map[species.NamespaceID]mounts.MountPathMap

NamespacedMountPathMap maps mount namespaces identified by their namespace ID to their corresponding mount path maps.

type Result

type Result struct {
	Options           DiscoverOpts           // options used during discovery.
	Namespaces        model.AllNamespaces    // all discovered namespaces, subject to filtering according to Options.
	InitialNamespaces model.NamespacesSet    // the 7 initial namespaces.
	UserNSRoots       []model.Namespace      // the topmost user namespace(s) in the hierarchy.
	PIDNSRoots        []model.Namespace      // the topmost PID namespace(s) in the hierarchy.
	Processes         model.ProcessTable     // processes checked for namespaces.
	PIDMap            model.PIDMapper        `json:"-"` // optional PID translator.
	Mounts            NamespacedMountPathMap // per mount-namespace mount paths and mount points.
	Containers        model.Containers       // all alive containers found
}

Result stores the results of a tour through Linux processes and kernel namespaces.

func Namespaces

func Namespaces(options ...DiscoveryOption) *Result

Namespaces returns the Linux kernel namespaces found, based on discovery options specified in the call. It is allowed to pass nil discovery options to allow more concise code without the need for lots of “if”s. The discovery results also specify the initial namespaces, as well the process table/tree on which the discovery bases at least in part.

func (*Result) SortedNamespaces

func (dr *Result) SortedNamespaces(nsidx model.NamespaceTypeIndex) []model.Namespace

SortedNamespaces returns a sorted list of discovered namespaces of the specified type. The namespaces are sorted by their identifier, which is an inode number (on the special "nsfs" filesystem), ignoring a namespace's device ID.

type UidUsernameMap

type UidUsernameMap map[uint32]string

UidUsernameMap maps user identifiers (uids) to their corresponding user names, if any.

func DiscoverUserNames

func DiscoverUserNames(namespaces model.AllNamespaces) UidUsernameMap

DiscoverUserNames returns the mapping from user identifiers (uids, found as owners of user namespaces) to their corresponding user names, if any. The namespaces information is required so that the information can be discovered from the initial mount namespace of the host.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL