You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by dm...@apache.org on 2022/06/21 18:24:52 UTC
[trafficserver] branch 10-Dev updated: JSON-RPC: Add support for handler to pass information about the specifics when registering. (#8865)
This is an automated email from the ASF dual-hosted git repository.
dmeden pushed a commit to branch 10-Dev
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/10-Dev by this push:
new ddcb091b8 JSON-RPC: Add support for handler to pass information about the specifics when registering. (#8865)
ddcb091b8 is described below
commit ddcb091b8c1eed26a175e102470a6ffa2c6db9f7
Author: Damian Meden <da...@gmail.com>
AuthorDate: Tue Jun 21 19:24:46 2022 +0100
JSON-RPC: Add support for handler to pass information about the specifics when registering. (#8865)
* This change adds support for passing Handler Options when registering a new JSON-RPC endpoint handler.
To support this a new class Context is introduced as well as a Handler option class, this changes sets the base for
future work around the handler registration and the Plugin API.
* This commit adds documentation to support the JSON-RPC handler registration. It also fixes some bad links and typos.
* Add late check for peers credentials on client's socket. RPC handler can now decide if they want to be administrative restricted or not.
Add documentation support for this.
* Address review suggestions
Co-authored-by: Damian Meden <dm...@apache.org>
---
doc/admin-guide/files/jsonrpc.yaml.en.rst | 45 ++++---
.../command-line/traffic_ctl_jsonrpc.en.rst | 2 +-
doc/developer-guide/jsonrpc/HandlerError.en.rst | 2 +-
doc/developer-guide/jsonrpc/index.en.rst | 1 +
doc/developer-guide/jsonrpc/jsonrpc-api.en.rst | 19 ++-
.../jsonrpc/jsonrpc-architecture.en.rst | 60 +--------
.../jsonrpc/jsonrpc-handler-development.en.rst | 7 +-
.../jsonrpc/jsonrpc-node-errors.en.rst | 143 +++++++++++++++++++++
doc/developer-guide/jsonrpc/jsonrpc-node.en.rst | 7 +-
.../jsonrpc/traffic_ctl-development.en.rst | 7 +-
include/ts/apidefs.h.in | 12 ++
mgmt2/config/FileManager.cc | 11 +-
mgmt2/rpc/Makefile.am | 4 +-
mgmt2/rpc/jsonrpc/Context.cc | 35 +++++
mgmt2/rpc/jsonrpc/Context.h | 77 +++++++++++
mgmt2/rpc/jsonrpc/Defs.h | 25 ++--
mgmt2/rpc/jsonrpc/JsonRPC.h | 10 +-
mgmt2/rpc/jsonrpc/JsonRPCManager.cc | 111 ++++++++--------
mgmt2/rpc/jsonrpc/JsonRPCManager.h | 56 +++++---
mgmt2/rpc/jsonrpc/error/RPCError.cc | 2 +
mgmt2/rpc/jsonrpc/error/RPCError.h | 5 +-
mgmt2/rpc/jsonrpc/json/YAMLCodec.h | 50 ++-----
.../rpc/jsonrpc/unit_tests/test_basic_protocol.cc | 14 +-
mgmt2/rpc/server/IPCSocketServer.cc | 42 +++++-
mgmt2/rpc/server/IPCSocketServer.h | 6 +
mgmt2/rpc/server/unit_tests/test_rpcserver.cc | 7 +
src/traffic_server/HostStatus.cc | 6 +-
src/traffic_server/RpcAdminPubHandlers.cc | 35 +++--
28 files changed, 550 insertions(+), 251 deletions(-)
diff --git a/doc/admin-guide/files/jsonrpc.yaml.en.rst b/doc/admin-guide/files/jsonrpc.yaml.en.rst
index 4c9970988..86fb8f368 100644
--- a/doc/admin-guide/files/jsonrpc.yaml.en.rst
+++ b/doc/admin-guide/files/jsonrpc.yaml.en.rst
@@ -1,3 +1,4 @@
+
.. Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
@@ -56,7 +57,7 @@ In order for programs to communicate with |TS|, the server exposes a
will find some of the configurable arguments that can be changed.
-.. _admnin-jsonrpc-configuration:
+.. _admin-jsonrpc-configuration:
Configuration
=============
@@ -68,9 +69,9 @@ Configuration
If a non-default configuration is needed, the following describes the configuration
structure.
-File `jsonrpc.yaml` is a YAML format. The default configuration is:
+File `jsonrpc.yaml` is a YAML format. The default configuration looks like:
-.. code:: yaml
+.. code-block:: yaml
rpc:
enabled: true
@@ -90,26 +91,34 @@ Field Name Description
IPC Socket (``unix``)
---------------------
-===================================== ==========================================
+Unix Domain Socket related configuration.
+
+===================================== =========================================================================================
Field Name Description
-===================================== ==========================================
-``lock_path_name`` Lock path, including the file name.
- (changing this may have impacts in
- :program:`traffic_ctl`)
-``sock_path_name`` Sock path, including the file name. This
- will be used as ``sockaddr_un.sun_path``
- (changing this may have impacts in :program:`traffic_ctl`)
+===================================== =========================================================================================
+``lock_path_name`` Lock path, including the file name. (changing this may have impacts in :program:`traffic_ctl`)
+``sock_path_name`` Sock path, including the file name. This will be used as ``sockaddr_un.sun_path``. (changing
+ this may have impacts in :program:`traffic_ctl`)
``backlog`` Check https://man7.org/linux/man-pages/man2/listen.2.html
-``max_retry_on_transient_errors`` Number of times the implementation is
- allowed to retry when a transient error is
- encountered.
-``restricted_api`` Used to set rpc unix socket permissions.
- If restricted `0700` will be set, otherwise
- `0777`. ``true`` by default.
-===================================== ==========================================
+``max_retry_on_transient_errors`` Number of times the implementation is allowed to retry when a transient error is encountered.
+``restricted_api`` This setting specifies whether the jsonrpc node access should be restricted to root processes.
+ If this is set to ``false``, then on platforms that support passing process credentials, non-root
+ processes will be allowed to make read-only JSONRPC calls. Any calls that modify server state
+ (eg. setting a configuration variable) will still be restricted to root processes. If set to ``true``
+ then only root processes will be allowed to perform any api call.
+ If restricted, the Unix Domain Socket will be created with `0700` permissions, otherwise `0777`.
+ ``true`` by default.
+ In case of an unauthorized call is made, a corresponding rpc error will be returned, you can
+ check :ref:`jsonrpc-node-errors-unauthorized-action` for details about the errors.
+===================================== =========================================================================================
.. note::
Currently, there is only 1 communication mechanism supported. Unix Domain Sockets
+
+See also
+========
+
+:ref:`traffic_ctl_jsonrpc`
diff --git a/doc/appendices/command-line/traffic_ctl_jsonrpc.en.rst b/doc/appendices/command-line/traffic_ctl_jsonrpc.en.rst
index acfe4f267..94f0e5667 100644
--- a/doc/appendices/command-line/traffic_ctl_jsonrpc.en.rst
+++ b/doc/appendices/command-line/traffic_ctl_jsonrpc.en.rst
@@ -604,5 +604,5 @@ See also
:manpage:`records.config(5)`,
:manpage:`storage.config(5)`,
-:ref:`admnin-jsonrpc-configuration`,
+:ref:`admin-jsonrpc-configuration`,
:ref:`jsonrpc-protocol`
diff --git a/doc/developer-guide/jsonrpc/HandlerError.en.rst b/doc/developer-guide/jsonrpc/HandlerError.en.rst
index 03f824b9d..cf78d0e91 100644
--- a/doc/developer-guide/jsonrpc/HandlerError.en.rst
+++ b/doc/developer-guide/jsonrpc/HandlerError.en.rst
@@ -26,7 +26,7 @@ API Handler error codes
***********************
High level handler error codes, each particular handler can be fit into one of the following categories.
-A good approach could be the following. This required coordination among all the errors, just for now, this soluction seems ok.
+A good approach could be the following. This required coordination among all the errors, just for now, this solution seems ok.
.. code-block:: cpp
diff --git a/doc/developer-guide/jsonrpc/index.en.rst b/doc/developer-guide/jsonrpc/index.en.rst
index 3706257fd..7d06cc713 100644
--- a/doc/developer-guide/jsonrpc/index.en.rst
+++ b/doc/developer-guide/jsonrpc/index.en.rst
@@ -28,6 +28,7 @@ JSONRPC
jsonrpc-architecture.en
jsonrpc-api.en
jsonrpc-node.en
+ jsonrpc-node-errors.en
jsonrpc-handler-development.en
jsonrpc-client-api.en
traffic_ctl-development.en
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
index 3391909fc..75179cfb8 100644
--- a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
+++ b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
@@ -960,7 +960,7 @@ Request:
Response:
-The response will contain the default `success_response` or an :cpp:class:`RPCErrorCode`.
+The response will contain the default `success_response` or a proper rpc error, check :ref:`jsonrpc-node-errors` for mode details.
Validation:
@@ -1070,7 +1070,7 @@ Parameters
Result
~~~~~~
-This api will only inform for errors during the metric update. Errors will be tracked down in the :cpp:class:`RPCErrorCode` field.
+This api will only inform for errors during the metric update. Errors will be tracked down in the `error` field.
.. note::
@@ -1095,7 +1095,7 @@ Request:
Response:
-The response will contain the default `success_response` or an :cpp:class:`RPCErrorCode`.
+The response will contain the default `success_response` or an error. :ref:`jsonrpc-node-errors`.
.. _admin_host_set_status:
@@ -1164,7 +1164,7 @@ marking this reason as "down" in that case.
Result
~~~~~~
-The response will contain the default `success_response` or an :cpp:class:`RPCErrorCode`.
+The response will contain the default `success_response` or an error. :ref:`jsonrpc-node-errors`.
Examples
@@ -1260,7 +1260,7 @@ Parameters
Result
~~~~~~
-The response will contain the default `success_response` or an :cpp:class:`RPCErrorCode`.
+The response will contain the default `success_response` or an error. :ref:`jsonrpc-node-errors`.
Examples
@@ -1302,7 +1302,7 @@ Field Type Description
Result
~~~~~~
-The response will contain the default `success_response` or an :cpp:class:`RPCErrorCode`.
+The response will contain the default `success_response` or an error. :ref:`jsonrpc-node-errors`.
.. note::
@@ -1380,7 +1380,7 @@ Field Type Description
Result
~~~~~~
-The response will contain the default `success_response` or an :cpp:class:`RPCErrorCode`.
+The response will contain the default `success_response` or an error. :ref:`jsonrpc-node-errors`.
Examples
~~~~~~~~
@@ -1781,3 +1781,8 @@ Response:
]
}
}
+
+See also
+========
+
+:ref:`jsonrpc-node-errors`
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-architecture.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-architecture.en.rst
index 4025cbd30..91489c5e9 100644
--- a/doc/developer-guide/jsonrpc/jsonrpc-architecture.en.rst
+++ b/doc/developer-guide/jsonrpc/jsonrpc-architecture.en.rst
@@ -55,7 +55,7 @@ IPC
The current server implementation runs on an IPC Socket(Unix Domain Socket). This server implements an iterative server style.
The implementation runs on a dedicated ``TSThread`` and as their style express, this performs blocking calls to all the registered handlers.
-Configuration for this particular server style can be found in the admin section :ref:`admnin-jsonrpc-configuration`.
+Configuration for this particular server style can be found in the admin section :ref:`admin-jsonrpc-configuration`.
Using the JSONRPC mechanism
@@ -464,61 +464,6 @@ Response:
}
-.. _rpc-error-code:
-
-Internally we have defined an ``enum`` class that keeps track of the errors that the server will inform in most of the cases.
-Some of this errors are already defined by the `JSONRPC`_ specs and some (``>=1``) are defined by |TS|.
-
-.. class:: RPCErrorCode
-
- Defines the API error codes that will be used in case of any RPC error.
-
- .. enumerator:: INVALID_REQUEST = -32600
- .. enumerator:: METHOD_NOT_FOUND = -32601
- .. enumerator:: INVALID_PARAMS = -32602
- .. enumerator:: INTERNAL_ERROR = -32603
- .. enumerator:: PARSE_ERROR = -32700
-
- `JSONRPC`_ defined errors.
-
- .. enumerator:: InvalidVersion = 1
-
- The passed version is invalid. must be 2.0
-
- .. enumerator:: InvalidVersionType = 2
-
- The passed version field type is invalid. must be a ``string``
-
- .. enumerator:: MissingVersion = 3
-
- Version field is missing from the request. This field is mandatory.
-
- .. enumerator:: InvalidMethodType = 4
-
- The passed method field type is invalid. must be a ``string``
-
- .. enumerator:: MissingMethod = 5
-
- Method field is missing from the request. This field is mandatory.
-
- .. enumerator:: InvalidParamType = 6
-
- The passed parameter field type is not valid.
-
- .. enumerator:: InvalidIdType = 7
-
- The passed id field type is invalid.
-
- .. enumerator:: NullId = 8
-
- The passed if is ``null``
-
- .. enumerator:: ExecutionError = 9
-
- An error occurred during the execution of the RPC call. This error is used as a generic High level error. The details details about
- the error, in most cases are specified in the ``data`` field.
-
-
.. information:
According to the |RPC| specs, if you get an error, the ``result`` field will not be set. |TS| will grant this.
@@ -535,5 +480,6 @@ Development Guide
See also
========
-:ref:`admnin-jsonrpc-configuration`,
+:ref:`admin-jsonrpc-configuration`,
+:ref:`jsonrpc-node-errors`,
:ref:`traffic_ctl_jsonrpc`
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-handler-development.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-handler-development.en.rst
index 94ae293a9..f79b21a2c 100644
--- a/doc/developer-guide/jsonrpc/jsonrpc-handler-development.en.rst
+++ b/doc/developer-guide/jsonrpc/jsonrpc-handler-development.en.rst
@@ -186,7 +186,7 @@ We recommend some ways to deal with this:
This can be set in case you would like to let the server to respond with an |RPC| error, ``ExecutionError`` will be used to catch all the
errors that are fired from within the function call, either by setting the proper errata or by throwing an exception.
-Please check the `rpc-error-code` and in particular ``ExecutionError = 9``. Also check :ref:`jsonrpc-handler-errors`
+Please check the :ref:`jsonrpc-node-errors` and in particular ``ExecutionError = 9``. Also check :ref:`jsonrpc-handler-errors`
.. important::
@@ -420,7 +420,10 @@ Important Notes
* To interact directly with the |RPC| node, please check :ref:`jsonrpc-node`
-:ref:`admnin-jsonrpc-configuration`
+See also
+========
+
+:ref:`admin-jsonrpc-configuration`
:ref:`jsonrpc-protocol`
:ref:`developer-guide-traffic_ctl-development`
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-node-errors.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-node-errors.en.rst
new file mode 100644
index 000000000..0d9253f08
--- /dev/null
+++ b/doc/developer-guide/jsonrpc/jsonrpc-node-errors.en.rst
@@ -0,0 +1,143 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+
+.. include:: ../../common.defs
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. _JSONRPC: https://www.jsonrpc.org/specification
+.. _JSON: https://www.json.org/json-en.html
+
+
+.. _jsonrpc-node-errors:
+
+
+JSON RPC errors
+***************
+
+A list of codes and descriptions of errors that could be send back from the JSONRPC server. JSONRPC response messages could contains
+different set of errors in the following format:
+
+.. note::
+
+ Check :ref:`jsonrpc-error` for details about the error structure.
+
+
+.. code-block::json
+
+ {
+ "error": {
+ "code": -32601,
+ "message": "Method not found"
+ },
+ "id": "ded7018e-0720-11eb-abe2-001fc69cc946",
+ "jsonrpc": "2.0"
+ }
+
+In some cases the data field could be populated:
+
+.. code-block:: json
+
+ {
+ "jsonrpc": "2.0",
+ "error":{
+ "code": 10,
+ "message": "Unauthorized action",
+ "data":[
+ {
+ "code": 2,
+ "message":"Denied privileged API access for uid=XXX gid=XXX"
+ }
+ ]
+ },
+ "id":"5e273ec0-3e3b-4a81-90ec-aeee3d38073f"
+ }
+
+
+.. _jsonrpc-node-errors-standard-errors:
+
+Standard errors
+===============
+
+=============== ========================================= =========================================================
+Field Message Description
+=============== ========================================= =========================================================
+-32700 Parse error Invalid JSON was received by the server.
+ An error occurred on the server while parsing the JSON text.
+-32600 Invalid Request The JSON sent is not a valid Request object.
+-32601 Method not found The method does not exist / is not available.
+-32602 Invalid params Invalid method parameter(s).
+-32603 Internal error Internal `JSONRPC`_ error.
+=============== ========================================= =========================================================
+
+.. _jsonrpc-node-errors-custom-errors:
+
+Custom errors
+=============
+
+The following error list are defined by the server.
+
+=============== ========================================= =========================================================
+Field Message Description
+=============== ========================================= =========================================================
+1 Invalid version, 2.0 only The server only accepts version field equal to `2.0`.
+2 Invalid version type, should be a string Version field should be a literal string.
+3 Missing version field No version field present, version field is mandatory.
+4 Invalid method type, should be a string The method field should be a literal string.
+5 Missing method field No method field present, method field is mandatory.
+6 Invalid params type. A Structured value Params field should be a structured type, list or structure.
+ is expected This is similar to `-32602`
+7 Invalid id type If field should be a literal string.
+8 Use of null as id is discouraged Id field value is null, as per the specs this is discouraged,
+ the server will not accept it.
+9 Error during execution An error occurred during the execution of the RPC call.
+ This error is used as a generic High level error. The specifics
+ details about the error, in most cases are specified in the
+ ``data`` field.
+10 Unauthorized action The rpc method will not be invoked because the action is not
+ permitted by some constraint or authorization issue.Check
+ :ref:`jsonrpc-node-errors-unauthorized-action` for mode details.
+=============== ========================================= =========================================================
+
+.. _jsonrpc-node-errors-unauthorized-action:
+
+Unauthorized action
+-------------------
+
+Under this error, the `data` field could be populated with the following errors, eventually more than one could be in set.
+
+.. code-block:: json
+
+ "data":[
+ {
+ "code":2,
+ "message":"Denied privileged API access for uid=XXX gid=XXX"
+ }
+ ]
+
+=============== ========================================= =========================================================
+Field Message Description
+=============== ========================================= =========================================================
+1 Error getting peer credentials: {} Something happened while trying to get the peers credentials.
+ The error string will show the error code(`errno`) returned by the
+ server.
+2 Denied privileged API access for uid={} Permission denied. Unix Socket credentials were checked and they haven't meet
+ gid={} the required policy. The handler was configured as restricted
+ and the socket credentials failed to validate. Check TBC for
+ more information.
+=============== ========================================= =========================================================
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-node.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-node.en.rst
index 204e7b357..25b63a505 100644
--- a/doc/developer-guide/jsonrpc/jsonrpc-node.en.rst
+++ b/doc/developer-guide/jsonrpc/jsonrpc-node.en.rst
@@ -36,7 +36,7 @@ IPC Node
========
You can directly connect to the Unix Domain Socket used for the |RPC| node, the location of the sockets
-will depend purely on how did you configure the server, please check :ref:`admnin-jsonrpc-configuration` for
+will depend purely on how did you configure the server, please check :ref:`admin-jsonrpc-configuration` for
information regarding configuration.
@@ -57,3 +57,8 @@ Using traffic_ctl
:program:`traffic_ctl` can also be used to directly send raw |RPC| messages to the server's node, :program:`traffic_ctl` provides
several options to achieve this, please check ``traffic_ctl_rpc``.
+
+Error responses
+---------------
+
+The server will indicate in case of any error processing the call, check :ref:`jsonrpc-node-errors` for more details.
diff --git a/doc/developer-guide/jsonrpc/traffic_ctl-development.en.rst b/doc/developer-guide/jsonrpc/traffic_ctl-development.en.rst
index 3a219578c..957c00a3f 100644
--- a/doc/developer-guide/jsonrpc/traffic_ctl-development.en.rst
+++ b/doc/developer-guide/jsonrpc/traffic_ctl-development.en.rst
@@ -208,7 +208,10 @@ There is code that was written in this way by design, ``RecordPrinter`` and ``Re
that needs to query and print records without any major hassle.
+See also
+========
-:ref:`admnin-jsonrpc-configuration`,
+:ref:`admin-jsonrpc-configuration`,
:ref:`traffic_ctl_jsonrpc`,
-:ref:`jsonrpc_development`
+:ref:`jsonrpc_development`,
+:ref:`jsonrpc-node-errors`
diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in
index 36f19b033..f0be4e8d6 100644
--- a/include/ts/apidefs.h.in
+++ b/include/ts/apidefs.h.in
@@ -1479,6 +1479,18 @@ namespace ts
}
#endif
+///
+/// @brief JSON-RPC Handler options
+///
+/// This class holds information about how a handler will be managed and delivered when called. The JSON-RPC manager would use this
+/// object to perform certain validation.
+///
+typedef struct TSRPCHandlerOptions_s {
+ struct Auth {
+ int restricted; ///< Tells the RPC Manager if the call can be delivered or not based on the config rules.
+ } auth;
+} TSRPCHandlerOptions;
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/mgmt2/config/FileManager.cc b/mgmt2/config/FileManager.cc
index a2880cf83..4bb6aa9a3 100644
--- a/mgmt2/config/FileManager.cc
+++ b/mgmt2/config/FileManager.cc
@@ -91,12 +91,11 @@ FileManager::FileManager()
this->registerCallback(&handle_file_reload);
// Register the files registry jsonrpc endpoint
- rpc::add_method_handler(
- "filemanager.get_files_registry",
- [this](std::string_view const &id, const YAML::Node &req) -> ts::Rv<YAML::Node> {
- return get_files_registry_rpc_endpoint(id, req);
- },
- &rpc::core_ats_rpc_service_provider_handle);
+ rpc::add_method_handler("filemanager.get_files_registry",
+ [this](std::string_view const &id, const YAML::Node &req) -> ts::Rv<YAML::Node> {
+ return get_files_registry_rpc_endpoint(id, req);
+ },
+ &rpc::core_ats_rpc_service_provider_handle, {{rpc::NON_RESTRICTED_API}});
}
// FileManager::~FileManager
diff --git a/mgmt2/rpc/Makefile.am b/mgmt2/rpc/Makefile.am
index 26ca19236..d4de43b0e 100644
--- a/mgmt2/rpc/Makefile.am
+++ b/mgmt2/rpc/Makefile.am
@@ -45,7 +45,9 @@ libjsonrpc_protocol_COMMON = \
jsonrpc/error/RPCError.cc \
jsonrpc/error/RPCError.h \
jsonrpc/JsonRPCManager.cc \
- jsonrpc/JsonRPCManager.h
+ jsonrpc/JsonRPCManager.h \
+ jsonrpc/Context.cc \
+ jsonrpc/Context.h
libjsonrpc_protocol_la_SOURCES = \
$(libjsonrpc_protocol_COMMON)
diff --git a/mgmt2/rpc/jsonrpc/Context.cc b/mgmt2/rpc/jsonrpc/Context.cc
new file mode 100644
index 000000000..a0a3793f8
--- /dev/null
+++ b/mgmt2/rpc/jsonrpc/Context.cc
@@ -0,0 +1,35 @@
+/**
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+*/
+#include "Context.h"
+
+namespace rpc
+{
+// --- Call Context impl
+ts::Errata
+Context::Auth::is_blocked(TSRPCHandlerOptions const &options) const
+{
+ ts::Errata out;
+ // check every registered callback and see if they have something to say. Then report back to the manager
+ for (auto &&check : _checkers) {
+ check(options, out);
+ }
+ return out;
+};
+} // namespace rpc
diff --git a/mgmt2/rpc/jsonrpc/Context.h b/mgmt2/rpc/jsonrpc/Context.h
new file mode 100644
index 000000000..fb78d338e
--- /dev/null
+++ b/mgmt2/rpc/jsonrpc/Context.h
@@ -0,0 +1,77 @@
+/**
+ @section license License
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+*/
+#pragma once
+
+#include <vector>
+#include <functional>
+#include <string_view>
+
+#include "tscore/Errata.h"
+#include "ts/apidefs.h"
+#include "rpc/handlers/common/ErrorUtils.h"
+
+namespace rpc
+{
+constexpr bool RESTRICTED_API{true};
+constexpr bool NON_RESTRICTED_API{false};
+///
+/// @brief RPC call context class.
+///
+/// This class is used to carry information from the transport logic to the rpc invocation logic, the transport may need to block
+/// some rpc handlers from being executed which at the time of finish ups reading the raw message is yet too early to know the
+/// actual handler.
+///
+class Context
+{
+ using checker_cb = std::function<void(TSRPCHandlerOptions const &, ts::Errata &)>;
+ /// @brief Internal class to hold the permission checker part.
+ struct Auth {
+ /// Checks for permissions. This function checks for every registered permission checker.
+ ///
+ /// @param options Registered handler options.
+ /// @return ts::Errata The errata will be filled by each of the registered checkers, if there was any issue validating the
+ /// call, then the errata reflects that.
+ ts::Errata is_blocked(TSRPCHandlerOptions const &options) const;
+
+ /// Add permission checkers.
+ template <typename F>
+ void
+ add_checker(F &&f)
+ {
+ _checkers.emplace_back(std::forward<F>(f));
+ }
+
+ private:
+ std::vector<checker_cb> _checkers; ///< cb collection.
+ } _auth;
+
+public:
+ Auth &
+ get_auth()
+ {
+ return _auth;
+ }
+ Auth const &
+ get_auth() const
+ {
+ return _auth;
+ }
+};
+} // namespace rpc
diff --git a/mgmt2/rpc/jsonrpc/Defs.h b/mgmt2/rpc/jsonrpc/Defs.h
index 9c321f6f1..f528ad111 100644
--- a/mgmt2/rpc/jsonrpc/Defs.h
+++ b/mgmt2/rpc/jsonrpc/Defs.h
@@ -45,12 +45,18 @@ public:
};
struct RPCResponseInfo {
- RPCResponseInfo(std::optional<std::string> const &id_) : id(id_) {}
+ RPCResponseInfo(std::string const &id_) : id{id_} {} // Convenient
RPCResponseInfo() = default;
- RPCHandlerResponse callResult;
- std::error_code rpcError;
- std::optional<std::string> id;
+ struct Error {
+ std::error_code ec;
+ ts::Errata data;
+ };
+
+ std::string id; //!< incoming request id (only used for method calls, empty means it's a notification as requests with empty id
+ //!< will not pass the validation)
+ Error error; //!< Error code and details.
+ RPCHandlerResponse callResult; //!< the actual handler's response
};
///
@@ -61,17 +67,18 @@ struct RPCResponseInfo {
struct RPCRequestInfo {
RPCRequestInfo() = default;
RPCRequestInfo(std::string const &version, std::string const &mid) : jsonrpc(version), id(mid) {}
- std::string jsonrpc; //!< JsonRPC version ( we only allow 2.0 ). @see yamlcpp_json_decoder
- std::string method; //!< incoming method name.
- std::optional<std::string> id; //!< incoming request if (only used for method calls.)
- YAML::Node params; //!< incoming parameter structure.
+ std::string jsonrpc; //!< JsonRPC version ( we only allow 2.0 ). @see yamlcpp_json_decoder
+ std::string method; //!< incoming method name.
+ std::string id; //!< incoming request id (only used for method calls, empty means it's a notification as requests with empty id
+ //!< will not pass the validation)
+ YAML::Node params; //!< incoming parameter structure.
/// Convenience functions that checks for the type of request. If contains id then it should be handle as method call, otherwise
/// will be a notification.
bool
is_notification() const
{
- return !id.has_value();
+ return id.empty();
}
bool
is_method() const
diff --git a/mgmt2/rpc/jsonrpc/JsonRPC.h b/mgmt2/rpc/jsonrpc/JsonRPC.h
index 8af97399e..acf8cfd07 100644
--- a/mgmt2/rpc/jsonrpc/JsonRPC.h
+++ b/mgmt2/rpc/jsonrpc/JsonRPC.h
@@ -24,7 +24,7 @@
namespace rpc
{
-/// Generic and global JSONRPC service provider info object. It's recommended to use this object when registring your new handler
+/// Generic and global JSONRPC service provider info object. It's recommended to use this object when registering your new handler
/// into the rpc system IF the implementor wants the handler to be listed as ATS's handler.
extern RPCRegistryInfo core_ats_rpc_service_provider_handle;
// -----------------------------------------------------------------------------
@@ -33,17 +33,17 @@ extern RPCRegistryInfo core_ats_rpc_service_provider_handle;
/// @see JsonRPCManager::add_method_handler for details
template <typename Func>
inline bool
-add_method_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info = nullptr)
+add_method_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt)
{
- return JsonRPCManager::instance().add_method_handler(name, std::forward<Func>(call), info);
+ return JsonRPCManager::instance().add_method_handler(name, std::forward<Func>(call), info, opt);
}
/// @see JsonRPCManager::add_notification_handler for details
template <typename Func>
inline bool
-add_notification_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info = nullptr)
+add_notification_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt)
{
- return JsonRPCManager::instance().add_notification_handler(name, std::forward<Func>(call), info);
+ return JsonRPCManager::instance().add_notification_handler(name, std::forward<Func>(call), info, opt);
}
} // namespace rpc
diff --git a/mgmt2/rpc/jsonrpc/JsonRPCManager.cc b/mgmt2/rpc/jsonrpc/JsonRPCManager.cc
index f84c7fc98..050f5a443 100644
--- a/mgmt2/rpc/jsonrpc/JsonRPCManager.cc
+++ b/mgmt2/rpc/jsonrpc/JsonRPCManager.cc
@@ -39,7 +39,12 @@ const std::string RPC_SERVICE_PROVIDER_KEY{"provider"};
const std::string RPC_SERVICE_SCHEMA_KEY{"schema"};
const std::string RPC_SERVICE_METHODS_KEY{"methods"};
const std::string RPC_SERVICE_NOTIFICATIONS_KEY{"notifications"};
+const std::string RPC_SERVICE_PRIVILEGED_KEY{"privileged"};
const std::string RPC_SERVICE_N_A_STR{"N/A"};
+
+// jsonrpc log tag.
+constexpr auto logTag = "rpc";
+constexpr auto logTagMsg = "rpc.msg";
} // namespace
namespace rpc
@@ -54,9 +59,15 @@ std::condition_variable g_rpcHandlingCompletion;
ts::Rv<YAML::Node> g_rpcHandlerResponseData;
bool g_rpcHandlerProcessingCompleted{false};
-// jsonrpc log tag.
-static constexpr auto logTag = "rpc";
-static constexpr auto logTagMsg = "rpc.msg";
+// --- Helpers
+std::pair<ts::Errata, error::RPCErrorCode>
+check_for_blockers(Context const &ctx, TSRPCHandlerOptions const &options)
+{
+ if (auto err = ctx.get_auth().is_blocked(options); !err.isOK()) {
+ return {err, error::RPCErrorCode::Unauthorized};
+ }
+ return {};
+}
// --- Dispatcher
JsonRPCManager::Dispatcher::Dispatcher()
@@ -66,32 +77,41 @@ JsonRPCManager::Dispatcher::Dispatcher()
void
JsonRPCManager::Dispatcher::register_service_descriptor_handler()
{
- // TODO: revisit this.
if (!this->add_handler<Dispatcher::Method, MethodHandlerSignature>(
"show_registered_handlers",
[this](std::string_view const &id, const YAML::Node &req) -> ts::Rv<YAML::Node> {
return show_registered_handlers(id, req);
},
- &core_ats_rpc_service_provider_handle)) {
+ &core_ats_rpc_service_provider_handle, {{NON_RESTRICTED_API}})) {
Warning("Handler already registered.");
}
if (!this->add_handler<Dispatcher::Method, MethodHandlerSignature>(
"get_service_descriptor",
[this](std::string_view const &id, const YAML::Node &req) -> ts::Rv<YAML::Node> { return get_service_descriptor(id, req); },
- &core_ats_rpc_service_provider_handle)) {
+ &core_ats_rpc_service_provider_handle, {{NON_RESTRICTED_API}})) {
Warning("Handler already registered.");
}
}
JsonRPCManager::Dispatcher::response_type
-JsonRPCManager::Dispatcher::dispatch(specs::RPCRequestInfo const &request) const
+JsonRPCManager::Dispatcher::dispatch(Context const &ctx, specs::RPCRequestInfo const &request) const
{
std::error_code ec;
auto const &handler = find_handler(request, ec);
if (ec) {
- return {std::nullopt, ec};
+ specs::RPCResponseInfo resp{request.id};
+ resp.error.ec = ec;
+ return resp;
+ }
+
+ // We have got a valid handler, we will now check if the context holds any restriction for this handler to be called.
+ if (auto &&[errata, ec] = check_for_blockers(ctx, handler.get_options()); !errata.isOK()) {
+ specs::RPCResponseInfo resp{request.id};
+ resp.error.ec = ec;
+ resp.error.data = errata;
+ return resp;
}
if (request.is_notification()) {
@@ -117,7 +137,7 @@ JsonRPCManager::Dispatcher::find_handler(specs::RPCRequestInfo const &request, s
return no_handler;
}
- // we need to make sure we the request is valid against the internal handler.
+ // Handler's method type should match the requested method type.
if ((request.is_method() && search->second.is_method()) || (request.is_notification() && !search->second.is_method())) {
return search->second;
}
@@ -143,10 +163,10 @@ JsonRPCManager::Dispatcher::invoke_method_handler(JsonRPCManager::Dispatcher::In
}
} catch (std::exception const &e) {
Debug(logTag, "Oops, something happened during the callback invocation: %s", e.what());
- return {std::nullopt, error::RPCErrorCode::ExecutionError};
+ response.error.ec = error::RPCErrorCode::ExecutionError;
}
- return {response, {}};
+ return response;
}
JsonRPCManager::Dispatcher::response_type
@@ -160,7 +180,7 @@ JsonRPCManager::Dispatcher::invoke_notification_handler(JsonRPCManager::Dispatch
// it's a notification so we do not care much.
}
- return response_type{};
+ return {std::nullopt};
}
bool
@@ -182,32 +202,8 @@ JsonRPCManager::remove_handler(std::string_view name)
return _dispatcher.remove_handler(name);
}
-static inline specs::RPCResponseInfo
-make_error_response(specs::RPCRequestInfo const &req, std::error_code const &ec)
-{
- specs::RPCResponseInfo resp;
-
- // we may have been able to collect the id, if so, use it.
- if (req.id) {
- resp.id = req.id;
- }
-
- resp.rpcError = ec;
-
- return resp;
-}
-
-static inline specs::RPCResponseInfo
-make_error_response(std::error_code const &ec)
-{
- specs::RPCResponseInfo resp;
-
- resp.rpcError = ec;
- return resp;
-}
-
std::optional<std::string>
-JsonRPCManager::handle_call(std::string const &request)
+JsonRPCManager::handle_call(Context const &ctx, std::string const &request)
{
Debug(logTagMsg, "--> JSONRPC request\n'%s'", request.c_str());
@@ -219,8 +215,9 @@ JsonRPCManager::handle_call(std::string const &request)
// If any error happened within the request, they will be kept inside each
// particular request, as they would need to be converted back in a proper error response.
if (ec) {
- auto response = make_error_response(ec);
- return Encoder::encode(response);
+ specs::RPCResponseInfo resp;
+ resp.error.ec = ec;
+ return Encoder::encode(resp);
}
specs::RPCResponse response{msg.is_batch()};
@@ -231,23 +228,19 @@ JsonRPCManager::handle_call(std::string const &request)
if (!decode_error) {
// request seems ok and ready to be dispatched. The dispatcher will tell us if the method exist and if so, it will dispatch
// the call and gives us back the response.
- auto &&[encodedResponse, ec] = _dispatcher.dispatch(req);
-
- // On any error, ec will have a value
- if (!ec) {
- // we only get valid responses if it was a method request, not
- // for notifications.
- if (encodedResponse) {
- response.add_message(*encodedResponse);
- }
- } else {
- // get an error response, we may have the id, so let's try to use it.
- response.add_message(make_error_response(req, ec));
- }
+ auto encodedResponse = _dispatcher.dispatch(ctx, req);
+
+ if (encodedResponse) {
+ // if any error was detected during invocation or before, the response will have the error field set, so this will
+ // internally be converted to the right response type.
+ response.add_message(std::move(*encodedResponse));
+ } // else it's a notification and no error.
} else {
// If the request was marked as an error(decode error), we still need to send the error back, so we save it.
- response.add_message(make_error_response(req, decode_error));
+ specs::RPCResponseInfo resp{req.id};
+ resp.error.ec = decode_error;
+ response.add_message(std::move(resp));
}
}
@@ -262,8 +255,9 @@ JsonRPCManager::handle_call(std::string const &request)
} catch (std::exception const &ex) {
ec = error::RPCErrorCode::INTERNAL_ERROR;
}
-
- return {Encoder::encode(make_error_response(ec))};
+ specs::RPCResponseInfo resp;
+ resp.error.ec = ec;
+ return {Encoder::encode(resp)};
}
// ---------------------------- InternalHandler ---------------------------------
@@ -281,13 +275,13 @@ JsonRPCManager::Dispatcher::InternalHandler::invoke(specs::RPCRequestInfo const
[&ret, &request](Method const &handler) -> void {
// Regular Method Handler call, No cond variable check here, this should have not be created by
// a plugin.
- ret = handler.cb(*request.id, request.params);
+ ret = handler.cb(request.id, request.params);
},
[&ret, &request](PluginMethod const &handler) -> void {
// We call the method handler, we'll lock and wait till the condition_variable
// gets set on the other side. The handler may return immediately with no response being set.
// cond var will give us green to proceed.
- handler.cb(*request.id, request.params);
+ handler.cb(request.id, request.params);
std::unique_lock<std::mutex> lock(g_rpcHandlingMutex);
g_rpcHandlingCompletion.wait(lock, []() { return g_rpcHandlerProcessingCompleted; });
g_rpcHandlerProcessingCompleted = false;
@@ -349,7 +343,8 @@ JsonRPCManager::Dispatcher::get_service_descriptor(std::string_view const &, con
} else {
provider = RPC_SERVICE_N_A_STR;
}
- method[RPC_SERVICE_PROVIDER_KEY] = provider;
+ method[RPC_SERVICE_PROVIDER_KEY] = provider;
+ method[RPC_SERVICE_PRIVILEGED_KEY] = handler.get_options().auth.restricted;
YAML::Node schema{YAML::NodeType::Map}; // no schema for now, but we have a placeholder for it. Schema should provide
// description and all the details about the call
method[RPC_SERVICE_SCHEMA_KEY] = std::move(schema);
diff --git a/mgmt2/rpc/jsonrpc/JsonRPCManager.h b/mgmt2/rpc/jsonrpc/JsonRPCManager.h
index e00b57f5b..88f297060 100644
--- a/mgmt2/rpc/jsonrpc/JsonRPCManager.h
+++ b/mgmt2/rpc/jsonrpc/JsonRPCManager.h
@@ -34,6 +34,7 @@
#include "ts/apidefs.h"
#include "Defs.h"
+#include "Context.h"
namespace rpc
{
@@ -44,9 +45,14 @@ namespace json_codecs
class yamlcpp_json_encoder;
} // namespace json_codecs
+///
+/// @brief This class keeps all relevant @c RPC provider's info.
+///
struct RPCRegistryInfo {
- std::string_view provider;
+ std::string_view provider; ///< Who's the rpc endpoint provider, could be ATS or a plugins. When requesting the service info from
+ ///< the rpc node, this will be part of the service info.
};
+
///
/// @brief JSONRPC registration and JSONRPC invocation logic https://www.jsonrpc.org/specification
/// doc TBC
@@ -74,9 +80,11 @@ public:
/// 'get_stats'...} .
/// @param call The function handler.
/// @param info RPCRegistryInfo pointer.
+ /// @param opt Handler options, used to pass information about the registered handler.
/// @return bool Boolean flag. true if the callback was successfully added, false otherwise
///
- template <typename Func> bool add_method_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info);
+ template <typename Func>
+ bool add_method_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt);
///
/// @brief Add new registered notification handler to the JSON RPC engine.
@@ -85,18 +93,21 @@ public:
/// @param name Name to be exposed by the RPC Engine.
/// @param call The callback function that needs handler.
/// @param info RPCRegistryInfo pointer.
+ /// @param opt Handler options, used to pass information about the registered handler.
/// @return bool Boolean flag. true if the callback was successfully added, false otherwise
///
- template <typename Func> bool add_notification_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info);
+ template <typename Func>
+ bool add_notification_handler(std::string_view name, Func &&call, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt);
///
/// @brief This function handles the incoming jsonrpc request and dispatch the associated registered handler.
///
+ /// @param ctx @c Context object used pass information between rpc layers.
/// @param jsonString The incoming jsonrpc 2.0 message. \link https://www.jsonrpc.org/specification
/// @return std::optional<std::string> For methods, a valid jsonrpc 2.0 json string will be passed back. Notifications will not
/// contain any json back.
///
- std::optional<std::string> handle_call(std::string const &jsonString);
+ std::optional<std::string> handle_call(Context const &ctx, std::string const &jsonString);
///
/// @brief Get the instance of the whole RPC engine.
@@ -144,9 +155,9 @@ private:
/// signatures inside a @c std::variant
class Dispatcher
{
- using response_type = std::pair<
- std::optional<specs::RPCResponseInfo>,
- std::error_code>; ///< The response type used internally, notifications won't fill in the optional response. @c ec will be set
+ /// The response type used internally, notifications won't fill in the optional response. Internal response's @ ec will be set
+ /// in case of any error.
+ using response_type = std::optional<specs::RPCResponseInfo>;
///
/// @brief Class that wraps the actual std::function<T>.
@@ -166,11 +177,11 @@ private:
/// Add a method handler to the internal container
/// @return True if was successfully added, False otherwise.
template <typename FunctionWrapperType, typename Handler>
- bool add_handler(std::string_view name, Handler &&handler, const RPCRegistryInfo *info);
+ bool add_handler(std::string_view name, Handler &&handler, const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt);
/// Find and call the request's callback. If any error occurs, the return type will have the specific error.
/// For notifications the @c RPCResponseInfo will not be set as part of the response. @c response_type
- response_type dispatch(specs::RPCRequestInfo const &request) const;
+ response_type dispatch(Context const &ctx, specs::RPCRequestInfo const &request) const;
/// Find a particular registered handler(method) by its associated name.
/// @return A pair. The handler itself and a boolean flag indicating that the handler was found. If not found, second will
@@ -206,7 +217,7 @@ private:
/// simplify the logic to insert and fetch callable objects from our container.
struct InternalHandler {
InternalHandler() = default;
- InternalHandler(const RPCRegistryInfo *info) : _regInfo(info) {}
+ InternalHandler(const RPCRegistryInfo *info, TSRPCHandlerOptions const &opt) : _regInfo(info), _options(opt) {}
/// Sets the handler.
template <class T, class F> void set_callback(F &&t);
explicit operator bool() const;
@@ -223,6 +234,13 @@ private:
return _regInfo;
}
+ /// Returns the configured options associated with this particular handler.
+ TSRPCHandlerOptions const &
+ get_options() const
+ {
+ return _options;
+ }
+
private:
// We need to keep this match with the order of types in the _func variant. This will help us to identify the holding type.
enum class VariantTypeIndexId : std::size_t { NOTIFICATION = 1, METHOD = 2, METHOD_FROM_PLUGIN = 3 };
@@ -231,6 +249,7 @@ private:
std::variant<std::monostate, Notification, Method, PluginMethod> _func;
const RPCRegistryInfo *_regInfo; ///< Can hold internal information about the handler, this could be null as it is optional.
///< This pointer can eventually holds important information about the call.
+ TSRPCHandlerOptions _options;
};
// We will keep all the handlers wrapped inside the InternalHandler class, this will help us
// to have a single container for all the types(method, notification & plugin method(cond var)).
@@ -244,19 +263,21 @@ private:
// ------------------------------ JsonRPCManager -------------------------------
template <typename Handler>
bool
-JsonRPCManager::add_method_handler(std::string_view name, Handler &&call, const RPCRegistryInfo *info)
+JsonRPCManager::add_method_handler(std::string_view name, Handler &&call, const RPCRegistryInfo *info,
+ TSRPCHandlerOptions const &opt)
{
- return _dispatcher.add_handler<Dispatcher::Method, Handler>(name, std::forward<Handler>(call), info);
+ return _dispatcher.add_handler<Dispatcher::Method, Handler>(name, std::forward<Handler>(call), info, opt);
}
template <typename Handler>
bool
-JsonRPCManager::add_notification_handler(std::string_view name, Handler &&call, const RPCRegistryInfo *info)
+JsonRPCManager::add_notification_handler(std::string_view name, Handler &&call, const RPCRegistryInfo *info,
+ TSRPCHandlerOptions const &opt)
{
- return _dispatcher.add_handler<Dispatcher::Notification, Handler>(name, std::forward<Handler>(call), info);
+ return _dispatcher.add_handler<Dispatcher::Notification, Handler>(name, std::forward<Handler>(call), info, opt);
}
-// ----------------------------- InternalHandler ------------------------------------
+// ----------------------------- InternalHandler -------------------------------
template <class T, class F>
void
JsonRPCManager::Dispatcher::InternalHandler::set_callback(F &&f)
@@ -276,10 +297,11 @@ bool inline JsonRPCManager::Dispatcher::InternalHandler::operator!() const
// ----------------------------- Dispatcher ------------------------------------
template <typename FunctionWrapperType, typename Handler>
bool
-JsonRPCManager::Dispatcher::add_handler(std::string_view name, Handler &&handler, const RPCRegistryInfo *info)
+JsonRPCManager::Dispatcher::add_handler(std::string_view name, Handler &&handler, const RPCRegistryInfo *info,
+ TSRPCHandlerOptions const &opt)
{
std::lock_guard<std::mutex> lock(_mutex);
- InternalHandler call{info};
+ InternalHandler call{info, opt};
call.set_callback<FunctionWrapperType>(std::forward<Handler>(handler));
return _handlers.emplace(name, std::move(call)).second;
}
diff --git a/mgmt2/rpc/jsonrpc/error/RPCError.cc b/mgmt2/rpc/jsonrpc/error/RPCError.cc
index 6cbb10b87..03b8f6a66 100644
--- a/mgmt2/rpc/jsonrpc/error/RPCError.cc
+++ b/mgmt2/rpc/jsonrpc/error/RPCError.cc
@@ -74,6 +74,8 @@ RPCErrorCategory::message(int ev) const
return {"Use of null as id is discouraged"};
case RPCErrorCode::ExecutionError:
return {"Error during execution"};
+ case RPCErrorCode::Unauthorized:
+ return {"Unauthorized action"};
default:
return "Rpc error " + std::to_string(ev);
}
diff --git a/mgmt2/rpc/jsonrpc/error/RPCError.h b/mgmt2/rpc/jsonrpc/error/RPCError.h
index b10c8b917..f945c62bf 100644
--- a/mgmt2/rpc/jsonrpc/error/RPCError.h
+++ b/mgmt2/rpc/jsonrpc/error/RPCError.h
@@ -59,10 +59,13 @@ enum class RPCErrorCode {
// execution errors
// Internal rpc error when executing the method.
- ExecutionError
+ //
+ ExecutionError, //!< Handler's general error.
+ Unauthorized //!< In case we want to block the call based on privileges, access permissions, etc.
};
// TODO: force non 0 check
std::error_code make_error_code(rpc::error::RPCErrorCode e);
+
} // namespace rpc::error
namespace std
diff --git a/mgmt2/rpc/jsonrpc/json/YAMLCodec.h b/mgmt2/rpc/jsonrpc/json/YAMLCodec.h
index 2998caf24..b00919f79 100644
--- a/mgmt2/rpc/jsonrpc/json/YAMLCodec.h
+++ b/mgmt2/rpc/jsonrpc/json/YAMLCodec.h
@@ -169,21 +169,6 @@ public:
///
class yamlcpp_json_encoder
{
- ///
- /// @brief Encode the ID if present.
- /// If the id is not present which could be interpret as null(should not happen), it will not be set into the emitter, it will be
- /// ignored. This is due the way that yamlcpp deals with the null, which instead of the literal null, it uses ~
- static void
- encode_id(const std::optional<std::string> &id, YAML::Emitter &json)
- {
- // workaround, we should find a better way, we should be able to use literal null if needed
- if (id) {
- json << YAML::Key << "id" << YAML::Value << *id;
- }
- // We do not insert null as it will break the json, we need literal null and not ~ (as per yaml)
- // json << YAML::Null;
- }
-
///
/// @brief Function to encode an error.
/// Error could be from two sources, presence of @c std::error_code means a high level and the @c ts::Errata a callee . Both will
@@ -214,13 +199,6 @@ class yamlcpp_json_encoder
json << YAML::EndMap;
}
- /// Convenience functions to call encode_error.
- static void
- encode_error(std::error_code error, YAML::Emitter &json)
- {
- ts::Errata errata{};
- encode_error(error, errata, json);
- }
/// Convenience functions to call encode_error.
static void
encode_error(ts::Errata const &errata, YAML::Emitter &json)
@@ -228,21 +206,6 @@ class yamlcpp_json_encoder
encode_error({error::RPCErrorCode::ExecutionError}, errata, json);
}
- static void
- encode_error_from_callee(ts::Errata const &errata, YAML::Emitter &json)
- {
- if (!errata.isOK()) {
- json << YAML::Key << "errors";
- json << YAML::BeginSeq;
- for (auto const &err : errata) {
- json << YAML::BeginMap;
- json << YAML::Key << "code" << YAML::Value << err.getCode();
- json << YAML::Key << "message" << YAML::Value << err.text();
- json << YAML::EndMap;
- }
- json << YAML::EndSeq;
- }
- }
///
/// @brief Function to encode a single response(no batch) into an emitter.
///
@@ -257,9 +220,9 @@ class yamlcpp_json_encoder
// Important! As per specs, errors have preference over the result, we ignore result if error was set.
- if (resp.rpcError) {
+ if (resp.error.ec) {
// internal library detected error: Decoding, etc.
- encode_error(resp.rpcError, json);
+ encode_error(resp.error.ec, resp.error.data, json);
}
// Registered handler error: They have set the error on the response from the registered handler. This uses ExecutionError as
// top error.
@@ -280,8 +243,13 @@ class yamlcpp_json_encoder
}
}
- // insert the id.
- encode_id(resp.id, json);
+ // ID. Only if present.
+ if (!resp.id.empty()) {
+ json << YAML::Key << "id" << YAML::Value << resp.id;
+ }
+ // else: We do not insert null as it will break the json, we need literal null and not ~ (as per yaml)
+ // json << YAML::Null;
+
json << YAML::EndMap;
}
diff --git a/mgmt2/rpc/jsonrpc/unit_tests/test_basic_protocol.cc b/mgmt2/rpc/jsonrpc/unit_tests/test_basic_protocol.cc
index ffe272a2d..0732a252b 100644
--- a/mgmt2/rpc/jsonrpc/unit_tests/test_basic_protocol.cc
+++ b/mgmt2/rpc/jsonrpc/unit_tests/test_basic_protocol.cc
@@ -37,19 +37,25 @@ struct JsonRpcUnitTest : rpc::JsonRPCManager {
bool
remove_handler(std::string const &name)
{
- return rpc::JsonRPCManager::remove_handler(name);
+ return base::remove_handler(name);
}
template <typename Func>
bool
add_notification_handler(const std::string &name, Func &&call)
{
- return base::add_notification_handler(name, std::forward<Func>(call), nullptr);
+ return base::add_notification_handler(name, std::forward<Func>(call), nullptr, {});
}
template <typename Func>
bool
add_method_handler(const std::string &name, Func &&call)
{
- return base::add_method_handler(name, std::forward<Func>(call), nullptr);
+ return base::add_method_handler(name, std::forward<Func>(call), nullptr, {});
+ }
+
+ std::optional<std::string>
+ handle_call(std::string const &jsonString)
+ {
+ return base::handle_call(rpc::Context{}, jsonString);
}
};
@@ -63,8 +69,6 @@ test_callback_ok_or_error(std::string_view const &id, YAML::Node const ¶ms)
if (YAML::Node n = params["return_error"]) {
auto yesOrNo = n.as<std::string>();
if (yesOrNo == "yes") {
- // Can we just have a helper for this?
- // resp.errata.push(static_cast<int>(TestErrors::ERR1), "Just an error message to add more meaning to the failure");
resp.errata().push(ErratId, static_cast<int>(TestErrors::ERR1), "Just an error message to add more meaning to the failure");
} else {
resp.result()["ran"] = "ok";
diff --git a/mgmt2/rpc/server/IPCSocketServer.cc b/mgmt2/rpc/server/IPCSocketServer.cc
index ffabea9ff..6176d3a7f 100644
--- a/mgmt2/rpc/server/IPCSocketServer.cc
+++ b/mgmt2/rpc/server/IPCSocketServer.cc
@@ -37,6 +37,8 @@
#include "tscore/Diags.h"
#include "tscore/bwf_std_format.h"
#include "records/I_RecProcess.h"
+#include "tscore/ink_sock.h"
+#include "utils/MgmtSocket.h"
#include <ts/ts.h>
@@ -46,6 +48,7 @@
namespace
{
constexpr size_t MAX_REQUEST_BUFFER_SIZE{32000};
+constexpr auto logTag = "rpc.net";
// Quick check for errors(base on the errno);
bool check_for_transient_errors();
@@ -72,8 +75,6 @@ poll_on_socket(Func &&check_poll_return, std::chrono::milliseconds timeout, int
namespace rpc::comm
{
-static constexpr auto logTag = "rpc.net";
-
IPCSocketServer::~IPCSocketServer()
{
unlink(_conf.sockPathName.c_str());
@@ -172,7 +173,12 @@ IPCSocketServer::run()
if (auto [ok, errStr] = client.read_all(bw); ok) {
const auto json = std::string{bw.data(), bw.size()};
- if (auto response = rpc::JsonRPCManager::instance().handle_call(json); response) {
+ rpc::Context ctx;
+ // we want to make sure the peer's credentials are ok.
+ ctx.get_auth().add_checker(
+ [&](TSRPCHandlerOptions const &opt, ts::Errata &errata) -> void { return late_check_peer_credentials(fd, opt, errata); });
+
+ if (auto response = rpc::JsonRPCManager::instance().handle_call(ctx, json); response) {
// seems a valid response.
if (client.write(*response, ec); ec) {
Debug(logTag, "Error sending the response: %s", ec.message().c_str());
@@ -260,7 +266,14 @@ IPCSocketServer::bind(std::error_code &ec)
return;
}
- mode_t mode = _conf.restrictedAccessApi ? 00700 : 00777;
+ // If the socket is not administratively restricted, check whether we have platform
+ // support. Otherwise, default to making it restricted.
+ bool restricted{true};
+ if (!_conf.restrictedAccessApi) {
+ restricted = !mgmt_has_peereid();
+ }
+
+ mode_t mode = restricted ? 00700 : 00777;
if (chmod(_conf.sockPathName.c_str(), mode) < 0) {
ec = std::make_error_code(static_cast<std::errc>(errno));
return;
@@ -381,6 +394,25 @@ IPCSocketServer::Config::Config()
lockPathName = Layout::relative_to(rundir, "jsonrpc20.lock");
sockPathName = Layout::relative_to(rundir, "jsonrpc20.sock");
}
+
+void
+IPCSocketServer::late_check_peer_credentials(int peedFd, TSRPCHandlerOptions const &options, ts::Errata &errata) const
+{
+ ts::LocalBufferWriter<256> w;
+ // For privileged calls, ensure we have caller credentials and that the caller is privileged.
+ if (mgmt_has_peereid() && options.auth.restricted) {
+ uid_t euid = -1;
+ gid_t egid = -1;
+ if (mgmt_get_peereid(peedFd, &euid, &egid) == -1) {
+ errata.push(1, static_cast<int>(UnauthorizedErrorCode::PEER_CREDENTIALS_ERROR),
+ w.print("Error getting peer credentials: {}\0", ts::bwf::Errno{}).data());
+ } else if (euid != 0 && euid != geteuid()) {
+ errata.push(1, static_cast<int>(UnauthorizedErrorCode::PERMISSION_DENIED),
+ w.print("Denied privileged API access for uid={} gid={}\0", euid, egid).data());
+ }
+ }
+}
+
} // namespace rpc::comm
namespace YAML
@@ -440,4 +472,4 @@ check_for_transient_errors()
return false;
}
}
-} // namespace
\ No newline at end of file
+} // namespace
diff --git a/mgmt2/rpc/server/IPCSocketServer.h b/mgmt2/rpc/server/IPCSocketServer.h
index 2817c95f3..1df85704d 100644
--- a/mgmt2/rpc/server/IPCSocketServer.h
+++ b/mgmt2/rpc/server/IPCSocketServer.h
@@ -46,6 +46,11 @@ namespace rpc::comm
/// Buffer size = 32k
class IPCSocketServer : public BaseCommInterface
{
+ // Error codes to track any unauthorized call to a rpc handler.
+ enum class UnauthorizedErrorCode {
+ PEER_CREDENTIALS_ERROR = 1, ///< Error while trying to read the peer credentials from the unix socket.
+ PERMISSION_DENIED = 2 ///< Client's socket credential didn't wasn't sufficient to execute the method.
+ };
///
/// @brief Connection abstraction class that deals with sending and receiving data from the connected peer.
///
@@ -129,6 +134,7 @@ private:
void bind(std::error_code &ec);
void listen(std::error_code &ec);
void close();
+ void late_check_peer_credentials(int peedFd, TSRPCHandlerOptions const &options, ts::Errata &errata) const;
std::atomic_bool _running;
diff --git a/mgmt2/rpc/server/unit_tests/test_rpcserver.cc b/mgmt2/rpc/server/unit_tests/test_rpcserver.cc
index ee00df856..7649b3b41 100644
--- a/mgmt2/rpc/server/unit_tests/test_rpcserver.cc
+++ b/mgmt2/rpc/server/unit_tests/test_rpcserver.cc
@@ -55,6 +55,13 @@ test_remove_handler(std::string_view name)
{
return rpc::JsonRPCManager::instance().remove_handler(name);
}
+
+template <typename Func>
+inline bool
+add_method_handler(const std::string &name, Func &&call)
+{
+ return rpc::JsonRPCManager::instance().add_method_handler(name, std::forward<Func>(call), nullptr, {});
+}
} // namespace rpc
static const std::string sockPath{"/tmp/jsonrpc20_test.sock"};
static const std::string lockPath{"/tmp/jsonrpc20_test.lock"};
diff --git a/src/traffic_server/HostStatus.cc b/src/traffic_server/HostStatus.cc
index 240f32ade..7579a96f5 100644
--- a/src/traffic_server/HostStatus.cc
+++ b/src/traffic_server/HostStatus.cc
@@ -139,8 +139,10 @@ HostStatus::HostStatus()
ink_rwlock_init(&host_status_rwlock);
// register JSON-RPC methods.
- rpc::add_method_handler("admin_host_set_status", &server_set_status, &rpc::core_ats_rpc_service_provider_handle);
- rpc::add_method_handler("admin_host_get_status", &server_get_status, &rpc::core_ats_rpc_service_provider_handle);
+ rpc::add_method_handler("admin_host_set_status", &server_set_status, &rpc::core_ats_rpc_service_provider_handle,
+ {{rpc::RESTRICTED_API}});
+ rpc::add_method_handler("admin_host_get_status", &server_get_status, &rpc::core_ats_rpc_service_provider_handle,
+ {{rpc::NON_RESTRICTED_API}});
}
HostStatus::~HostStatus()
diff --git a/src/traffic_server/RpcAdminPubHandlers.cc b/src/traffic_server/RpcAdminPubHandlers.cc
index d97d668aa..8ca8b9d70 100644
--- a/src/traffic_server/RpcAdminPubHandlers.cc
+++ b/src/traffic_server/RpcAdminPubHandlers.cc
@@ -35,29 +35,40 @@ register_admin_jsonrpc_handlers()
{
// Config
using namespace rpc::handlers::config;
- rpc::add_method_handler("admin_config_set_records", &set_config_records, &core_ats_rpc_service_provider_handle);
- rpc::add_method_handler("admin_config_reload", &reload_config, &core_ats_rpc_service_provider_handle);
+ rpc::add_method_handler("admin_config_set_records", &set_config_records, &core_ats_rpc_service_provider_handle,
+ {{rpc::RESTRICTED_API}});
+ rpc::add_method_handler("admin_config_reload", &reload_config, &core_ats_rpc_service_provider_handle, {{rpc::RESTRICTED_API}});
// Records
using namespace rpc::handlers::records;
- rpc::add_method_handler("admin_lookup_records", &lookup_records, &core_ats_rpc_service_provider_handle);
- rpc::add_method_handler("admin_clear_all_metrics_records", &clear_all_metrics_records, &core_ats_rpc_service_provider_handle);
- rpc::add_method_handler("admin_clear_metrics_records", &clear_metrics_records, &core_ats_rpc_service_provider_handle);
+ rpc::add_method_handler("admin_lookup_records", &lookup_records, &core_ats_rpc_service_provider_handle,
+ {{rpc::NON_RESTRICTED_API}});
+ rpc::add_method_handler("admin_clear_all_metrics_records", &clear_all_metrics_records, &core_ats_rpc_service_provider_handle,
+ {{rpc::RESTRICTED_API}});
+ rpc::add_method_handler("admin_clear_metrics_records", &clear_metrics_records, &core_ats_rpc_service_provider_handle,
+ {{rpc::RESTRICTED_API}});
// plugin
using namespace rpc::handlers::plugins;
- rpc::add_method_handler("admin_plugin_send_basic_msg", &plugin_send_basic_msg, &core_ats_rpc_service_provider_handle);
+ rpc::add_method_handler("admin_plugin_send_basic_msg", &plugin_send_basic_msg, &core_ats_rpc_service_provider_handle,
+ {{rpc::RESTRICTED_API}});
// server
using namespace rpc::handlers::server;
- rpc::add_method_handler("admin_server_start_drain", &server_start_drain, &core_ats_rpc_service_provider_handle);
- rpc::add_method_handler("admin_server_stop_drain", &server_stop_drain, &core_ats_rpc_service_provider_handle);
- rpc::add_notification_handler("admin_server_shutdown", &server_shutdown, &core_ats_rpc_service_provider_handle);
- rpc::add_notification_handler("admin_server_restart", &server_shutdown, &core_ats_rpc_service_provider_handle);
+ rpc::add_method_handler("admin_server_start_drain", &server_start_drain, &core_ats_rpc_service_provider_handle,
+ {{rpc::RESTRICTED_API}});
+ rpc::add_method_handler("admin_server_stop_drain", &server_stop_drain, &core_ats_rpc_service_provider_handle,
+ {{rpc::RESTRICTED_API}});
+ rpc::add_notification_handler("admin_server_shutdown", &server_shutdown, &core_ats_rpc_service_provider_handle,
+ {{rpc::RESTRICTED_API}});
+ rpc::add_notification_handler("admin_server_restart", &server_shutdown, &core_ats_rpc_service_provider_handle,
+ {{rpc::RESTRICTED_API}});
// storage
using namespace rpc::handlers::storage;
- rpc::add_method_handler("admin_storage_set_device_offline", &set_storage_offline, &core_ats_rpc_service_provider_handle);
- rpc::add_method_handler("admin_storage_get_device_status", &get_storage_status, &core_ats_rpc_service_provider_handle);
+ rpc::add_method_handler("admin_storage_set_device_offline", &set_storage_offline, &core_ats_rpc_service_provider_handle,
+ {{rpc::RESTRICTED_API}});
+ rpc::add_method_handler("admin_storage_get_device_status", &get_storage_status, &core_ats_rpc_service_provider_handle,
+ {{rpc::NON_RESTRICTED_API}});
}
} // namespace rpc::admin