diff --git a/bubble-server/src/main/java/bubble/app/request/RequestProtectorAppConfigDriver.java b/bubble-server/src/main/java/bubble/app/request/RequestProtectorAppConfigDriver.java index 21af38fe..52dfcb7f 100644 --- a/bubble-server/src/main/java/bubble/app/request/RequestProtectorAppConfigDriver.java +++ b/bubble-server/src/main/java/bubble/app/request/RequestProtectorAppConfigDriver.java @@ -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 params) { switch (view) { - case VIEW_manageCookieReplacements: + case VIEW_manageHeaderReplacements: return loadManageCookiesReplacements(account, app); } throw notFoundEx(view); } - private Set loadManageCookiesReplacements(Account account, BubbleApp app) { + private Set 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 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 addCookieReplacement(Account account, BubbleApp app, JsonNode data) { + private Set 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 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 removeCookieReplacement(Account account, BubbleApp app, String regex) { + private Set 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(); } } diff --git a/bubble-server/src/main/java/bubble/rule/AppRuleDriver.java b/bubble-server/src/main/java/bubble/rule/AppRuleDriver.java index beea7bef..aa966d28 100644 --- a/bubble-server/src/main/java/bubble/rule/AppRuleDriver.java +++ b/bubble-server/src/main/java/bubble/rule/AppRuleDriver.java @@ -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 getPrimedRejectDomains () { return null; } @@ -54,6 +55,7 @@ public interface AppRuleDriver { default Set getPrimedFilterDomains () { return null; } default Set getPrimedFlexDomains () { return null; } default Set getPrimedFlexExcludeDomains () { return null; } + default Set 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)); diff --git a/bubble-server/src/main/java/bubble/rule/request/CookieReplacement.java b/bubble-server/src/main/java/bubble/rule/request/HeaderReplacement.java similarity index 64% rename from bubble-server/src/main/java/bubble/rule/request/CookieReplacement.java rename to bubble-server/src/main/java/bubble/rule/request/HeaderReplacement.java index 5318214c..afe06414 100644 --- a/bubble-server/src/main/java/bubble/rule/request/CookieReplacement.java +++ b/bubble-server/src/main/java/bubble/rule/request/HeaderReplacement.java @@ -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 { +public class HeaderReplacement implements Comparable { - 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()); } } diff --git a/bubble-server/src/main/java/bubble/rule/request/RequestProtectorConfig.java b/bubble-server/src/main/java/bubble/rule/request/RequestProtectorConfig.java index 39ae5bac..ae2555ab 100644 --- a/bubble-server/src/main/java/bubble/rule/request/RequestProtectorConfig.java +++ b/bubble-server/src/main/java/bubble/rule/request/RequestProtectorConfig.java @@ -18,20 +18,17 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.empty; @Slf4j @Accessors(chain=true) public class RequestProtectorConfig { - @Getter @Setter private Set 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 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; } } diff --git a/bubble-server/src/main/java/bubble/rule/request/RequestProtectorRuleDriver.java b/bubble-server/src/main/java/bubble/rule/request/RequestProtectorRuleDriver.java index 9dfad25d..860585ee 100644 --- a/bubble-server/src/main/java/bubble/rule/request/RequestProtectorRuleDriver.java +++ b/bubble-server/src/main/java/bubble/rule/request/RequestProtectorRuleDriver.java @@ -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 Class getConfigClass() { return (Class) RequestProtectorConfig.class; } + @Override public Set 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 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(); } } diff --git a/bubble-server/src/main/java/bubble/service/stream/StandardAppPrimerService.java b/bubble-server/src/main/java/bubble/service/stream/StandardAppPrimerService.java index b9f64a3e..e8e73c70 100644 --- a/bubble-server/src/main/java/bubble/service/stream/StandardAppPrimerService.java +++ b/bubble-server/src/main/java/bubble/service/stream/StandardAppPrimerService.java @@ -142,73 +142,7 @@ public class StandardAppPrimerService implements AppPrimerService { dataDAO.registerCallback(app.getUuid(), dataCallback.createCallback(account, app, configuration)); } for (Device device : devices) { - final Set rejectDomains = new HashSet<>(); - final Set blockDomains = new HashSet<>(); - final Set whiteListDomains = new HashSet<>(); - final Set filterDomains = new HashSet<>(); - final Set flexDomains = new HashSet<>(); - final Set flexExcludeDomains = new HashSet<>(); - for (AppMatcher matcher : matchers) { - final AppRuleDriver appRuleDriver = rule.initDriver(app, driver, matcher, account, device); - final Set 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 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 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 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 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 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> accountDeviceIps, BubbleApp app, + List matchers, AppRule rule, RuleDriver driver, Device device) { + final Set rejectDomains = new HashSet<>(); + final Set blockDomains = new HashSet<>(); + final Set whiteListDomains = new HashSet<>(); + final Set filterDomains = new HashSet<>(); + final Set flexDomains = new HashSet<>(); + final Set flexExcludeDomains = new HashSet<>(); + final Set requestHeaderModifiers = new HashSet<>(); + + boolean areAllSetsEmpty = true; + for (AppMatcher matcher : matchers) { + final AppRuleDriver appRuleDriver = rule.initDriver(app, driver, matcher, account, device); + + final Set 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 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 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 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 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 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 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)); + } + } + } + } diff --git a/bubble-server/src/main/resources/models/apps/request/bubbleApp_request.json b/bubble-server/src/main/resources/models/apps/request/bubbleApp_request.json index 6990f10d..21428635 100644 --- a/bubble-server/src/main/resources/models/apps/request/bubbleApp_request.json +++ b/bubble-server/src/main/resources/models/apps/request/bubbleApp_request.json @@ -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" } ] }] } diff --git a/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_api.py b/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_api.py index 47f88908..7486adc8 100644 --- a/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_api.py +++ b/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_api.py @@ -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') diff --git a/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_modify.py b/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_modify.py index 4b18af78..6ee3906e 100644 --- a/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_modify.py +++ b/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_modify.py @@ -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):