Kea 3.3.4
rbac_callouts.cc
Go to the documentation of this file.
1// Copyright (C) 2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
11#include <hooks/hooks.h>
13#include <process/daemon.h>
14
15#include <rbac/rbac.h>
16#include <rbac/rbac_config.h>
17#include <rbac_log.h>
18
19using namespace isc;
20using namespace isc::config;
21using namespace isc::data;
22using namespace isc::hooks;
23using namespace isc::http;
24using namespace isc::log;
25using namespace isc::process;
26using namespace isc::rbac;
27using namespace std;
28
29// Functions accessed by the hooks framework use C linkage to avoid the name
30// mangling that accompanies use of the C++ compiler as well as to avoid
31// issues related to namespaces.
32extern "C" {
33
38int
40 try {
41 // Make the hook library loadable by DHCP servers.
42 const string& proc_name = Daemon::getProcName();
43 if ((proc_name != "kea-dhcp4") && (proc_name != "kea-dhcp6") &&
44 (proc_name != "kea-dhcp-ddns")) {
45 isc_throw(Unexpected, "Bad process name: " << proc_name
46 << ", expected kea-dhcp4, kea-dhcp6 or kea-dhcp-ddns");
47 }
48
49 // Load the configuration.
51 rbacConfig.init();
53 } catch (const std::exception& ex) {
55 .arg(ex.what());
56 return (1);
57 }
58
60 return (0);
61}
62
66int
68 if (roleAssign) {
69 roleAssign->setup(false);
70 }
71 roleAssign.reset();
72 roleConfigTable.clear();
73 defaultRoleConfig.reset();
74 unknownRoleConfig.reset();
75 aclTable.clear();
76 apiTable.clear();
77 apiAccesses.clear();
78 apiHooks.clear();
79 responseFilterTable.clear();
80
82 return (0);
83}
84
89 return (1);
90}
91
96int
98 auto failwith = [](string msg) {
100 .arg(msg);
101 };
102 if (!roleAssign) {
105 failwith("disabled: no role assigned");
106 return (0);
107 }
108
109 // Get the parameters.
110 HttpRequestPtr request;
111 HttpResponseJsonPtr response;
112 handle.getArgument("request", request);
113 handle.getArgument("response", response);
114
115 if (response) {
118 .arg(response->toBriefString());
119 failwith("response already exists");
120 return (0);
121 }
122
123 auto status_code = HttpStatusCode::FORBIDDEN;
124 bool updated = false;
125 try {
126 // Most errors will be caught later.
127 if (!request) {
130 failwith("no request");
131 return (0);
132 }
133 // Check TLS.
134 if (rbacConfig.getRequireTls() && !Role::requireTls(request)) {
137 failwith("TLS required but not used");
138 status_code = HttpStatusCode::UNAUTHORIZED;
139 response = RoleConfig::createReject(request, status_code);
140 updated = true;
141 isc_throw(Unexpected, "local exit");
142 }
143 PostHttpRequestJsonPtr request_json =
144 boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
145 if (!request_json) {
146 failwith("request is not a JSON one");
149 return (0);
150 }
151 ConstElementPtr body = request_json->getBodyAsJson();
152 if (!body) {
153 failwith("request body is empty");
156 return (0);
157 }
158 if (body->getType() != Element::map) {
161 failwith("request body is not a map");
162 return (0);
163 }
164 ConstElementPtr elem = body->get(CONTROL_COMMAND);
165 if (!elem) {
168 failwith("no command found in request");
169 return (0);
170 }
171 if (elem->getType() != Element::string) {
174 failwith("request command is not a string");
175 return (0);
176 }
177 string command = elem->stringValue();
180 .arg(command);
181
182 // Assign role.
183 const string& role = roleAssign->assign(request);
185 .arg(role);
186 RoleConfigPtr role_config = RoleConfig::getConfig(role);
187 if (!role_config) {
188 isc_throw(Unexpected, "can't find a role configuration");
189 }
190
191 // Filter command.
192 if (role_config->match(command)) {
195 .arg(role_config->name_)
196 .arg(role)
197 .arg(command);
198 if ((command == "config-set") || (command == "config-reload")) {
199 // Ask the handle to be reset so the hook can be unloaded
200 // without keeping a pointer to its address space...
202 } else if (!role_config->response_filters_.empty()) {
203 // Save values in the context for the response callout.
204 handle.setContext("command", command);
205 handle.setContext("role_config", role_config);
206 }
207 } else {
209 .arg(role_config->name_)
210 .arg(role)
211 .arg(command);
212 response = RoleConfig::createReject(request, status_code);
213 updated = true;
214 }
215 } catch (const std::exception& ex) {
216 // Not updated means build an error message.
217 if (!updated) {
219 .arg(ex.what());
221 response = RoleConfig::createReject(request, status_code);
222 updated = true;
223 }
224 }
225
226 // Set parameters.
227 if (updated) {
229 .arg(response->toBriefString());
230 handle.setArgument("response", response);
231 }
232
233 return (0);
234}
235
240int
242 auto failwith = [](string msg) {
244 .arg(msg);
245 };
246 if (!roleAssign) {
249 failwith("disabled: no role assigned");
250 return (0);
251 }
252
253 // Get the parameters.
254 HttpRequestPtr request;
255 HttpResponseJsonPtr response;
256 handle.getArgument("request", request);
257 handle.getArgument("response", response);
258
259 if (!request || !response) {
262 if (!request && !response) {
263 failwith("no request nor response");
264 } else if (!request) {
265 failwith("no request");
266 } else {
267 failwith("no response");
268 }
269 return (0);
270 }
271
272 // Decode response.
273 ConstElementPtr body = response->getBodyAsJson();
274 if (!body) {
277 failwith("response body is empty");
278 return (0);
279 }
280 if (body->getType() != Element::list) {
281 // Should be an error response, anyway nothing to filter.
284 failwith("response body is not a list");
285 return (0);
286 }
287 if (body->size() < 1) {
290 failwith("response body is an empty list");
291 return (0);
292 }
293
294 // Recover command and role config;
295 string command;
296 RoleConfigPtr role_config;
297 handle.getOptionalContext("command", command);
298 handle.getOptionalContext("role_config", role_config);
299 if (!role_config || (role_config->response_filters_.empty())) {
300 return (0);
301 }
302
305 .arg(command)
306 .arg(role_config->name_);
307
308 // Apply filters on each list item.
309 bool modified = false;
310 for (auto const& answer : body->listValue()) {
311 if (role_config->filter(command, answer)) {
312 modified = true;
313 }
314 }
315 if (modified) {
318 response->setBodyAsJson(body);
319 response->resetContentLength();
320 }
321
322 return (0);
323}
324
325}
@ NEXT_STEP_SKIP
skip the next processing step
@ map
Definition data.h:160
@ list
Definition data.h:159
@ string
Definition data.h:157
A generic exception that is thrown when an unexpected error condition occurs.
Per-packet callout handle.
void setContext(const std::string &name, T value)
Set context.
void setStatus(const CalloutNextStep next)
Sets the next processing step.
void getArgument(const std::string &name, T &value) const
Get argument.
void setArgument(const std::string &name, T value)
Set argument.
bool getOptionalContext(const std::string &name, T &value) const
Fetch an optional named element from the current library context.
isc::data::ConstElementPtr getParameters()
Get configuration parameter common code.
static std::string getProcName()
returns the process name This value is used as when forming the default PID file name
Definition daemon.cc:156
static void parse(data::ConstElementPtr cfg)
Parse the configuration.
static http::HttpResponseJsonPtr createReject(const http::HttpRequestPtr &request, const http::HttpStatusCode &status_code)
Create a reject response.
Definition rbac.cc:129
static RoleConfigPtr getConfig(const std::string &role)
Return the role config.
Definition rbac.cc:41
static bool requireTls(const http::HttpRequestPtr &request)
Require TLS.
Definition rbac_role.h:61
This file contains several functions and constants that are used for handling commands and responses ...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
const char * CONTROL_COMMAND
String used for commands ("command").
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
boost::shared_ptr< PostHttpRequestJson > PostHttpRequestJsonPtr
Pointer to PostHttpRequestJson.
boost::shared_ptr< HttpResponseJson > HttpResponseJsonPtr
Pointer to the HttpResponseJson object.
boost::shared_ptr< HttpRequest > HttpRequestPtr
Pointer to the HttpRequest object.
Definition request.h:30
const int DBGLVL_TRACE_BASIC
Trace basic operations.
boost::shared_ptr< RoleConfig > RoleConfigPtr
Type of shared pointers to role configurations.
Definition rbac.h:23
RolePtr roleAssign
The role assignment.
Definition rbac.cc:22
isc::log::Logger rbac_logger("rbac-hooks")
Definition rbac_log.h:18
set< string > apiHooks
The API hook list.
Definition rbac_api.cc:32
ResponseFilterTable responseFilterTable
The response filter table.
ApiTable apiTable
The API command table.
Definition rbac_api.cc:28
set< string > apiAccesses
The API access list (should be "read" and "write").
Definition rbac_api.cc:30
RoleConfigPtr defaultRoleConfig
The default (role is "") role configuration.
Definition rbac.cc:26
RoleConfigPtr unknownRoleConfig
The unknown (role is not in the table) role configuration.
Definition rbac.cc:28
AclTable aclTable
The access control list table.
Definition rbac_acl.cc:23
Config rbacConfig
The configuration.
RoleConfigTable roleConfigTable
The role configurations table.
Definition rbac.cc:24
Defines the logger used by the top-level component of kea-lfc.
int http_response(CalloutHandle &handle)
This callout is called at the "http_response" hook.
int multi_threading_compatible()
This function is called to retrieve the multi-threading compatibility.
int unload()
This function is called when the library is unloaded.
int http_auth(CalloutHandle &handle)
This callout is called at the "http_auth" callout.
int load(LibraryHandle &handle)
This function is called when the library is loaded.
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_RESPONSE
const isc::log::MessageID RBAC_LOAD_FAILED
const isc::log::MessageID RBAC_HTTP_AUTH_REJECT
const isc::log::MessageID RBAC_HTTP_AUTH_FAILED
const isc::log::MessageID RBAC_UNLOAD_OK
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_BAD_COMMAND_TYPE
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_NO_TLS_REJECT
const isc::log::MessageID RBAC_TRACE_HTTP_RESPONSE_EMPTY_BODY
const isc::log::MessageID RBAC_HTTP_AUTH_RESPONSE
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_NO_COMMAND
const isc::log::MessageID RBAC_TRACE_HTTP_RESPONSE_CONTEXT
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_DISABLED
const isc::log::MessageID RBAC_TRACE_HTTP_RESPONSE_BAD_BODY_TYPE
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_ACCEPT
const isc::log::MessageID RBAC_LOAD_OK
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_NO_JSON
const isc::log::MessageID RBAC_HTTP_AUTH_ERROR
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_EMPTY_BODY
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_ROLE
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_COMMAND
const isc::log::MessageID RBAC_TRACE_HTTP_RESPONSE_DISABLED
const isc::log::MessageID RBAC_TRACE_HTTP_RESPONSE_EMPTY_BODY_LIST
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_BAD_BODY_TYPE
const isc::log::MessageID RBAC_TRACE_HTTP_RESPONSE_NO_ARGUMENTS
const isc::log::MessageID RBAC_TRACE_HTTP_AUTH_NO_REQUEST
const isc::log::MessageID RBAC_TRACE_HTTP_RESPONSE_MODIFIED
const isc::log::MessageID RBAC_HTTP_RESPONSE_FAILED