Skip to content

Addition of auditing classes #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: nelson-test
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.uid2</groupId>
<artifactId>uid2-shared</artifactId>
<version>1.8.0-SNAPSHOT</version>
<version>1.9.0</version>

<name>${project.groupId}:${project.artifactId}</name>
<description>Library for all the shared uid2 operations</description>
Expand Down Expand Up @@ -153,6 +153,11 @@
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>software.amazon.qldb</groupId>
<artifactId>amazon-qldb-driver-java</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>

<build>
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/uid2/shared/audit/Actions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.uid2.shared.audit;

public enum Actions {
LIST,
GET,
UPDATE,
CREATE,
DELETE,
DISABLE,
ENABLE,
REVEAL
}
31 changes: 31 additions & 0 deletions src/main/java/com/uid2/shared/audit/AuditFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.uid2.shared.audit;

import io.vertx.core.json.JsonObject;

import java.util.HashMap;
import java.util.Map;

/**
* AuditFactory controls the instantiation/creation of AuditMiddleware objects.
* Depending on the needs of the specific implementation, the AuditFactory
* can be implemented to always return the same AuditMiddleware object, create a new
* AuditMiddleware object for every class that calls getAuditMiddleware(Class), or
* exhibit some other behavior.
*/
public class AuditFactory {
private static final Map<JsonObject, IAuditMiddleware> middlewareMap = new HashMap<>();

/**
* Returns an AuditMiddleware object with the designated configuration. If one does
* not already exist, creates a new AuditMiddleware object using the configuration.
*
* @return the designated AuditMiddleware object for the passed class.
*/
public static IAuditMiddleware getAuditMiddleware(JsonObject config){
if(!middlewareMap.containsKey(config)){
middlewareMap.put(config, new AuditMiddlewareImpl(new QLDBAuditWriter(config)));
}
return middlewareMap.get(config);
}

}
71 changes: 71 additions & 0 deletions src/main/java/com/uid2/shared/audit/AuditMiddlewareImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.uid2.shared.audit;

import com.uid2.shared.auth.IAuthorizable;
import io.vertx.ext.web.RoutingContext;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

public class AuditMiddlewareImpl implements IAuditMiddleware{
private final IAuditWriter auditWriter;

public AuditMiddlewareImpl(IAuditWriter writer){
this.auditWriter = writer;
}

@Override
public Function<List<OperationModel>, Boolean> handle(RoutingContext rc) {
InnerAuditHandler auditHandler = new InnerAuditHandler(rc, auditWriter);
return auditHandler::writeLogs;
}

private static class InnerAuditHandler{
private final RoutingContext rc;
private final IAuditWriter auditWriter;
private InnerAuditHandler(RoutingContext rc, IAuditWriter auditWriter) {
this.rc = rc;
this.auditWriter = auditWriter;
}

public boolean writeLogs(List<OperationModel> modelList){
String ipAddress = getIPAddress(rc);
List<IAuditModel> auditModelList = new ArrayList<>();
for(OperationModel model : modelList) {
auditModelList.add(new QLDBAuditModel(model.itemType, model.itemKey, model.actionTaken, ipAddress,
rc != null ? ((IAuthorizable) rc.data().get("api-client")).getContact() : null,
System.getenv("HOSTNAME"), Instant.now().getEpochSecond(), model.itemHash, model.summary));
}
return auditWriter.writeLogs(auditModelList);
}

private static String getIPAddress(RoutingContext rc) {
if(rc == null){
return null;
}
List<String> listIP = rc.request().headers().getAll("X-Forwarded-For");
List<InetAddress> publicIPs = new ArrayList<>();
for(String str : listIP){
try {
InetAddress address = InetAddress.getByName(str);
if(!address.isSiteLocalAddress()){
publicIPs.add(address);
}
}
catch(UnknownHostException ignored){

}

}
if(publicIPs.isEmpty()){
return rc.request().remoteAddress().toString();
}
else{
return publicIPs.get(0).getHostAddress(); //arbitrary if multiple
}
}
}
}
16 changes: 16 additions & 0 deletions src/main/java/com/uid2/shared/audit/IAuditInit.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.uid2.shared.audit;

import java.util.Collection;

/**
* Responsible for the initialization of the QLDB table and any initial entries it must contain.
*/
public interface IAuditInit {

/**
* Creates a table with the name specified in the config, with all entries as specified by modelList, and sets up
* any necessary configuration.
* @param modelList the models to add to the audit database.
*/
void init(Collection<OperationModel> modelList);
}
22 changes: 22 additions & 0 deletions src/main/java/com/uid2/shared/audit/IAuditMiddleware.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.uid2.shared.audit;

import io.vertx.ext.web.RoutingContext;

import java.util.List;
import java.util.function.Function;

/**
* AuditMiddleware objects are intended to be attached to any endpoint that the system
* wants to keep track of via logging to an external source, and pass logging data to an AuditWriter object.
*/
public interface IAuditMiddleware {

/**
* Handle to attach to any route whose actions require logging.
*
* @param rc the RoutingContext of the endpoint access that initiated the request.
* @return a function that takes a List of OperationModels and returns whether the audit
* writing was successful.
*/
Function<List<OperationModel>, Boolean> handle(RoutingContext rc);
}
34 changes: 34 additions & 0 deletions src/main/java/com/uid2/shared/audit/IAuditModel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.uid2.shared.audit;

import io.vertx.core.json.JsonObject;

/**
* An AuditModel contains fields that collectively logs all necessary details of an action
* that reads or writes sensitive information in the uid2-admin server. AuditModel objects should
* be <b>unmodifiable</b> and answer the following questions:
*
* • what happened?
* • when did it happen?
* • who initiated it?
* • on what did it happen?
* • where was it observed?
* • from where was it initiated?
* • to where was it going?
*/
public interface IAuditModel {

/**
* Converts the AuditModel to JSON format to be used in document-store databases.
* Every field should be a key in the resulting JSON object.
*
* @return a JSON representation of this AuditModel.
*/
JsonObject writeToJson();

/**
* Converts the AuditModel into a readable String format to be used in text logs.
*
* @return a String representation of this AuditModel.
*/
String writeToString();
}
16 changes: 16 additions & 0 deletions src/main/java/com/uid2/shared/audit/IAuditWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.uid2.shared.audit;

import java.util.Collection;

/**
* AuditWriter is responsible for the logic to write out to designated logging databases.
*/
public interface IAuditWriter {
/**
* Logs the information in the AuditModel to an external database(s).
* Does not log any information if model == null.
*
* @param model the AuditModel to write out.
*/
boolean writeLogs(Collection<IAuditModel> model);
}
25 changes: 25 additions & 0 deletions src/main/java/com/uid2/shared/audit/OperationModel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.uid2.shared.audit;

/**
* Store the type of data, action that occurred, and any extra information necessary to know about the operation.
* Also stores the itemKey representing the operation. itemKey should be designed such that all read/writes
* affecting the same row(s) in the same table share the same value. It should be hashed in case the row identifier
* itself is sensitive information.
*/
public class OperationModel {

public final Type itemType;
public final String itemKey;
public final Actions actionTaken;
public final String itemHash;
public final String summary;

public OperationModel(Type itemType, String itemKey, Actions actionTaken,
String itemHash, String summary){
this.itemType = itemType;
this.itemKey = itemKey;
this.actionTaken = actionTaken;
this.itemHash = itemHash;
this.summary = summary;
}
}
88 changes: 88 additions & 0 deletions src/main/java/com/uid2/shared/audit/QLDBAuditModel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.uid2.shared.audit;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vertx.core.json.JsonObject;

import java.io.IOException;

public class QLDBAuditModel implements IAuditModel{

/**
* The table that the user accesses.
*/
@JsonIgnore
public final Type itemType;
/**
* An identifier for the row in the table that is accessed. Is null if more than one row is accessed at the same
* time, e.g. listing all values in a table. If itemKey should be secret, hash before putting it into the model.
*/
@JsonIgnore
public final String itemKey;
/**
* Describes the action the user performed on the table (e.g. read ("GET"), write ("CREATE"/"DELETE")...)
*/
public final Actions actionTaken;
/**
* The IP of the user making the HTTP request.
*/
public final String clientIP;
/**
* The email of the user making the HTTP request.
*/
public final String userEmail;
/**
* The server that processed the HTTP request.
*/
public final String hostNode;
/**
* The time that the HTTP request was received by the server.
*/
public final long timeEpochSecond;
/**
* The hash of the entire item being accessed/modified by the user. Is null if more than one
* row is accessed at the same time (which should only be get/list queries; otherwise make multiple
* queries).
*/
public final String itemHash;
/**
* Names the exact operation done to the item (e.g. rekeyed, revealed, disabled, etc.)
*/
public final String summary;

public QLDBAuditModel(Type itemType, String itemKey, Actions actionTaken, String clientIP,
String userEmail, String hostNode, long timeEpochSecond, String itemHash, String summary){
this.itemType = itemType;
this.itemKey = itemKey;
this.actionTaken = actionTaken;
this.clientIP = clientIP;
this.userEmail = userEmail;
this.hostNode = hostNode;
this.timeEpochSecond = timeEpochSecond;
this.itemHash = itemHash;
this.summary = summary;
}

@Override
public JsonObject writeToJson() {
ObjectMapper mapper = new ObjectMapper();
JsonObject jo;
try {
jo = new JsonObject(mapper.writeValueAsString(this));
}
catch(IOException e){
e.printStackTrace();
jo = new JsonObject();
}
JsonObject outerJo = new JsonObject();
outerJo.put("itemType", itemType);
outerJo.put("itemKey", itemKey);
outerJo.put("data", jo);
return outerJo;
}

@Override
public String writeToString() {
return writeToJson().toString();
}
}
Loading