Skip to content

Commit

Permalink
ICAP Filter for DLP and Antivirus protection (OpenIdentityPlatform#29)
Browse files Browse the repository at this point in the history
{
	"type": "ConditionalFilter",
	"config": {
		"condition": "${not empty system['icap'] and (request.method == 'POST' or request.method == 'PUT')}",
		"delegate": {
			"type": "ICAP",
			"name": "icap-dlp",
			"config": {
				"server": "${system['icap']}",
				"service": "${empty system['icap.service'] ?  '' : system['icap.service']}",
				"rewrite": "${empty system['icap.rewrite'] ?  'true' : system['icap.rewrite']}",
				"connect_timeout": "${empty system['icap.connect_timeout'] ?  '5000' : system['icap.connect_timeout']}",
				"read_timeout": "${empty system['icap.read_timeout'] ?  '20000' : system['icap.read_timeout']}"
			}
		}
	}
}
  • Loading branch information
vharseko authored Mar 23, 2022
1 parent 916be74 commit 777561f
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ jobs:
os: [ 'ubuntu-latest', 'macos-latest', 'windows-latest' ]
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Java ${{ matrix.Java }} (${{ matrix.os }})
uses: actions/setup-java@v2
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
cat <(echo -e "${{ secrets.GPG_PRIVATE_KEY }}") | gpg --batch --import
gpg --list-secret-keys --keyid-format LONG
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Set up Java for publishing to Maven Central Repository OSS
uses: actions/setup-java@v2
with:
Expand Down
9 changes: 3 additions & 6 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
[submodule "OpenDJ"]
path = OpenDJ
url = https://github.com/OpenIdentityPlatform/OpenDJ
[submodule "OpenAM"]
path = OpenAM
url = https://github.com/OpenIdentityPlatform/OpenAM
[submodule "openig-core/icap-client"]
path = openig-core/icap-client
url = https://github.com/OpenIdentityPlatform/icap-client.git
1 change: 1 addition & 0 deletions openig-core/icap-client
Submodule icap-client added at 5e7d6d
28 changes: 23 additions & 5 deletions openig-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.5.1</version>
</dependency>
<dependency>
<groupId>org.openidentityplatform.commons</groupId>
Expand All @@ -69,7 +68,6 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.210</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -116,7 +114,6 @@
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>3.1.2</version>
</dependency>
<!-- <dependency> -->
<!-- <groupId>javax.jms</groupId> -->
Expand Down Expand Up @@ -146,6 +143,10 @@
<groupId>org.openidentityplatform.commons</groupId>
<artifactId>json-resource-http</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.grizzly</groupId>
<artifactId>grizzly-http-server</artifactId>
Expand Down Expand Up @@ -180,7 +181,6 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>simple-jndi</groupId>
Expand Down Expand Up @@ -212,7 +212,6 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<goals>
Expand All @@ -221,6 +220,25 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/main/java</source>
<source>icap-client/src/main/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import org.forgerock.openig.security.TrustAllManager;
import org.forgerock.openig.security.TrustManagerHeaplet;
import org.forgerock.openig.thread.ScheduledExecutorServiceHeaplet;
import org.openidentityplatform.openig.filter.ICAPFilter;
import org.openidentityplatform.openig.mq.EmbeddedKafka;
import org.openidentityplatform.openig.mq.MQ_IBM;
import org.openidentityplatform.openig.mq.MQ_Kafka;
Expand Down Expand Up @@ -117,6 +118,7 @@ public class CoreClassAliasResolver implements ClassAliasResolver {
ALIASES.put("EmbeddedKafka", EmbeddedKafka.class);
ALIASES.put("MQ_Kafka", MQ_Kafka.class);
ALIASES.put("MQ_IBM", MQ_IBM.class);
ALIASES.put("ICAP", ICAPFilter.class);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.forgerock.util.time.TimeService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

/**
* Configuration class for configuring the OpenIG Gateway.
Expand All @@ -75,6 +76,12 @@ public final class GatewayHttpApplication implements HttpApplication {

private static final Logger logger = LoggerFactory.getLogger(GatewayHttpApplication.class);

static {
logger.debug("redirect jul to slf4j ...");
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
}

private static final JsonValue DEFAULT_CLIENT_HANDLER =
json(object(field("name", CLIENT_HANDLER_HEAP_KEY),
field("type", "ClientHandler")));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.openidentityplatform.openig.filter;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map.Entry;

import org.forgerock.http.Filter;
import org.forgerock.http.Handler;
import org.forgerock.http.protocol.Request;
import org.forgerock.http.protocol.Response;
import org.forgerock.http.protocol.Status;
import org.forgerock.openig.heap.GenericHeaplet;
import org.forgerock.openig.heap.HeapException;
import org.forgerock.services.context.Context;
import org.forgerock.util.promise.NeverThrowsException;
import org.forgerock.util.promise.Promise;
import org.forgerock.util.promise.Promises;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.rfc3507.client.ICAPClient;
import net.rfc3507.client.ICAPException;
import net.rfc3507.client.ICAPRequest;
import net.rfc3507.client.ICAPRequest.Mode;
import net.rfc3507.client.ICAPResponse;

public class ICAPFilter implements Filter {

private static final Logger logger = LoggerFactory.getLogger(ICAPFilter.class);

ICAPClient icap=null;
String service=null;
Boolean rewrite=true;

public static class Heaplet extends GenericHeaplet {

ICAPFilter filter;

@Override
public Object create() throws HeapException {
filter=new ICAPFilter();
return filter;
}

@Override
public void start() throws HeapException {
super.start();
String server=config.get("server").required().as(expression(String.class)).eval();

if (server==null || server.trim().isEmpty()) {
logger.debug("server is empty");
return;
}
try {
final URI uri=new URI(server);
filter.icap=new ICAPClient(uri.getHost(), uri.getPort()>0?uri.getPort():1344);
filter.icap.setConnectTimeout(Integer.parseInt(config.get("connect_timeout").defaultTo("5000").as(expression(String.class)).eval()));
filter.icap.setReadTimeout(Integer.parseInt(config.get("read_timeout").defaultTo("15000").as(expression(String.class)).eval()));
filter.service=config.get("service").defaultTo("").as(expression(String.class)).eval();
filter.rewrite=Boolean.parseBoolean(config.get("rewrite").defaultTo("true").as(expression(String.class)).eval());
logger.info("start {} connect_timeout={} read_timeout={}",uri,filter.icap.getConnectTimeout(),filter.icap.getReadTimeout());
} catch (URISyntaxException e) {
logger.warn("invalid server format: \"{}\" use icap://server:port",server);
}
}

@Override
public void destroy() {
super.destroy();
if (filter.icap!=null) {
filter.icap=null;
}
}
}

@Override
public Promise<Response, NeverThrowsException> filter(Context context, Request request, Handler next) {
if (icap!=null && !request.getEntity().isRawContentEmpty()) {
final ICAPRequest req=new ICAPRequest(service, Mode.REQMOD);
try {
req.setHttpRequestBody(request.getEntity().getBytes());
final ICAPResponse res=icap.execute(req);
if (res.getStatus()==204) { //allow content
return next.handle(context, request);
}else if (res.getStatus()==200) { //modify response
request.getHeaders().add("x-icap-status", ""+res.getStatus());
request.getHeaders().add("x-icap-message", res.getMessage());
for (Entry<String, List<String>> header : res.getHeaderEntries().entrySet()) {
request.getHeaders().add(header.getKey(), header.getValue());
}
if (rewrite) {
final ICAPResponse.ResponseHeader responseHeader=res.getResponseHeader();
final Response response = new Response(Status.valueOf(responseHeader.getStatus(), responseHeader.getMessage()));
for (Entry<String, List<String>> header : responseHeader.getHeaderEntries().entrySet()) {
response.getHeaders().add(header.getKey(), header.getValue());
}
response.setEntity(res.getHttpShrinkResponseBody());
return Promises.newResultPromise(response);
}
}else { //ignore error
logger.error("{}",res);
}
}catch (ICAPException e) {
logger.error("{}",e.toString());
}catch (IOException e) {
logger.warn("{}",e.toString());
}
}
return next.handle(context, request);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.openidentityplatform.openig.icap;

import java.io.IOException;

import org.forgerock.openig.heap.HeapException;
import org.junit.Test;

import net.rfc3507.client.ICAPClient;
import net.rfc3507.client.ICAPException;
import net.rfc3507.client.ICAPRequest;
import net.rfc3507.client.ICAPRequest.Mode;
import net.rfc3507.client.ICAPResponse;


public class ICAP_Test {


@Test
public void test() throws HeapException, IOException {
try {
final ICAPClient client=new ICAPClient("localhost", 1344);
final ICAPRequest request=new ICAPRequest("srv_clamav", Mode.REQMOD);
request.setHttpRequestBody(new String("X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*").getBytes());
final ICAPResponse response=client.execute(request);
System.out.println(new String(response.getHttpRawResponseBody()));
}catch (ICAPException e) {
// # start service - after start you can use the java library
// docker run --rm --name icap-server -p 1344:1344 toolarium/toolarium-icap-calmav-docker:0.0.1
//
// # optional you can login into the container
// docker exec -it icap-server /bin/bash
//
// # the configuration you will see under
// more /etc/c-icap/c-icap.conf
//
// # view / tail access-log
// tail -f /var/log/c-icap/access.log
//
// # view / tail server-log
// tail -f /var/log/c-icap/server.log
//
// # test with c-icap client inside the container
// c-icap-client -v -f entrypoint.sh -s "srv_clamav" -w 1024 -req http://request -d 5
//
// # stop service
// docker stop icap-server
}

}

}

0 comments on commit 777561f

Please sign in to comment.