Java Security - Restrict IO Permissions using SecurityManager

dimitrilc 2 Tallied Votes 307 Views Share

Introduction

In this tutorial, we will focus on how to manage IO permissions to read, write, and delete local files using the SecurityManager class.

Goals

At the end of this tutorial, you would have learned:

  1. What policy files are and how to use them.
  2. How to use policy files and the SecurityManager class to manage IO permissions.

Prerequisite Knowledge

  1. Basic Java.
  2. Java IO/NIO Concepts.

Tools Required

  1. A Java IDE that supports JDK 11 (I prefer NIO2/Path over older IO/File).

Concept Overview

The Java class SecurityManager can be used to limit the permissions of a program to perform tasks in these categories: File, Socket, Net, Security, Runtime, Property, AWT, Reflect, and Serializable. The focus of this tutorial would only be on File read, write, and delete permissions.

By default, a SecurityManager is not provided in the runtime environment. In order to use the SecurityManager, one must either set the runtime flag java.security.manager on the command line or provide an instance of SecurityManager in code. Our example will use the latter approach, which will create an instance of SecurityManager manually.

It is possible to extend the SecurityManager class, but that is out of scope of this tutorial, so the instance of SecurityManager that we will create later will be of the default implementation.

The java.policy File

When the SecurityManager instance is loaded, it will automatically look for java.policy files located at the paths below:

    java.home/lib/security/java.policy (Solaris/Linux)
    java.home\lib\security\java.policy (Windows)

    user.home/.java.policy (Solaris/Linux)
    user.home\.java.policy (Windows)

In case you are unaware, java.home and user.home are environment variables that can be retrieved with the System#getProperty Java method. For this tutorial, we will create a java.policy file at the user.home location (on Windows).

Go ahead and create a blank file java.policy under your user home directory. For example, if my username is John, then the java.policy file would be located at:

C:\Users\John\java.policy

The grant keyword

Copy and paste the text below into the java.policy file.

    grant {
      //permission java.io.FilePermission "c:/ioPractice/test.txt", "read";
      //permission java.io.FilePermission "c:/ioPractice/test.txt", "write";
      //permission java.io.FilePermission "c:/ioPractice/test.txt", "delete";
      //permission java.io.FilePermission "c:/ioPractice/test.txt", "read, write, delete";
    };

To allow read, write, or delete permissions for a file, we can use the grant keyword with the syntax above.

Note that all of the permissions are commented out at this stage. You can either combine all of the permissions together or add them separately.

Setting up the Test environment

To see how the SecurityManger works, we need to perform the steps below to set up our test environment.

  1. Create a folder called IOPractice in the C:\ Drive (or / if you are on Linux/Mac).

  2. Inside IOPractice, create a file called test.txt.

  3. Add the text “Hello World!” (without quotes) into test.txt.

  4. The path to your test.txt file should look like this

     C:\IOPractice\test.txt
  5. Create a new Java project.

  6. Create a new package com.example.

  7. Create a new Java class Entry.java inside the com.example package.

  8. Copy and paste the code below into Entry.java.

     package com.example;
    
     import java.io.IOException;
     import java.nio.file.Files;
     import java.nio.file.Path;
     import java.nio.file.StandardOpenOption;
    
     public class Entry {
        private static final SecurityManager SECURITY_MANAGER = new SecurityManager(); //1
    
        static {
            //System.setSecurityManager(SECURITY_MANAGER); //2
        }
    
        public static void main(String[] args) {
            Path testFile = Path.of("C:\\ioPractice\\test.txt"); //3
            readFile(testFile); //4
            writeFile(testFile); //5
            deleteFile(testFile); //6
        }
    
        private static void readFile(Path file) { //7
            try {
                String content = Files.readString(file);
                System.out.println(content);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private static void writeFile(Path file) { //8
            try {
                Files.writeString(file, "Hello World Again!", StandardOpenOption.APPEND);
                readFile(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private static void deleteFile(Path file) { //9
            try {
                Files.delete(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
     }

Here are the explanations for the code above:

  1. Line 1 is where we instantiate a SecurityManager object using the default implementation.
  2. But creating a SecurityManagerobject is not enough for the program to use it, we also have to set it manually at line 2, but that is commented out for now to show you the effect later.
  3. At line 3, we create a Path object with an absolute path to the test.txt file.
  4. Lines 4, 5, 6 are call sites for the 3 convenient methods readFile, writeFile, and deleteFile.
  5. Lines 7, 8, 9 are where the convenient methods were created.

Test the code

Because the SecurityManager has not been set yet, when we execute the code above, everything will run without any exception(but you must re-create the test.txt file each time because it would be deleted on a successful run).

Now uncomment out line 2, and we would see the code failing to run, throwing this exception:

Exception in thread "main" java.security.AccessControlException: access denied ("java.io.FilePermission" "C:\ioPractice\test.txt" "read")

If you uncomment the very first permission, read, in the java.policy file and save, you can see that your program is able to read the file content, but it is still unable to write to the file, so we would see an exception regarding the write permission:

Exception in thread "main" java.security.AccessControlException: access denied ("java.io.FilePermission" "C:\ioPractice\test.txt" "write")

Once again, after we uncomment the write permission in the file, our program can now write to the file successfully, but obviously we will still run into an exception with the delete permission.

    Exception in thread "main" java.security.AccessControlException: access denied ("java.io.FilePermission" "C:\ioPractice\test.txt" "delete")

Uncomment the delete permission in java.policy, and all methods will have permission to run successfully.

Solution Code

    package com.example;

    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.StandardOpenOption;

    public class Entry {
       private static final SecurityManager SECURITY_MANAGER = new SecurityManager(); //1

       static {
           System.setSecurityManager(SECURITY_MANAGER); //2
       }

       public static void main(String[] args) {
           Path testFile = Path.of("C:\\ioPractice\\test.txt"); //3
           readFile(testFile); //4
           writeFile(testFile); //5
           deleteFile(testFile); //6
       }

       private static void readFile(Path file) { //7
           try {
               String content = Files.readString(file);
               System.out.println(content);
           } catch (IOException e) {
               e.printStackTrace();
           }
       }

       private static void writeFile(Path file) { //8
           try {
               Files.writeString(file, "Hello World Again!", StandardOpenOption.APPEND);
               readFile(file);
           } catch (IOException e) {
               e.printStackTrace();
           }
       }

       private static void deleteFile(Path file) { //9
           try {
               Files.delete(file);
           } catch (IOException e) {
               e.printStackTrace();
           }
       }

    }

java.policy

grant {
  permission java.io.FilePermission "c:/ioPractice/test.txt", "read";
  permission java.io.FilePermission "c:/ioPractice/test.txt", "write";
  permission java.io.FilePermission "c:/ioPractice/test.txt", "delete";
  //permission java.io.FilePermission "c:/ioPractice/test.txt", "read, write, delete";
};

Summary

We have successfully learned how to enable the SecurityManager and manage IO permissions using the java.policy file.

Even though we have this capability, be aware that SecurityManager usage should be reserved as a last resort to secure your APIs. Your APIs should be designed for security in the first place.

The full project code can be downloaded here: https://github.com/dmitrilc/DaniWebJavaIoSecurityManager