(First draft) |
|||
| Line 25: | Line 25: | ||
===Defining the problem=== | ===Defining the problem=== | ||
| − | Applications running under root privileges has always been a major problem, especially in Linux. | + | Applications running under root privileges has always been a major problem, especially in Linux. When an application needed to perform a privileged operation, it had to be started as root, or even worse had a setuid bit. Both approaches were suboptimal under every point of view. |
| + | |||
| + | Using an helper was possible, although the security of the helper itself was very bad as there was no way to identify the caller, hence authenticating a possible pipe, and the approach wasn't really flexible and easy to implement. | ||
===Defining the solution=== | ===Defining the solution=== | ||
| Line 35: | Line 37: | ||
The helper, before doing anything, checks if the caller (identified through the IPC call) is authorized through the authorization system. If the authorization is negative, the helper rejects the call, otherwise executes it. This system, despite its semplicity, offers an extremely high level of security and also allows the critical part of your code to be run separately. | The helper, before doing anything, checks if the caller (identified through the IPC call) is authorized through the authorization system. If the authorization is negative, the helper rejects the call, otherwise executes it. This system, despite its semplicity, offers an extremely high level of security and also allows the critical part of your code to be run separately. | ||
| − | + | ===How KAuth implements this solution=== | |
KAuth works with two different static backends (chosen at compile time in KDELibs and not interchangeable afterwards): one for the authorization system, the other for elevation and IPC (which usually are provided by the same framework). | KAuth works with two different static backends (chosen at compile time in KDELibs and not interchangeable afterwards): one for the authorization system, the other for elevation and IPC (which usually are provided by the same framework). | ||
| + | |||
| + | The IPC backend takes care of spawning the helper, performing all the needed checks in the main application and in the helper, including authorization, and lets the helper start his actions (as you will see in the next paragraph) only when all the checks and authorizations went fine. | ||
| + | |||
| + | It also creates a pipe which can be used from the helper (and in the future from the application as well), that streams encoded binary data (in the future it will be possible to have it encrypted as well) from/to the application/helper. | ||
| + | |||
| + | ==Creating a helper with KAuth== | ||
| + | An helper, from an implementation point of view, is a single class inheriting from QObject. | ||
| + | |||
| + | ===Helper basics=== | ||
| + | Each helper can implement an unlimited number of actions '''given that they belong to the same namespace'''. You can see an analogy with ''.actions files'': in fact each helper is meant to be associated with one and only one .actions file and vice-versa. | ||
| + | |||
| + | The helper will be identified by the namespace of the actions it implements. So if you're implementing an helper for ''org.kde.auth.example.read'', ''org.kde.auth.example.write'' and ''org.kde.auth.example.longaction'', your helper will be identified with '''org.kde.auth.example'''. | ||
| + | |||
| + | Each action is represented by a public slot you have to declare explicitely, named after the action's name. | ||
| + | |||
| + | ===The Helper's header=== | ||
| + | Here's how a possible header can look like: | ||
| + | |||
| + | <code cpp> | ||
| + | #include <kauth.h> | ||
| + | |||
| + | using namespace KAuth; | ||
| + | |||
| + | class MyHelper : public QObject | ||
| + | { | ||
| + | Q_OBJECT | ||
| + | |||
| + | public slots: | ||
| + | ActionReply read(QVariantMap args); | ||
| + | ActionReply write(QVariantMap args); | ||
| + | ActionReply longaction(QVariantMap args); | ||
| + | }; | ||
| + | </code> | ||
| + | |||
| + | The first thing you should note is ''using namespace KAuth''. This directive is compulsory as each action's signature must look like this: | ||
| + | |||
| + | ActionReply <actionname>(QVariantMap args); | ||
| + | |||
| + | This is fundamental for your helper to work. As you can see, we defined the three actions we want to implement through the helper. | ||
| + | |||
| + | ===Implementing the action=== | ||
| + | Let's try implementing the ''read'' action: | ||
| + | |||
| + | <code cpp> | ||
| + | ActionReply MyHelper::read(QVariantMap args) | ||
| + | { | ||
| + | ActionReply reply; | ||
| + | QString filename = args["filename"].toString(); | ||
| + | QFile file(filename); | ||
| + | |||
| + | if (!file.open(QIODevice::ReadOnly)) { | ||
| + | reply = ActionReply::HelperErrorReply; | ||
| + | reply.setErrorCode(file.error()); | ||
| + | |||
| + | return reply; | ||
| + | } | ||
| + | |||
| + | QTextStream stream(&file); | ||
| + | QString contents; | ||
| + | stream >> contents; | ||
| + | |||
| + | QVariantMap retdata; | ||
| + | retdata["contents"] = contents; | ||
| + | reply.setData(contents); | ||
| + | return reply; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | The QVariantMap received by read is nothing else but the arguments set through ''Action::setArguments()'' by the caller. This way you can call any action with any custom argument. The action above opens a file and streams back the contents to the caller. | ||
| + | |||
| + | To report back to the caller | ||
Contents |
Languages: عربي | Asturianu | Català | Česky | Kaszëbsczi | Dansk | Deutsch | English | Esperanto | Español | Eesti | فارسی | Suomi | Français | Galego | Italiano | 日本語 | 한국어 | Norwegian | Polski | Português Brasileiro | Română | Русский | Svenska | Slovenčina | Slovenščina | српски | Türkçe | Tiếng Việt | Українська | 简体中文 | 繁體中文
| Tutorial Series | KAuth Tutorial |
| Previous | KAuth Basics |
| What's Next | Creating a KCM requiring authorization upon saving |
| Further Reading | KAuth::Action Class Reference |
If you're reading this tutorial, it's probably because you need to perform a (hopefully) small piece of code in your application as a privileged user. Before moving on, please consider the following things in your approach:
Even if these technical details are not necessary for you to use KAuth, it is quite important for you to understand what's going on under the hood and how KAuth approaches privilege escalation.
Applications running under root privileges has always been a major problem, especially in Linux. When an application needed to perform a privileged operation, it had to be started as root, or even worse had a setuid bit. Both approaches were suboptimal under every point of view.
Using an helper was possible, although the security of the helper itself was very bad as there was no way to identify the caller, hence authenticating a possible pipe, and the approach wasn't really flexible and easy to implement.
Frameworks like polkit or Authorization Services opened the door to a possible new approach when dealing with applications that need to perform some privileged actions, combined with privilege escalation tools, which already existed for many platforms. KAuth combines internally authorization, escalation and other features to provide developers with an extremely easy tool to perform privileged actions staying as secure as possible.
You already know what's an helper from KAuth Basics, now let's see how it works. The process is spawned already elevated (as root, for example), or the authorization happens contestually with the elevation (not very common). KAuth uses the first approach.
The helper, before doing anything, checks if the caller (identified through the IPC call) is authorized through the authorization system. If the authorization is negative, the helper rejects the call, otherwise executes it. This system, despite its semplicity, offers an extremely high level of security and also allows the critical part of your code to be run separately.
KAuth works with two different static backends (chosen at compile time in KDELibs and not interchangeable afterwards): one for the authorization system, the other for elevation and IPC (which usually are provided by the same framework).
The IPC backend takes care of spawning the helper, performing all the needed checks in the main application and in the helper, including authorization, and lets the helper start his actions (as you will see in the next paragraph) only when all the checks and authorizations went fine.
It also creates a pipe which can be used from the helper (and in the future from the application as well), that streams encoded binary data (in the future it will be possible to have it encrypted as well) from/to the application/helper.
An helper, from an implementation point of view, is a single class inheriting from QObject.
Each helper can implement an unlimited number of actions given that they belong to the same namespace. You can see an analogy with .actions files: in fact each helper is meant to be associated with one and only one .actions file and vice-versa.
The helper will be identified by the namespace of the actions it implements. So if you're implementing an helper for org.kde.auth.example.read, org.kde.auth.example.write and org.kde.auth.example.longaction, your helper will be identified with org.kde.auth.example.
Each action is represented by a public slot you have to declare explicitely, named after the action's name.
Here's how a possible header can look like:
using namespace KAuth;
class MyHelper : public QObject {
Q_OBJECT
public slots:
ActionReply read(QVariantMap args);
ActionReply write(QVariantMap args);
ActionReply longaction(QVariantMap args);
};
The first thing you should note is using namespace KAuth. This directive is compulsory as each action's signature must look like this:
ActionReply <actionname>(QVariantMap args);
This is fundamental for your helper to work. As you can see, we defined the three actions we want to implement through the helper.
Let's try implementing the read action:
ActionReply MyHelper::read(QVariantMap args)
{
ActionReply reply; QString filename = args["filename"].toString(); QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
reply = ActionReply::HelperErrorReply;
reply.setErrorCode(file.error());
return reply; }
QTextStream stream(&file); QString contents; stream >> contents;
QVariantMap retdata; retdata["contents"] = contents; reply.setData(contents); return reply;
}
The QVariantMap received by read is nothing else but the arguments set through Action::setArguments() by the caller. This way you can call any action with any custom argument. The action above opens a file and streams back the contents to the caller.
To report back to the caller