Development/Tutorials/KAuth/Helper HowTo: Difference between revisions

From KDE TechBase
No edit summary
(Mark for updating)
 
(19 intermediate revisions by 5 users not shown)
Line 1: Line 1:
{{Template:I18n/Language Navigation Bar|Development/Tutorials/KAuth/KAuth Actions}}
 


{{TutorialBrowser|
{{TutorialBrowser|
Line 13: Line 13:
reading=[http://api.kde.org/4.x-api/kdelibs-apidocs/kdecore/html/namespaceKAuth_1_1HelperSupport.html KAuth::HelperSupport Namespace Reference]
reading=[http://api.kde.org/4.x-api/kdelibs-apidocs/kdecore/html/namespaceKAuth_1_1HelperSupport.html KAuth::HelperSupport Namespace Reference]
}}
}}
{{Review|Port to KF5}}


==Before you start==
==Before you start==
Line 18: Line 20:


*'''Include in the helper just the strictly needed code'''. Each line you're writing is another line that might cause bugs in a privileged environment. Before including any code that could be in the main application (KAuth offers pipes also for this), think twice and ask yourself if it's worth it.
*'''Include in the helper just the strictly needed code'''. Each line you're writing is another line that might cause bugs in a privileged environment. Before including any code that could be in the main application (KAuth offers pipes also for this), think twice and ask yourself if it's worth it.
*'''Link against and use as less libraries as you can'''. The helper itself requires KDECore. Adding other libraries can lead to possible risks, as written above. Again, think twice before linking against a library if you can split that part out of the helper. Also, please try to avoid using parts of KDE that require spawning a new session (such as KIO), for obvious reasons.
*'''Link against and use as few libraries as you can'''. The helper itself requires KDECore. Adding other libraries can lead to possible risks, as written above. Again, think twice before linking against a library if you can split that part out of the helper. Also, please try to avoid using parts of KDE that require spawning a new session (such as KIO), for obvious reasons.
*'''Break down actions'''. KAuth gives you the possibility of breaking down the jobs in actions, as you learned in the previous tutorials. Try to fine-grain as possible the actions, so that system administrators will have an higher flexibility in giving permissions to users.
*'''Break down actions'''. KAuth gives you the possibility of breaking down the jobs in actions, as you learned in the previous tutorials. Try to fine-grain as possible the actions, so that system administrators will have an higher flexibility in giving permissions to users.


Line 25: Line 27:


===Defining the problem===
===Defining the problem===
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.
Applications running under root privileges have 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.
Using a 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 or easy to implement.


===Defining the solution===
===Defining the solution===
Line 33: Line 35:


====The "helper" concept, and how escalation and authorization are combined====
====The "helper" concept, and how escalation and authorization are combined====
You already know what's an helper from [[Development/Tutorials/KAuth/KAuth_Basics|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.
You already know what's a helper from [[Development/Tutorials/KAuth/KAuth_Basics|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.
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 simplicity, 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===
===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 Frameworks 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.
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.
Line 45: Line 47:


==Creating a helper with KAuth==
==Creating a helper with KAuth==
An helper, from an implementation point of view, is a single class inheriting from QObject.
A helper, from an implementation point of view, is a single class inheriting from QObject.


===Helper basics===
===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.
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'''.
The helper will be identified by the namespace of the actions it implements. So if you're implementing a 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.
Each action is represented by a public slot you have to declare explicitely, named after the action's name.
Line 57: Line 59:
Here's how a possible header can look like:
Here's how a possible header can look like:


<code cpp>
<syntaxhighlight lang="cpp">
#include <kauth.h>
#include <kauth.h>


Line 71: Line 73:
         ActionReply longaction(QVariantMap args);
         ActionReply longaction(QVariantMap args);
};
};
</code>
</syntaxhighlight>


The first thing you should note is ''using namespace KAuth''. This directive is compulsory as each action's signature must look like this:
The first thing you should note is ''using namespace KAuth''. This directive is compulsory as each action's signature must look like this:
Line 82: Line 84:
Let's try implementing the ''read'' action:
Let's try implementing the ''read'' action:


<code cpp>
<syntaxhighlight lang="cpp">
ActionReply MyHelper::read(QVariantMap args)
ActionReply MyHelper::read(QVariantMap args)
{
{
Line 97: Line 99:


     QTextStream stream(&file);
     QTextStream stream(&file);
     QString contents;
     QString contents = stream.readAll();
    stream >> contents;


     QVariantMap retdata;
     QVariantMap retdata;
     retdata["contents"] = contents;
     retdata["contents"] = contents;


     reply.setData(contents);
     reply.setData(retdata);
     return reply;
     return reply;
}
}
</code>
</syntaxhighlight>


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.
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.
Line 115: Line 116:
If the action you have to perform is long, you probably want to report the application some kind of progress or real time data. Let's see how to do it by implementing ''longaction''.
If the action you have to perform is long, you probably want to report the application some kind of progress or real time data. Let's see how to do it by implementing ''longaction''.


<code cpp>
<syntaxhighlight lang="cpp">
ActionReply MyHelper::longaction(QVariantMap args)
ActionReply MyHelper::longaction(QVariantMap args)
{
{
Line 132: Line 133:
     return ActionReply::SuccessReply;
     return ActionReply::SuccessReply;
}
}
</code>
</syntaxhighlight>


There are two things to note here: the first is that the helper can accept stop requests, as you have seen in the [[Development/Tutorials/KAuth/KAuth_Actions|KAuth actions tutorial]], but it's up to you whether to accept them or not. You can check if you have a stop request pending through HelperSupport::isStopped and then act accordingly.
There are two things to note here: the first is that the helper can accept stop requests, as you have seen in the [[Development/Tutorials/KAuth/KAuth_Actions|KAuth actions tutorial]], but it's up to you whether to accept them or not. You can check if you have a stop request pending through HelperSupport::isStopped and then act accordingly.


Then there's HelperSupport::progressStep. This has a convenience implementation that streams a percentage, and one that streams a QVariantMap. Calling one of these causes the corresponding signal in ''ActionWatcher'' to be triggered in the caller.
Then there's HelperSupport::progressStep. This has a convenience implementation that streams a percentage, and one that streams a QVariantMap. Calling one of these causes the percent(int) signal to be called in the corresponding KJob object.


===Other useful features===
===Other useful features===
The helper supports remote debugging through qDebug. This means that every call to qDebug() and friends will be reported to the application, and printed using the same qt debugging system, with the same debug level. So such a call in any actions of your helper:
The helper supports remote debugging through qDebug. This means that every call to qDebug() and friends will be reported to the application, and printed using the same qt debugging system, with the same debug level. So such a call in any actions of your helper:


<code cpp> qDebug() << "I'm in the helper";</code>
<syntaxhighlight lang="cpp"> qDebug() << "I'm in the helper";</syntaxhighlight>


will print the following in the main application:
will print the following in the main application:
Line 152: Line 153:
When you are done with your helper implementation, you have to add some small things to it and to your buildsystem to make it work. First of all, at the bottom of your helper's cpp file, you'll have to put the following macro:
When you are done with your helper implementation, you have to add some small things to it and to your buildsystem to make it work. First of all, at the bottom of your helper's cpp file, you'll have to put the following macro:


<code cpp>KDE4_AUTH_HELPER_MAIN("org.kde.auth.example", MyHelper)</code>
<syntaxhighlight lang="cpp">KAUTH_HELPER_MAIN("org.kde.auth.example", MyHelper)</syntaxhighlight>


Which accepts as a parameter the helper identifier, and the class name of the helper itself. This macro will take care of generating the needed main function for the helper and all the required code for interaction with the authorization system.
Which accepts as a parameter the helper identifier, and the class name of the helper itself. This macro will take care of generating the needed main function for the helper and all the required code for interaction with the authorization system.
Line 158: Line 159:
At this point, let's see how to compile the helper:
At this point, let's see how to compile the helper:


<code cmake>
<syntaxhighlight lang="cmake">
kde4_add_executable(kauth_example_helper  helper.cpp)
add_executable(kauth_example_helper  helper.cpp)
target_link_libraries(kauth_example_helper ${KDE4_KDECORE_LIBS})
target_link_libraries(kauth_example_helper ${KDE4_KDECORE_LIBS})
install(TARGETS kauth_example_helper DESTINATION ${LIBEXEC_INSTALL_DIR})
install(TARGETS kauth_example_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
 
kauth_install_helper_files(kauth_example_helper org.kde.auth.example root)
</syntaxhighlight>
 
Let's see each macro one at a time: of course the first two create the helper executable and link it to the needed libraries (only QtCore is needed for a basic helper).
 
The install macro '''has to install the helper in ${KAUTH_HELPER_INSTALL_DIR}'''. Installing it elsewhere will prevent the helper from working.


kde4_install_auth_helper_files(kauth_example_helper org.kde.auth.example root)
''kauth_install_helper_files'' generates and installs all the needed files for the helper to work: this macro has the following format:
</code>


Let's see each macro one at a time: of course the first two create the helper executable and link it to the needed libraries (only kdecore is needed for a basic helper).
kauth_install_helper_files(<helper_cmake_target> <helper_id> <user>)


The install macro '''has to install the helper in ${LIBEXEC_INSTALL_DIR}'''. Installing it elsewhere will prevent the helper from working.
''user'' is the user under which the helper will be run, in this case root. But if, for example, you're creating a helper to modify a file owned and editable by the user "apache", you can make the helper be run by "apache" and be even more secure.


''kde4_install_auth_helper_files'' generates and installs all the needed files for the helper to work: this macro has the following format:
== Conclusion ==
In the end, the main steps for creating an helper are:


kde4_install_auth_helper_files(<helper_cmake_target> <helper_id> <user>)
*Creating a corresponding .actions file
*Implementing the helper class
*Adding KAUTH_HELPER_MAIN macro to the helper's cpp file
*Build the helper as described above


''user'' is the user under which the helper will be run, in this case root. But if, for example, you're creating an helper to modify a file owned and editable by the user "apache", you can make the helper be run by "apache" and be even more secure.
As you have seen the process is quite straightforward and well matched, both API and behavior wise, to what happens in the caller. Remember to keep in mind all the needed conventions and caveats, mainly for the action namespaces and the header's signatures.

Latest revision as of 11:44, 31 May 2019


Creating a KAuth helper to perform a privileged action
Tutorial Series   KAuth Tutorial
Previous   KAuth Basics
What's Next   Creating a KCM requiring authorization upon saving
Further Reading   KAuth::HelperSupport Namespace Reference
Warning
This page needs a review and probably holds information that needs to be fixed.

Parts to be reviewed:

Port to KF5

Before you start

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:

  • Include in the helper just the strictly needed code. Each line you're writing is another line that might cause bugs in a privileged environment. Before including any code that could be in the main application (KAuth offers pipes also for this), think twice and ask yourself if it's worth it.
  • Link against and use as few libraries as you can. The helper itself requires KDECore. Adding other libraries can lead to possible risks, as written above. Again, think twice before linking against a library if you can split that part out of the helper. Also, please try to avoid using parts of KDE that require spawning a new session (such as KIO), for obvious reasons.
  • Break down actions. KAuth gives you the possibility of breaking down the jobs in actions, as you learned in the previous tutorials. Try to fine-grain as possible the actions, so that system administrators will have an higher flexibility in giving permissions to users.

How the helper mechanism works

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.

Defining the problem

Applications running under root privileges have 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 a 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 or easy to implement.

Defining the solution

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.

The "helper" concept, and how escalation and authorization are combined

You already know what's a 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 simplicity, 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 Frameworks 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

A 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 a 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:

#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);
};

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:

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.readAll();

    QVariantMap retdata;
    retdata["contents"] = contents;

    reply.setData(retdata);
    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 a KAuth::ActionReply is used. In your helper implementation, you can return either an HelperErrorReply or a SuccessReply, which is the default value. In an error reply you can specify an error code and a description, and in both you're able to return the application some data through ActionReply::setData

Reporting progress in real time

If the action you have to perform is long, you probably want to report the application some kind of progress or real time data. Let's see how to do it by implementing longaction.

ActionReply MyHelper::longaction(QVariantMap args)
{
    for (int i = 1; i <= 100; i++) {
       if (HelperSupport::isStopped()) {
          break;
       }
       if (i == 54) {
           QVariantMap data;
           data["string"] = "Some very important data";
           HelperSupport::progressStep(data);
       }
       HelperSupport::progressStep(i);
       usleep(250000);
    }
    return ActionReply::SuccessReply;
}

There are two things to note here: the first is that the helper can accept stop requests, as you have seen in the KAuth actions tutorial, but it's up to you whether to accept them or not. You can check if you have a stop request pending through HelperSupport::isStopped and then act accordingly.

Then there's HelperSupport::progressStep. This has a convenience implementation that streams a percentage, and one that streams a QVariantMap. Calling one of these causes the percent(int) signal to be called in the corresponding KJob object.

Other useful features

The helper supports remote debugging through qDebug. This means that every call to qDebug() and friends will be reported to the application, and printed using the same qt debugging system, with the same debug level. So such a call in any actions of your helper:

 qDebug() << "I'm in the helper";

will print the following in the main application:

Debug message from the helper: I'm in the helper

Remember that the debug level is preserved, so if you use qFatal() you won't only abort the helper (which isn't suggested anyway), but also the application.

Needed macros and CMake

When you are done with your helper implementation, you have to add some small things to it and to your buildsystem to make it work. First of all, at the bottom of your helper's cpp file, you'll have to put the following macro:

KAUTH_HELPER_MAIN("org.kde.auth.example", MyHelper)

Which accepts as a parameter the helper identifier, and the class name of the helper itself. This macro will take care of generating the needed main function for the helper and all the required code for interaction with the authorization system.

At this point, let's see how to compile the helper:

add_executable(kauth_example_helper  helper.cpp)
target_link_libraries(kauth_example_helper ${KDE4_KDECORE_LIBS})
install(TARGETS kauth_example_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR})

kauth_install_helper_files(kauth_example_helper org.kde.auth.example root)

Let's see each macro one at a time: of course the first two create the helper executable and link it to the needed libraries (only QtCore is needed for a basic helper).

The install macro has to install the helper in ${KAUTH_HELPER_INSTALL_DIR}. Installing it elsewhere will prevent the helper from working.

kauth_install_helper_files generates and installs all the needed files for the helper to work: this macro has the following format:

kauth_install_helper_files(<helper_cmake_target> <helper_id> <user>)

user is the user under which the helper will be run, in this case root. But if, for example, you're creating a helper to modify a file owned and editable by the user "apache", you can make the helper be run by "apache" and be even more secure.

Conclusion

In the end, the main steps for creating an helper are:

  • Creating a corresponding .actions file
  • Implementing the helper class
  • Adding KAUTH_HELPER_MAIN macro to the helper's cpp file
  • Build the helper as described above

As you have seen the process is quite straightforward and well matched, both API and behavior wise, to what happens in the caller. Remember to keep in mind all the needed conventions and caveats, mainly for the action namespaces and the header's signatures.