Skip to content

GSIP 74 Finer Grained Admin Security

jdeolive edited this page Jun 11, 2014 · 1 revision

GSIP 74 - Finer Grained Admin Security

Overview

The ability to provide limited administrative access to non administrator accounts.

Proposed By

Justin Deoliveira

Assigned to Release

2.2

State

Under Discussion, In Progress, Completed, Rejected, Deferred

Motivation

The current model of administration in GeoServer is that admin users can do everything, and non-admin users can do nothing. This proposal adds the ability to have semi-administrative accounts that allow for a limited set of administrative functionality.

Proposal

Quick Info

The current security subsystem allows for an administrator to lock down access to an entire workspace, or a subset of layers within that workspace. The level of access (access mode) is either “read” or “write”. This proposal adds a third access mode named “admin” which is meant to signify administrative access privileges.

The “admin” access mode is the highest level of access, and implies read/write, the same way write access implies read access. Admin access rules are specified in layers.properties. For example:

topp.*.a=ROLE_TOPP_ADMIN,ROLE_ADMINISTRATOR

The above would grant administrative access to the topp workspace to any user granted the ROLE*TOPP*ADMIN role, and to the full administrator.


This proposal does not allow for setting admin access on anything below a workspace, for example setting admin access to a specific layer. However this could be a future improvement.


AdminRequest Thread Local

The current read and write access modes are meant to be applied to services, and not to administrative scenarios such as access via the web app or via the REST config api. In order to determine how security rules should be applied GeoServer must be able to tell if a request is an administrative one, or a “normal” service request. To do this we introduce a thread local variable named AdminRequest that is used to flag the current request as an administrative one.

The class is a simple one:

/**
 * Thread local that maintains the state of an administrative request. 
 * <p>
 * Such requests are typically used to configure the server, be it via the web ui, restconfig,
 *  etc...
 * </p>
 */
public class AdminRequest {

    static ThreadLocal<Object> REQUEST = new ThreadLocal<Object>();

    public static void start(Object request) {
        REQUEST.set(request);
    }

    public static Object get() {
        return REQUEST.get();
    }

    public static void finish() {
        REQUEST.remove();
    }
}

The thread local must be set by any application that provides administrative access to GeoServer. Currently this is only the web ui, and the restconfig api. Achieving this via the web admin ui is a straight forward wicket callback:

/**
 * Wicket callback that sets the {@link AdminRequest} thread local.
 *
 */
public class AdminRequestWicketCallback implements WicketCallback {

    @Override
    public void onBeginRequest() {
        AdminRequest.start(this);
    }

    @Override
    public void onEndRequest() {
        AdminRequest.finish();
    }
}

A similar callback for the rest api is employed, although in that case we must explicitly check for access via restconfig.

/**
 * Rest callback that sets the {@link AdminRequest} thread local.
 */
public class AdminRequestCallback implements DispatcherCallback {

    @Override
    public void dispatched(Request request, Response response, Restlet restlet) {
        if (restlet instanceof AbstractCatalogFinder) {
            //restconfig request
            AdminRequest.start(this);
        }
    }

    @Override
    public void finished(Request request, Response response) {
        AdminRequest.finish();
    }

}

Web UI

In the wicket web ui components such as menu items and pages are protected by an authorizer that allow for access control to the component based on the current authenticated user. Most pages use the “admin” component authorizer, meaning that the component is accessible to a user with the role ROLE\_ADMINISTRATOR.

A new type of ComponentAuthorizer is added that simply checks if the user authenticated.

/**
 * Authorizer that allows access if the user has authenticated.
 */
public class AuthenticatedComponentAuthorizer implements ComponentAuthorizer {

    @Override
    public boolean isAccessAllowed(Class componentClass, Authentication authentication) {
        return authentication != null && authentication.isAuthenticated();
    }
}

For convenience an instance of the authorizer is made available as a static constant on the ComponentAuthorizer itself.

public interface ComponentAuthorizer extends Serializable {

    ...

    /**
     * authorizer that grants access if the user has authenticated
     */
    static ComponentAuthorizer AUTHENTICATED = new AuthenticatedComponentAuthorizer(); 

}

With the menu items in the spring context and the pages themselves for all the data pages (workspaces, stores, etc…) are then modified to use the authenticated component authorizer. For example:

  <!-- the authorizer itself -->
  <bean id="authenticatedAuthorizer" 
     class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
     <property name="staticField" value="org.geoserver.web.ComponentAuthorizer.AUTHENTICATED"/>
  </bean>

  <!-- workspace page menu item -->
  <bean id="workspaceMenuPage" class="org.geoserver.web.MenuPageInfo">
    <property name="id" value="workspaces" />
    <property name="titleKey" value="WorkspacePage.title" />
    <property name="descriptionKey" value="WorkspacePage.description" />
    <property name="componentClass"
      value="org.geoserver.web.data.workspace.WorkspacePage" />
    <property name="category" ref="dataCategory" />
    <property name="icon" value="../../img/icons/silk/folder.png" />
    <property name="order" value="10" />

    <!-- user the authenticated authorizer -->
    <property name="authorizer" ref="authenticatedAuthorizer"/>
  </bean>

public class WorkspacePage extends GeoServerSecuredPage {
    ...
    @Override
    protected ComponentAuthorizer getPageAuthorizer() {
        return ComponentAuthorizer.AUTHENTICATED;
    }
}

With that a non administrative user sees a limited view of the catalog based on the rules allowing/disallowing admin accesss in layers.properties.

Feedback

Issue/Concern Resolution
WorkspaceAccessLimits broken constructor, add an additional constructor
coming up with a good default for the “adminable” flag Came up with
default for adminable flag by checking for admin role in current
authentication
Later adding ability to set admin rights to layers/styles,etc… without
breaking ResourceAccessManager
Better gui component authorization, only show menu items if user does
actually have admin rights to any workspace, and disable any links to
add new workspaces Added

Backwards Compatibility

In terms of configuration format work is backward compatible but changes the format of layers.properties. However going back to an older data directory those rules that use an admin access mode should simply be ignored, so this should be forward compatible as well.

Voting

Andrea Aime: +1 Alessio Fabiani: +0 Ben Caradoc Davies: +1 Gabriel Roldan: Justin Deoliveira: +1 Jody Garnett: +1 Mark Leslie: Rob Atkinson: Simone Giannecchini:+0

Links

JIRA Task Email Discussion Wiki Page

Clone this wiki locally