April 24, 2020

Apache Derby Database JVM Security Policy

Abstract

I have already posted a number of blogs about Derby:

This wasn't intended to be a series. But over the years I've been using Derby more and more. I started using Derby as my database of choice for my Microservice architecture. These are personal-use applications, so Derby is more than sufficient. Even though these are personal-use applications, I require multiple servers with limited user permissions, and of course database backup and restoration. The final requirement is security. I run my Derby databases on an Ubuntu Linux VM with the derby usr account. Although the derby usr account has limited permissions on the VM, any extra layer of security is good. So the purpose of this blog is to demonstrate how to run Derby with a Java security policy to limit the JVM's permissions and enhance runtime security.

Disclaimer

This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.

Requirements

I did all of the work for this post using the following major technologies. You may be able to do the same thing with different technologies or versions, but no guarantees.

  • Apache Derby 10.14.2.0
  • Java zulu11.39.15-ca-jdk11.0.7-linux_x64

I am not going to go through the process of downloading and installing these technologies. I'll leave that as an exercise for you.

NOTE Starting with version 10.15, the Derby project has been updated to use the Java 9 module system. As a result, the JAR files have changed quite a bit. It's unlikely the security.policy below will work with version 10.15+. As of this blog's publication date, I've yet to try it.

Linux bash scripts

In order to manage Derby to run with a Java security policy, you need 3 scripts. The 1st script will setup the setup environment variables to configure Derby. The 2nd script will start the Derby network server, passing the correct command line parameters. The 3rd will stop the Derby network server.

Listing 1.1 shows you the first of these scripts. It exports a number of system environment variables with configuration values specific to run Derby in your environment.

Listing 1.1 - setenv.sh

#!/bin/bash

export DERBY_HOME=/home/derby/opt/derby
export PATH="$DERBY_HOME/bin:$PATH"
echo "DERBY_HOME=$DERBY_HOME"

export JAVA_HOME=/home/derby/opt/java
echo "JAVA_HOME=$JAVA_HOME"

export NS_HOME=/var/local/derby/1527
mkdir -p $NS_HOME
echo "NS_HOME=$NS_HOME"

export NS_PORT=1527
echo "NS_PORT=$NS_PORT"

export NS_HOST=0.0.0.0
echo "NS_HOST=$NS_HOST"

export DERBY_OPTS=""
export DERBY_OPTS="$DERBY_OPTS -Dderby.drda.host=$NS_HOST"
export DERBY_OPTS="$DERBY_OPTS -Dderby.drda.portNumber=$NS_PORT"
export DERBY_OPTS="$DERBY_OPTS -Dderby.system.home=$NS_HOME"
# Security Policy
export DERBY_OPTS="$DERBY_OPTS -Dderby.stream.error.logSeverityLevel=0"
export DERBY_OPTS="$DERBY_OPTS -Dderby.security.port=$NS_PORT"
export DERBY_OPTS="$DERBY_OPTS -Dderby.install.url=file:$DERBY_HOME/lib/"
export DERBY_OPTS="$DERBY_OPTS -Djava.security.manager"
export DERBY_OPTS="$DERBY_OPTS -Djava.security.policy=$NS_HOME/security.policy"

DERBY_HOME is self explanatory. It's where Derby is unzipped (installed). Add Derby's bin directory to the PATH.

JAVA_HOME is self explanatory. It's where Java is unzipped (installed). Add Java's bin directory to the PATH.

NS_HOME is "Network Server Home". This is the directory the Derby network server will use to store its configuration and databases. Whenever a new database is created on this Derby network server, a new sub-directory will be created under NS_HOME for the new database. This allows multiple Derby network servers running on the same host to keep their data separate.

NS_PORT is "Network Server Port". It's the port the Derby network server uses to listen for connections. This allows multiple Derby network servers to run on the same host.

NS_HOST is "Network Server Host". It sets the network interface used by the Derby network server when listening for connections. By default, the Derby network server only listens for connections on the loopback address of 127.0.0.1. This default means clients must run on the same host as the network server - not very useful. By setting the host to 0.0.0.0, the Derby network server will listen for connections on any network interface on the host. If your VM has multiple network interfaces, NS_HOST should be set to the IP of one of those interfaces. Setting this value allows clients to be remote.

DERBY_OPTS is the system property used to get all of the configuration options to Derby. Its value is created by concatenating together the appropriate Derby system properties with their associated values. The first 3 properties are needed to start Derby with or without a security policy.

  1. derby.drda.host
  2. derby.drda.portNumber
  3. derby.system.home

The final 5 properties are needed for configuring Derby to run with a security policy.

  1. derby.stream.error.logSeverityLevel
  2. derby.security.port
  3. derby.install.url
  4. java.security.manager
  5. java.security.policy

One of the most important properties is java.security.policy=$NS_HOME/security.policy". The value of this property points to a security.policy file which will configure the Java SecurityManager. You will read about creating the security.policy file in just a little bit. Next, you will look at the script for starting the server.

Listing 1.2 shows you the second of these scripts. It starts the Derby networks server, passing the correct command line parameters so Derby runs with a security policy.

Listing 1.2 - start.sh

#!/bin/bash

# Directory of the script
SD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )

# Source in common variables
source $SD/setenv.sh

# Symlink the network server configurations
ln -sf $SD/../conf/security.policy $NS_HOME/security.policy
ln -sf $SD/../conf/derby.properties $NS_HOME/derby.properties

startNetworkServer

SD is Script Directory. The evaluation determines the fully-qualified file system location of the start.sh script and assigns it to SD. This is useful when referencing other scripts.

source is self explanatory. It sources in the system environment variables to configure the Derby network server. See listing 1.1 for details.

Symlink configuration is for the security.policy file and the derby.properties file. The purpose of the symlinks is to get these 2 files into the $NS_HOME directory. Derby looks for the derby.properties file in the $NS_HOME directory, so it needs to be there. For consistency (not a necessity), you want to put the security.policy file there as well. In listing 1.1 the java.security.policy=$NS_HOME/security.policy" property configures this location. For my environment, I have separated the $NS_HOME directory from the directory where I keep the management scripts and other Derby configuration files. The reason I do this is because of disaster recovery. I consider the $NS_HOME directory to be volitile, meaning if for some reason it goes missing (deleted, disk drive error, corrupted, new VM built, etc) I must be able to restore the database data, management scripts (setenv.sh, start.sh, stop.sh) and configuration files (security.policy, derby.properties) from my cloud backups. The real configuration files are kept outside of the $NS_HOME directory and start.sh symlinks them in the proper location.

startNetworkServer is a script provided by Derby ($DERBY_HOME/bin) to start the network server. The DERBY_OPTS variable - set in setenv.sh - is used to configure the network server. By default, Derby runs with a limited security policy. However, since you configured the security policy, Derby will use your configuration instead of the default.

You now have the Derby server environment configuration and start script. What you don't have yet is the ability to stop the Derby network server. Stopping the server is easy. You will look at the script for stopping the server next.

NOTE The security.policy file is also needed still. You will read about it in just a few moments, I promise!

Listing 1.3 shows you the third of these scripts. It stops the Derby networks server. Not too exciting, but it's important to have a managed shutdown of the server to prevent data corruption.

Listing 1.3 - stop.sh

#!/bin/bash

# Directory of the script
SD=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )

# Source in common variables
source $SD/setenv.sh

stopNetworkServer

All of this is self explanatory. No further comments are needed for this script.

The security.policy file

Derby comes with a demo security policy file. It is located in DERBY_HOME/demo/templates/security.policy. Using this file as the starting point, I was able to produce a final version that met my requirements for:

  • Network (remote) access
  • Localhost access
  • Startup
  • Shutdown
  • Backup

Listing 2.1 - security.policy

//
//   Licensed to the Apache Software Foundation (ASF) under one or more
//   contributor license agreements.  See the NOTICE file distributed with
//   this work for additional information regarding copyright ownership.
//   The ASF licenses this file to You under the Apache License, Version 2.0
//   (the "License"); you may not use this file except in compliance with
//   the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//

grant codeBase "${derby.install.url}derby.jar"
{
  // These permissions are needed for everyday, embedded Derby usage.
  //
  permission java.lang.RuntimePermission "createClassLoader";
  permission java.util.PropertyPermission "derby.*", "read";
  permission java.util.PropertyPermission "user.dir", "read";
  permission org.apache.derby.security.SystemPermission "engine", "usederbyinternals";

  // The next two properties are used to determine if the VM is 32 or 64 bit.
  //
  permission java.util.PropertyPermission "sun.arch.data.model", "read";
  permission java.util.PropertyPermission "os.arch", "read";
  permission java.io.FilePermission "${derby.system.home}","read";
  permission java.io.FilePermission "${derby.system.home}${/}-",
      "read,write,delete";

  // Needed by sysinfo. A file permission is needed to check the existence of
  // jars on the classpath. You can limit this permission to just the locations
  // which hold your jar files. This block is reproduced for all codebases
  // which include the sysinfo classes--the policy file syntax does not let you
  // grant permissions to several codebases all at once.
  //
  permission java.util.PropertyPermission "user.*", "read";
  permission java.util.PropertyPermission "java.home", "read";
  permission java.util.PropertyPermission "java.class.path", "read";
  permission java.util.PropertyPermission "java.runtime.version", "read";
  permission java.util.PropertyPermission "java.fullversion", "read";
  permission java.lang.RuntimePermission "getProtectionDomain";
  permission java.io.FilePermission "java.runtime.version", "read";
  permission java.io.FilePermission "java.fullversion", "read";
  permission java.io.FilePermission "${derby.install.path}${/}-", "read";
  permission java.io.FilePermission "/tmp${/}-", "read,write,delete";

  // Permissions needed for JMX based management and monitoring.
  //
  // Allows this code to create an MBeanServer:
  //
  permission javax.management.MBeanServerPermission "createMBeanServer";

  // Allows access to Derby's built-in MBeans, within the domain
  // org.apache.derby.  Derby must be allowed to register and unregister these
  // MBeans.  To fine tune this permission, see the javadoc of
  // javax.management.MBeanPermission or the JMX Instrumentation and Agent
  // Specification.
  //
  permission javax.management.MBeanPermission
       "org.apache.derby.*#[org.apache.derby:*]",
       "registerMBean,unregisterMBean";

  // Trusts Derby code to be a source of MBeans and to register these in the
  // MBean server.
  //
  permission javax.management.MBeanTrustPermission "register";

  // Gives permission for jmx to be used against Derby but only if JMX
  // authentication is not being used.  In that case the application would need
  // to create a whole set of fine-grained permissions to allow specific users
  // access to MBeans and actions they perform.
  //
  permission org.apache.derby.security.SystemPermission "jmx", "control";
  permission org.apache.derby.security.SystemPermission "engine", "monitor";
  permission org.apache.derby.security.SystemPermission "server", "monitor";

  // getProtectionDomain is an optional permission needed for printing
  // classpath information to derby.log
  //
  permission java.lang.RuntimePermission "getProtectionDomain";

  // The following permission must be granted for Connection.abort(Executor) to
  // work. Note that this permission must also be granted to outer
  // (application) code domains.
  //
  permission java.sql.SQLPermission "callAbort";
  permission java.sql.SQLPermission "deregisterDriver";

  // Needed by FileUtil#limitAccessToOwner
  //
  permission java.lang.RuntimePermission "accessUserInformation";
  permission java.lang.RuntimePermission "getFileStoreAttributes";
};


grant codeBase "${derby.install.url}derbynet.jar"
{
  // These permissions lets the Network Server manage connections from clients.

  // Accept connections from any host. Derby is listening to the host interface
  // specified via the -h option to "NetworkServerControl start" on the command
  // line, via the address parameter to the
  // org.apache.derby.drda.NetworkServerControl constructor in the API or via
  // the property derby.drda.host; the default is localhost.  You may want to
  // restrict allowed hosts, e.g. to hosts in a specific subdomain,
  // e.g. "*.example.com".
  //
  permission java.net.SocketPermission "*", "accept";

  // Allow the server to listen to the socket on the port specified with the
  // -p option to "NetworkServerControl start" on the command line, or with
  // the portNumber parameter to the NetworkServerControl constructor in the
  // API, or with the property derby.drda.portNumber. The default is 1527.  
  permission java.net.SocketPermission "localhost:${derby.security.port}",
      "listen";
  permission java.net.SocketPermission "${derby.drda.host}:${derby.security.port}",
      "listen";      


  // Needed for server tracing.
  //
  permission java.io.FilePermission "${derby.drda.traceDirectory}${/}-",
      "read,write,delete";

  // Needed by FileUtil#limitAccessToOwner
  //
  permission java.lang.RuntimePermission "accessUserInformation";
  permission java.lang.RuntimePermission "getFileStoreAttributes";

  // Needed for NetworkServerMBean access (see JMX section above)
  //
  permission org.apache.derby.security.SystemPermission "server",
      "control,monitor";
  permission org.apache.derby.security.SystemPermission "engine", "usederbyinternals";

  // Needed by sysinfo. A file permission is needed to check the existence of
  // jars on the classpath. You can limit this permission to just the locations
  // which hold your jar files. This block is reproduced for all codebases
  // which include the sysinfo classes--the policy file syntax does not let you
  // grant permissions to several codebases all at once.
  //
  permission java.util.PropertyPermission "user.*", "read";
  permission java.util.PropertyPermission "java.home", "read";
  permission java.util.PropertyPermission "java.class.path", "read";
  permission java.util.PropertyPermission "java.runtime.version", "read";
  permission java.util.PropertyPermission "java.fullversion", "read";
  permission java.lang.RuntimePermission "getProtectionDomain";
  permission java.io.FilePermission "java.runtime.version", "read";
  permission java.io.FilePermission "java.fullversion", "read";
  permission java.io.FilePermission "${derby.install.path}${/}-", "read";

  permission java.util.PropertyPermission "derby.*", "read,write";

  permission java.net.SocketPermission "localhost:${derby.security.port}", "connect,resolve";
  permission java.net.SocketPermission "${derby.drda.host}:${derby.security.port}", "connect,resolve";  
};


grant codeBase "${derby.install.url}derbytools.jar"
{
  // Needed by sysinfo. A file permission is needed to check the existence of
  // jars on the classpath. You can limit this permission to just the locations
  // which hold your jar files. This block is for all codebases which include
  // the sysinfo classes--the policy file syntax does not let you grant
  // permissions to several codebases all at once.
  //
  permission java.util.PropertyPermission "user.*", "read";
  permission java.util.PropertyPermission "java.home", "read";
  permission java.util.PropertyPermission "java.class.path", "read";
  permission java.util.PropertyPermission "java.runtime.version", "read";
  permission java.util.PropertyPermission "java.fullversion", "read";
  permission java.lang.RuntimePermission "getProtectionDomain";
  permission java.io.FilePermission "<<ALL FILES>>", "read";
  permission java.io.FilePermission "java.runtime.version", "read";
  permission java.io.FilePermission "java.fullversion", "read";

  permission java.util.PropertyPermission "*", "read,write";
};

grant codeBase "${derby.install.url}derbyclient.jar"
{
  // Needed by sysinfo. A file permission is needed to check the existence of
  // jars on the classpath. You can limit this permission to just the locations
  // which hold your jar files. This block is reproduced for all codebases
  // which include the sysinfo classes--the policy file syntax does not let you
  // grant permissions to several codebases all at once.
  //
  permission java.util.PropertyPermission "user.*", "read";
  permission java.util.PropertyPermission "java.home", "read";
  permission java.util.PropertyPermission "java.class.path", "read";
  permission java.util.PropertyPermission "java.runtime.version", "read";
  permission java.util.PropertyPermission "java.fullversion", "read";
  permission java.lang.RuntimePermission "getProtectionDomain";
  permission java.io.FilePermission "${derby.install.path}${/}-", "read";

  // The following permission must be granted for Connection.abort(Executor) to
  // work.  Note that this permission must also be granted to outer
  // (application) code domains.
  //
  permission java.sql.SQLPermission "callAbort";

  permission java.net.SocketPermission "localhost:${derby.security.port}", "connect,resolve";
  permission java.net.SocketPermission "${derby.drda.host}:${derby.security.port}", "connect,resolve";
};

Policy files are a lot to take in. After 20 years using Java, I've only come across them just a handful of times. I don't pretend to know everything that goes into a policy file. All I know is this file is working for all my requirements. Each Derby update requires testing and maybe some tweeking. The derby-users@db.apache.org mailing list is your best source of information.

A big shout out to Rick Hillegas from the derby-users@db.apache.org mailing list for helping me get to this version of the policy file. He provided most of it and I added the following to meet my requirements.

Line 50 permission java.io.FilePermission "/tmp${/}-", "read,write,delete";. My database backup process uses CALL SYSCS_UTIL.SYSCS_BACKUP_DATABASE (‘/tmp/resiste-backup/1527’). So the derby.jar file needs read,write,delete permissions to the /tmp directory on the file system so it can write the backup into that directory.

Line 92 permission java.sql.SQLPermission "deregisterDriver";. When administering my Derby database with the the ij tool, found an exception in the derby.log file about deregisterDriver. So I added this permission to the derby.jar file as well.

Line 160 permission java.net.SocketPermission "${derby.drda.host}:${derby.security.port}", "connect,resolve";. Properties derby.drda.host and derby.security.port are set in the setenv.sh script (listing 1.1). I had to add this permission because my Derby network server is accessed by remote (non-localhost) clients. In setenv.sh, I use -Dderby.drda.host=0.0.0.0 to override the default localhost-only interface listening. I also found I needed this in the policy file while testing the stop.sh script (listing 1.3).

Summary

That's it. I hope you enjoyed learning how to run a Derby network server with a security policy.

No comments:

Post a Comment