Skip to content

Commit

Permalink
[GEOS-11616] GSIP 229 - File system access isolation (geoserver#8052)
Browse files Browse the repository at this point in the history
* [GEOS-11616] GSIP 229 - File system access isolation

* [GEOS-11616] GSIP 229 - File system access isolation. Windows compatibility
  • Loading branch information
aaime authored Nov 25, 2024
1 parent 7f4cfa7 commit 62a2235
Show file tree
Hide file tree
Showing 48 changed files with 1,926 additions and 112 deletions.
6 changes: 6 additions & 0 deletions doc/en/user/source/configuration/properties/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,12 @@ GeoServer Property Reference
- x
- x
- x
* - GEOSERVER_FILESYSTEM_SANDBOX

:doc:`/security/sandbox`
- x
- x
- x

Setting Application property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
4 changes: 4 additions & 0 deletions doc/en/user/source/extensions/importer/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ There are two primary advantages to using the Importer over the standard GeoServ

#. **Creates unique styles** for each layer, rather than linking to the same (existing) styles.

.. warning:: The importer extension allows upload of data and is currently unable to respect the file system sandbox,
it uses a configurable location inside the data directory instead. Store creation will fail if the importer
is used and the sandbox is set. See also :ref:`security_sandbox`.

This section will discuss the Importer extension.

.. toctree::
Expand Down
1 change: 1 addition & 0 deletions doc/en/user/source/security/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The first page discusses configuration options in the web administration interfa
root
service
layer
sandbox
rest
urlchecks
disable
Expand Down
61 changes: 61 additions & 0 deletions doc/en/user/source/security/sandbox.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
.. _security_sandbox:

Filesystem sandboxing
=====================

GeoServer administrators can usually explore the full file system of the server where GeoServer
is running into, with the same privileges as the user running the servlet container.

This can be limited by setting up a sandbox, which will restrict the access to the file system
to a specific directory tree. The sandbox can be set up at two levels:

* **System sandbox**: the GeoServer administrator is sandboxed into a specific directory, and won't be
able to access files outside of it, nor change the sandbox configuration.
* **Regular sandbox**: the GeoServer administrator can still access the full file system, but can set up
a sandbox for each workspace, where the workspace administrators will be sandboxed into.

.. warning:: The importer extension allows upload of data and is currently unable to respect the file system sandbox,
it uses a configurable location inside the data directory instead. Store creation will fail if the importer
is used and the sandbox is set.

Setting up a system sandbox
---------------------------

The system sandbox is configured by setting the ``GEOSERVER_FILESYSTEM_SANDBOX`` variable to the
directory where the GeoServer administrator should be sandboxed into.
The variable can be provided as a Java system variable, as a servlet context parameter, or as an
environment variable, please consult the :ref:`application_properties` section for more details.

When the system sandbox is set:

* The GeoServer administrator will be sandboxed into the configured directory,
and won't be able to access files outside of it, nor change the sandbox configuration.
* The GeoServer workspace administrators will be sandboxed into ``<sandbox>/<workspace>``, where
``<workspace>`` is the name of any workspace they can access.

The system sandbox is best suited in hosting environments, where the GeoServer administrator and the
operating system administrator are different people, and the GeoServer administrator should not be
able to access the full file system.

Setting up a regular sandbox
----------------------------

The regular sandbox can be configured by GeoServer full administrators in the user interface,
from the :guilabel:`Security` -> :guilabel:`Data` page, or by adding the following entry in the
``layers.properties`` file:

.. code-block:: properties
# Set the sandbox for the workspace
filesystemSandbox=/path/to/sandbox
When the regular sandbox is set:

* The GeoServer administrator will still be able to access the full file system,
as well as change the sandbox configuration if they so desire.
* The GeoServer workspace administrators will be sandboxed into ``<sandbox>/<workspace>``, where
``<workspace>`` is the name of any workspace they can access.

The regular sandbox is best suited in multi-tenant environments where the main GeoServer administrator
also has access to the server operating system, while each tenant is modelled as a workspace
administrator and should be able to manage its own data, but not access the data of other tenants.
31 changes: 31 additions & 0 deletions doc/en/user/source/security/webadmin/data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,34 @@ This mode configures how GeoServer will advertise secured layers and behave when
:align: center

*Catalog mode*

File sandbox
------------

The sandbox allows a GeoServer full administrator to limit file system access to workspace administrators.
In particular, both GUI and REST API will sandbox workspace administrators to ``<sandbox>/<workspace>``,
and prevent them from accessing files outside of it.

.. figure:: images/fs_sandbox.png
:align: center

*Filesystem sandbox*

This part of the page is not visible if the operating system administrator has established
a sandbox for the whole GeoServer instance, in which case even the GeoServer full administrators
will be limited in the configured sandbox and won't be able to change it.

When the sandbox is configured, the file system chooser will show the accessible directories as
the file system roots. E.g., if a sandbox has been set to `/var/lib/geoserver`, and the current
workspace administrator has access to both the `sf` and `ne` workspaces, the file system
chooser will look as follows:

.. figure:: images/fs_sandbox_chooser.png
:align: center

*File system chooser*

Any attempt to manually set a path outside of the sandbox will result in a validation error
and prevent the store from being saved (both from the UI and the REST API).

For more information please refer to :ref:`security_sandbox` for more details.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ public CoverageStoreEditPage(CoverageStoreInfo store) {
}

@Override
protected void doSaveStore(CoverageStoreInfo info) {
protected boolean doSaveStore(CoverageStoreInfo info) {
if (info.getId() != null) {
super.doSaveStore(info);
return super.doSaveStore(info);
}
// do nothing, not part of catalog yet
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ public DataStoreEditPage(DataStoreInfo store) {
}

@Override
protected void doSaveStore(DataStoreInfo info) {
protected boolean doSaveStore(DataStoreInfo info) {
if (info.getId() != null) {
super.doSaveStore(info);
return super.doSaveStore(info);
}

// do nothing, not part of catalog yet
return true;
}
}
10 changes: 10 additions & 0 deletions src/main/src/main/java/applicationSecurityContext.xml
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,14 @@
<constructor-arg ref="dataDirectory"/>
</bean>

<bean id="defaultFileAdminAccessManager" class="org.geoserver.security.impl.DefaultFileAccessManager">
<constructor-arg ref="accessRulesDao"/>
<constructor-arg ref="secureCatalog"/>
<constructor-arg ref="geoServerSecurityManager"/>
</bean>

<bean id="fileSandboxEnforcer" class="org.geoserver.security.impl.FileSandboxEnforcer">
<constructor-arg ref="catalog"/>
</bean>

</beans>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org.geoserver.catalog;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.geoserver.GeoServerConfigurationLock;
import org.geoserver.GeoServerConfigurationLock.LockType;
Expand Down Expand Up @@ -33,7 +34,13 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
if (lockType == LockType.READ && isWriteMethod(method)) {
configurationLock.tryUpgradeLock();
}
return method.invoke(delegate, args);
try {
return method.invoke(delegate, args);
} catch (InvocationTargetException e) {
// preserve the original exception
Throwable cause = e.getCause();
throw cause;
}
}

private boolean isWriteMethod(Method method) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1690,7 +1690,8 @@ private GridCoverageReader getGridCoverageReader(
// /////////////////////////////////////////////////////////
final String urlString = expandedStore.getURL();
Object readObject =
getObjectToRead(urlString, coverageInfo, expandedStore, hints);
getCoverageStoreSource(
urlString, coverageInfo, expandedStore, hints);

// readers might change the provided hints, pass down a defensive copy
reader = gridFormat.getReader(readObject, hints);
Expand Down Expand Up @@ -1763,13 +1764,13 @@ private GridCoverageReader getGridCoverageReader(
}

/**
* Attempted to convert the URL-ish string to a parseable input object, otherwise just returns
* the string itself
* Attempted to convert the URL-ish string to a parseable input object for coverage reading
* purposes, otherwise just returns the string itself
*
* @param urlString the url string to parse, which may actually be a path
* @return an object appropriate for passing to a grid coverage reader
*/
private Object getObjectToRead(
public static Object getCoverageStoreSource(
String urlString,
CoverageInfo coverageInfo,
CoverageStoreInfo coverageStoreInfo,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* (c) 2024 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.catalog.event;

import org.geoserver.catalog.CatalogException;

/**
* A base class for {@link CatalogListener} that implements all listener methods without any action.
* Useful for listeners that are only interested in a subset of events.
*/
public class AbstractCatalogListener implements CatalogListener {

@Override
public void handleAddEvent(CatalogAddEvent event) throws CatalogException {
// nothing to do
}

@Override
public void handleRemoveEvent(CatalogRemoveEvent event) throws CatalogException {
// nothing to do
}

@Override
public void handleModifyEvent(CatalogModifyEvent event) throws CatalogException {
// nothing to do
}

@Override
public void handlePostModifyEvent(CatalogPostModifyEvent event) throws CatalogException {
// nothing to do
}

@Override
public void reloaded() {
// nothing to do
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* (c) 2024 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security;

import java.io.File;
import java.util.List;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.security.impl.DefaultFileAccessManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

/**
* Provides the GUI, REST API and catalog checks with directives on what parts of the file system
* the current user can access.
*/
public interface FileAccessManager {
/**
* Returns the file system roots available for the current user (or <code>null</code> if there
* are no restrictions)
*/
public List<File> getAvailableRoots();

/**
* Returns the sandbox root directory, if there is one, or <code>null</code> if there is none
* (i.e., the user can access the whole file system). This is used by the REST API to
* automatically prepend the sandbox root to the uploaded file paths.
*/
public File getSandbox();

/**
* Checks if the specified file is accessible in the context of the current request
*
* @param file the file to check
*/
public boolean checkAccess(File file);

/**
* Looks up the {@link FileAccessManager} to use, preferring a custom implementation if
* available, otherwise falling back on the default one. Mimics the behavior in {@link
* org.geoserver.security.SecureCatalogImpl}
*/
public static FileAccessManager lookupFileAccessManager() {
List<FileAccessManager> managers = GeoServerExtensions.extensions(FileAccessManager.class);
if (managers.isEmpty())
throw new RuntimeException("Unexpected, no FileAdminAccessManager found");

FileAccessManager manager = null;
for (FileAccessManager resourceAccessManager : managers) {
if (!DefaultFileAccessManager.class.equals(resourceAccessManager.getClass())) {
manager = resourceAccessManager;
break;
}
}

// no custom manager found?
if (manager == null) manager = managers.get(0);

return manager;
}

/** Returns the current user authentication */
default Authentication user() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
Loading

0 comments on commit 62a2235

Please sign in to comment.