Development/Tutorials/PolicyKit/Helper HowTo: Difference between revisions
No edit summary |
No edit summary |
||
(10 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
{{TutorialBrowser| | {{TutorialBrowser| | ||
Line 13: | Line 13: | ||
reading=None | reading=None | ||
}} | }} | ||
{{ | |||
Remember|3=WARNING|1=THIS TUTORIAL IS OBSOLETE. PLEASE USE THE NEW KAUTH FRAMEWORK INSTEAD (TUTORIAL WILL BE UPDATED SOON)}} | |||
== Before you start == | == Before you start == | ||
Line 22: | Line 25: | ||
If you are aware of this, and you're also sure that your application actually needs root privileges, you can go on reading. | If you are aware of this, and you're also sure that your application actually needs root privileges, you can go on reading. | ||
This tutorial assumes you're using Polkit-Qt >= 0.9.2 | |||
== What we need to do == | == What we need to do == | ||
What is cool about PolicyKit and this approach is that we need to write a minimum portion of code, don't need hacks or executon bits, and we actually get root privileges for a minimum portion of code. Suppose we still have our foo application we saw in the precedent Tutorial. From our .policy file, we know action2 actually does something that requires authentication as root. In fact, the following lines of code in action2 definitely require root privileges: | What is cool about PolicyKit and this approach is that we need to write a minimum portion of code, don't need hacks or executon bits, and we actually get root privileges for a minimum portion of code. Suppose we still have our foo application we saw in the precedent Tutorial. From our .policy file, we know action2 actually does something that requires authentication as root. In fact, the following lines of code in action2 definitely require root privileges: | ||
< | <syntaxhighlight lang="cpp"> | ||
eraseHardDrive(); | eraseHardDrive(); | ||
killUser(); | killUser(); | ||
detonatePC(); | detonatePC(); | ||
runAsFastAsYouCan(); | runAsFastAsYouCan(); | ||
</ | </syntaxhighlight> | ||
But foo is a huge program that runs as standard user apart from those lines. So the option is to make it run as root as a whole, or split those 4 lines in a different program running as root. That is the approach that we will take in this tutorial. | But foo is a huge program that runs as standard user apart from those lines. So the option is to make it run as root as a whole, or split those 4 lines in a different program running as root. That is the approach that we will take in this tutorial. | ||
Line 49: | Line 54: | ||
We suppose you already know how to create DBus interfaces from XML files. In our interface we need to specify the means of communication between the helper and the main application. Consider that the only way we have to talk to our helper is DBus, so we need to rely on signals and slots streamed through the Bus. In our foo application, we have a signal that tells us when the helper has completed the action. So: | We suppose you already know how to create DBus interfaces from XML files. In our interface we need to specify the means of communication between the helper and the main application. Consider that the only way we have to talk to our helper is DBus, so we need to rely on signals and slots streamed through the Bus. In our foo application, we have a signal that tells us when the helper has completed the action. So: | ||
< | <syntaxhighlight lang="xml"> | ||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> | <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> | ||
<node> | <node> | ||
Line 59: | Line 64: | ||
</interface> | </interface> | ||
</node> | </node> | ||
</ | </syntaxhighlight> | ||
And that's enough. Remember to specify in the interface any needed signal/slot to communicate with the main application or library. | And that's enough. Remember to specify in the interface any needed signal/slot to communicate with the main application or library. | ||
Line 68: | Line 73: | ||
Our helper will register itself on the system bus, as the session bus is reserved for the current user. DBus by default does not allow to register names on the System Bus, so we need a policy file for it. That's how it looks: | Our helper will register itself on the system bus, as the session bus is reserved for the current user. DBus by default does not allow to register names on the System Bus, so we need a policy file for it. That's how it looks: | ||
< | <syntaxhighlight lang="xml"> | ||
<!DOCTYPE busconfig PUBLIC | <!DOCTYPE busconfig PUBLIC | ||
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" | "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" | ||
Line 87: | Line 92: | ||
</busconfig> | </busconfig> | ||
</ | </syntaxhighlight> | ||
What does this file implies? It tells DBus that only root can register the name org.kde.foohelper on the System Bus (so that we avoid misusage), and anyone is allowed to call or receive signals from it. Don't worry about security: this will come later. | What does this file implies? It tells DBus that only root can register the name org.kde.foohelper on the System Bus (so that we avoid misusage), and anyone is allowed to call or receive signals from it. Don't worry about security: this will come later. | ||
Line 96: | Line 101: | ||
This is where the fun starts. DBus gives us an impressive and powerful functionality: DBus activation. By defining a simple .service file, DBus will take care of activating the interface (and so the program) for us. Let's see this in detail: | This is where the fun starts. DBus gives us an impressive and powerful functionality: DBus activation. By defining a simple .service file, DBus will take care of activating the interface (and so the program) for us. Let's see this in detail: | ||
< | <syntaxhighlight lang="ini"> | ||
[D-BUS Service] | [D-BUS Service] | ||
Name=org.kde.foohelper | Name=org.kde.foohelper | ||
Exec=@LIBEXEC_INSTALL_DIR@/foohelper | Exec=@LIBEXEC_INSTALL_DIR@/foohelper | ||
User=root | User=root | ||
</ | </syntaxhighlight> | ||
You might already have understood what this files tells DBus to do: whenever someone calls a method on org.kde.foohelper, if the interface is not present on the bus, DBus takes care of starting foohelper, that will provide the interface, and then call the method. The magic of starting it as root happens by appending "User=root". There is nothing else you have to do: now your helper is ready to be auto-activated as the root user. | You might already have understood what this files tells DBus to do: whenever someone calls a method on org.kde.foohelper, if the interface is not present on the bus, DBus takes care of starting foohelper, that will provide the interface, and then call the method. The magic of starting it as root happens by appending "User=root". There is nothing else you have to do: now your helper is ready to be auto-activated as the root user. | ||
Line 114: | Line 119: | ||
Our helper will consist of a single main class. Let's see how to do it: | Our helper will consist of a single main class. Let's see how to do it: | ||
< | <syntaxhighlight lang="cpp"> | ||
class FooHelper : public QObject, protected QDBusContext | class FooHelper : public QObject, protected QDBusContext | ||
{ | { | ||
Line 128: | Line 133: | ||
signals: | signals: | ||
void action2Completed(); | void action2Completed(); | ||
</ | </syntaxhighlight> | ||
The inheritance from QObject can be changed as you like, but '''inheriting from QDBusContext is compulsory.''' You'll understand why very soon. Let's define our constructor: | The inheritance from QObject can be changed as you like, but '''inheriting from QDBusContext is compulsory.''' You'll understand why very soon. Let's define our constructor: | ||
< | <syntaxhighlight lang="cpp"> | ||
(void) new FooHelperAdaptor(this); | (void) new FooHelperAdaptor(this); | ||
if (!QDBusConnection::systemBus().registerService("org.kde.foohelper")) { | if (!QDBusConnection::systemBus().registerService("org.kde.foohelper")) { | ||
Line 143: | Line 148: | ||
QCoreApplication::instance()->quit(); | QCoreApplication::instance()->quit(); | ||
} | } | ||
</ | </syntaxhighlight> | ||
We simply connect to the '''system bus (be careful, not the session one!)''', and if something fail we quit, because it's quite likely that we're going over something weird. Now that we have done that and our application is inside it's main loop, it's time to see what action2() should do: | We simply connect to the '''system bus (be careful, not the session one!)''', and if something fail we quit, because it's quite likely that we're going over something weird. Now that we have done that and our application is inside it's main loop, it's time to see what action2() should do: | ||
< | <syntaxhighlight lang="cpp"> | ||
void FooHelper::action2() | void FooHelper::action2() | ||
{ | { | ||
qDebug() << "Starting DB Update"; | qDebug() << "Starting DB Update"; | ||
PolkitQt::Auth::Result result; | |||
result = PolkitQt::Auth::isCallerAuthorized("org.kde.foo.action2", | result = PolkitQt::Auth::isCallerAuthorized("org.kde.foo.action2", | ||
message().service(), | message().service(), | ||
true); | true); | ||
if (result == | if (result == PolkitQt::Auth::Yes) { | ||
qDebug() << message().service() << QString(" authorized"); | qDebug() << message().service() << QString(" authorized"); | ||
} else { | } else { | ||
Line 173: | Line 178: | ||
emit action2Completed(); | emit action2Completed(); | ||
} | } | ||
</ | </syntaxhighlight> | ||
Let's see this in detail. The '''FIRST THING you always have to do is calling the authorization check, otherwise everything will be useless'''. As you can see, we are using message().service() from QDBusContext, hence the compulsory inheritance. What we are doing boils down to: | Let's see this in detail. The '''FIRST THING you always have to do is calling the authorization check, otherwise everything will be useless'''. As you can see, we are using message().service() from QDBusContext, hence the compulsory inheritance. What we are doing boils down to: | ||
* Check through Polkit-qt if the caller was authorized. Using the DBus name also grants us that the action can be called through DBus only. | * Check through Polkit-qt if the caller was authorized. Using the DBus name also grants us that the action can be called through DBus only. | ||
* If the result is not | * If the result is not Auth::Yes, we simply quit, as we are not authorized to do the action. You can obviously provide means of authentication here, but we are supposing (in this tutorial) that you are doing this from the main application | ||
So the security happens here: if PolicyKit says no, we simply quit out. This way, only authorized application will be able to get through the real method. | So the security happens here: if PolicyKit says no, we simply quit out. This way, only authorized application will be able to get through the real method. | ||
Line 195: | Line 200: | ||
Sure, we could use some signaling to trigger authorization, but Polkit-qt already provides us a really nice and easy method to manage the authentication automatically. Let's see how: | Sure, we could use some signaling to trigger authorization, but Polkit-qt already provides us a really nice and easy method to manage the authentication automatically. Let's see how: | ||
< | <syntaxhighlight lang="cpp"> | ||
PolkitQt::ActionButton *bt = new PolkitQt::ActionButton(m_action2Button, "org.kde.foo.action2", this); | PolkitQt::ActionButton *bt = new PolkitQt::ActionButton(m_action2Button, "org.kde.foo.action2", this); | ||
bt->setText("Trigger Action2"); | bt->setText("Trigger Action2"); | ||
Line 203: | Line 208: | ||
connect(bt, SIGNAL(clicked(QAbstractButton*, bool)), bt, SLOT(activate())); | connect(bt, SIGNAL(clicked(QAbstractButton*, bool)), bt, SLOT(activate())); | ||
connect(bt, SIGNAL(activated()), this, SLOT(performAction2())); | connect(bt, SIGNAL(activated()), this, SLOT(performAction2())); | ||
</ | </syntaxhighlight> | ||
And that's it: the slot performAction2() will be called when the user will get the authorization. Everything else will be done for you, dialog included. | And that's it: the slot performAction2() will be called when the user will get the authorization. Everything else will be done for you, dialog included. | ||
Line 212: | Line 217: | ||
We are almost done, we just need to define performAction2(). That's it: | We are almost done, we just need to define performAction2(). That's it: | ||
< | <syntaxhighlight lang="cpp"> | ||
void FooImportantClass::performAction2() | void FooImportantClass::performAction2() | ||
{ | { | ||
Line 229: | Line 234: | ||
QDBusConnection::systemBus().call(message, QDBus::NoBlock); | QDBusConnection::systemBus().call(message, QDBus::NoBlock); | ||
} | } | ||
</ | </syntaxhighlight> | ||
That's it. Why is it so easy? Let's remember that: | That's it. Why is it so easy? Let's remember that: | ||
Line 242: | Line 247: | ||
We are done. The last glue we miss lies in CMake. Our special files should be handled like this: | We are done. The last glue we miss lies in CMake. Our special files should be handled like this: | ||
< | <syntaxhighlight lang="cmake"> | ||
dbus_add_activation_system_service(org.kde.foohelper.service.in) | dbus_add_activation_system_service(org.kde.foohelper.service.in) | ||
install(FILES org.kde.foohelper.conf DESTINATION | install(FILES org.kde.foohelper.conf DESTINATION ${SYSCONF_INSTALL_DIR}/dbus-1/system.d) | ||
install(FILES org.kde.foohelper.policy DESTINATION ${POLICY_FILES_INSTALL_DIR}) | install(FILES org.kde.foohelper.policy DESTINATION ${POLICY_FILES_INSTALL_DIR}) | ||
</ | </syntaxhighlight> | ||
Remember that you need to include FindPolkitQt.cmake to access some macros. | Remember that you need to include FindPolkitQt.cmake to access some macros. |
Latest revision as of 12:43, 18 July 2012
Tutorial Series | PolicyKit Tutorial |
Previous | Development/Tutorials/PolicyKit/Introduction |
What's Next | None
reading=None |
Further Reading | n/a |
Remember |
---|
THIS TUTORIAL IS OBSOLETE. PLEASE USE THE NEW KAUTH FRAMEWORK INSTEAD (TUTORIAL WILL BE UPDATED SOON) |
Before you start
Applications running under root privileges has always been a major problem in Linux, and PolicyKit was created exactly to make the whole process easier and more secure. Though, running applications as root, even if small and controlled, can be still a major issue. So there are a few things you should take in account that will help you minimize possible issues:
- Include in the helper just the strictly needed code. The fact itself that is a helper delegated to run just small parts of code implies that only the few lines of code you need to run as root should go in
- Link against and use the minor number of libraries possible. The helper itself requires only QtCore and QtDBus. In particular, try not to use KDELibs as possible, as they were not designed to be used as root.
- DO NOT USE SETUID IN HELPER. We'll see in this tutorial how to get root privileges without messing with the setuid bit.
If you are aware of this, and you're also sure that your application actually needs root privileges, you can go on reading.
This tutorial assumes you're using Polkit-Qt >= 0.9.2
What we need to do
What is cool about PolicyKit and this approach is that we need to write a minimum portion of code, don't need hacks or executon bits, and we actually get root privileges for a minimum portion of code. Suppose we still have our foo application we saw in the precedent Tutorial. From our .policy file, we know action2 actually does something that requires authentication as root. In fact, the following lines of code in action2 definitely require root privileges:
eraseHardDrive();
killUser();
detonatePC();
runAsFastAsYouCan();
But foo is a huge program that runs as standard user apart from those lines. So the option is to make it run as root as a whole, or split those 4 lines in a different program running as root. That is the approach that we will take in this tutorial.
Creating the Helper
Our helper will be a standard QCoreApplication. We need to create the following stuff:
- A .policy file defining the various actions, that we should have already done
- A DBus interface for our Helper
- A DBus policy file
- A file for DBus Activation
- The main class in the helper
We already know how create Policy files, let's get deeper on the other points and let's try to understand what we need to do, how and why.
The DBus interface
We suppose you already know how to create DBus interfaces from XML files. In our interface we need to specify the means of communication between the helper and the main application. Consider that the only way we have to talk to our helper is DBus, so we need to rely on signals and slots streamed through the Bus. In our foo application, we have a signal that tells us when the helper has completed the action. So:
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.foohelper">
<method name="action2" >
</method>
<signal name="action2completed">
</signal>
</interface>
</node>
And that's enough. Remember to specify in the interface any needed signal/slot to communicate with the main application or library.
Let's save this file as org.kde.foohelper.xml
The DBus policy file
Our helper will register itself on the system bus, as the session bus is reserved for the current user. DBus by default does not allow to register names on the System Bus, so we need a policy file for it. That's how it looks:
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- Only user root can own the foo helper -->
<policy user="root">
<allow own="org.kde.foohelper"/>
</policy>
<!-- Allow anyone to call into the service - we'll reject callers using PolicyKit -->
<policy context="default">
<allow send_interface="org.kde.foohelper"/>
<allow receive_sender="org.kde.foohelper"/>
<allow receive_interface="org.kde.foohelper"/>
</policy>
</busconfig>
What does this file implies? It tells DBus that only root can register the name org.kde.foohelper on the System Bus (so that we avoid misusage), and anyone is allowed to call or receive signals from it. Don't worry about security: this will come later.
Let's save this file as org.kde.foohelper.conf
DBus Activation
This is where the fun starts. DBus gives us an impressive and powerful functionality: DBus activation. By defining a simple .service file, DBus will take care of activating the interface (and so the program) for us. Let's see this in detail:
[D-BUS Service]
Name=org.kde.foohelper
Exec=@LIBEXEC_INSTALL_DIR@/foohelper
User=root
You might already have understood what this files tells DBus to do: whenever someone calls a method on org.kde.foohelper, if the interface is not present on the bus, DBus takes care of starting foohelper, that will provide the interface, and then call the method. The magic of starting it as root happens by appending "User=root". There is nothing else you have to do: now your helper is ready to be auto-activated as the root user.
@LIBEXEC_INSTALL_DIR@ will be resolved by cmake. So let's save this file as org.kde.foohelper.service.in, as it will be configured at build time.
So now we have the infrastructure to run an helper as root, but anyone can do it, and that's definitely not what we want. But that's when PolicyKit comes into play.
The helper's main class
Our helper will consist of a single main class. Let's see how to do it:
class FooHelper : public QObject, protected QDBusContext
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.foohelper")
public:
FooHelper(QObject *parent = 0);
~FooHelper();
public slots:
void action2();
signals:
void action2Completed();
The inheritance from QObject can be changed as you like, but inheriting from QDBusContext is compulsory. You'll understand why very soon. Let's define our constructor:
(void) new FooHelperAdaptor(this);
if (!QDBusConnection::systemBus().registerService("org.kde.foohelper")) {
qDebug() << "another helper is already running";
QCoreApplication::instance()->quit();
}
if (!QDBusConnection::systemBus().registerObject("/", this)) {
qDebug() << "unable to register service interface to dbus";
QCoreApplication::instance()->quit();
}
We simply connect to the system bus (be careful, not the session one!), and if something fail we quit, because it's quite likely that we're going over something weird. Now that we have done that and our application is inside it's main loop, it's time to see what action2() should do:
void FooHelper::action2()
{
qDebug() << "Starting DB Update";
PolkitQt::Auth::Result result;
result = PolkitQt::Auth::isCallerAuthorized("org.kde.foo.action2",
message().service(),
true);
if (result == PolkitQt::Auth::Yes) {
qDebug() << message().service() << QString(" authorized");
} else {
qDebug() << QString("Not authorized");
QCoreApplication::instance()->quit();
return;
}
// If we got here, we have been authorized, so let's go:
eraseHardDrive();
killUser();
detonatePC();
runAsFastAsYouCan();
// We have done our job, so let's notify everyone:
emit action2Completed();
}
Let's see this in detail. The FIRST THING you always have to do is calling the authorization check, otherwise everything will be useless. As you can see, we are using message().service() from QDBusContext, hence the compulsory inheritance. What we are doing boils down to:
- Check through Polkit-qt if the caller was authorized. Using the DBus name also grants us that the action can be called through DBus only.
- If the result is not Auth::Yes, we simply quit, as we are not authorized to do the action. You can obviously provide means of authentication here, but we are supposing (in this tutorial) that you are doing this from the main application
So the security happens here: if PolicyKit says no, we simply quit out. This way, only authorized application will be able to get through the real method.
So what do we have now? We have a simple helper, running as root, depending only on QtDBus and QtCore, that has is able to reject unauthorized callers and can act only if explicitely authorized, and it has been really easy. We just need to add the missing glue.
Integrating the helper in the main application
We are not getting in details for designing applications or anything. We suppose you have a push button, m_action2Button, that should call action 2 upon click. What we need to do:
- Associate the button to the action using PolkitQt::ActionButton
- Call the helper upon authorization
Fair enough, let's start:
Associate the button with the PolicyKit action
Sure, we could use some signaling to trigger authorization, but Polkit-qt already provides us a really nice and easy method to manage the authentication automatically. Let's see how:
PolkitQt::ActionButton *bt = new PolkitQt::ActionButton(m_action2Button, "org.kde.foo.action2", this);
bt->setText("Trigger Action2");
bt->setIcon(KIcon("Action2"));
bt->setAuthIcon(KIcon("Action2-auth"));
bt->setYesIcon(KIcon("Action2-letsgo"));
connect(bt, SIGNAL(clicked(QAbstractButton*, bool)), bt, SLOT(activate()));
connect(bt, SIGNAL(activated()), this, SLOT(performAction2()));
And that's it: the slot performAction2() will be called when the user will get the authorization. Everything else will be done for you, dialog included.
Also note the setAuthIcon and setYesIcon methods: Polkit-qt is able to change the properties of your button accordingly to the result PolicyKit associates to your action. So you can also display to your user some more feedback about his privileges on the action or if he needs to authentication. Dive into Polkit-qt to discover some more neat things you can do.
Calling the helper
We are almost done, we just need to define performAction2(). That's it:
void FooImportantClass::performAction2()
{
QDBusMessage message;
QDBusConnection::systemBus().connect("org.kde.foohelper",
"/", "org.kde.foohelper", "action2Completed",
this, SLOT(callThePoliceAndFast()));
qDebug() << "Starting action2";
message = QDBusMessage::createMethodCall("org.kde.foohelper",
"/",
"org.kde.foohelper",
QLatin1String("action2"));
QDBusConnection::systemBus().call(message, QDBus::NoBlock);
}
That's it. Why is it so easy? Let's remember that:
- Our service is DBus-activated. No need to start it ourselves
- Remember that DBus starts our service as root when it gets activated
- Check happens right in the helper
Just mind for the ::NoBlock parameter. You want to return immediately in most cases. Qt 4.5 also offers you the ::callAsync method that lets you handle an eventual return value asynchronously
The last glue
We are done. The last glue we miss lies in CMake. Our special files should be handled like this:
dbus_add_activation_system_service(org.kde.foohelper.service.in)
install(FILES org.kde.foohelper.conf DESTINATION ${SYSCONF_INSTALL_DIR}/dbus-1/system.d)
install(FILES org.kde.foohelper.policy DESTINATION ${POLICY_FILES_INSTALL_DIR})
Remember that you need to include FindPolkitQt.cmake to access some macros.
Conclusion
I hope you enjoyed this tutorial and learned how to use helpers correctly. Of course, I have just shown you a case where you obtain authorization in the GUI elements: you could also have obtained it in performAction2() or even in the helper itself, even if this is highly discouraged as it is better to manage authorizations in the caller.
This tutorial was just meant to let you understand the concept behind the caller-helper method, and why it is secure and the best and easier approach you could take. Obviously, you can make this a lot more advanced, see the Polkit-qt docs for more details.