Using Geofence to add layer level security in Geoserver

We all love GeoServer for its ability to host various data types and make it blazing fast to use in applications with OGC standard endpoints and represent data beautifully with Styling. P.S. If you hate writing SLD-XML code to make complex styling, check out my blog or video! This will make your life a lot easy. But it also matters to secure our data and allowing different users to either access/hide/partially see the data according to roles assigned to them

Geoserver’s inbuilt security system is great in itself. It allows us to create multiple Roles, Groups, Users and make combinations of them thus securing our data.

Geoserver’s default security system

When you log in to GeoServer, you can see the security panel on the left side, under which multiple tabs are available.

Settings — We define the active role service over here, GeoServer will have default selected over here, which means the default roles will be used to authenticate GeoServer, we can also select encryption type for password. weak PBE is fine.
Authentication — This tab has a lot of options, a separate blog for that will be out for it shortly. For now, let’s stick with the default settings
Password — This tab defines the provider for the master password.
Roles, Groups, Users — This tab hold information about services, Roles, Groups, Users information. We can save the user credentials in various ways like files (XML), databases, LDAP, etc.
Data — In this tab, we write the data rules, such as which Roles should have access to what workspaces, layers, etc.
Services — In this tab, we define service-specific rules. e.g. allowing WFS getCapabilities to the specific role.

Problem statement ❓

Imagine a scenario, where you have a master layer that holds information for all regions and all attributes. e.g. Places layers which can be found at https://www.naturalearthdata.com/downloads/10m-cultural-vectors/10m-populated-places/

Now we want to create different use cases as follows:

To achieve this, we will be using geofencing, which allows us to go up to layer level and set the permission as per our need. Geofencing tool can be used via UI or as a REST API as well.

Working with geofence 🛡

Installing and setup 🛠

If you are using geoserver newer to 2.18, remember to only install the geofencing server, not the client plugin. Since I’m on version 2.18.4, I’ll head over to the download page http://geoserver.org/release/2.18.4/

once downloaded, put all jars in /geoserver/webapps/geoserver/WEB-INF/lib/ and restart the geoserver. Once done you will see new options getting added under the security tab

Geofence — Head over to this tab and click on the test connection button, you should see the connection successful message. If you don’t see this, that means you haven’t installed the server plugin properly.

Geofence Data Rules — In this tab, we define all the rules. Rules are listed based on priority.
Geofence Admin Rules — In this tab, we define all the rules related to the admin role. Rules are listed based on priority.

Defining Roles, Users 👨

We can define the users, roles as per the problem statement in Settings -> Users, Groups, Roles tab. It will look like this

Defining Geofencing Data Rules 🔐

We can write Geofencing Data Rules as per the problem statement in Settings -> Geofencing Data Rules.

Click on add new rule

Priority — keep this default
Role — Select the name of the role from the dropdown
Username — If you want to create a rule specific to username, select the name, otherwise, keep the selected value as *
Service — If you want to create a rule specific to the service(WMS, WFS, etc.), select the service name, otherwise keep the selected value as *
Request — If you select any specific service, then requests attached to it will be available for selection
Workspace — Select the workspace in which your layer belongs, keeping * will apply a rule to all workspaces
Layer — If you want to apply the rule to a specific layer in the selected workspace, select the name, keeping * will apply the rule to all layers in the workspace
IP Address range — Allowed IP address which can use geoserver can be written here, keeping it blank will make geoserver available globally
Access — Here we define whether we want to allow selected user/role the access of selected workspace/layer the access. choices available are ALLOW, DENY, LIMIT

Layer details — If you select a specific layer name, then this option is available. Here we define the rule according to the Layer specification. ✅ the specify layer details checkbox to see further options.

Allowed Styles — Here you can define the allowed style visible to the user, if kept blank, all styles will be available
CQL Read Filter — Here you can define the default CQL filter that will be applied to the layer, thus limiting the features based on filtering
CQL Write Filter — Here you can define the filter that will be applied in write request (e.g. WFSTransaction)
Allowed area (WKT) — Using this you can restrict the visibility of the features specific to a certain areas only. Convert the extent into polygon WKT and provide it here. An example of WKT is as follows.

SRID=4326;POLYGON ((73.212890625 16.46769474828897, 80.68359375 16.46769474828897, 80.68359375 22.350075806124867, 73.212890625 22.350075806124867, 73.212890625 16.46769474828897))

Layer Attributes — Select the layer’s attributes which you want to allow users to use. Make sure that the geometry attribute is always ON so that you can see the layer. Options available to select from are
None — to hide the attribute
Readonly — Just to read the data, but transaction won’t be allowed
Readwrite — User can read and write this attribute

Like this, all rules will be defined.

Testing time ⚔️

When we login as Indian_govwe’ll see the following data

When we login as Class_1we’ll see the following data

When we log in as Class_2we’ll see the following data

When we login as normalwe’ll see the following data

When we login as asian_user , we’ll see the following data

Using with web application 🌐

We can create the simple Openlayers application to load the data using login credentials as follows.

<!DOCTYPE html>
<html>
  <head>
    <title>Secured layer access - Krishna Lodha</title>
    <link rel="stylesheet" href="https://openlayers.org/en/v4.6.5/css/ol.css" type="text/css">
    <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?  features=requestAnimationFrame,Element.prototype.classList,URL"></script>
    <script src="https://openlayers.org/en/v4.6.5/build/ol.js"></script>
  </head>
  <body>
    <div id="map" class="map"></div>
    <div id="info">&nbsp;</div>
    <script>
    (function(open) {
    XMLHttpRequest.prototype.open = function() {
        var method = arguments[0].toLowerCase();
        var url = arguments[1];
        if(url == undefined) {
            console.log(arguments)
        }
        if((method === 'get' || method ==='post') ) {
            this.withCredentials = true;
            open.apply(this, arguments);
            this.setRequestHeader("Authorization", "Basic "+btoa(user+":"+pwd));
        } else {
            open.apply(this, arguments);
        }
    };
})(XMLHttpRequest.prototype.open);
var user = 'gov' //geoserver username
var pwd = 'geoserver'//geoserver password
function xhrTileLoadFunction(tile, src) {
    var xhr = new XMLHttpRequest();
    xhr.responseType = "arraybuffer";
    xhr.onload = function() {
        var arrayBufferView = new Uint8Array(this.response);
        var blob = new Blob([arrayBufferView], { type: 'image/png' });
        var urlCreator = window.URL || window.webkitURL;
        var imageUrl = urlCreator.createObjectURL(blob);
        tile.getImage().src = imageUrl;
    };
    xhr.open("GET", src);
    xhr.send();
}

      var wmsSource = new ol.source.TileWMS({
        url: 'http://localhost:8080/geoserver/cite/wms',
        params: {'LAYERS': 'cite:ne_10m_populated_places'},
       tileLoadFunction: xhrTileLoadFunction,
        serverType: 'geoserver',
        crossOrigin: 'use-credentials'
      });

      var wmsLayer = new ol.layer.Tile({
        source: wmsSource
      });

      var view = new ol.View({
        projection:'EPSG:4326',
        center: [71, 20],
        zoom: 5
      });

      var map = new ol.Map({
        layers: [new ol.layer.Tile({
            source: new ol.source.OSM()
          }),
          wmsLayer],
        target: 'map',
        view: view
      });

    </script>
  </body>
</html>

Now we’ll fetch information using getFeatureInfo (fetch) call as well, this will also use the credentials

      //on click info
      map.on('singleclick', function(evt) {
        var viewResolution = /** @type {number} */ (view.getResolution());
        var url = wmsSource.getGetFeatureInfoUrl(
            evt.coordinate, viewResolution, 'EPSG:4326',
            {'INFO_FORMAT': 'application/json'});
        if (url) {
         fetch(url,{
          headers: new Headers({
     'Authorization': 'Basic '+btoa(user+":"+pwd), 
   }),
         })
         .then(res => res.json())
         .then(data => console.log(data))
        }
      });

On click, properties will be shown as the username

This is how we can use a geofence to secure the data based on layer geometry and attributes level.

Leave a Comment

Your email address will not be published. Required fields are marked *