Skip to content

Commit

Permalink
SGCS-34 Validation of sg_roles_mapping.yml
Browse files Browse the repository at this point in the history
  • Loading branch information
jochenkressin committed Dec 4, 2016
1 parent c5de7e7 commit ac0f02a
Show file tree
Hide file tree
Showing 23 changed files with 218 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ public abstract class AbstractApiAction extends BaseRestHandler {
private final AdminDNs adminDNs;
private final ConfigurationLoader cl;
private final ClusterService cs;
private final AuditLog auditLog;

static {
printLicenseInfo();
Expand All @@ -80,7 +79,6 @@ protected AbstractApiAction(final Settings settings, final RestController contro
this.adminDNs = adminDNs;
this.cl = cl;
this.cs = cs;
this.auditLog = auditLog;
}

protected abstract AbstractConfigurationValidator getValidator(final Method method, BytesReference ref);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.elasticsearch.ElasticsearchException;
Expand All @@ -31,10 +34,15 @@
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.rest.RestRequest.Method;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.google.common.base.Joiner;

public abstract class AbstractConfigurationValidator {

JsonFactory factory = new JsonFactory();

/* public for testing */
public final static String INVALID_KEYS_KEY = "invalid_keys";

Expand All @@ -47,12 +55,14 @@ public abstract class AbstractConfigurationValidator {
protected final ESLogger log = Loggers.getLogger(this.getClass());

/** Define the various keys for this validator */
protected final Set<String> allowedKeys = new HashSet<>();
protected final Map<String, DataType> allowedKeys = new HashMap<>();

protected final Set<String> mandatoryKeys = new HashSet<>();

protected final Set<String> mandatoryOrKeys = new HashSet<>();

protected final Map<String, String> wrongDatatypes = new HashMap<>();

/** Contain errorneous keys */
protected final Set<String> missingMandatoryKeys = new HashSet<>();

Expand Down Expand Up @@ -92,7 +102,9 @@ public boolean validateSettings() {
return false;
}

Set<String> requested = settingsBuilder.build().names();
Settings settings = settingsBuilder.build();

Set<String> requested = settings.names();
// check if payload is accepted at all
if (!this.payloadAllowed && !requested.isEmpty()) {
this.errorType = ErrorType.PAYLOAD_NOT_ALLOWED;
Expand All @@ -103,7 +115,6 @@ public boolean validateSettings() {
this.errorType = ErrorType.PAYLOAD_MANDATORY;
return false;
}
// payload ok, check allowed, mandatory and mandatory OR keys

// mandatory settings, one of ...
if (Collections.disjoint(requested, mandatoryOrKeys)) {
Expand All @@ -116,16 +127,61 @@ public boolean validateSettings() {
missingMandatoryKeys.addAll(mandatory);

// invalid settings
Set<String> allowed = new HashSet<>(allowedKeys);
Set<String> allowed = new HashSet<>(allowedKeys.keySet());
requested.removeAll(allowed);
this.invalidKeys.addAll(requested);
boolean valid = missingMandatoryKeys.isEmpty() && invalidKeys.isEmpty() && missingMandatoryOrKeys.isEmpty();
if (!valid) {
this.errorType = ErrorType.INVALID_CONFIGURATION;
}

// check types
try {
if (!checkDatatypes()) {
this.errorType = ErrorType.WRONG_DATATYPE;
return false;
}
} catch (Exception e) {
this.errorType = ErrorType.BODY_NOT_PARSEABLE;
return false;
}

return valid;
}

private boolean checkDatatypes() throws Exception {
String contentAsJson = XContentHelper.convertToJson(content, false);
JsonParser parser = factory.createParser(contentAsJson);
JsonToken token = null;
while ((token = parser.nextToken()) != null) {
if(token.equals(JsonToken.FIELD_NAME)) {
String currentName = parser.getCurrentName();
DataType dataType = allowedKeys.get(currentName);
if(dataType != null) {
JsonToken valueToken = parser.nextToken();
switch (dataType) {
case STRING:
if(!valueToken.equals(JsonToken.VALUE_STRING)) {
wrongDatatypes.put(currentName, "String expected");
}
break;
case ARRAY:
if(!valueToken.equals(JsonToken.START_ARRAY) && !valueToken.equals(JsonToken.END_ARRAY)) {
wrongDatatypes.put(currentName, "Array expected");
}
break;
case OBJECT:
if(!valueToken.equals(JsonToken.START_OBJECT) && !valueToken.equals(JsonToken.END_OBJECT)) {
wrongDatatypes.put(currentName, "Object expected");
}
break;
}
}
}
}
return wrongDatatypes.isEmpty();
}

public XContentBuilder errorsAsXContent() {
try {
final XContentBuilder builder = XContentFactory.jsonBuilder();
Expand All @@ -140,6 +196,13 @@ public XContentBuilder errorsAsXContent() {
addErrorMessage(builder, MISSING_MANDATORY_KEYS_KEY, missingMandatoryKeys);
addErrorMessage(builder, MISSING_MANDATORY_OR_KEYS_KEY, missingMandatoryKeys);
break;
case WRONG_DATATYPE:
builder.field("status", "error");
builder.field("reason", ErrorType.WRONG_DATATYPE.getMessage());
for (Entry<String, String> entry : wrongDatatypes.entrySet()) {
builder.field( entry.getKey(), entry.getValue());
}
break;
default:
builder.field("status", "error");
builder.field("reason", errorType.getMessage());
Expand Down Expand Up @@ -177,10 +240,17 @@ private Settings.Builder toSettingsBuilder(final BytesReference ref) {
}
}

public static enum DataType {
STRING,
ARRAY,
OBJECT;
}

public static enum ErrorType {
NONE("ok"),
INVALID_CONFIGURATION("invalid configuration"),
BODY_NOT_PARSEABLE("Coud not parse content of request."),
NONE("ok"),
INVALID_CONFIGURATION("Invalid configuration"),
WRONG_DATATYPE("Wrong datatype"),
BODY_NOT_PARSEABLE("Could not parse content of request."),
PAYLOAD_NOT_ALLOWED("Request body not allowed for this action."),
PAYLOAD_MANDATORY("Request body required for this action.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class ActionGroupValidator extends AbstractConfigurationValidator {
public ActionGroupValidator(Method method, BytesReference ref) {
super(method, ref);
this.payloadMandatory = true;
allowedKeys.add("permissions");
allowedKeys.put("permissions", DataType.ARRAY);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ public class InternalUsersValidator extends AbstractConfigurationValidator {
public InternalUsersValidator(final Method method, BytesReference ref) {
super(method, ref);
this.payloadMandatory = true;
allowedKeys.add("hash");
allowedKeys.add("password");
allowedKeys.add("roles");
allowedKeys.put("hash", DataType.STRING);
allowedKeys.put("password", DataType.STRING);
allowedKeys.put("roles", DataType.ARRAY);
mandatoryOrKeys.add("hash");
mandatoryOrKeys.add("password");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ public class RolesMappingValidator extends AbstractConfigurationValidator {
public RolesMappingValidator(final Method method, final BytesReference ref) {
super(method, ref);
this.payloadMandatory = true;
allowedKeys.add("backendroles");
allowedKeys.add("hosts");
allowedKeys.add("users");
allowedKeys.put("backendroles", DataType.ARRAY);
allowedKeys.put("hosts", DataType.ARRAY);
allowedKeys.put("users", DataType.ARRAY);

mandatoryOrKeys.add("backendroles");
mandatoryOrKeys.add("hosts");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class RolesValidator extends AbstractConfigurationValidator {
public RolesValidator(final Method method, final BytesReference ref) {
super(method, ref);
this.payloadMandatory = true;
allowedKeys.add("indices");
allowedKeys.add("cluster");
allowedKeys.put("indices", DataType.OBJECT);
allowedKeys.put("cluster", DataType.ARRAY);

mandatoryOrKeys.add("indices");
mandatoryOrKeys.add("cluster");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ protected void addUserWithPassword(String username, String password, int status)
boolean sendHTTPClientCertificate = rh.sendHTTPClientCertificate;
rh.sendHTTPClientCertificate = true;
HttpResponse response = rh.executePutRequest("/_searchguard/api/user/" + username,
"{password: \"" + password + "\"}", new Header[0]);
"{\"password\": \"" + password + "\"}", new Header[0]);
Assert.assertEquals(status, response.getStatusCode());
rh.sendHTTPClientCertificate = sendHTTPClientCertificate;
}

protected void addUserWithPassword(String username, String password, String[] roles, int status) throws Exception {
boolean sendHTTPClientCertificate = rh.sendHTTPClientCertificate;
rh.sendHTTPClientCertificate = true;
String payload = "{" + "password: \"" + password + "\"," + "roles: [";
String payload = "{" + "\"password\": \"" + password + "\"," + "\"roles\": [";
for (int i = 0; i < roles.length; i++) {
payload += "\" " + roles[i] + " \"";
if (i + 1 < roles.length) {
Expand All @@ -103,7 +103,7 @@ protected void addUserWithHash(String username, String hash) throws Exception {
protected void addUserWithHash(String username, String hash, int status) throws Exception {
boolean sendHTTPClientCertificate = rh.sendHTTPClientCertificate;
rh.sendHTTPClientCertificate = true;
HttpResponse response = rh.executePutRequest("/_searchguard/api/user/" + username, "{hash: \"" + hash + "\"}",
HttpResponse response = rh.executePutRequest("/_searchguard/api/user/" + username, "{\"hash\": \"" + hash + "\"}",
new Header[0]);
Assert.assertEquals(status, response.getStatusCode());
rh.sendHTTPClientCertificate = sendHTTPClientCertificate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,17 @@ public void testRolesApi() throws Exception {
Assert.assertTrue(
settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("kluster"));

// put new configuration with wrong datatypes, must fail
response = rh.executePutRequest("/_searchguard/api/roles/sg_role_starfleet",
FileHelper.loadFile("roles_wrong_datatype.json"), new Header[0]);
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason"));
Assert.assertTrue(settings.get("indices").equals("Object expected"));
Assert.assertTrue(settings.get("cluster").equals("Array expected"));



// restore starfleet role
response = rh.executePutRequest("/_searchguard/api/roles/sg_role_starfleet",
FileHelper.loadFile("roles_starfleet.json"), new Header[0]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,34 @@ public void testRolesMappingApi() throws Exception {
settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thebackendroles"));
Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("thehosts"));

// wrong datatypes
response = rh.executePutRequest("/_searchguard/api/rolesmapping/sg_role_starfleet_captains",
FileHelper.loadFile("rolesmapping_backendroles_captains_single_wrong_datatype.json"), new Header[0]);
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason"));
Assert.assertTrue(settings.get("backendroles").equals("Array expected"));
Assert.assertTrue(settings.get("hosts") == null);
Assert.assertTrue(settings.get("users") == null);

response = rh.executePutRequest("/_searchguard/api/rolesmapping/sg_role_starfleet_captains",
FileHelper.loadFile("rolesmapping_hosts_single_wrong_datatype.json"), new Header[0]);
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason"));
Assert.assertTrue(settings.get("hosts").equals("Array expected"));
Assert.assertTrue(settings.get("backendroles") == null);
Assert.assertTrue(settings.get("users") == null);

response = rh.executePutRequest("/_searchguard/api/rolesmapping/sg_role_starfleet_captains",
FileHelper.loadFile("rolesmapping_users_picard_single_wrong_datatype.json"), new Header[0]);
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason"));
Assert.assertTrue(settings.get("hosts").equals("Array expected"));
Assert.assertTrue(settings.get("users").equals("Array expected"));
Assert.assertTrue(settings.get("backendroles").equals("Array expected"));

response = rh.executePutRequest("/_searchguard/api/rolesmapping/sg_role_starfleet_captains",
FileHelper.loadFile("rolesmapping_all_access.json"), new Header[0]);
Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import com.floragunn.searchguard.configuration.ConfigurationService;
import com.floragunn.searchguard.dlic.rest.validation.AbstractConfigurationValidator;
import com.floragunn.searchguard.test.helper.file.FileHelper;
import com.floragunn.searchguard.test.helper.rest.RestHelper.HttpResponse;
import com.google.common.base.Strings;

Expand Down Expand Up @@ -83,15 +84,22 @@ public void testUserApi() throws Exception {
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage());

// Wrong config keys
// Missing quotes in JSON
response = rh.executePutRequest("/_searchguard/api/user/nagilum", "{some: \"thing\", other: \"thing\"}",
new Header[0]);
Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(AbstractConfigurationValidator.ErrorType.BODY_NOT_PARSEABLE.getMessage(), settings.get("reason"));

// Wrong config keys
response = rh.executePutRequest("/_searchguard/api/user/nagilum", "{\"some\": \"thing\", \"other\": \"thing\"}",
new Header[0]);
Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(settings.get("reason"), AbstractConfigurationValidator.ErrorType.INVALID_CONFIGURATION.getMessage());
Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("some"));
Assert.assertTrue(settings.get(AbstractConfigurationValidator.INVALID_KEYS_KEY + ".keys").contains("other"));

// add user with correct setting. User is in role "sg_all_access"

// check access not allowed
Expand Down Expand Up @@ -139,6 +147,40 @@ public void testUserApi() throws Exception {
// create index first
setupStarfleetIndex();

// wrong datatypes in roles file
rh.sendHTTPClientCertificate = true;
response = rh.executePutRequest("/_searchguard/api/user/picard", FileHelper.loadFile("users_wrong_datatypes.json"), new Header[0]);
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason"));
Assert.assertTrue(settings.get("roles").equals("Array expected"));
rh.sendHTTPClientCertificate = false;

rh.sendHTTPClientCertificate = true;
response = rh.executePutRequest("/_searchguard/api/user/picard", FileHelper.loadFile("users_wrong_datatypes.json"), new Header[0]);
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason"));
Assert.assertTrue(settings.get("roles").equals("Array expected"));
rh.sendHTTPClientCertificate = false;

rh.sendHTTPClientCertificate = true;
response = rh.executePutRequest("/_searchguard/api/user/picard", FileHelper.loadFile("users_wrong_datatypes2.json"), new Header[0]);
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason"));
Assert.assertTrue(settings.get("password").equals("String expected"));
Assert.assertTrue(settings.get("roles") == null);
rh.sendHTTPClientCertificate = false;

rh.sendHTTPClientCertificate = true;
response = rh.executePutRequest("/_searchguard/api/user/picard", FileHelper.loadFile("users_wrong_datatypes3.json"), new Header[0]);
settings = Settings.builder().loadFromSource(response.getBody()).build();
Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
Assert.assertEquals(AbstractConfigurationValidator.ErrorType.WRONG_DATATYPE.getMessage(), settings.get("reason"));
Assert.assertTrue(settings.get("roles").equals("Array expected"));
rh.sendHTTPClientCertificate = false;

// use backendroles when creating user. User picard does not exist in
// the internal user DB
// and is also not assigned to any role by username
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
Expand All @@ -25,12 +24,9 @@
import java.nio.charset.StandardCharsets;

import org.apache.commons.io.IOUtils;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;

import com.floragunn.searchguard.test.AbstractSGUnitTest;
Expand Down

0 comments on commit ac0f02a

Please sign in to comment.