@@ -92,6 +92,7 @@ public class ApiConstants { | |||
public static final String AUTH_ENDPOINT = "/auth"; | |||
public static final String EP_ACTIVATE = "/activate"; | |||
public static final String EP_CONFIGS = "/configs"; | |||
public static final String EP_READY = "/ready"; | |||
public static final String EP_REGISTER = "/register"; | |||
public static final String EP_LOGIN = "/login"; | |||
public static final String EP_APP_LOGIN = "/appLogin"; | |||
@@ -10,8 +10,8 @@ import bubble.dao.app.AppDataDAO; | |||
import bubble.model.device.BubbleDeviceType; | |||
import bubble.model.device.Device; | |||
import bubble.server.BubbleConfiguration; | |||
import lombok.NonNull; | |||
import bubble.service.cloud.DeviceIdService; | |||
import lombok.NonNull; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.hibernate.criterion.Order; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
@@ -20,24 +20,29 @@ import org.springframework.stereotype.Repository; | |||
import javax.transaction.Transactional; | |||
import java.io.File; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import static bubble.ApiConstants.HOME_DIR; | |||
import static bubble.model.device.Device.UNINITIALIZED_DEVICE_LIKE; | |||
import static bubble.model.device.Device.newUninitializedDevice; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||
import static java.util.concurrent.TimeUnit.MINUTES; | |||
import static java.util.concurrent.TimeUnit.SECONDS; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.now; | |||
import static org.cobbzilla.util.io.FileUtil.abs; | |||
import static org.cobbzilla.util.io.FileUtil.touch; | |||
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||
import static org.cobbzilla.util.system.Sleep.sleep; | |||
@Repository @Slf4j | |||
public class DeviceDAO extends AccountOwnedEntityDAO<Device> { | |||
private static final File VPN_REFRESH_USERS_FILE = new File(HOME_DIR, ".algo_refresh_users"); | |||
private static final short SPARE_DEVICES_PER_ACCOUNT_MAX = 10; | |||
private static final short SPARE_DEVICES_PER_ACCOUNT_THRESHOLD = 5; | |||
private static final short SPARE_DEVICES_PER_ACCOUNT_THRESHOLD = 10; | |||
private static final long DEVICE_INIT_TIMEOUT = MINUTES.toMillis(5); | |||
@Autowired private BubbleConfiguration configuration; | |||
@Autowired private AccountDAO accountDAO; | |||
@Autowired private AppDataDAO dataDAO; | |||
@Autowired private DeviceIdService deviceIdService; | |||
@@ -58,38 +63,54 @@ public class DeviceDAO extends AccountOwnedEntityDAO<Device> { | |||
return super.preCreate(device); | |||
} | |||
private static final Object createLock = new Object(); | |||
@Transactional | |||
@Override public Device create(@NonNull final Device device) { | |||
if (device.uninitialized()) return super.create(device); | |||
device.initDeviceType(); | |||
final var accountUuid = device.getAccount(); | |||
final var uninitializedDevices = findByAccountAndUninitialized(accountUuid); | |||
var newDevicesCreated = false; | |||
if (uninitializedDevices.size() <= SPARE_DEVICES_PER_ACCOUNT_THRESHOLD | |||
&& !configuration.getBean(AccountDAO.class).findByUuid(accountUuid).isRoot()) { | |||
newDevicesCreated = ensureAllSpareDevices(accountUuid, device.getNetwork()); | |||
synchronized (createLock) { | |||
if (device.uninitialized()) return super.create(device); | |||
device.initDeviceType(); | |||
final var accountUuid = device.getAccount(); | |||
var uninitializedDevices = findByAccountAndUninitialized(accountUuid); | |||
if (uninitializedDevices.size() <= SPARE_DEVICES_PER_ACCOUNT_THRESHOLD | |||
&& !configuration.getBean(AccountDAO.class).findByUuid(accountUuid).isRoot()) { | |||
if (ensureAllSpareDevices(accountUuid, device.getNetwork())) refreshVpnUsers(); | |||
} | |||
final Device result; | |||
uninitializedDevices = findByAccountAndUninitialized(accountUuid); | |||
if (uninitializedDevices.isEmpty()) { | |||
log.warn("create: no uninitialized devices for account " + accountUuid); | |||
// just create the device now: | |||
device.initTotpKey(); | |||
result = super.create(device); | |||
} else { | |||
final Device uninitialized; | |||
Optional<Device> availableDevice = uninitializedDevices.stream().filter(Device::configsOk).findAny(); | |||
final long start = now(); | |||
while (availableDevice.isEmpty() && now() - start < DEVICE_INIT_TIMEOUT) { | |||
if (configuration.testMode()) { | |||
log.warn("create: no available uninitialized devices and in test mode, using first uninitialized device..."); | |||
availableDevice = Optional.of(uninitializedDevices.get(0)); | |||
} else { | |||
// wait for configs to be ok | |||
log.warn("create: no available uninitialized devices, waiting..."); | |||
sleep(SECONDS.toMillis(5), "waiting for available uninitialized device"); | |||
availableDevice = uninitializedDevices.stream().filter(Device::configsOk).findAny(); | |||
} | |||
} | |||
if (availableDevice.isEmpty()) return die("create: timeout waiting for available uninitialized device"); | |||
uninitialized = availableDevice.get(); | |||
copy(uninitialized, device); | |||
result = super.update(uninitialized); | |||
} | |||
deviceIdService.setDeviceSecurityLevel(result); | |||
return result; | |||
} | |||
final Device result; | |||
// run the above creation of spare devices in parallel, but if there were no spare devices loaded before that, | |||
// create a brand new entry here: | |||
if (uninitializedDevices.isEmpty()) { | |||
log.info("create: no uninitialized devices for account " + accountUuid); | |||
// just create the device now: | |||
device.initTotpKey(); | |||
result = super.create(device); | |||
newDevicesCreated = true; | |||
} else { | |||
final var uninitialized = uninitializedDevices.get(0); | |||
copy(uninitialized, device); | |||
result = super.update(uninitialized); | |||
} | |||
if (newDevicesCreated) refreshVpnUsers(); | |||
deviceIdService.setDeviceSecurityLevel(result); | |||
return result; | |||
} | |||
@Override @NonNull public Device update(@NonNull final Device updateRequest) { | |||
@@ -100,6 +121,7 @@ public class DeviceDAO extends AccountOwnedEntityDAO<Device> { | |||
toUpdate.update(updateRequest); | |||
final var updated = super.update(toUpdate); | |||
deviceIdService.setDeviceSecurityLevel(updated); | |||
refreshVpnUsers(); | |||
return updated; | |||
} | |||
@@ -4,6 +4,7 @@ | |||
*/ | |||
package bubble.model.device; | |||
import bubble.ApiConstants; | |||
import bubble.model.account.Account; | |||
import bubble.model.account.HasAccount; | |||
import bubble.model.cloud.BubbleNetwork; | |||
@@ -18,12 +19,11 @@ import org.cobbzilla.wizard.model.IdentifiableBase; | |||
import org.cobbzilla.wizard.model.entityconfig.annotations.*; | |||
import org.hibernate.annotations.Type; | |||
import javax.persistence.Column; | |||
import javax.persistence.Entity; | |||
import javax.persistence.EnumType; | |||
import javax.persistence.Enumerated; | |||
import javax.persistence.*; | |||
import javax.validation.constraints.Size; | |||
import java.io.File; | |||
import static bubble.ApiConstants.EP_DEVICES; | |||
import static bubble.model.device.BubbleDeviceType.other; | |||
import static bubble.model.device.BubbleDeviceType.uninitialized; | |||
@@ -50,6 +50,12 @@ public class Device extends IdentifiableBase implements HasAccount { | |||
public static final String UNINITIALIZED_DEVICE = "__uninitialized_device__"; | |||
public static final String UNINITIALIZED_DEVICE_LIKE = UNINITIALIZED_DEVICE+"%"; | |||
public static final String VPN_CONFIG_PATH = ApiConstants.HOME_DIR + "/configs/localhost/wireguard/"; | |||
public File qrFile () { return new File(Device.VPN_CONFIG_PATH+getUuid()+".png"); } | |||
public File vpnConfFile () { return new File(Device.VPN_CONFIG_PATH+getUuid()+".conf"); } | |||
public boolean configsOk () { return qrFile().exists() && vpnConfFile().exists(); } | |||
public Device (Device other) { copy(this, other, CREATE_FIELDS); } | |||
public Device (String uuid) { setUuid(uuid); } | |||
@@ -4,11 +4,8 @@ | |||
*/ | |||
package bubble.resources; | |||
import bubble.server.BubbleConfiguration; | |||
import bubble.service.cloud.RequestCoordinationService; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.glassfish.jersey.server.ContainerRequest; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
import javax.ws.rs.Consumes; | |||
@@ -28,9 +25,6 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.ok; | |||
@Service @Slf4j | |||
public class BubbleMagicResource { | |||
@Autowired private BubbleConfiguration configuration; | |||
@Autowired private RequestCoordinationService requestService; | |||
@GET | |||
public Response get(@Context ContainerRequest ctx) { | |||
return ok("you are ok. the magic is ok too."); | |||
@@ -13,6 +13,7 @@ import bubble.dao.bill.AccountPaymentMethodDAO; | |||
import bubble.dao.bill.BubblePlanDAO; | |||
import bubble.dao.cloud.BubbleNodeDAO; | |||
import bubble.dao.cloud.BubbleNodeKeyDAO; | |||
import bubble.dao.device.DeviceDAO; | |||
import bubble.model.CertType; | |||
import bubble.model.account.*; | |||
import bubble.model.account.message.*; | |||
@@ -92,6 +93,7 @@ public class AuthResource { | |||
@Autowired private StandardAuthenticatorService authenticatorService; | |||
@Autowired private PromotionService promoService; | |||
@Autowired private DeviceIdService deviceIdService; | |||
@Autowired private DeviceDAO deviceDAO; | |||
@Autowired private BubbleNodeKeyDAO nodeKeyDAO; | |||
@Autowired private NodeManagerService nodeManagerService; | |||
@@ -107,6 +109,20 @@ public class AuthResource { | |||
return ok(configuration.getPublicSystemConfigs()); | |||
} | |||
@GET @Path(EP_READY) | |||
public Response getNodeIsReady(@Context ContainerRequest ctx) { | |||
try { | |||
if (deviceDAO.findByAccountAndUninitialized(accountDAO.getFirstAdmin().getUuid()) | |||
.stream() | |||
.anyMatch(Device::configsOk)) { | |||
return ok(); | |||
} | |||
} catch (Exception e) { | |||
log.warn("getNodeIsReady: "+shortError(e)); | |||
} | |||
return invalid("err.node.notReady"); | |||
} | |||
@GET @Path(EP_ACTIVATE) | |||
public Response isActivated(@Context ContainerRequest ctx) { return ok(accountDAO.activated()); } | |||
@@ -4,7 +4,6 @@ | |||
*/ | |||
package bubble.resources.account; | |||
import bubble.ApiConstants; | |||
import bubble.model.account.Account; | |||
import bubble.model.device.Device; | |||
import lombok.extern.slf4j.Slf4j; | |||
@@ -30,14 +29,12 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.*; | |||
@Slf4j | |||
public class VpnConfigResource { | |||
public static final String VPN_CONFIG_PATH = ApiConstants.HOME_DIR + "/configs/localhost/wireguard/"; | |||
private Device device; | |||
public VpnConfigResource(Device device) { this.device = device; } | |||
public File getQRfile() { | |||
final File qrFile = new File(VPN_CONFIG_PATH+device.getUuid()+".png"); | |||
final File qrFile = device.qrFile(); | |||
if (!qrFile.exists()) { | |||
// todo: try to regenerate algo users? | |||
log.error("qrCode: file not found: "+abs(qrFile)); | |||
@@ -47,7 +44,7 @@ public class VpnConfigResource { | |||
} | |||
public File getVpnConfFile() { | |||
final File confFile = new File(VPN_CONFIG_PATH+device.getUuid()+".conf"); | |||
final File confFile = device.vpnConfFile(); | |||
if (!confFile.exists()) { | |||
// todo: try to regenerate algo users? | |||
log.error("confFile: file not found: "+abs(confFile)); | |||
@@ -35,6 +35,10 @@ public class StandardSyncPasswordService implements SyncPasswordService { | |||
log.warn("syncPassword: thisNetwork was null, sync_password is impossible"); | |||
return; | |||
} | |||
if (!account.admin()) { | |||
log.info("syncPassword: not syncing non-admin password"); | |||
return; | |||
} | |||
final AnsibleInstallType installType = thisNetwork.getInstallType(); | |||
final SyncPasswordNotification notification = new SyncPasswordNotification(account); | |||
if (installType == AnsibleInstallType.sage) { | |||
@@ -60,9 +60,9 @@ public class NodeProgressMeterConstants { | |||
{METER_TICK_LAUNCHING_NODE, 1}, | |||
{METER_TICK_PREPARING_ROLES, 5}, | |||
{METER_TICK_PREPARING_INSTALL, 7}, | |||
{METER_TICK_STARTING_INSTALL, 33}, | |||
{METER_TICK_COPYING_ANSIBLE, 34}, | |||
{METER_TICK_RUNNING_ANSIBLE, 37} | |||
{METER_TICK_STARTING_INSTALL, 23}, | |||
{METER_TICK_COPYING_ANSIBLE, 24}, | |||
{METER_TICK_RUNNING_ANSIBLE, 27} | |||
}); | |||
public static List<NodeProgressMeterTick> getStandardTicks(NewNodeNotification nn) { | |||
@@ -4,6 +4,7 @@ | |||
*/ | |||
package bubble.service.cloud; | |||
import bubble.client.BubbleNodeClient; | |||
import bubble.cloud.CloudAndRegion; | |||
import bubble.cloud.compute.ComputeServiceDriver; | |||
import bubble.dao.account.AccountDAO; | |||
@@ -74,7 +75,8 @@ import static bubble.service.boot.StandardSelfNodeService.*; | |||
import static bubble.service.cloud.NodeProgressMeter.getProgressMeterKey; | |||
import static bubble.service.cloud.NodeProgressMeter.getProgressMeterPrefix; | |||
import static bubble.service.cloud.NodeProgressMeterConstants.*; | |||
import static java.util.concurrent.TimeUnit.*; | |||
import static java.util.concurrent.TimeUnit.MINUTES; | |||
import static java.util.concurrent.TimeUnit.SECONDS; | |||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; | |||
import static org.cobbzilla.util.daemon.Await.awaitAll; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||
@@ -104,6 +106,7 @@ public class StandardNetworkService implements NetworkService { | |||
private static final long NET_DEADLOCK_TIMEOUT = MINUTES.toMillis(20); | |||
private static final long PLAN_ENABLE_TIMEOUT = PURCHASE_DELAY + SECONDS.toMillis(10); | |||
private static final long NODE_START_JOB_TIMEOUT = MINUTES.toMillis(30); | |||
private static final long NODE_READY_TIMEOUT = MINUTES.toMillis(6); | |||
@Autowired private AccountDAO accountDAO; | |||
@Autowired private AccountSshKeyDAO sshKeyDAO; | |||
@@ -346,6 +349,28 @@ public class StandardNetworkService implements NetworkService { | |||
} | |||
if (!setupOk) return die("newNode: error setting up, all retries failed for node: "+node.getUuid()); | |||
// wait for node to be ready | |||
final long readyStart = now(); | |||
boolean ready = false; | |||
BubbleNodeClient nodeClient = null; | |||
while (now() - readyStart < NODE_READY_TIMEOUT) { | |||
sleep(SECONDS.toMillis(2), "newNode: waiting for node ("+node.id()+") to be ready"); | |||
if (nodeKeyDAO.findFirstByNode(node.getUuid()) == null) continue; | |||
try { | |||
if (nodeClient == null) nodeClient = node.getApiQuickClient(configuration); | |||
if (nodeClient.get(AUTH_ENDPOINT + EP_READY).isSuccess()) { | |||
log.info("newNode: node ("+node.id()+") is ready!"); | |||
ready = true; | |||
break; | |||
} | |||
} catch (Exception e) { | |||
log.warn("newNode: node ("+node.id()+") error checking if ready: "+shortError(e)); | |||
} | |||
} | |||
if (!ready) { | |||
return die("newNode: timeout waiting for node ("+node.id()+") to be ready"); | |||
} | |||
// we are good. | |||
final BubbleNetworkState finalState = nn.hasRestoreKey() ? BubbleNetworkState.restoring : BubbleNetworkState.running; | |||
if (network.getState() != finalState) { | |||
@@ -355,6 +380,7 @@ public class StandardNetworkService implements NetworkService { | |||
node.setState(BubbleNodeState.running); | |||
nodeDAO.update(node); | |||
progressMeter.completed(); | |||
log.info("newNode: ready in "+formatDuration(now() - start)); | |||
} catch (Exception e) { | |||
log.error("newNode: "+e, e); | |||
@@ -1 +1 @@ | |||
bubble.version=0.12.5 | |||
bubble.version=0.12.6 |
@@ -1,13 +1,13 @@ | |||
[ | |||
{ "percent": 38,"messageKey":"ansible_deps", "match": "prefix", "pattern":"Building wheel for PyYAML (setup.py): started" }, | |||
{ "percent": 41,"messageKey":"config_node", "match": "prefix", "pattern":"PLAY [Configure new bubble node]" }, | |||
{ "percent": 42,"messageKey":"nginx_dhparam", "match": "prefix", "pattern":"TASK [nginx : Create a strong dhparam.pem]" }, | |||
{ "percent": 55,"messageKey":"nginx_dh_conf", "match": "prefix", "pattern":"TASK [Create dhparam nginx conf]" }, | |||
{ "percent": 56,"messageKey":"nginx_certbot", "match": "prefix", "pattern":"TASK [nginx : Init certbot]" }, | |||
{ "percent": 58,"messageKey":"bubble_db", "match": "prefix", "pattern":"TASK [bubble : Populate database]" }, | |||
{ "percent": 65,"messageKey":"algo_sh", "match": "prefix", "pattern":"TASK [Write install_algo.sh template]" }, | |||
{ "percent": 92,"messageKey":"restart_algo", "match": "prefix", "pattern":"TASK [Restart algo monitors]" }, | |||
{ "percent": 95,"messageKey":"mitmproxy_set_cert","match": "prefix", "pattern":"TASK [mitmproxy : Set the cert name]" }, | |||
{ "percent": 98,"messageKey":"touch_first_setup", "match": "prefix", "pattern":"TASK [finalizer : Touch first-time setup file]" }, | |||
{ "percent": 100,"messageKey":"ssh_keys", "match": "prefix", "pattern":"TASK [finalizer : Ensure authorized SSH keys are up-to-date]" } | |||
{ "percent": 29,"messageKey":"ansible_deps", "match": "prefix", "pattern":"Building wheel for PyYAML (setup.py): started" }, | |||
{ "percent": 31,"messageKey":"config_node", "match": "prefix", "pattern":"PLAY [Configure new bubble node]" }, | |||
{ "percent": 32,"messageKey":"nginx_dhparam", "match": "prefix", "pattern":"TASK [nginx : Create a strong dhparam.pem]" }, | |||
{ "percent": 41,"messageKey":"nginx_dh_conf", "match": "prefix", "pattern":"TASK [Create dhparam nginx conf]" }, | |||
{ "percent": 42,"messageKey":"nginx_certbot", "match": "prefix", "pattern":"TASK [nginx : Init certbot]" }, | |||
{ "percent": 44,"messageKey":"bubble_db", "match": "prefix", "pattern":"TASK [bubble : Populate database]" }, | |||
{ "percent": 49,"messageKey":"algo_sh", "match": "prefix", "pattern":"TASK [Write install_algo.sh template]" }, | |||
{ "percent": 69,"messageKey":"restart_algo", "match": "prefix", "pattern":"TASK [Restart algo monitors]" }, | |||
{ "percent": 72,"messageKey":"mitmproxy_set_cert","match": "prefix", "pattern":"TASK [mitmproxy : Set the cert name]" }, | |||
{ "percent": 76,"messageKey":"touch_first_setup", "match": "prefix", "pattern":"TASK [finalizer : Touch first-time setup file]" }, | |||
{ "percent": 81,"messageKey":"ssh_keys", "match": "prefix", "pattern":"TASK [finalizer : Ensure authorized SSH keys are up-to-date]" } | |||
] |
@@ -359,7 +359,7 @@ meter_tick_running_ansible=Whipping the batter... | |||
# Launch progress meter: install ticks | |||
meter_tick_ansible_deps=Mixing the pie filling... | |||
meter_tick_config_node=Filling the pie... | |||
meter_tick_nginx_dhparam=Gently adding the lattice top crust... | |||
meter_tick_nginx_dhparam=Gently weaving the lattice top crust... | |||
meter_tick_nginx_dh_conf=Glazing the top crust... | |||
meter_tick_nginx_certbot=Checking the temperature... | |||
meter_tick_bubble_db=Putting pie into the oven... | |||
@@ -367,7 +367,7 @@ meter_tick_algo_sh=Baking the pie... | |||
meter_tick_restart_algo=Removing pie from the oven... | |||
meter_tick_mitmproxy_set_cert=Letting the pie cool a bit... | |||
meter_tick_touch_first_setup=Setting the table... | |||
meter_tick_ssh_keys=Hey everybody, the pie is ready! | |||
meter_tick_ssh_keys=Ringing the bell... | |||
#meter_tick_ansible_deps=Installing installer dependencies | |||
#meter_tick_config_node=Configuration installation | |||
#meter_tick_nginx_dhparam=Securing SSL libraries | |||
@@ -404,11 +404,10 @@ meter_unknown_error=An unknown error occurred | |||
title_launch_help_html=Next Steps | |||
message_launch_help_html=<p>Your Bubble will take about 10 minutes to launch and configure itself.</p> | |||
message_launch_help_apps=While you wait for your Bubble to be ready, please install the Bubble app on each of your devices. | |||
message_launch_help_apps=Please install the Bubble app on each of your devices to connect them to your Bubble. | |||
message_launch_success_help_html=<p>Congratulations! Your Bubble is now running.</p> | |||
message_launch_support=<p>Having trouble? Any questions? Check our our <a target="_blank" rel="noopener noreferrer" href="/support">{{messages.link_support}}</a> resources.</p> | |||
message_launch_success_apps=Install the Bubble app on each of your devices and get connected to your Bubble! | |||
message_launch_success_apps=Install the Bubble app on each of your devices to start using your Bubble! | |||
# Network statuses | |||
msg_network_state_created=initialized | |||
@@ -190,6 +190,7 @@ err.name.invalid=Name is invalid | |||
err.name.networkNameAlreadyExists=Name is already in use | |||
err.name.regexFailed=Name must start with a letter and can only contain letters, numbers, hyphens, periods and underscores | |||
err.name.mismatch=Name mismatch | |||
err.node.notReady=Bubble is not ready yet | |||
err.password.required=Password is required | |||
err.password.tooShort=Password must be at least 8 characters | |||
err.password.invalid=Password must contain at least one letter, one number, and one special character | |||
@@ -4,7 +4,11 @@ | |||
# | |||
LOG=/tmp/bubble.algo_refresh_users.log | |||
ALGO_BASE=/root/ansible/roles/algo/algo | |||
REFRESH_MARKER=${ALGO_BASE}/.refreshing_users | |||
function die { | |||
rm -f ${REFRESH_MARKER} | |||
echo 1>&2 "${1}" | |||
log "${1}" | |||
exit 1 | |||
@@ -14,7 +18,6 @@ function log { | |||
echo "$(date): ${1}" >> ${LOG} | |||
} | |||
ALGO_BASE=/root/ansible/roles/algo/algo | |||
if [[ ! -d ${ALGO_BASE} ]] ; then | |||
die "Algo VPN directory ${ALGO_BASE} not found" | |||
fi | |||
@@ -27,32 +30,61 @@ if [[ ! -f "${ALGO_BASE}/config.cfg.hbs" ]] ; then | |||
die "No ${ALGO_BASE}/config.cfg.hbs found" | |||
fi | |||
START_TIME=$(date +%s) | |||
REFRESH_TIMEOUT=300 | |||
OK_TO_REFRESH=0 | |||
if [[ -f ${REFRESH_MARKER} ]] ; then | |||
log "Refresh marker exists: ${REFRESH_MARKER}, waiting for previous refresh run to finish" | |||
while [[ $(expr $(date +%s) - ${START_TIME}) -lt ${REFRESH_TIMEOUT} ]] ; do | |||
if [[ ! -f ${REFRESH_MARKER} ]] ; then | |||
OK_TO_REFRESH=1 | |||
break | |||
fi | |||
sleep 1s | |||
done | |||
if [[ ${OK_TO_REFRESH} -eq 0 ]] ; then | |||
log "Timeout waiting for previous refresh, continuing anyway" | |||
fi | |||
touch ${REFRESH_MARKER} | |||
fi | |||
ALGO_CONFIG="${ALGO_BASE}/config.cfg" | |||
ALGO_CONFIG_SHA="$(sha256sum ${ALGO_CONFIG} | cut -f1 -d' ')" | |||
log "Regenerating algo config..." | |||
java -cp /home/bubble/api/bubble.jar bubble.main.BubbleMain generate-algo-conf --algo-config ${ALGO_BASE}/config.cfg.hbs || die "Error writing algo config.cfg" | |||
log "Updating algo VPN users..." | |||
cd ${ALGO_BASE} && \ | |||
python3 -m virtualenv --python="$(command -v python3)" .env \ | |||
&& source .env/bin/activate \ | |||
&& python3 -m pip install -U pip virtualenv \ | |||
&& python3 -m pip install -r requirements.txt \ | |||
&& ansible-playbook users.yml --tags update-users --skip-tags debug \ | |||
-e "ca_password=$(cat ${CA_PASS_FILE}) | |||
provider=local | |||
server=localhost | |||
store_cakey=true | |||
ondemand_cellular=false | |||
ondemand_wifi=false | |||
store_pki=true | |||
dns_adblocking=false | |||
ssh_tunneling=false | |||
endpoint={{ endpoint }} | |||
server_name={{ server_name }}" 2>&1 | tee -a ${LOG} || die "Error running algo users.yml" | |||
# Archive configs in a place that the BackupService can pick them up | |||
log "Sync'ing algo VPN users to bubble..." | |||
CONFIGS_BACKUP=/home/bubble/.BUBBLE_ALGO_CONFIGS.tgz | |||
cd ${ALGO_BASE} && tar czf ${CONFIGS_BACKUP} configs && chgrp bubble ${CONFIGS_BACKUP} && chmod 660 ${CONFIGS_BACKUP} || die "Error backing up algo configs" | |||
cd /home/bubble && rm -rf configs/* && tar xzf ${CONFIGS_BACKUP} && chgrp -R bubble configs && chown -R bubble configs && chmod 500 configs || die "Error unpacking algo configs to bubble home" | |||
log "VPN users successfully sync'd to bubble" | |||
java -cp /home/bubble/api/bubble.jar bubble.main.BubbleMain generate-algo-conf --algo-config ${ALGO_CONFIG}.hbs || die "Error writing algo config.cfg" | |||
NEW_ALGO_CONFIG_SHA="$(sha256sum ${ALGO_CONFIG} | cut -f1 -d' ')" | |||
if [[ ! -z "${ALGO_CONFIG_SHA}" && "${ALGO_CONFIG_SHA}" == "${NEW_ALGO_CONFIG_SHA}" ]] ; then | |||
log "Algo configuration is unchanged, not refreshing: ${ALGO_CONFIG}" | |||
else | |||
log "Updating algo VPN users..." | |||
cd ${ALGO_BASE} && \ | |||
python3 -m virtualenv --python="$(command -v python3)" .env \ | |||
&& source .env/bin/activate \ | |||
&& python3 -m pip install -U pip virtualenv \ | |||
&& python3 -m pip install -r requirements.txt \ | |||
&& ansible-playbook users.yml --tags update-users --skip-tags debug \ | |||
-e "ca_password=$(cat ${CA_PASS_FILE}) | |||
provider=local | |||
server=localhost | |||
store_cakey=true | |||
ondemand_cellular=false | |||
ondemand_wifi=false | |||
store_pki=true | |||
dns_adblocking=false | |||
ssh_tunneling=false | |||
endpoint={{ endpoint }} | |||
server_name={{ server_name }}" 2>&1 | tee -a ${LOG} || die "Error running algo users.yml" | |||
# Archive configs in a place that the BackupService can pick them up | |||
log "Sync'ing algo VPN users to bubble..." | |||
CONFIGS_BACKUP=/home/bubble/.BUBBLE_ALGO_CONFIGS.tgz | |||
cd ${ALGO_BASE} && tar czf ${CONFIGS_BACKUP} configs && chgrp bubble ${CONFIGS_BACKUP} && chmod 660 ${CONFIGS_BACKUP} || die "Error backing up algo configs" | |||
cd /home/bubble && rm -rf configs/* && tar xzf ${CONFIGS_BACKUP} && chgrp -R bubble configs && chown -R bubble configs && chmod 500 configs || die "Error unpacking algo configs to bubble home" | |||
log "VPN users successfully sync'd to bubble. Refresh completed in $(expr $(date +%s) - ${START_TIME}) seconds" | |||
fi | |||
rm -f ${REFRESH_MARKER} |
@@ -41,9 +41,8 @@ log "Watching marker file..." | |||
while : ; do | |||
if [[ $(stat -c %Y ${BUBBLE_USER_MARKER}) -gt $(stat -c %Y ${ALGO_USER_MARKER}) ]] ; then | |||
touch ${ALGO_USER_MARKER} | |||
sleep 5s | |||
log "Refreshing VPN users..." | |||
/usr/local/bin/algo_refresh_users.sh && log "VPN users successfully refreshed" || log "Error refreshing Algo VPN users" | |||
fi | |||
sleep 10s | |||
sleep 2s | |||
done |
@@ -223,13 +223,13 @@ def next_layer(next_layer): | |||
check = check_connection(client_addr, server_addr, fqdns, security_level) | |||
if check is None or ('passthru' in check and check['passthru']): | |||
bubble_log('next_layer: enabling passthru for server_addr' + server_addr+', fqdns='+str(fqdns)) | |||
bubble_log('next_layer: enabling passthru for server=' + server_addr+', fqdns='+str(fqdns)) | |||
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) | |||
elif 'block' in check and check['block']: | |||
bubble_log('next_layer: enabling block for server_addr' + server_addr+', fqdns='+str(fqdns)) | |||
bubble_log('next_layer: enabling block for server=' + server_addr+', fqdns='+str(fqdns)) | |||
bubble_activity_log(client_addr, server_addr, 'conn_block', fqdns) | |||
next_layer.__class__ = TlsBlock | |||
@@ -108,8 +108,8 @@ | |||
"response": { | |||
"check": [ | |||
{"condition": "json.length === 2"}, | |||
{"condition": "json[0].getName() === 'root-renamed-device'"}, | |||
{"condition": "json[1].getName() === 'user-added-device'"} | |||
{"condition": "_find(json, function (d) { return d.getName() == 'user-added-device'; }) != null"}, | |||
{"condition": "_find(json, function (d) { return d.getName() == 'root-renamed-device'; }) != null"} | |||
] | |||
} | |||
}, | |||