Knopflerfish 6.1.1
Contents
Release Notes
License
Getting Started
Installing
Running
Building
Programming
Testing
Developer's Doc
Tutorials
Java API docs
Framework and Bundle docs
Bundle jar docs
www.knopflerfish.org
Bug tracker
Bundle repository
Source (git)
Eclipse plugin
Maintained by Makewave
Knopflerfish Pro
Professional Services
Training

OSGi for Business Use

Running Knopflerfish OSGi with Security

Contents

  1. Why Security?
  2. Choose your Security
  3. Turn it On
  4. An Easy Set-up
  5. Using the Console
  6. Using Conditional Permission Admin
  7. The Tested Bundle
  8. A Calling Bundle
  9. References

Why Security?

For most of us, the normal way is to run our OSGi frameworks with security off. When we have absolute control of the set of installed bundles, turning on security only gives us a set-up hassle and a performance hit.

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

To complicate things, there is more than one way to set up 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

Turning security on in Knopflerfish is easy, just a framework property that needs to be set.

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

An easy set-up for permissions is to continue to grant AllPermission for bundles that you have control over/are not security testing.

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

Make sure that the bundle frameworkcommands is installed.

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

Let us write a bundle (the admin bundle) that will set up the polices for us using the CPA service that is published by the framework.

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:

  1. Install the admin bundle, schedule it for start.
  2. Launch the framework. This will start the admin bundle.
  3. 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.
This way we make sure that no bundle can 'sneak in' and do something without being subjected to the CPA policies we want to be in effect.

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

This section is about the bundle that we are developing and that we want to verify is working correctly when running in a framework with security on.

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.

When we add local permissions to our user bundle, it is all right to use a policy in the admin bundle that grants the user bundle AllPermission. The hard coded policies in the admin bundle will now be:
  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

To proper test our resource bundle we also need to write a bundle that uses our UserService.

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.