Running Knopflerfish OSGi with Security
Contents
- Why Security?
- Choose your Security
- Turn it On
- An Easy Set-up
- Using the Console
- Using Conditional Permission Admin
- The Tested Bundle
- A Calling Bundle
- References
Why Security?
But even if your framework is not exposed to bundles from unknown sources, you might have to run with security on in order to properly test bundles that need to do security right, in case someone else decides to run their framework with security on.
The scenario for this tutorial is that we are developing a bundle and it is now time to test how it behaves in a framework with security on.
Choose your Security
The OSGi core specification contains both the Permission Admin and the Conditional Permission Admin that supersedes the former one. On top of that, it is also possible to specify a Java 2 policy file that grants permissions to code from specified code sources (based on location or signature).
It is preferred to use Conditional Permission Admin (CPA) in favor of the old Permission Admin (as it is newer, but also more powerful). The Java 2 policy file is a lot less flexible than using CPA since it is not really possible to change permissions dynamically that way. This tutorial will use CPA, the policy file will only be used to grant the framework sufficient permission.
A few final words about the policy file. It can be useful to know
that the Knopflerfish framework will by default use a Java 2 policy
file that is included in the framework jar file. But if the system
property java.security.policy
is set, the specified
policy file will be used instead. The KF default policy file grants
AllPermission to everyone, which is likely to be what you want; the
framework needs AllPermission and since CPA will be used to control
permissions for the bundles, you want the policy file to have -- no
policy at all.
Turn it On
To set a security manager and thus turn on security, set the framework
environment property org.osgi.framework.security
to
osgi
. For Knopflerfish this will set the
KFSecurityManager as security manager.
With a security manager set (either using
-Djava.security.manager
or
-Forg.osgi.framework.security=osgi
) the KF system bundle
will publish services PermissionAdmin and ConditionalPermissionAdmin.
If you know that PermissionAdmin is not going to be used, you can turn
it off by setting
-Forg.knopflerfish.framework.service.permissionadmin=false
.
As you can see, turning security on is easy. Unfortunately, just turning it on will not do much since the default (as long as no permission has been set with CPA) is to grant everyone AllPermission. All we have accomplished so far is to slow thing down a little bit by installing a security manager.
An Easy Set-up
In this tutorial we are supposed to be testing a bundle under development. We do not really care about other bundles that might be installed and running, like the KF log, cm, http, event, etc bundles. To keep these working like before, we will grant them AllPermission by adding a policy that allow AllPermission under the condition that the bundle location matches the location where the KF bundles are stored in the local file system:
ALLOW { [org.osgi.service.condpermadmin.BundleLocationCondition "file:jars/*"] (java.security.AllPermission) }
A bundle that we do not trust/are testing should be given less permission. A good starting point is to give them permission to import any package. If we give no permissions at all, the bundle will not even be allowed to resolve and will be difficult to test. An even more restricted starting point is to only allow import of the org.osgi.framework package.
In this tutorial the bundle that we are testing is stored in the local
file system at /opt/kf/totest
, and to give all bundles
located here permission to import any package we need:
ALLOW { [org.osgi.service.condpermadmin.BundleLocationCondition "file:/opt/kf/totest/*"] (org.osgi.framework.PackagePermission "*" "import") }
A note to all Windows users out there: watch your backslashes. For
example if the path to your bundles to test is c:\kf\totest
,
you might think that file:c:\\kf\\totest\\*
will do fine as
location argument. But in this argument the last backslash is not
interpreted as a file separator, but as an escape character for the
wildcard star, meaning that it is not going to be treated as a wildcard!
file:c:\\kf\\totest*
will work much better. This is
documented in the javadoc for BundleLocationCondition but this little
feature is easy to miss.
Both of the policies above use a bundle location condition. It is also possible to use a bundle signer condition to grant permission to bundles that are signed by a particular signature, or to use no condition at all if a permission should be granted to all bundles.
When we start to test our bundle, we will probably need to give it more permission. For example, if it is using the Log service, it will need a ServicePermisson for that.
One way to add these policies is to use the setcondpermission command in the KF framework command group. Another way is to write a bundle that uses the ConditionalPermissinAdmin service to programmatically add conditional permissions.
Using the Console
In the console, type
> framework setcondpermission \ '[org.osgi.service.condpermadmin.BundleLocationCondition "file:jars/*"]' \ (java.security.AllPermission)and
> fr setcond '[org.osgi.service.condpermadmin.BundleLocationCondition \ "file:/opt/kf/totest/*"]' \ '(org.osgi.framework.PackagePermission "*" "import")'
The policies will be part of the framework state, they will survive
a restart as long as you do not use the -init
flag to
clear the state.
Using the Conditional Permission Admin
The admin bundle will be our policy administration bundle so it will need permission to set polices using the CPA service. This means that it is in full control of the CPA and thereby able to grant itself or another bundle any kind of permission. It is easily realized that the admin bundle will have to be a trusted bundle. We could let it set the required CPA permissions for itself but to keep thing simple in this tutorial we will just treat it as trusted bundle and make sure it is loaded from the same location as the rest of the trusted KF bundles.
Here is the source code and jar f ile for the admin bundle. We have a method, installPolicies that is called when the bundle starts:
01: void installPolicies(ConditionalPermissionAdmin cpa, String[] pInfos) { 02: ConditionalPermissionUpdate cpu = cpa.newConditionalPermissionUpdate(); 03: List piList = cpu.getConditionalPermissionInfos(); 04: 05: for (int i = 0; i < pInfos.length; i++) { 06: String pInfo = pInfos[i]; 07: ConditionalPermissionInfo cpi = cpa.newConditionalPermissionInfo(pInfo); 08: piList.add(cpi); 09: } 10: 11: cpu.commit(); 12: }On line 02 and 03 we use the CPA to create a permission update object and get the list that our permissions will be added to. An instance of ConditionalPermissionInfo is created for each permission. The CPA service is used to create an instance from an encoded string. The string syntax is specified in the CPA chapter of the OSGi core specification. The policies that we defined in An Easy Set-up would look like this:
private static final String[] ENCODED_PINFO = { "allow { [org.osgi.service.condpermadmin.BundleLocationCondition \ \"file:jars/*\"] (java.security.AllPermission) } \"allToTrusted\"", "allow { [org.osgi.service.condpermadmin.BundleLocationCondition \ \"file:/opt/kf/totest/*\"] (org.osgi.framework.PackagePermission \ \"*\" \"import\") } \"importToTested\"" };
Each ConditionalPermissionInfo instance is added to the update list and then the update is committed to the CPA service on line 11.
Our version of the admin bundle uses hard coded policy strings to keep it simple. A more realistic version would probably read the policy data from a file or some other source.
In a real world system, the functionality of the admin bundle could be integrated into a management agent. It would set the CPA policies and then it would take care of installing and starting the rest of the bundles that should be included in the system. We can follow this strategy without writing a complex management agent by using the xargs functionality in KF. Here is a props.xargs and an init.xargs that will:
- Install the admin bundle, schedule it for start.
- Launch the framework. This will start the admin bundle.
- Install and start the rest of the bundles, in this case a minimal set of KF bundles that includes the log and some console bundles for the TTY console, plus our bundle to test and a test bundle.
Debug output for permissions is enabled in props.xargs. A lot of output is produced but it can be useful to have it enabled when you are working with a security enabled framework.
The Tested Bundle
The to-test bundle is called user. It will publish a fake service (UserService) that we pretend is used to notify native components about the currently logged in OSGi user. Notification is done using the file system.
The bundle will be using the framework and the LogService so we are going to need permission for that. We also need permission to read and write to the file system. Finally, we need permission to export a package and publish a service for other bundles to import and use. The policy for these permissions could look like:
ALLOW { [org.osgi.service.condpermadmin.BundleLocationCondition "file:/opt/kf/totest/cpaexample_user*"] (org.osgi.framework.PackagePermission "org.osgi.framework.*" "import") (org.osgi.framework.PackagePermission "org.osgi.service.log" "import") (org.osgi.framework.ServicePermission "org.osgi.service.log.LogService" "get") (java.io.FilePermission "/tmp/osgiuser" "read,write,delete") (org.osgi.framework.PackagePermission "com.acme.resource" "exportonly") (org.osgi.framework.ServicePermission "com.acme.resource.ResourceService" "register") }While it is not mandatory, it is recommended that a bundle lists the permissions that it requires. The listed permissions are called local permissions. If defined, a bundle can never get more permissions than its local permissions. This feature makes it possible for a deployer to audit the permission requirements for a bundle. If the list of required permissions is acceptable, there is no need for the deployer to set up permissions in detail, the bundle can be given AllPermission.
Local permissions for a bundle are defined in the resource file
OSGI_INF/permissions.perm
. Each permission is listed on
its own line in this file, in the encoded form describes in
Using Conditional Permission Admin.
private static final String[] ENCODED_PINFO = { "allow { [org.osgi.service.condpermadmin.BundleLocationCondition \ \"file:jars/*\"] (java.security.AllPermission) } \"allToTrusted\"", "allow { [org.osgi.service.condpermadmin.BundleLocationCondition \ \"file:/opt/kf/totest/cpaexample_user*\"] \ (java.security.AllPermission) } \"allToUser\"" };
The source code for the user bundle is here, the jar including local permission definition is here. The interesting parts for this security tutorial can be found in the login and logout methods of UserService. Here is the login method:
01: public void login(final String name) { 02: final File f = new File(fileName); 03: 04: AccessController.doPrivileged(new PrivilegedAction() { 05: public Object run() { 06: if (f.exists()) { 07: throw new IllegalStateException("User already logged in"); 08: } 09: 10: try { 11: OutputStream os = new FileOutputStream(f); 12: os.write(name.getBytes("UTF-8")); 13: os.close(); 14: log(LogService.LOG_INFO, "User " + name + " logged in"); 15: } catch (IOException ioe) { 16: log(LogService.LOG_WARNING, "Problem logging user in: " + ioe); 17: } 18: return null; 19: } 20: }); 21: }When doing method calls that will lead to permission checks (lines 06, 11, 14 and 16) we need to mark us as privileged. When marked as privileged, the permission check will check that we have the required permissions but it will not continue to check that our caller (the bundle that is calling the login method in our service) also have the required permissions. Without the doPrivileged wrapper construction on line 04, it would be required for a bundle that is using our service to have the same permissions that we have.
A Calling Bundle
We have to make sure that permissions required by the user bundle is not also required by any user of its registered service. A user should only be required to have permission to import the package from the user bundle and to get the UserService.
This third bundle, called caller, is nothing special. All it does is getting the UserService and logging in a user in the start method and then logging out in the stop method. Here is the source and jar file.References
Chapter 9 of OSGi Service Platform Core Specification, Conditional Permission Admin Specification, contains a lot of information about the CPA service and security in OSGi in general. The specification can be downloaded from here.