@@ -8,7 +8,7 @@ import bubble.model.account.Account; | |||
import bubble.model.app.AppRule; | |||
import bubble.model.app.BubbleApp; | |||
import bubble.model.app.config.AppConfigDriverBase; | |||
import bubble.rule.request.CookieReplacement; | |||
import bubble.rule.request.HeaderReplacement; | |||
import bubble.rule.request.RequestProtectorConfig; | |||
import bubble.rule.request.RequestProtectorRuleDriver; | |||
import com.fasterxml.jackson.databind.JsonNode; | |||
@@ -25,27 +25,27 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.notFoundEx; | |||
@Slf4j | |||
public class RequestProtectorAppConfigDriver extends AppConfigDriverBase { | |||
public static final String VIEW_manageCookieReplacements = "manageCookieReplacements"; | |||
public static final String VIEW_manageHeaderReplacements = "manageHeaderReplacements"; | |||
@Override public Object getView(Account account, BubbleApp app, String view, Map<String, String> params) { | |||
switch (view) { | |||
case VIEW_manageCookieReplacements: | |||
case VIEW_manageHeaderReplacements: | |||
return loadManageCookiesReplacements(account, app); | |||
} | |||
throw notFoundEx(view); | |||
} | |||
private Set<CookieReplacement> loadManageCookiesReplacements(Account account, BubbleApp app) { | |||
private Set<HeaderReplacement> loadManageCookiesReplacements(Account account, BubbleApp app) { | |||
final RequestProtectorConfig config = getConfig(account, app); | |||
return config.getCookieReplacements(); | |||
return config.getHeaderReplacements(); | |||
} | |||
private RequestProtectorConfig getConfig(Account account, BubbleApp app) { | |||
return getConfig(account, app, RequestProtectorRuleDriver.class, RequestProtectorConfig.class); | |||
} | |||
public static final String ACTION_addCookieReplacement = "addCookieReplacement"; | |||
public static final String ACTION_removeCookieReplacement = "removeCookieReplacement"; | |||
public static final String ACTION_addHeaderReplacement = "addHeaderReplacement"; | |||
public static final String ACTION_removeHeaderReplacement = "removeHeaderReplacement"; | |||
public static final String PARAM_REGEX = "regex"; | |||
public static final String PARAM_REPLACEMENT = "replacement"; | |||
@@ -53,17 +53,17 @@ public class RequestProtectorAppConfigDriver extends AppConfigDriverBase { | |||
@Override public Object takeAppAction(Account account, BubbleApp app, String view, String action, | |||
Map<String, String> params, JsonNode data) { | |||
switch (action) { | |||
case ACTION_addCookieReplacement: | |||
return addCookieReplacement(account, app, data); | |||
case ACTION_addHeaderReplacement: | |||
return addHeaderReplacement(account, app, data); | |||
} | |||
if (log.isWarnEnabled()) log.warn("takeAppAction: action not found: "+action); | |||
throw notFoundEx(action); | |||
} | |||
private Set<CookieReplacement> addCookieReplacement(Account account, BubbleApp app, JsonNode data) { | |||
private Set<HeaderReplacement> addHeaderReplacement(Account account, BubbleApp app, JsonNode data) { | |||
final JsonNode regexNode = data.get(PARAM_REGEX); | |||
if (regexNode == null || regexNode.textValue() == null || empty(regexNode.textValue().trim())) { | |||
throw invalidEx("err.requestProtector.cookieRegexRequired"); | |||
throw invalidEx("err.requestProtector.headerRegexRequired"); | |||
} | |||
final String regex = regexNode.textValue().trim().toLowerCase(); | |||
@@ -72,44 +72,44 @@ public class RequestProtectorAppConfigDriver extends AppConfigDriverBase { | |||
? "" | |||
: replacementNode.textValue().trim().toLowerCase(); | |||
final RequestProtectorConfig config = getConfig(account, app).addCookieReplacement(regex, replacement); | |||
final RequestProtectorConfig config = getConfig(account, app).addHeaderReplacement(regex, replacement); | |||
final AppRule rule = loadRule(account, app); | |||
loadDriver(account, rule, RequestProtectorRuleDriver.class); // validate proper driver | |||
if (log.isDebugEnabled()) { | |||
log.debug("addCookieReplacement: updating rule: " + rule.getName() + ", adding regex: " + regex); | |||
log.debug("addHeaderReplacement: updating rule: " + rule.getName() + ", adding regex: " + regex); | |||
} | |||
ruleDAO.update(rule.setConfigJson(json(config))); | |||
return config.getCookieReplacements(); | |||
return config.getHeaderReplacements(); | |||
} | |||
@Override public Object takeItemAction(Account account, BubbleApp app, String view, String action, String id, | |||
Map<String, String> params, JsonNode data) { | |||
switch (action) { | |||
case ACTION_removeCookieReplacement: | |||
return removeCookieReplacement(account, app, id); | |||
case ACTION_removeHeaderReplacement: | |||
return removeHeaderReplacement(account, app, id); | |||
} | |||
if (log.isWarnEnabled()) log.warn("takeItemAction: action not found: "+action); | |||
throw notFoundEx(action); | |||
} | |||
private Set<CookieReplacement> removeCookieReplacement(Account account, BubbleApp app, String regex) { | |||
private Set<HeaderReplacement> removeHeaderReplacement(Account account, BubbleApp app, String regex) { | |||
final AppRule rule = loadRule(account, app); | |||
loadDriver(account, rule, RequestProtectorRuleDriver.class); // validate proper driver | |||
final RequestProtectorConfig config = getConfig(account, app); | |||
if (log.isDebugEnabled()) { | |||
log.debug("removeCookieReplacement: removing regex: " + regex + " from config.cookiesReplacements: " | |||
+ config.getCookieReplacements().toString()); | |||
log.debug("removeHeaderReplacement: removing regex: " + regex + " from config.cookiesReplacements: " | |||
+ config.getHeaderReplacements().toString()); | |||
} | |||
final RequestProtectorConfig updated = config.removeCookieReplacement(regex); | |||
final RequestProtectorConfig updated = config.removeHeaderReplacement(regex); | |||
if (log.isDebugEnabled()) { | |||
log.debug("removeCookieReplacement: updated.cookiesReplacements: " | |||
+ updated.getCookieReplacements().toString()); | |||
log.debug("removeHeaderReplacement: updated.cookiesReplacements: " | |||
+ updated.getHeaderReplacements().toString()); | |||
} | |||
ruleDAO.update(rule.setConfigJson(json(updated))); | |||
return updated.getCookieReplacements(); | |||
return updated.getHeaderReplacements(); | |||
} | |||
} |
@@ -46,6 +46,7 @@ public interface AppRuleDriver { | |||
String REDIS_FILTER_LISTS = "filterLists"; | |||
String REDIS_FLEX_LISTS = "flexLists"; // used in mitmproxy and dnscrypt-proxy for flex routing | |||
String REDIS_FLEX_EXCLUDE_LISTS = "flexExcludeLists"; // used in mitmproxy and dnscrypt-proxy for flex routing | |||
String REDIS_RESPONSE_HEADER_MODIFIER_LISTS = "responseHeaderModifierLists"; // used in mitmproxy | |||
String REDIS_LIST_SUFFIX = "~UNION"; | |||
default Set<String> getPrimedRejectDomains () { return null; } | |||
@@ -54,6 +55,7 @@ public interface AppRuleDriver { | |||
default Set<String> getPrimedFilterDomains () { return null; } | |||
default Set<String> getPrimedFlexDomains () { return null; } | |||
default Set<String> getPrimedFlexExcludeDomains () { return null; } | |||
default Set<String> getPrimedResponseHeaderModifiers () { return null; } | |||
static void defineRedisRejectSet(RedisService redis, String ip, String list, String[] rejectDomains) { | |||
defineRedisSet(redis, ip, REDIS_REJECT_LISTS, list, rejectDomains); | |||
@@ -79,12 +81,22 @@ public interface AppRuleDriver { | |||
defineRedisSet(redis, ip, REDIS_FLEX_EXCLUDE_LISTS, list, flexExcludeDomains); | |||
} | |||
static void defineRedisSet(RedisService redis, String ip, String listOfListsName, String listName, String[] domains) { | |||
static void defineRedisResponseHeaderModifiersSet(RedisService redis, String ip, String list, | |||
String[] modifiers) { | |||
defineRedisSet(redis, ip, REDIS_RESPONSE_HEADER_MODIFIER_LISTS, list, modifiers); | |||
} | |||
/** | |||
* `settings` parameter may be list of domains or any other list of strings - i.e. list of JSONs with specific setup | |||
* for the prime option of the driver. | |||
*/ | |||
static void defineRedisSet(RedisService redis, String ip, String listOfListsName, String listName, | |||
String[] settings) { | |||
final String listOfListsForIp = listOfListsName + "~" + ip; | |||
final String unionSetName = listOfListsForIp + REDIS_LIST_SUFFIX; | |||
final String ipList = listOfListsForIp + "~" + listName; | |||
final String tempList = ipList + "~"+now()+randomAlphanumeric(5); | |||
redis.sadd_plaintext(tempList, domains); | |||
redis.sadd_plaintext(tempList, settings); | |||
redis.rename(tempList, ipList); | |||
redis.sadd_plaintext(listOfListsForIp, ipList); | |||
final Long count = redis.sunionstore(unionSetName, redis.smembers(listOfListsForIp)); | |||
@@ -4,6 +4,7 @@ | |||
*/ | |||
package bubble.rule.request; | |||
import com.fasterxml.jackson.annotation.JsonIgnore; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.NonNull; | |||
@@ -11,20 +12,20 @@ import lombok.Setter; | |||
import lombok.experimental.Accessors; | |||
@NoArgsConstructor @Accessors(chain=true) | |||
public class CookieReplacement implements Comparable<CookieReplacement> { | |||
public class HeaderReplacement implements Comparable<HeaderReplacement> { | |||
public String getId() { return regex; } | |||
public void setId(String id) {} // noop | |||
@JsonIgnore public String getId() { return regex; } | |||
@JsonIgnore public void setId(String id) {} // noop | |||
@Getter @Setter private String regex; | |||
@Getter @Setter private String replacement; | |||
public CookieReplacement(@NonNull final String regex, @NonNull final String replacement) { | |||
public HeaderReplacement(@NonNull final String regex, @NonNull final String replacement) { | |||
this.regex = regex; | |||
this.replacement = replacement; | |||
} | |||
@Override public int compareTo(@NonNull final CookieReplacement o) { | |||
@Override public int compareTo(@NonNull final HeaderReplacement o) { | |||
return getRegex().compareTo(o.getRegex().toLowerCase()); | |||
} | |||
} |
@@ -18,20 +18,17 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
@Slf4j @Accessors(chain=true) | |||
public class RequestProtectorConfig { | |||
@Getter @Setter private Set<CookieReplacement> cookieReplacements = new TreeSet<>(); | |||
public boolean hasCookieReplacements() { return !empty(cookieReplacements); } | |||
public boolean hasCookieReplacementFor(@NonNull final String regex) { | |||
return hasCookieReplacements() && cookieReplacements.stream().anyMatch(r -> r.getRegex().equals(regex)); | |||
} | |||
@Getter @Setter private Set<HeaderReplacement> headerReplacements = new TreeSet<>(); | |||
public boolean hasHeaderReplacements() { return !empty(headerReplacements); } | |||
@NonNull public RequestProtectorConfig addCookieReplacement(@NonNull final String regex, | |||
@NonNull public RequestProtectorConfig addHeaderReplacement(@NonNull final String regex, | |||
@NonNull final String replacement) { | |||
cookieReplacements.add(new CookieReplacement(regex, replacement)); | |||
headerReplacements.add(new HeaderReplacement(regex, replacement)); | |||
return this; | |||
} | |||
@NonNull public RequestProtectorConfig removeCookieReplacement(@NonNull final String regex) { | |||
if (hasCookieReplacements()) cookieReplacements.removeIf(r -> r.getRegex().equals(regex)); | |||
@NonNull public RequestProtectorConfig removeHeaderReplacement(@NonNull final String regex) { | |||
if (hasHeaderReplacements()) headerReplacements.removeIf(r -> r.getRegex().equals(regex)); | |||
return this; | |||
} | |||
} |
@@ -9,46 +9,30 @@ import bubble.model.app.AppMatcher; | |||
import bubble.model.app.AppRule; | |||
import bubble.model.app.BubbleApp; | |||
import bubble.model.device.Device; | |||
import bubble.resources.stream.FilterHttpRequest; | |||
import bubble.rule.AbstractAppRuleDriver; | |||
import com.fasterxml.jackson.databind.JsonNode; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.apache.commons.io.input.ReaderInputStream; | |||
import org.cobbzilla.util.io.regex.RegexFilterReader; | |||
import org.cobbzilla.util.json.JsonUtil; | |||
import java.io.InputStream; | |||
import java.util.Iterator; | |||
import static org.cobbzilla.util.string.StringUtil.UTF8cs; | |||
import java.util.Set; | |||
import java.util.stream.Collectors; | |||
@Slf4j | |||
public class RequestProtectorRuleDriver extends AbstractAppRuleDriver { | |||
@Override public <C> Class<C> getConfigClass() { return (Class<C>) RequestProtectorConfig.class; } | |||
@Override public Set<String> getPrimedResponseHeaderModifiers() { | |||
final RequestProtectorConfig config = getRuleConfig(); | |||
return config.getHeaderReplacements().stream().map(JsonUtil::json).collect(Collectors.toSet()); | |||
} | |||
@Override public void init(JsonNode config, JsonNode userConfig, BubbleApp app, AppRule rule, AppMatcher matcher, | |||
Account account, Device device) { | |||
super.init(config, userConfig, app, rule, matcher, account, device); | |||
// refresh list | |||
final RequestProtectorConfig ruleConfig = getRuleConfig(); | |||
ruleConfig.getCookieReplacements(); | |||
} | |||
@Override public InputStream doFilterResponse(FilterHttpRequest filterRequest, InputStream in) { | |||
final RequestProtectorConfig config = getRuleConfig(); | |||
if (!config.hasCookieReplacements()) return in; | |||
final Iterator<CookieReplacement> crIterator = config.getCookieReplacements().iterator(); | |||
CookieReplacement cr = crIterator.next(); | |||
RegexFilterReader reader = new RegexFilterReader(in, new HttpHeaderReplacementFilter(cr.getRegex(), | |||
cr.getReplacement())); | |||
while (crIterator.hasNext()) { | |||
cr = crIterator.next(); | |||
reader = new RegexFilterReader(reader, new HttpHeaderReplacementFilter(cr.getRegex(), | |||
cr.getReplacement())); | |||
} | |||
return new ReaderInputStream(reader, UTF8cs); | |||
ruleConfig.getHeaderReplacements(); | |||
} | |||
} |
@@ -142,73 +142,7 @@ public class StandardAppPrimerService implements AppPrimerService { | |||
dataDAO.registerCallback(app.getUuid(), dataCallback.createCallback(account, app, configuration)); | |||
} | |||
for (Device device : devices) { | |||
final Set<String> rejectDomains = new HashSet<>(); | |||
final Set<String> blockDomains = new HashSet<>(); | |||
final Set<String> whiteListDomains = new HashSet<>(); | |||
final Set<String> filterDomains = new HashSet<>(); | |||
final Set<String> flexDomains = new HashSet<>(); | |||
final Set<String> flexExcludeDomains = new HashSet<>(); | |||
for (AppMatcher matcher : matchers) { | |||
final AppRuleDriver appRuleDriver = rule.initDriver(app, driver, matcher, account, device); | |||
final Set<String> rejects = appRuleDriver.getPrimedRejectDomains(); | |||
if (empty(rejects)) { | |||
log.debug("_prime: no rejectDomains for device/app/rule/matcher: " + device.getName() + "/" + app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
rejectDomains.addAll(rejects); | |||
} | |||
final Set<String> blocks = appRuleDriver.getPrimedBlockDomains(); | |||
if (empty(blocks)) { | |||
log.debug("_prime: no blockDomains for device/app/rule/matcher: " + device.getName() + "/" + app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
blockDomains.addAll(blocks); | |||
} | |||
final Set<String> whiteList = appRuleDriver.getPrimedWhiteListDomains(); | |||
if (empty(whiteList)) { | |||
log.debug("_prime: no whiteListDomains for device/app/rule/matcher: " + device.getName() + "/" + app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
whiteListDomains.addAll(whiteList); | |||
} | |||
final Set<String> filters = appRuleDriver.getPrimedFilterDomains(); | |||
if (empty(filters)) { | |||
log.debug("_prime: no filterDomains for device/app/rule/matcher: " + device.getName() + "/" + app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
filterDomains.addAll(filters); | |||
} | |||
final Set<String> flexes = appRuleDriver.getPrimedFlexDomains(); | |||
if (empty(flexes)) { | |||
log.debug("_prime: no flexDomains for device/app/rule/matcher: " + device.getName() + "/" + app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
flexDomains.addAll(flexes); | |||
} | |||
final Set<String> flexExcludes = appRuleDriver.getPrimedFlexExcludeDomains(); | |||
if (empty(flexExcludes)) { | |||
log.debug("_prime: no flexExcludeDomains for device/app/rule/matcher: " + device.getName() + "/" + app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
flexExcludeDomains.addAll(flexExcludes); | |||
} | |||
} | |||
if (!empty(rejectDomains) || !empty(blockDomains) || !empty(filterDomains) || !empty(flexDomains) || !empty(flexExcludeDomains)) { | |||
for (String ip : accountDeviceIps.get(device.getUuid())) { | |||
if (!empty(rejectDomains)) { | |||
AppRuleDriver.defineRedisRejectSet(redis, ip, app.getName() + ":" + app.getUuid(), rejectDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(blockDomains)) { | |||
AppRuleDriver.defineRedisBlockSet(redis, ip, app.getName() + ":" + app.getUuid(), blockDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(whiteListDomains)) { | |||
AppRuleDriver.defineRedisWhiteListSet(redis, ip, app.getName() + ":" + app.getUuid(), whiteListDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(filterDomains)) { | |||
AppRuleDriver.defineRedisFilterSet(redis, ip, app.getName() + ":" + app.getUuid(), filterDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(flexDomains)) { | |||
AppRuleDriver.defineRedisFlexSet(redis, ip, app.getName() + ":" + app.getUuid(), flexDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(flexExcludeDomains)) { | |||
AppRuleDriver.defineRedisFlexExcludeSet(redis, ip, app.getName() + ":" + app.getUuid(), flexExcludeDomains.toArray(String[]::new)); | |||
} | |||
} | |||
} | |||
defineRedisSets(account, accountDeviceIps, app, matchers, rule, driver, device); | |||
} | |||
} | |||
} | |||
@@ -219,4 +153,117 @@ public class StandardAppPrimerService implements AppPrimerService { | |||
} | |||
} | |||
private void defineRedisSets(Account account, Map<String, List<String>> accountDeviceIps, BubbleApp app, | |||
List<AppMatcher> matchers, AppRule rule, RuleDriver driver, Device device) { | |||
final Set<String> rejectDomains = new HashSet<>(); | |||
final Set<String> blockDomains = new HashSet<>(); | |||
final Set<String> whiteListDomains = new HashSet<>(); | |||
final Set<String> filterDomains = new HashSet<>(); | |||
final Set<String> flexDomains = new HashSet<>(); | |||
final Set<String> flexExcludeDomains = new HashSet<>(); | |||
final Set<String> requestHeaderModifiers = new HashSet<>(); | |||
boolean areAllSetsEmpty = true; | |||
for (AppMatcher matcher : matchers) { | |||
final AppRuleDriver appRuleDriver = rule.initDriver(app, driver, matcher, account, device); | |||
final Set<String> rejects = appRuleDriver.getPrimedRejectDomains(); | |||
if (empty(rejects)) { | |||
log.debug("_prime: no rejectDomains for device/app/rule/matcher: " + device.getName() | |||
+ "/" + app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
rejectDomains.addAll(rejects); | |||
areAllSetsEmpty = false; | |||
} | |||
final Set<String> blocks = appRuleDriver.getPrimedBlockDomains(); | |||
if (empty(blocks)) { | |||
log.debug("_prime: no blockDomains for device/app/rule/matcher: " + device.getName() + "/" | |||
+ app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
blockDomains.addAll(blocks); | |||
areAllSetsEmpty = false; | |||
} | |||
final Set<String> whiteList = appRuleDriver.getPrimedWhiteListDomains(); | |||
if (empty(whiteList)) { | |||
log.debug("_prime: no whiteListDomains for device/app/rule/matcher: " + device.getName() + "/" | |||
+ app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
whiteListDomains.addAll(whiteList); | |||
areAllSetsEmpty = false; | |||
} | |||
final Set<String> filters = appRuleDriver.getPrimedFilterDomains(); | |||
if (empty(filters)) { | |||
log.debug("_prime: no filterDomains for device/app/rule/matcher: " + device.getName() + "/" | |||
+ app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
filterDomains.addAll(filters); | |||
areAllSetsEmpty = false; | |||
} | |||
final Set<String> flexes = appRuleDriver.getPrimedFlexDomains(); | |||
if (empty(flexes)) { | |||
log.debug("_prime: no flexDomains for device/app/rule/matcher: " + device.getName() + "/" | |||
+ app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
flexDomains.addAll(flexes); | |||
areAllSetsEmpty = false; | |||
} | |||
final Set<String> flexExcludes = appRuleDriver.getPrimedFlexExcludeDomains(); | |||
if (empty(flexExcludes)) { | |||
log.debug("_prime: no flexExcludeDomains for device/app/rule/matcher: " + device.getName() + "/" | |||
+ app.getName() + "/" + rule.getName() + "/" + matcher.getName()); | |||
} else { | |||
flexExcludeDomains.addAll(flexExcludes); | |||
areAllSetsEmpty = false; | |||
} | |||
final Set<String> modifiers = appRuleDriver.getPrimedResponseHeaderModifiers(); | |||
if (empty(modifiers)) { | |||
log.debug("_prime: no responseHeaderModifiers for device/app/rule/matcher: " | |||
+ device.getName() + "/" + app.getName() + "/" + rule.getName() + "/" | |||
+ matcher.getName()); | |||
} else { | |||
requestHeaderModifiers.addAll(modifiers); | |||
areAllSetsEmpty = false; | |||
} | |||
} | |||
if (areAllSetsEmpty) return; | |||
for (String ip : accountDeviceIps.get(device.getUuid())) { | |||
if (!empty(rejectDomains)) { | |||
AppRuleDriver.defineRedisRejectSet(redis, ip, app.getName() + ":" + app.getUuid(), | |||
rejectDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(blockDomains)) { | |||
AppRuleDriver.defineRedisBlockSet(redis, ip, app.getName() + ":" + app.getUuid(), | |||
blockDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(whiteListDomains)) { | |||
AppRuleDriver.defineRedisWhiteListSet(redis, ip, app.getName() + ":" + app.getUuid(), | |||
whiteListDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(filterDomains)) { | |||
AppRuleDriver.defineRedisFilterSet(redis, ip, app.getName() + ":" + app.getUuid(), | |||
filterDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(flexDomains)) { | |||
AppRuleDriver.defineRedisFlexSet(redis, ip, app.getName() + ":" + app.getUuid(), | |||
flexDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(flexExcludeDomains)) { | |||
AppRuleDriver.defineRedisFlexExcludeSet(redis, ip, app.getName() + ":" + app.getUuid(), | |||
flexExcludeDomains.toArray(String[]::new)); | |||
} | |||
if (!empty(requestHeaderModifiers)) { | |||
AppRuleDriver.defineRedisResponseHeaderModifiersSet(redis, ip, app.getName() + ":" + app.getUuid(), | |||
requestHeaderModifiers.toArray(String[]::new)); | |||
} | |||
} | |||
} | |||
} |
@@ -15,16 +15,16 @@ | |||
{"name": "replacement", "truncate": false} | |||
], | |||
"configViews": [{ | |||
"name": "manageCookieReplacements", | |||
"name": "manageHeaderReplacements", | |||
"scope": "app", | |||
"root": "true", | |||
"fields": [ "regex", "replacement" ], | |||
"actions": [ | |||
{"name": "removeCookieReplacement", "index": 10}, | |||
{"name": "removeHeaderReplacement", "index": 10}, | |||
{ | |||
"name": "addCookieReplacement", "scope": "app", "index": 10, | |||
"name": "addHeaderReplacement", "scope": "app", "index": 10, | |||
"params": [ "regex", "replacement" ], | |||
"button": "addCookieReplacement" | |||
"button": "addHeaderReplacement" | |||
} | |||
] | |||
}] | |||
@@ -41,7 +41,7 @@ | |||
"template": true, | |||
"driver": "RequestProtectorRuleDriver", | |||
"priority": -1000, | |||
"config": { "cookieReplacements": [] } | |||
"config": { "headerReplacements": [] } | |||
}], | |||
"AppMessage": [{ | |||
"locale": "en_US", | |||
@@ -54,21 +54,21 @@ | |||
"value": "Change or remove parts of request/response - i.e. remove cross-domain cookies from response" | |||
}, | |||
{ "name": "config.view.manageCookieReplacements", "value": "Manage Cookie Replacements" }, | |||
{ "name": "config.view.manageHeaderReplacements", "value": "Manage Header Replacements" }, | |||
{ "name": "config.field.regex", "value": "RegEx" }, | |||
{ | |||
"name": "config.field.regex.description", | |||
"value": "Regular expression compared with full set cookie string value" | |||
"value": "Regular expression compared with full header's line string value" | |||
}, | |||
{ "name": "config.field.replacement", "value": "Replacement" }, | |||
{ | |||
"name": "config.field.replacement.description", | |||
"value": "May use reference from regex as in Java's replaceAll method" | |||
"value": "May use reference from regex as in python's re.Pattern.sub method. If set to empty string, found header will be fully removed from response" | |||
}, | |||
{ "name": "config.action.addCookieReplacement", "value": "Add" }, | |||
{ "name": "config.action.removeCookieReplacement", "value": "Remove" }, | |||
{ "name": "config.action.addHeaderReplacement", "value": "Add" }, | |||
{ "name": "config.action.removeHeaderReplacement", "value": "Remove" }, | |||
{ "name": "err.requestProtector.cookieRegexRequired", "value": "RegEx field is required" } | |||
{ "name": "err.requestProtector.headerRegexRequired", "value": "RegEx field is required" } | |||
] | |||
}] | |||
} |
@@ -450,6 +450,26 @@ def original_flex_ip(client_addr, fqdns): | |||
return None | |||
def response_header_modify(flow): | |||
return None if flow.response is None else _header_modify(flow.client_conn.address[0], flow.response.headers) | |||
def _header_modify(client_addr, headers): | |||
modifiers_set = 'responseHeaderModifierLists~' + client_addr + '~UNION' | |||
modifiers = REDIS.smembers(modifiers_set) | |||
repl_count = 0 | |||
if modifiers: | |||
for modifier in modifiers: | |||
modifier_config = json.loads(modifier) | |||
repl_count += headers.replace(modifier_config['regex'], modifier_config['replacement']) | |||
if bubble_log.isEnabledFor(DEBUG): | |||
bubble_log.debug('_header_modify: replacing headers - replacements count: ' + repl_count) | |||
return repl_count | |||
def health_check_response(flow): | |||
# if bubble_log.isEnabledFor(DEBUG): | |||
# bubble_log.debug('health_check_response: special bubble health check request, responding with OK') | |||
@@ -16,7 +16,7 @@ from bubble_api import CTX_BUBBLE_MATCHERS, CTX_BUBBLE_ABORT, CTX_BUBBLE_LOCATIO | |||
is_bubble_special_path, is_bubble_health_check, health_check_response, special_bubble_response, \ | |||
CTX_BUBBLE_REQUEST_ID, CTX_CONTENT_LENGTH, CTX_CONTENT_LENGTH_SENT, CTX_BUBBLE_FILTERED, \ | |||
HEADER_CONTENT_TYPE, HEADER_CONTENT_ENCODING, HEADER_LOCATION, HEADER_CONTENT_LENGTH, \ | |||
HEADER_USER_AGENT, HEADER_FILTER_PASSTHRU, HEADER_CONTENT_SECURITY_POLICY, REDIS, redis_set | |||
HEADER_USER_AGENT, HEADER_FILTER_PASSTHRU, HEADER_CONTENT_SECURITY_POLICY, REDIS, redis_set, response_header_modify | |||
from bubble_flex import process_flex | |||
import logging | |||
@@ -285,6 +285,7 @@ def responseheaders(flow): | |||
else: | |||
flex_flow = None | |||
bubble_filter_response(flow, flex_flow) | |||
response_header_modify(flow) | |||
def bubble_filter_response(flow, flex_flow): | |||