Skip to content

Commit

Permalink
CORE-1464 - Vault alias decorator (#304)
Browse files Browse the repository at this point in the history
* vault alias decorator

* fix tests

* refactor

* fix it

* tests
  • Loading branch information
k-sever authored Aug 16, 2022
1 parent bda0fe1 commit 3cacff4
Show file tree
Hide file tree
Showing 9 changed files with 365 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.verygood.security.larky.modules;

import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@AllArgsConstructor
@Builder
public class DecoratorConfig {

public static class InvalidDecoratorConfigException extends RuntimeException {

public InvalidDecoratorConfigException(String message) {
super(message);
}
}

@Getter
@AllArgsConstructor
@Builder
public static class NonLuhnValidTransformPattern {

private final String search;
private final String replace;
}

@Getter
@AllArgsConstructor
@Builder
public static class NonLuhnValidPattern {

private final String validatePattern;
private final List<NonLuhnValidTransformPattern> transformPatterns;
}

private final String searchPattern;
private final String replacePattern;
private final NonLuhnValidPattern nonLuhnValidPattern;


public static DecoratorConfig fromObject(Object decoratorConfig) {
if (!(decoratorConfig instanceof Map)) {
return null;
}
Map map = (Map) decoratorConfig;

DecoratorConfigBuilder decoratorConfigBuilder = DecoratorConfig.builder();

decoratorConfigBuilder.searchPattern(getString(map, "searchPattern"));
decoratorConfigBuilder.replacePattern(getString(map, "replacePattern"));

Map nonLuhnValidPattern = getMap(map, "nonLuhnValidPattern");
if (nonLuhnValidPattern != null) {
NonLuhnValidPattern.NonLuhnValidPatternBuilder nonLuhnValidPatternBuilder = NonLuhnValidPattern.builder();
nonLuhnValidPatternBuilder.validatePattern(getString(nonLuhnValidPattern, "validatePattern"));
ImmutableList.Builder<NonLuhnValidTransformPattern> transformPatterns = ImmutableList.builder();
for (Object transformPattern : getList(nonLuhnValidPattern, "transformPatterns")) {
NonLuhnValidTransformPattern.NonLuhnValidTransformPatternBuilder transformPatternBuilder = NonLuhnValidTransformPattern.builder();
Map transformPatternMap = toMap(transformPattern);
transformPatternBuilder.search(getString(transformPatternMap, "search"));
transformPatternBuilder.replace(getString(transformPatternMap, "replace"));
transformPatterns.add(transformPatternBuilder.build());
}
nonLuhnValidPatternBuilder.transformPatterns(transformPatterns.build());
decoratorConfigBuilder.nonLuhnValidPattern(nonLuhnValidPatternBuilder.build());
}
return decoratorConfigBuilder.build();
}

private static Map toMap(Object obj) {
if (obj == null) {
return null;
}
if (!(obj instanceof Map)) {
throw new InvalidDecoratorConfigException(
String.format("'%s' must be dict", obj)
);
}
return (Map) obj;
}

private static String getString(Map map, String field) {
if (!map.containsKey(field)) {
return null;
}
Object value = map.get(field);
if (!(value instanceof String)) {
throw new InvalidDecoratorConfigException(
String.format("'%s' field must be string", field)
);
}
return (String) value;
}

private static Map getMap(Map map, String field) {
if (!map.containsKey(field)) {
return null;
}
Object value = map.get(field);
if (!(value instanceof Map)) {
throw new InvalidDecoratorConfigException(
String.format("'%s' field must be dict", field)
);
}
return (Map) value;
}

private static List getList(Map map, String field) {
if (!map.containsKey(field)) {
return Collections.emptyList();
}
Object value = map.get(field);
if (!(value instanceof List)) {
throw new InvalidDecoratorConfigException(
String.format("'%s' field must be array", field)
);
}
return (List) value;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.verygood.security.larky.modules;

import com.google.common.collect.ImmutableList;
import com.verygood.security.larky.modules.DecoratorConfig.InvalidDecoratorConfigException;
import com.verygood.security.larky.modules.vgs.vault.NoopVault;
import com.verygood.security.larky.modules.vgs.vault.defaults.DefaultVault;
import com.verygood.security.larky.modules.vgs.vault.spi.LarkyVault;
import com.verygood.security.larky.modules.vgs.vault.NoopVault;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import net.starlark.java.annot.Param;
import net.starlark.java.annot.ParamType;
import net.starlark.java.annot.StarlarkBuiltin;
Expand All @@ -12,9 +16,6 @@
import net.starlark.java.eval.NoneType;
import net.starlark.java.eval.Starlark;

import java.util.List;
import java.util.ServiceLoader;


@StarlarkBuiltin(
name = "vault",
Expand Down Expand Up @@ -107,13 +108,23 @@ public VaultModule() {
allowedTypes = {
@ParamType(type = List.class)
}),
@Param(
name = "decorator_config",
doc = "alias decorator config",
named = true,
defaultValue = "None",
allowedTypes = {
@ParamType(type = NoneType.class),
@ParamType(type = Map.class),
}),
})
@Override
public Object redact(Object value, Object storage, Object format, List<Object> tags) throws EvalException {
public Object redact(Object value, Object storage, Object format, List<Object> tags, Object decoratorConfig) throws EvalException {
validateStorage(storage);
validateFormat(format);
validateDecoratorConfig(decoratorConfig);

return vault.redact(value, storage, format, tags);
return vault.redact(value, storage, format, tags, decoratorConfig);
}

@StarlarkMethod(
Expand Down Expand Up @@ -174,4 +185,23 @@ private void validateFormat(Object format) throws EvalException {
}
}

private void validateDecoratorConfig(Object decoratorConfig) throws EvalException {
if (decoratorConfig != null && !(decoratorConfig instanceof NoneType) && !(decoratorConfig instanceof Map)) {
throw Starlark.errorf(String.format(
"Decorator config of type %s is not supported in VAULT, expecting Map",
decoratorConfig.getClass().getName()
));
} else {
try {
DecoratorConfig.fromObject(decoratorConfig);
} catch (InvalidDecoratorConfigException e) {
throw Starlark.errorf(String.format(
"Decorator config '%s' is invalid. %s",
decoratorConfig, e.getMessage()
));
}
}
}


}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.verygood.security.larky.modules.vgs.vault;

import com.verygood.security.larky.modules.vgs.vault.spi.LarkyVault;
import java.util.Map;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Starlark;

import java.util.List;

public class NoopVault implements LarkyVault {
@Override
public Object redact(Object value, Object storage, Object format, List<Object> tags) throws EvalException {
public Object redact(Object value, Object storage, Object format, List<Object> tags, Object decoratorConfig) throws EvalException {
throw Starlark.errorf("vault.redact operation must be overridden");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.verygood.security.larky.modules.vgs.vault.defaults;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.starlark.java.eval.EvalException;

public class AliasDecorator implements TokenizeFunction {

private final TokenizeFunction tokenizeFunction;
private final Pattern searchPattern;
private final String replacePattern;


public AliasDecorator(
TokenizeFunction tokenizeFunction,
String searchPattern,
String replacePattern) {
this.tokenizeFunction = tokenizeFunction;
this.searchPattern = Pattern.compile(searchPattern);
this.replacePattern = replacePattern;
}

@Override
public String tokenize(String toTokenize) throws EvalException {

final Matcher matcher = searchPattern.matcher(toTokenize);

if (!matcher.find()) {
// Fallback to generic
return new UUIDAliasGenerator().generate(toTokenize);

}
return tokenize(matcher, replacePattern);
}

private String tokenize(Matcher matcher, String replacePattern) throws EvalException {
final String tokenGroup = matcher.group("token");
String tokenized = tokenizeFunction.tokenize(tokenGroup);
String preFormatted = replacePattern.replace("${token}", "%s");
return matcher.replaceFirst(String.format(preFormatted, tokenized));
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.verygood.security.larky.modules.vgs.vault.defaults;

import com.google.common.collect.ImmutableMap;
import com.verygood.security.larky.modules.DecoratorConfig;
import com.verygood.security.larky.modules.vgs.vault.spi.LarkyVault;

import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.NoneType;
import net.starlark.java.eval.Starlark;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.NoneType;
import net.starlark.java.eval.Starlark;


public class DefaultVault implements LarkyVault {
Expand Down Expand Up @@ -40,13 +40,32 @@ public class DefaultVault implements LarkyVault {
.build();

@Override
public Object redact(Object value, Object storage, Object format, List<Object> tags) throws EvalException {
public Object redact(Object value, Object storage, Object format, List<Object> tags, Object decoratorConfig) throws EvalException {
String sValue = getValue(value);
String alias = getAliasGenerator(format).generate(sValue);
AliasGenerator generator = getAliasGenerator(format);
Optional<AliasDecorator> aliasDecorator = resolveAliasDecorator(decoratorConfig, generator);

String alias = aliasDecorator.isPresent()
? aliasDecorator.get().tokenize(sValue)
: generator.generate(sValue);
getStorage(storage).put(alias, value);
return alias;
}

private Optional<AliasDecorator> resolveAliasDecorator(Object decoratorConfig, AliasGenerator generator) {
DecoratorConfig config = DecoratorConfig.fromObject(decoratorConfig);
if (config != null && config.getSearchPattern() != null && config.getReplacePattern() != null) {
AliasDecorator decorator = new AliasDecorator(
generator::generate,
config.getSearchPattern(),
config.getReplacePattern()
);
return Optional.of(decorator);
} else {
return Optional.empty();
}
}

@Override
public Object reveal(Object value, Object storage) throws EvalException {
String sValue = getValue(value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.verygood.security.larky.modules.vgs.vault.defaults;

import net.starlark.java.eval.EvalException;

@FunctionalInterface
public interface TokenizeFunction {

String tokenize(String value) throws EvalException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

public interface LarkyVault extends StarlarkValue {

Object redact(Object value, Object storage, Object format, List<Object> tags) throws EvalException;
Object redact(Object value, Object storage, Object format, List<Object> tags, Object decoratorConfig) throws EvalException;

Object reveal(Object value, Object storage) throws EvalException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void testNoopModule_exception() throws Exception {
// Assert Exceptions
Assertions.assertThrows(EvalException.class,
() -> {
vault.redact("fail", Starlark.NONE, Starlark.NONE, null);
vault.redact("fail", Starlark.NONE, Starlark.NONE, null, null);
},
"vault.redact operation must be overridden"
);
Expand All @@ -68,7 +68,7 @@ public void testDefaultModule_ok() throws Exception {

// Invoke Vault
String secret = "4111111111111111";
String alias = (String) vault.redact(secret, Starlark.NONE, Starlark.NONE, null);
String alias = (String) vault.redact(secret, Starlark.NONE, Starlark.NONE, null, null);
String result = (String) vault.reveal(alias, Starlark.NONE);

// Assert OK
Expand All @@ -85,7 +85,7 @@ public void testSPIModule_single_ok() throws Exception {

// Invoke Vault
String secret = "4111111111111111";
String alias = (String) vault.redact(secret, Starlark.NONE, Starlark.NONE, null);
String alias = (String) vault.redact(secret, Starlark.NONE, Starlark.NONE, null, null);
String result = (String) vault.reveal(alias, Starlark.NONE);

// Assert OK
Expand Down
Loading

0 comments on commit 3cacff4

Please sign in to comment.