From 552c8331539c23342bfebd848b8289c4f3a8b63b Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Wed, 24 Jun 2020 15:17:48 -0400 Subject: [PATCH] support device security level in mitmproxy --- .../java/bubble/dao/device/DeviceDAO.java | 3 + .../model/device/DeviceSecurityLevel.java | 6 +- .../resources/app/AppSitesResource.java | 4 +- .../bubble/resources/app/AppsResource.java | 2 +- .../stream/ReverseProxyResource.java | 3 +- .../listener/NodeInitializerListener.java | 4 +- .../bubble/service/cloud/DeviceIdService.java | 106 +------------- .../cloud/StandardDeviceIdService.java | 131 ++++++++++++++++++ .../DbFilterDeviceIdService.java | 22 +++ .../post_auth/ResourceMessages.properties | 5 +- .../packer/roles/algo/tasks/main.yml | 2 +- .../roles/mitmproxy/files/bubble_passthru.py | 101 ++++++-------- 12 files changed, 216 insertions(+), 173 deletions(-) create mode 100644 bubble-server/src/main/java/bubble/service/cloud/StandardDeviceIdService.java create mode 100644 bubble-server/src/main/java/bubble/service_dbfilter/DbFilterDeviceIdService.java diff --git a/bubble-server/src/main/java/bubble/dao/device/DeviceDAO.java b/bubble-server/src/main/java/bubble/dao/device/DeviceDAO.java index 7575af3a..eb78e849 100644 --- a/bubble-server/src/main/java/bubble/dao/device/DeviceDAO.java +++ b/bubble-server/src/main/java/bubble/dao/device/DeviceDAO.java @@ -12,6 +12,7 @@ import bubble.model.cloud.BubbleNetwork; import bubble.model.device.BubbleDeviceType; import bubble.model.device.Device; import bubble.server.BubbleConfiguration; +import bubble.service.cloud.DeviceIdService; import lombok.extern.slf4j.Slf4j; import org.hibernate.criterion.Order; import org.springframework.beans.factory.annotation.Autowired; @@ -34,6 +35,7 @@ public class DeviceDAO extends AccountOwnedEntityDAO { @Autowired private BubbleConfiguration configuration; @Autowired private AccountDAO accountDAO; @Autowired private AppDataDAO dataDAO; + @Autowired private DeviceIdService deviceIdService; @Override public Order getDefaultSortOrder() { return ORDER_CTIME_ASC; } @@ -90,6 +92,7 @@ public class DeviceDAO extends AccountOwnedEntityDAO { } final Device updated = super.update(device); ensureSpareDevice(device.getAccount(), device.getNetwork(), true); + deviceIdService.setDeviceSecurityLevel(updated); return updated; } diff --git a/bubble-server/src/main/java/bubble/model/device/DeviceSecurityLevel.java b/bubble-server/src/main/java/bubble/model/device/DeviceSecurityLevel.java index 3fd2da48..1a74dae4 100644 --- a/bubble-server/src/main/java/bubble/model/device/DeviceSecurityLevel.java +++ b/bubble-server/src/main/java/bubble/model/device/DeviceSecurityLevel.java @@ -6,10 +6,10 @@ import static bubble.ApiConstants.enumFromString; public enum DeviceSecurityLevel { -// disabled, // todo: when we can identify client IP in dnscrypt-proxy, this setting will disabled DNS domain blocking - basic, + maximum, standard, - maximum; + basic, + disabled; @JsonCreator public static DeviceSecurityLevel fromString (String v) { return enumFromString(DeviceSecurityLevel.class, v); } diff --git a/bubble-server/src/main/java/bubble/resources/app/AppSitesResource.java b/bubble-server/src/main/java/bubble/resources/app/AppSitesResource.java index b108103a..3ce5ba81 100644 --- a/bubble-server/src/main/java/bubble/resources/app/AppSitesResource.java +++ b/bubble-server/src/main/java/bubble/resources/app/AppSitesResource.java @@ -6,10 +6,10 @@ package bubble.resources.app; import bubble.dao.app.AppSiteDAO; import bubble.model.account.Account; -import bubble.model.app.config.AppDataDriver; -import bubble.model.app.config.AppDataView; import bubble.model.app.AppSite; import bubble.model.app.BubbleApp; +import bubble.model.app.config.AppDataDriver; +import bubble.model.app.config.AppDataView; import bubble.model.device.Device; import bubble.resources.account.AccountOwnedTemplateResource; import bubble.service.cloud.DeviceIdService; diff --git a/bubble-server/src/main/java/bubble/resources/app/AppsResource.java b/bubble-server/src/main/java/bubble/resources/app/AppsResource.java index 6446e676..9cd3bba3 100644 --- a/bubble-server/src/main/java/bubble/resources/app/AppsResource.java +++ b/bubble-server/src/main/java/bubble/resources/app/AppsResource.java @@ -5,9 +5,9 @@ package bubble.resources.app; import bubble.model.account.Account; +import bubble.model.app.BubbleApp; import bubble.model.app.config.AppDataDriver; import bubble.model.app.config.AppDataView; -import bubble.model.app.BubbleApp; import bubble.model.device.Device; import bubble.service.cloud.DeviceIdService; import lombok.extern.slf4j.Slf4j; diff --git a/bubble-server/src/main/java/bubble/resources/stream/ReverseProxyResource.java b/bubble-server/src/main/java/bubble/resources/stream/ReverseProxyResource.java index 724f8105..15f18a66 100644 --- a/bubble-server/src/main/java/bubble/resources/stream/ReverseProxyResource.java +++ b/bubble-server/src/main/java/bubble/resources/stream/ReverseProxyResource.java @@ -35,7 +35,8 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.die; import static org.cobbzilla.util.daemon.ZillaRuntime.empty; import static org.cobbzilla.util.http.HttpContentTypes.CONTENT_TYPE_ANY; import static org.cobbzilla.util.json.JsonUtil.json; -import static org.cobbzilla.wizard.resources.ResourceUtil.*; +import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; +import static org.cobbzilla.wizard.resources.ResourceUtil.userPrincipal; @Path(PROXY_ENDPOINT) @Service @Slf4j diff --git a/bubble-server/src/main/java/bubble/server/listener/NodeInitializerListener.java b/bubble-server/src/main/java/bubble/server/listener/NodeInitializerListener.java index 22f96c34..1a599ca1 100644 --- a/bubble-server/src/main/java/bubble/server/listener/NodeInitializerListener.java +++ b/bubble-server/src/main/java/bubble/server/listener/NodeInitializerListener.java @@ -14,6 +14,7 @@ import bubble.model.cloud.BubbleNode; import bubble.model.cloud.CloudService; import bubble.server.BubbleConfiguration; import bubble.service.boot.SelfNodeService; +import bubble.service.cloud.DeviceIdService; import bubble.service.cloud.NetworkMonitorService; import bubble.service.stream.AppPrimerService; import lombok.extern.slf4j.Slf4j; @@ -107,7 +108,7 @@ public class NodeInitializerListener extends RestServerLifecycleListenerBase findIpsByDevice(String deviceUuid); - public static final String IP_FILE_PREFIX = "ip_"; - public static final FilenamePrefixFilter IP_FILE_FILTER = new FilenamePrefixFilter(IP_FILE_PREFIX); + void initDeviceSecurityLevels(); - public static final String DEVICE_FILE_PREFIX = "device_"; - - @Autowired private DeviceDAO deviceDAO; - @Autowired private BubbleConfiguration configuration; - @Autowired private AccountDAO accountDAO; - - private final Map deviceCache = new ExpirationMap<>(MINUTES.toMillis(10)); - - public Device findDeviceByIp (String ipAddr) { - - if (!WG_DEVICES_DIR.exists()) { - if (configuration.testMode()) return findTestDevice(ipAddr); - reportError("findDeviceByIp: err.deviceDir.notFound"); - return null; - } - - return deviceCache.computeIfAbsent(ipAddr, ip -> { - try { - // try the simple case first - final File ipFile = new File(WG_DEVICES_DIR, "ip_" + ip); - if (ipFile.exists() && ipFile.length() > 0) { - final String deviceUuid = FileUtil.toString(ipFile).trim(); - return deviceDAO.findByUuid(deviceUuid); - } - - // walk through each ip file, finding one whose name semantically matches the given IP - // this may be required for IPv6 since there can be multiple string representation of the same IP - final String[] ipFiles = WG_DEVICES_DIR.list(IP_FILE_FILTER); - if (ipFiles == null || ipFile.length() == 0) return null; - - final InetAddress addr = InetAddress.getByName(ip); - for (String f : ipFiles) { - try { - if (InetAddress.getByName(f.substring(IP_FILE_PREFIX.length())).equals(addr)) { - final String deviceUuid = FileUtil.toString(new File(WG_DEVICES_DIR, f)).trim(); - return deviceDAO.findByUuid(deviceUuid); - } - } catch (Exception e) { - log.warn("findDeviceByIp("+ip+"): error handling IP file: "+f+": "+shortError(e)); - } - } - log.warn("findDeviceByIp("+ip+"): no devices found for IP: "+ip); - return null; - - } catch (IOException e) { - return die("findDeviceByIp("+ip+") error: "+e, e); - } - }); - } - - public List findIpsByDevice(String deviceUuid) { - if (!WG_DEVICES_DIR.exists()) throw invalidEx("err.deviceDir.notFound"); - final File deviceFile = new File(WG_DEVICES_DIR, DEVICE_FILE_PREFIX+deviceUuid); - if (!deviceFile.exists() || deviceFile.length() == 0) return Collections.emptyList(); - try { - return FileUtil.toStringList(deviceFile); - } catch (IOException e) { - return die("findIpsByDevice("+deviceUuid+") error: "+e, e); - } - } - - private Device findTestDevice(String ipAddr) { - final String adminUuid = accountDAO.getFirstAdmin().getUuid(); - final List adminDevices = deviceDAO.findByAccount(adminUuid); - if (empty(adminDevices)) { - log.warn("findDeviceByIp("+ipAddr+") test mode and no admin devices, returning dummy device"); - return new Device().setAccount(adminUuid).setName("dummy"); - } else { - log.warn("findDeviceByIp("+ipAddr+") test mode, returning and possibly initializing first admin device"); - final Device device = adminDevices.get(0); - return device.uninitialized() ? deviceDAO.update(device.initTotpKey()) : device; - } - } + void setDeviceSecurityLevel(Device device); } diff --git a/bubble-server/src/main/java/bubble/service/cloud/StandardDeviceIdService.java b/bubble-server/src/main/java/bubble/service/cloud/StandardDeviceIdService.java new file mode 100644 index 00000000..bfa1a823 --- /dev/null +++ b/bubble-server/src/main/java/bubble/service/cloud/StandardDeviceIdService.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) 2020 Bubble, Inc. All rights reserved. + * For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ + */ +package bubble.service.cloud; + +import bubble.dao.account.AccountDAO; +import bubble.dao.device.DeviceDAO; +import bubble.model.device.Device; +import bubble.server.BubbleConfiguration; +import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.util.collection.ExpirationMap; +import org.cobbzilla.util.io.FileUtil; +import org.cobbzilla.util.io.FilenamePrefixFilter; +import org.cobbzilla.wizard.cache.redis.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static bubble.ApiConstants.HOME_DIR; +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.cobbzilla.util.daemon.ZillaRuntime.*; +import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; +import static org.cobbzilla.wizard.server.RestServerBase.reportError; + +@Service @Slf4j +public class StandardDeviceIdService implements DeviceIdService { + + public static final File WG_DEVICES_DIR = new File(HOME_DIR, "wg_devices"); + + public static final String IP_FILE_PREFIX = "ip_"; + public static final FilenamePrefixFilter IP_FILE_FILTER = new FilenamePrefixFilter(IP_FILE_PREFIX); + + public static final String DEVICE_FILE_PREFIX = "device_"; + + // used in dnscrypt-proxy and mitmproxy to check device security level + public static final String REDIS_KEY_DEVICE_SECURITY_LEVEL_PREFIX = "bubble_device_security_level_"; + + @Autowired private DeviceDAO deviceDAO; + @Autowired private AccountDAO accountDAO; + @Autowired private RedisService redis; + @Autowired private BubbleConfiguration configuration; + + private final Map deviceCache = new ExpirationMap<>(MINUTES.toMillis(10)); + + @Override public Device findDeviceByIp (String ipAddr) { + + if (!WG_DEVICES_DIR.exists()) { + if (configuration.testMode()) return findTestDevice(ipAddr); + reportError("findDeviceByIp: err.deviceDir.notFound"); + return null; + } + + return deviceCache.computeIfAbsent(ipAddr, ip -> { + try { + // try the simple case first + final File ipFile = new File(WG_DEVICES_DIR, "ip_" + ip); + if (ipFile.exists() && ipFile.length() > 0) { + final String deviceUuid = FileUtil.toString(ipFile).trim(); + return deviceDAO.findByUuid(deviceUuid); + } + + // walk through each ip file, finding one whose name semantically matches the given IP + // this may be required for IPv6 since there can be multiple string representation of the same IP + final String[] ipFiles = WG_DEVICES_DIR.list(IP_FILE_FILTER); + if (ipFiles == null || ipFile.length() == 0) return null; + + final InetAddress addr = InetAddress.getByName(ip); + for (String f : ipFiles) { + try { + if (InetAddress.getByName(f.substring(IP_FILE_PREFIX.length())).equals(addr)) { + final String deviceUuid = FileUtil.toString(new File(WG_DEVICES_DIR, f)).trim(); + return deviceDAO.findByUuid(deviceUuid); + } + } catch (Exception e) { + log.warn("findDeviceByIp("+ip+"): error handling IP file: "+f+": "+shortError(e)); + } + } + log.warn("findDeviceByIp("+ip+"): no devices found for IP: "+ip); + return null; + + } catch (IOException e) { + return die("findDeviceByIp("+ip+") error: "+e, e); + } + }); + } + + @Override public List findIpsByDevice(String deviceUuid) { + if (!WG_DEVICES_DIR.exists()) throw invalidEx("err.deviceDir.notFound"); + final File deviceFile = new File(WG_DEVICES_DIR, DEVICE_FILE_PREFIX+deviceUuid); + if (!deviceFile.exists() || deviceFile.length() == 0) return Collections.emptyList(); + try { + return FileUtil.toStringList(deviceFile); + } catch (IOException e) { + return die("findIpsByDevice("+deviceUuid+") error: "+e, e); + } + } + + @Override public void initDeviceSecurityLevels() { + for (Device device : deviceDAO.findAll()) { + if (device.uninitialized()) continue; + setDeviceSecurityLevel(device); + } + } + + @Override public void setDeviceSecurityLevel(Device device) { + for (String ip : findIpsByDevice(device.getUuid())) { + redis.set_plaintext(REDIS_KEY_DEVICE_SECURITY_LEVEL_PREFIX+ip, device.getSecurityLevel().name()); + } + } + + private Device findTestDevice(String ipAddr) { + final String adminUuid = accountDAO.getFirstAdmin().getUuid(); + final List adminDevices = deviceDAO.findByAccount(adminUuid); + if (empty(adminDevices)) { + log.warn("findDeviceByIp("+ipAddr+") test mode and no admin devices, returning dummy device"); + return new Device().setAccount(adminUuid).setName("dummy"); + } else { + log.warn("findDeviceByIp("+ipAddr+") test mode, returning and possibly initializing first admin device"); + final Device device = adminDevices.get(0); + return device.uninitialized() ? deviceDAO.update(device.initTotpKey()) : device; + } + } + +} diff --git a/bubble-server/src/main/java/bubble/service_dbfilter/DbFilterDeviceIdService.java b/bubble-server/src/main/java/bubble/service_dbfilter/DbFilterDeviceIdService.java new file mode 100644 index 00000000..76a9ebcd --- /dev/null +++ b/bubble-server/src/main/java/bubble/service_dbfilter/DbFilterDeviceIdService.java @@ -0,0 +1,22 @@ +package bubble.service_dbfilter; + +import bubble.model.device.Device; +import bubble.service.cloud.DeviceIdService; +import org.springframework.stereotype.Service; + +import java.util.List; + +import static org.cobbzilla.util.daemon.ZillaRuntime.notSupported; + +@Service +public class DbFilterDeviceIdService implements DeviceIdService { + + @Override public Device findDeviceByIp(String ip) { return notSupported("findDeviceByIp"); } + + @Override public List findIpsByDevice(String deviceUuid) { return notSupported("findIpsByDevice"); } + + @Override public void initDeviceSecurityLevels() { notSupported("initDeviceSecurityLevels"); } + + @Override public void setDeviceSecurityLevel(Device device) { notSupported("setDeviceSecurityLevel"); } + +} diff --git a/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties b/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties index f1f333ad..ca6b3189 100644 --- a/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties +++ b/bubble-server/src/main/resources/message_templates/en_US/server/post_auth/ResourceMessages.properties @@ -501,9 +501,8 @@ device_security_level_standard=Standard device_security_level_standard_description=Standard protection. Unrecognized traffic is allowed. VPN apps enabled. device_security_level_basic=Basic device_security_level_basic_description=Basic protection. Unrecognized traffic is allowed. VPN apps disabled. -# todo: when we can identify client IP in dnscrypt-proxy, this setting will disabled DNS domain blocking -#device_security_level_disabled=Disabled -#device_security_level_disabled_description=No protection. Unrecognized traffic is allowed. VPN apps disabled. +device_security_level_disabled=Off +device_security_level_disabled_description=No protection. Unrecognized traffic is allowed. VPN apps disabled. # Cert types message_os_uninitialized=Uninitialized diff --git a/bubble-server/src/main/resources/packer/roles/algo/tasks/main.yml b/bubble-server/src/main/resources/packer/roles/algo/tasks/main.yml index 0ed08917..bd4ff0a7 100644 --- a/bubble-server/src/main/resources/packer/roles/algo/tasks/main.yml +++ b/bubble-server/src/main/resources/packer/roles/algo/tasks/main.yml @@ -13,7 +13,7 @@ get_url: url: https://github.com/getbubblenow/bubble-dist/raw/master/algo/master.zip dest: /tmp/algo.zip - checksum: sha256:357e613833385626e88564c97f0b5726f49686c33a774be9a7766bd1a1249915 + checksum: sha256:5a4e72d9671a38ff3ce9b5d6724c05222b343ddc408d690f1f511577d2673122 - name: Unzip algo master.zip unarchive: diff --git a/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_passthru.py b/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_passthru.py index 8848d8fc..ca51053f 100644 --- a/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_passthru.py +++ b/bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_passthru.py @@ -33,26 +33,22 @@ import subprocess REDIS_DNS_PREFIX = 'bubble_dns_' REDIS_PASSTHRU_PREFIX = 'bubble_passthru_' -REDIS_CLIENT_CERT_STATUS_PREFIX = 'bubble_cert_status_' +REDIS_KEY_DEVICE_SECURITY_LEVEL_PREFIX = 'bubble_device_security_level_' # defined in StandardDeviceIdService REDIS_PASSTHRU_DURATION = 60 * 60 # 1 hour timeout on passthru REDIS = redis.Redis(host='127.0.0.1', port=6379, db=0) +FORCE_PASSTHRU = {'passthru': True} + cert_validation_host = None local_ips = None -def get_ip_cert_status(client_addr): - status = REDIS.get(REDIS_CLIENT_CERT_STATUS_PREFIX+client_addr) - if status is None: - return None - enabled = status.decode() == "True" - return enabled - - -def set_ip_cert_status(client_addr, enabled): - REDIS.set(REDIS_CLIENT_CERT_STATUS_PREFIX+client_addr, str(enabled)) - bubble_log('set_ip_cert_status: set '+client_addr+' = '+str(enabled)) +def get_device_security_level(client_addr): + level = REDIS.get(REDIS_KEY_DEVICE_SECURITY_LEVEL_PREFIX+client_addr) + if level is None: + return 'maximum' + return level.decode() def get_local_ips(): @@ -64,17 +60,6 @@ def get_local_ips(): return local_ips -def get_cert_validation_host(): - global cert_validation_host - if cert_validation_host is None: - cert_validation_host = REDIS.get('certValidationHost') - if cert_validation_host is not None: - cert_validation_host = cert_validation_host.decode() - # bubble_log('get_cert_validation_host: initialized to '+cert_validation_host) - # bubble_log('get_cert_validation_host: returning '+cert_validation_host) - return cert_validation_host - - def passthru_cache_prefix(client_addr, server_addr): return REDIS_PASSTHRU_PREFIX + client_addr + '_' + server_addr @@ -103,29 +88,15 @@ class TlsFeedback(TlsLayer): fqdns = fqdns_for_addr(server_address) try: super(TlsFeedback, self)._establish_tls_with_client() - if fqdns and get_cert_validation_host() in fqdns: - # bubble_log('_establish_tls_with_client: TLS success for '+repr(server_address)+', enabling SSL interception for client '+client_address) - set_ip_cert_status(client_address, True) except TlsProtocolException as e: cache_key = passthru_cache_prefix(client_address, server_address) bubble_log('_establish_tls_with_client: TLS error for '+repr(server_address)+', enabling passthru for client '+client_address+' with cache_key='+cache_key) - if fqdns and get_cert_validation_host() in fqdns: - set_ip_cert_status(client_address, False) - else: - redis_set(cache_key, json.dumps({'fqdns': fqdns, 'addr': server_address, 'passthru': True}), ex=REDIS_PASSTHRU_DURATION) + redis_set(cache_key, json.dumps({'fqdns': fqdns, 'addr': server_address, 'passthru': True}), ex=REDIS_PASSTHRU_DURATION) raise e def check_bubble_passthru(client_addr, addr, fqdns): - cert_status = get_ip_cert_status(client_addr) - - if cert_status is not None and not cert_status: - bubble_log('check_bubble_passthru: returning True because cert_status for '+client_addr+' was False') - return {'fqdns': fqdns, 'addr': addr, 'passthru': True} - else: - bubble_log('check_bubble_passthru: request is NOT for cert_validation_host: '+cert_validation_host+", it is for one of fqdn="+repr(fqdns)+", checking bubble_passthru...") - passthru = bubble_passthru(client_addr, addr, fqdns) if passthru is None or passthru: bubble_log('check_bubble_passthru: bubble_passthru returned '+repr(passthru)+' for FQDN/addr '+repr(fqdns)+'/'+repr(addr)+', returning True') @@ -139,9 +110,6 @@ def should_passthru(client_addr, addr, fqdns): if addr in get_local_ips(): # bubble_log('should_passthru: local ip is always passthru: '+addr) return {'fqdns': fqdns, 'addr': addr, 'passthru': True} - else: - # bubble_log('should_passthru: addr ('+addr+') is not a local ip: '+repr(get_local_ips())) - pass cache_key = passthru_cache_prefix(client_addr, addr) prefix = 'should_passthru: ip='+repr(addr)+' (fqdns='+repr(fqdns)+') cache_key='+cache_key+': ' @@ -163,25 +131,38 @@ def should_passthru(client_addr, addr, fqdns): def next_layer(next_layer): if isinstance(next_layer, TlsLayer) and next_layer._client_tls: - client_address = next_layer.client_conn.address[0] - server_address = next_layer.server_conn.address[0] + client_addr = next_layer.client_conn.address[0] + server_addr = next_layer.server_conn.address[0] - fqdns = fqdns_for_addr(server_address) - validation_host = get_cert_validation_host() - if fqdns and validation_host in fqdns: - bubble_log('next_layer: never passing thru (always getting feedback) for cert_validation_host='+validation_host) - next_layer.__class__ = TlsFeedback + fqdns = fqdns_for_addr(server_addr) + no_fqdns = fqdns is None or len(fqdns) == 0 + security_level = get_device_security_level(client_addr) + if server_addr in get_local_ips(): + bubble_log('next_layer: enabling passthru for LOCAL server='+server_addr+' regardless of security_level='+security_level+' for client='+client_addr) + passthru = FORCE_PASSTHRU + + elif security_level == 'disabled' or security_level == 'basic': + bubble_log('next_layer: enabling passthru for server='+server_addr+' because security_level='+security_level+' for client='+client_addr) + passthru = FORCE_PASSTHRU + + elif security_level == 'standard' and no_fqdns: + bubble_log('next_layer: enabling passthru for server='+server_addr+' because no FQDN found and security_level='+security_level+' for client='+client_addr) + passthru = FORCE_PASSTHRU + + elif security_level == 'maximum' and no_fqdns: + bubble_log('next_layer: disabling passthru (no TlsFeedback) for server='+server_addr+' because no FQDN found and security_level='+security_level+' for client='+client_addr) + return else: - bubble_log('next_layer: checking should_passthru for client_address='+client_address) - passthru = should_passthru(client_address, server_address, fqdns) - if passthru is None or passthru['passthru']: - # bubble_log('next_layer: TLS passthru for ' + repr(next_layer.server_conn.address)) - if passthru is not None and 'fqdns' in passthru: - bubble_activity_log(client_address, server_address, 'tls_passthru', passthru['fqdns']) - next_layer_replacement = RawTCPLayer(next_layer.ctx, ignore=True) - next_layer.reply.send(next_layer_replacement) - else: - # bubble_log('next_layer: NO PASSTHRU (getting feedback) for client_address='+client_address+', server_address='+server_address) - bubble_activity_log(client_address, server_address, 'tls_intercept', passthru['fqdns']) - next_layer.__class__ = TlsFeedback + bubble_log('next_layer: checking should_passthru for server='+server_addr+', client='+client_addr+' with security_level='+security_level) + passthru = should_passthru(client_addr, server_addr, fqdns) + + if passthru is None or passthru['passthru']: + bubble_log('next_layer: enabling passthru for ' + repr(next_layer.server_conn.address)) + bubble_activity_log(client_addr, server_addr, 'tls_passthru', fqdns) + next_layer_replacement = RawTCPLayer(next_layer.ctx, ignore=True) + next_layer.reply.send(next_layer_replacement) + else: + bubble_log('next_layer: disabling passthru (with TlsFeedback) for client_addr='+client_addr+', server_addr='+server_addr) + bubble_activity_log(client_addr, server_addr, 'tls_intercept', fqdns) + next_layer.__class__ = TlsFeedback