Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial work on limiting what names an agent can register by the seliux context. #1023

Merged
merged 15 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ Checks: >
-readability-else-after-return,
-misc-include-cleaner,
-bugprone-multi-level-implicit-pointer-conversion,
-cppcoreguidelines-macro-to-enum
-cppcoreguidelines-macro-to-enum,
-cert-int09-c,
-readability-enum-initial-value,
-readability-math-missing-parentheses

CheckOptions:
- key: readability-function-cognitive-complexity.Threshold
Expand Down
2 changes: 1 addition & 1 deletion .copr/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.PHONY: installdeps srpm

installdeps:
dnf -y install gcc gcc-c++ git jq meson systemd-devel rpm-build
dnf -y install gcc gcc-c++ git jq libselinux-devel meson systemd-devel rpm-build

srpm: installdeps
git config --global --add safe.directory "$(shell pwd)"
Expand Down
1 change: 1 addition & 0 deletions .packit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ srpm_build_deps:
- gcc-c++
- git
- jq
- libselinux-devel
- meson
- systemd-devel
- rpm-build
Expand Down
1 change: 1 addition & 0 deletions bluechi.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ BuildRequires: gcc
# Meson needs to detect C++, because part of inih library (which we don't use) provides C++ functionality
BuildRequires: gcc-c++
BuildRequires: meson
BuildRequires: libselinux-devel
BuildRequires: systemd-devel
BuildRequires: systemd-rpm-macros
BuildRequires: golang-github-cpuguy83-md2man
Expand Down
64 changes: 45 additions & 19 deletions doc/man/bluechi-controller.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ The basic file definition used to bootstrap bluechi-controller.
The bluechi-controller configuration file is using the
[systemd configuration file format](https://www.freedesktop.org/software/systemd/man/systemd.syntax.html). Contrary to this, there is no need for the `\` symbol at the end of a line to continue a value on the next line. A value continued on multiple lines will just be concatenated by bluechi. The maximum line length supported is 500 characters. If the value exceeds this limitation, use multiple, indented lines.

### **bluechi-controller** section
Global options for the bluechi controller are contained in the
**[bluechi-controller]** section. In addition, sections named **[node
NODENAME]** allow setting options that are specific to the a
particular node.

All fields to bootstrap the bluechi controller are contained in the **bluechi-controller** section. The following keys are understood by `bluechi-controller`.
## Global options

#### **UseTCP** (string)
### **UseTCP** (string)

If this flag is set to `true`, it enables the connection handler for agents to connect
remotely via TCP/IP to the controller. If disabled, all other TCP options such as the
Expand All @@ -26,33 +29,35 @@ The TCP handler can run alongside the handler for local connections (Unix Domain
as well as the systemd socket activated handler.
Default: true.

#### **UseUDS** (string)
### **UseUDS** (string)

If this flag is set to `true`, it enables the connection handler for agents to connect
locally via Unix Domain Socket (UDS) to the controller.
The UDS handler can run alongside the handler for remote connections (TCP/IP) as well as
the systemd socket activated handler.
Default: true.

#### **ControllerPort** (uint16_t)
### **ControllerPort** (uint16_t)

The port the bluechi-controller listens on to establish connections with the `bluechi-agent`. By default port `842` is used.

#### **AllowedNodeNames** (string)
### **AllowedNodeNames** (string)

A comma separated list of unique bluechi-agent names. It's mandatory to set the option, only nodes with names mentioned
in the list can connect to `bluechi-controller`. These names are defined in the agent's configuration file under `NodeName`
option (see `bluechi-agent.conf(5)`).
A comma separated list of unique bluechi-agent names. Only nodes with
names mentioned in the list, or nodes with **Allowed=true** in a
separate node section can connect to `bluechi-controller`.

#### **HeartbeatInterval** (long)
These names are defined in the agent's configuration file under `NodeName` option (see `bluechi-agent.conf(5)`).

### **HeartbeatInterval** (long)

The interval to periodically check node's connectivity based on heartbeat signals sent to bluechi, in milliseconds. A value of 0 disables it.

#### **NodeHeartbeatThreshold** (long)
### **NodeHeartbeatThreshold** (long)

The threshold in milliseconds to determine whether a node is disconnected. If the node's last heartbeat signal was received before this threshold, bluechi assumes that the node is down or the connection was cut off and performs a disconnect.

#### **LogLevel** (string)
### **LogLevel** (string)

The level used for logging. Supported values are:

Expand All @@ -63,7 +68,7 @@ The level used for logging. Supported values are:

By default `INFO` level is used for logging.

#### **LogTarget** (string)
### **LogTarget** (string)

The target where logs are written to. Supported values are:

Expand All @@ -73,11 +78,11 @@ The target where logs are written to. Supported values are:

By default `journald` is used as the target.

#### **LogIsQuiet** (string)
### **LogIsQuiet** (string)

If this flag is set to `true`, no logs are written by bluechi. By default the flag is set to `false`.

#### **IPReceiveErrors** (string)
### **IPReceiveErrors** (string)

If this flag is set to `true`, it enables extended, reliable error message passing for
the peer connection with all agents. This results in BlueChi receiving errors such as
Expand All @@ -86,25 +91,40 @@ useful to detect disconnects faster, but should be used with care as this might
unnecessary disconnects in less robut networks.
Default: true.

#### **TCPKeepAliveTime** (long)
### **TCPKeepAliveTime** (long)

The number of seconds the individual TCP connection with an agent needs to be idle
before keepalive packets are sent. When `TCPKeepAliveTime` is set to 0, the system
default will be used.
Default: 1s.

#### **TCPKeepAliveInterval** (long)
### **TCPKeepAliveInterval** (long)

The number of seconds between each keepalive packet. When `TCPKeepAliveInterval` is set to 0,
the system default will be used.
Default: 1s.

#### **TCPKeepAliveCount** (long)
### **TCPKeepAliveCount** (long)

The number of keepalive packets without ACK from an agent till the individual connection is dropped.
When `TCPKeepAliveCount` is set to 0, the system default will be used.
Default: 6.

## Per-node options

### **Allowed** (boolean)

If true, the node with the section name is allowed to connect. See **AllowedNodeNames** above.

### **RequiredSelinuxContext** (string)

If set, the connecting node process must have the specified SELinux
type label. Typically for local connections these would be
bluechi_agent_t or haproxy_t.

Note: this option only works when the agent connects using unix domain
sockets.

## Example

A basic example of a configuration file for `bluechi`:
Expand All @@ -116,9 +136,13 @@ AllowedNodeNames=agent-007,agent-123
LogLevel=DEBUG
LogTarget=journald
LogIsQuiet=false

[node agent-009]
Allowed=true
RequiredSelinuxContext=haproxy_t
```

Using a value that is continued on multiple lines:
Example using a value that is continued on multiple lines:

```
[bluechi-controller]
Expand All @@ -137,6 +161,8 @@ LogIsQuiet=false
Distributions provide the **/usr/share/bluechi/config/controller.conf** file which defines bluechi configuration defaults. Administrators can copy this file to **/etc/bluechi/controller.conf** and specify their own configuration.

Administrators can also use a "drop-in" directory **/etc/bluechi/controller.conf.d** to drop their configuration changes.
Such drop-ins are especially useful for per-node configuration as they can extend the list of available nodes without
modifying the base configuration file.

## SEE ALSO

Expand Down
8 changes: 7 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ conf = configuration_data()
# Enable GNU extensions.
conf.set('_GNU_SOURCE', true)

with_selinux = get_option('with_selinux')
if with_selinux
selinux_dep = dependency('libselinux', required: true)
conf.set('CONFIG_H_USE_SELINUX', true)
endif

prefixdir = get_option('prefix')

conf.set_quoted('CONFIG_H_UDS_SOCKET_PATH', get_option('unix_domain_socket_path'))
Expand Down Expand Up @@ -99,7 +105,7 @@ subdir('src/libbluechi')
subdir('config')

# selinux
if get_option('with_selinux')
if with_selinux
subdir('selinux')
endif

Expand Down
28 changes: 28 additions & 0 deletions src/controller/controller.c
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,34 @@ bool controller_apply_config(Controller *controller) {
}
}

_cleanup_freev_ char **sections = cfg_list_sections(controller->config);
if (sections == NULL) {
bc_log_error("Failed to list config sections");
return false;
}
for (size_t i = 0; sections[i] != NULL; i++) {
const char *section = sections[i];
if (!str_has_prefix(section, CFG_SECT_NODE_PREFIX)) {
continue;
}
const char *node_name = section + strlen(CFG_SECT_NODE_PREFIX);
Node *node = controller_find_node(controller, node_name);
if (node == NULL) {
/* Add it to the list if it is marked allowed */
bool allowed = cfg_s_get_bool_value(controller->config, section, CFG_ALLOWED);
if (!allowed) {
continue;
}
node = controller_add_node(controller, node_name);
}

const char *selinux_context = cfg_s_get_value(
controller->config, section, CFG_REQUIRED_SELINUX_CONTEXT);
if (selinux_context && !node_set_required_selinux_context(node, selinux_context)) {
return false;
}
}

const char *interval_msec = cfg_get_value(controller->config, CFG_HEARTBEAT_INTERVAL);
if (interval_msec) {
if (!controller_set_heartbeat_interval(controller, interval_msec)) {
Expand Down
2 changes: 1 addition & 1 deletion src/controller/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ static void usage(char *argv[]) {
"Available options are:\n"
"\t-%c %s\t\t Print this help message.\n"
"\t-%c %s\t\t The port of bluechi to connect to.\n"
"\t-%c %s\t A path to a config file used to bootstrap bluechi-agent.\n"
"\t-%c %s\t A path to a config file used to bootstrap bluechi-controller.\n"
"\t-%c %s\t Print current bluechi version.\n",
argv[0],
ARG_HELP_SHORT,
Expand Down
16 changes: 11 additions & 5 deletions src/controller/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@ ctrl_src = [
'main.c',
]

executable(
'bluechi-controller',
ctrl_src,
dependencies: [
controller_deps = [
systemd_dep,
inih_dep,
hashmapc_dep,
],
]

if with_selinux
controller_deps += [ selinux_dep, ]
endif

executable(
'bluechi-controller',
ctrl_src,
dependencies: controller_deps,
link_with: [
bluechi_lib,
],
Expand Down
37 changes: 37 additions & 0 deletions src/controller/node.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
*/
#include <systemd/sd-bus.h>

#ifdef CONFIG_H_USE_SELINUX
# include <selinux/selinux.h>
#endif

#include "libbluechi/bus/bus.h"
#include "libbluechi/bus/utils.h"
#include "libbluechi/common/parse-util.h"
Expand Down Expand Up @@ -186,6 +190,7 @@ Node *node_new(Controller *controller, const char *name) {
}
}
node->peer_ip = NULL;
node->peer_selinux_context = NULL;

node->is_shutdown = false;

Expand Down Expand Up @@ -225,6 +230,7 @@ void node_unref(Node *node) {
free_and_null(node->name);
free_and_null(node->object_path);
free_and_null(node->peer_ip);
free_and_null(node->required_selinux_context);
free(node);
}

Expand All @@ -237,6 +243,14 @@ void node_shutdown(Node *node) {
}
}

bool node_set_required_selinux_context(Node *node, const char *selinux_context) {
node->required_selinux_context = strdup(selinux_context);
if (node->required_selinux_context == NULL) {
return false;
}
return true;
}

bool node_export(Node *node) {
Controller *controller = node->controller;

Expand Down Expand Up @@ -674,6 +688,17 @@ bool node_set_agent_bus(Node *node, sd_bus *bus) {
node->peer_ip = steal_pointer(&peer_ip);
}

#ifdef CONFIG_H_USE_SELINUX
char *peercon = NULL;
if (getpeercon(sd_bus_get_fd(bus), &peercon) == 0) {
node->peer_selinux_context = parse_selinux_type(peercon);
freecon(peercon);
if (node->peer_selinux_context == NULL) {
bc_log_errorf("Failed to parse peer selinux type '%s'", peercon);
}
}
#endif

if (node->name == NULL) {
// We only connect to this on the unnamed nodes so register
// can be called. We can't reconnect it during migration.
Expand Down Expand Up @@ -869,6 +894,7 @@ void node_unset_agent_bus(Node *node) {
sd_bus_unrefp(&node->agent_bus);
node->agent_bus = NULL;

free_and_null(node->peer_selinux_context);
free_and_null(node->peer_ip);

if (was_online) {
Expand Down Expand Up @@ -904,6 +930,17 @@ static int node_method_register(sd_bus_message *m, void *userdata, UNUSED sd_bus
return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_SERVICE_UNKNOWN, "Unexpected node name");
}

if (named_node->required_selinux_context &&
(node->peer_selinux_context == NULL ||
!streq(node->peer_selinux_context, named_node->required_selinux_context))) {
bc_log_errorf("Node tried to register as '%s' with wrong selinux context '%s', expected '%s'",
name,
node->peer_selinux_context ? node->peer_selinux_context : "(missing)",
named_node->required_selinux_context);

return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_ACCESS_DENIED, "Node name not allowed");
}

if (node_has_agent(named_node)) {
return sd_bus_reply_method_errorf(
m, SD_BUS_ERROR_ADDRESS_IN_USE, "The node is already connected");
Expand Down
3 changes: 3 additions & 0 deletions src/controller/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ struct Node {
LIST_FIELDS(Node, nodes);

char *name; /* NULL for not yet registered nodes */
char *required_selinux_context;
char *object_path;
char *peer_ip;
char *peer_selinux_context;

LIST_HEAD(AgentRequest, outstanding_requests);
LIST_HEAD(ProxyMonitor, proxy_monitors);
Expand All @@ -79,6 +81,7 @@ bool node_has_agent(Node *node);
bool node_is_online(Node *node);
bool node_set_agent_bus(Node *node, sd_bus *bus);
void node_unset_agent_bus(Node *node);
bool node_set_required_selinux_context(Node *node, const char *selinux_context);

AgentRequest *node_request_list_units(
Node *node, agent_request_response_t cb, void *userdata, free_func_t free_userdata);
Expand Down
Loading
Loading