ALFI Java Integration Guide

Connect to the ALFI Maven Repository

Gradle

Respository

repositories {
    maven {
        url 'https://maven.gremlin.com/'
    }
}

Dependencies

Required

compile group: 'com.gremlin', name: 'alfi-core', version: '0.5+'

Optional

// If your application is hosted on AWS EC2 or Lambda, use this to integrate with AWS 
// (like Parameter Store Configuration support)
compile group: 'com.gremlin', name: 'alfi-aws', version: '0.5+'

// DynamoDB Injection Points
compile group: 'com.gremlin', name: 'alfi-aws-dynamodb-client', version: '0.5+'

// Apache HTTP Client Injection Points
compile group: 'com.gremlin', name: 'alfi-apache-http-client', version: '0.5+'

Maven

Repository

<!-- Source for all Gremlin artifacts -->
<repository>
    <snapshots>
        <enabled>false</enabled>
    </snapshots>
    <id>gremlin</id>
    <name>The Gremlin Repository</name>
    <url>https://maven.gremlin.com/</url>
</repository>

Dependencies

Required

<dependency>
    <groupId>com.gremlin</groupId>
    <artifactId>alfi-core</artifactId>
    <version>[0.5.0, )</version>
</dependency>

Optional

<!-- If your application is hosted on AWS EC2 or Lambda, use this to integrate with AWS 
    (like Parameter Store Configuration support) -->
<dependency>
    <groupId>com.gremlin</groupId>
    <artifactId>alfi-aws</artifactId>
    <version>[0.5.0, )</version>
</dependency>

<!-- DynamoDB Injection Points -->
<dependency>
    <groupId>com.gremlin</groupId>
    <artifactId>alfi-aws-dynamodb-client</artifactId>
    <version>[0.5.0, )</version>
</dependency>

<!-- Apache HTTP Client Injection Points -->
<dependency>
    <groupId>com.gremlin</groupId>
    <artifactId>alfi-apache-http-client</artifactId>
    <version>[0.5.0, )</version>
</dependency>

Authenticate your Application with Gremlin

In order to authenticate to Gremlin, you must provide the following configuration values to your application.

  • GREMLIN_ALFI_IDENTIFIER : A unique identifier for the application. This will be used to distinguish all of the application instances from one another
  • GREMLIN_TEAM_ID : The Team ID that this application belongs to. Only users in that team may conduct attacks on it.
  • GREMLIN_TEAM_CERTIFICATE_OR_FILE : Certificate for authenticating to Gremlin. See below for syntax on permissible values.
  • GREMLIN_TEAM_PRIVATE_KEY_OR_FILE : Private key for authenticating to Gremlin. See below for syntax on permissible values.

You may set these as environment variables or in a gremlin.properties file on the classpath. Certificates can be downloaded for each team from the Settings Page.

Examples:

As a raw value

GREMLIN_TEAM_CERTIFICATE_OR_FILE=-----BEGIN CERTIFICATE-----...

Or pointing to a file

GREMLIN_TEAM_CERTIFICATE_OR_FILE=file:///usr/gremlin/certificate.pem

Optional Configuration

The following keys may be set to tune how ALFI operates.

  • GREMLIN_ALFI_ENABLED : If set to anything other than true, all functionality is turned off. This is designed to give you the ability to safely deploy ALFI, knowing you’ve got a simple off-switch. When the functionality is off, no failures are ever injected by ALFI, no calls are made to the API, and no logging past configuration-time occurs.
  • GREMLIN_REFRESH_INTERVAL_MS : You may optionally provide this value to set the frequency with which the library will contact the Gremlin API. Minimum of 1000 (1 second), maximum of 300000 (5 minutes). Default of 10000 (10 seconds). This determines how quickly your application reacts to attacks being halted or created and the amount of network traffic generated by the library.

Examples:

  • GREMLIN_ALFI_ENABLED=true
  • GREMLIN_ALFI_IDENTIFIER=recommendation-service-i-0ab123456
  • GREMLIN_REFRESH_INTERVAL_MS=20000

Code integration

Get a GremlinService instance

The main class you’ll be interacting with is com.gremlin.GremlinService. This class abstracts all of the machinery to register with the Gremlin API, find and cache experiments, and report success back to the Gremlin API. An instance of this class is used in each place in your application where you would like to have the option of creating an attack.

IMPORTANT : This class is designed to be a singleton

To create an instance of this class manually, create a GremlinServiceFactory, then get the GremlinService from that:

import com.gremlin.GremlinService;
import com.gremlin.GremlinServiceFactory;

private final GremlinServiceFactory gremlinServiceFactory = new GremlinServiceFactory();
private final GremlinService gremlinService = gremlinServiceFactory.getService();

To enforce the singleton-ness of this object, you may use dependency-injection (DI) as well. The following example uses hk2, but any library can represent this object graph.

import com.gremlin.GremlinService;
import com.gremlin.GremlinServiceFactory;
import org.glassfish.hk2.api.Factory;
import org.jvnet.hk2.annotations.Service;

import javax.inject.Inject;

@Service
public class GremlinHk2ServiceFactory extends GremlinServiceFactory implements Factory<GremlinService> {

    // Cache `GremlinService` as a singleton here
    private final GremlinService gremlinServiceSingleton;

    @Inject
    public GremlinHk2ServiceFactory() {
        super();
        this.gremlinServiceSingleton = this.getGremlinService();
    }

    @Override
    public GremlinService provide() {
        return gremlinServiceSingleton;
    }

    @Override
    public void dispose(GremlinService instance) {

    }
}
import com.gremlin.GremlinService;
import org.glassfish.hk2.utilities.binding.AbstractBinder;

import javax.inject.Singleton;

public class GremlinDiBinder extends AbstractBinder {

    @Override
    protected void configure() {
        bind(GremlinHk2ServiceFactory.class).to(GremlinHk2ServiceFactory.class).in(Singleton.class);
        bindFactory(GremlinHk2ServiceFactory.class).to(GremlinService.class).in(Singleton.class);
    }
}

Initialize ApplicationCoordinates

An important concept in ALFI is that each application has a set of identifying attributes. This set of attributes is named ApplicationCoordinates and is used to determine when an application matches an attack. Some example sets of ApplicationCoordinates are:

  • {"type"="AwsLambda", "region"="us-west-1", "name"="event-handler"}
  • {"type"="MyServiceType", "region"="us-east-1", "service"="recommendations", "criticality"="2", "userfacing"="true"}

alfi-aws includes integrations for running on AWS Lambda and EC2. In the case of AWS Lambda, the attributes type=AwsLambda, name, and region can be set for you. In the case of AWS EC2, the attributes type=AwsEc2, region, az, and instanceId can be set for you. These attributes are inferred from the environment using AwsApplicationCoordinatesResolver.inferFromEnvironment().

If you have other facets of your application that would be useful for targeting, you may also create your own. To do so, you need a subclass of GremlinCoordinatesProvider. This abstract class has 2 methods. To create your own ApplicationCoordinates, override initializeApplicationCoordinates(). The auto-generated ApplicationCoordinates (if any) are supplied as an argument to this method, so you can append to those if they exist. Here’s an example of creating your own ApplicationCoordinates:

import com.gremlin.ApplicationCoordinates;
import com.gremlin.GremlinCoordinatesProvider;

public class MyCoordinatesProvider extends GremlinCoordinatesProvider {

    @Override
    public ApplicationCoordinates initializeApplicationCoordinates() {
        return AwsApplicationCoordinatesResolver.inferFromEnvironment().map(c -> {
            c.putField("userfacing", "true");
            return c;
        }).orElseGet(() -> new ApplicationCoordinates.Builder()
                .withType("MyServiceType")
                .withField("name", "recommendations")
                .withField("userfacing", "true")
                .build());
    }
}

This set of ApplicationCoordinates are then used to match attacks. So if you create an attack that matches userfacing=true, this application will be included in the attack. This is how you can start narrowing down the scope of your attack to only applications which are useful in your scenario.

Configure TrafficCoordinates

Once you’ve described your application in terms of ApplicationCoordinates, you can then start targeting individual requests within your application. This will allow you to further refine your attack. Any piece of data may be used as a facet, and therefore as something to match on when constructing an attack. Some example TrafficCoordinates are: {"type": "OutboundHttp", "name": "customer-api", "verb": "GET", "customerId": 456} and {"type": "AwsDynamo", "table": "AuditLogs", "operation": "PutItem", "deviceType": "iPad"}

Gremlin provides a way to automatically construct TrafficCoordinates for common request types, as well as a way to create your own from scratch.

Apache HTTP Client TrafficCoordinates

Here’s how it looks to integrate fault-injection around Apache HTTP Client. This will populate TrafficCoordinates with “type” = “OutboundHttp”, “verb”, and “clientName”.

import com.gremlin.http.client.GremlinApacheHttpRequestInterceptor;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClientBuilder;

final HttpRequestInterceptor gremlinHttpInterceptor = 
    new GremlinApacheHttpRequestInterceptor(gremlinService, "sample");
final HttpClient outgoingHttpClient = HttpClientBuilder.create()
    .setDefaultRequestConfig(RequestConfig
        .custom()
        .setConnectTimeout(500)
        .setSocketTimeout(1000)
        .build()
    )
    .addInterceptorLast(gremlinHttpInterceptor)
    .build();

DynamoDB TrafficCoordinates

Here’s how it looks to integrate fault-injection around Dynamo DB. This will populate TrafficCoordinates with “type” = “AwsDynamo”, “table”, and “operation”.

import com.amazonaws.ClientConfiguration;
import com.amazonaws.handlers.RequestHandler2;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.gremlin.db.dynamo.client.GremlinDynamoRequestInterceptor;


final RequestHandler2 gremlinDynamoInterceptor = new GremlinDynamoRequestInterceptor(gremlinService, 1500, 500));
final AmazonDynamoDBClientBuilder builder = AmazonDynamoDBClientBuilder.standard()
    .withRegion(region.getName())
    .withClientConfiguration(new ClientConfiguration()
        .withClientExecutionTimeout(1500)
        .withConnectionTimeout(500)
        .withMaxErrorRetry(2)
    )
    .withRequestHandlers(gremlinDynamoInterceptor)
);

Custom TrafficCoordinates

You may also create your own TrafficCoordinates from scratch. In this case, you completely define the type of the TrafficCoordinates and all attributes. In this code example, assume that the code looks like this prior to Gremlin integration:

final Customer leader = redisClient.getLeader(contestId);

You may want to simulate failures of the Redis client per-contest. That way, you could verify how your UI/monitoring tools/operators react to this situation. To accomplish that, a Gremlin integration would look like:

import com.gremlin.TrafficCoordinates;

final TrafficCoordinates coordinates = new TrafficCoordinates.Builder()
    .withType("Redis")
    .withField("callType", "getLeader")
    .withField("contestId", contestId)
    .build();
final Customer leader = this.svc.execute(coordinates, () -> redisClient.getLeader(contestId));

Once you’ve written that code and deployed it, you may create attacks in the UI that fail specific Redis calls and pick out specific contestIds.

Extend TrafficCoordinates with request-level attributes

Often, companies set up their infrastructure to maintain a per-request data structure and use this information to provide logging, monitoring, and observability data points. A common pattern is to set up a RequestContext and have authentication filters put in information like customerId or deviceId into the RequestContext object. This object then permits access from any later point, so that those attributes are easily available. These are often excellent facets to create attacks on. If your system operates in this way, then you can set up a mapping to populate these values on all TrafficCoordinates. This code lives in a concrete subclass of GremlinCoordinatesProvider, which you’ve already seen in: Initialize Application Coordinates.

import com.gremlin.GremlinCoordinatesProvider;
import com.gremlin.TrafficCoordinates;

public class MyCoordinatesProvider extends GremlinCoordinatesProvider {

    @Override
    public TrafficCoordinates extendEachTrafficCoordinates(TrafficCoordinates incomingCoordinates) {
        incomingCoordinates.putField("customerId", MyRequestContext.getCustomerId());
        incomingCoordinates.putField("deviceId", MyRequestContext.getDeviceId());
        incomingCoordinates.putField("country", MyRequestContext.getCountry());
        return incomingCoordinates;
    }
}

With this code wired into the construction of your GremlinService instance, all TrafficCoordinates will now get those 3 attributes and they are eligible to be matched for any type of traffic you’d like to attack.

Alternate configuration mechanism

As described above, the default configuration resolution mechanism is to use either properties defined in gremlin.properties, or in environment variables where your application runs. If those don’t fit your needs, then you can provide an alterate mechanism by subclassing GremlinConfigurationResolver and supplying it to GremlinServiceFactory at construction-time.