ソースを参照

Merge branch 'master' into kris/fix_traffic_analytics_app

pull/57/head
コミット
64d5e49822
26個のファイルの変更441行の追加49行の削除
  1. +19
    -0
      bubble-server/src/main/java/bubble/dao/account/AccountSshKeyDAO.java
  2. +4
    -0
      bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java
  3. +4
    -0
      bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java
  4. +2
    -1
      bubble-server/src/main/java/bubble/model/app/AppMatcher.java
  5. +3
    -1
      bubble-server/src/main/java/bubble/service/cloud/StandardNetworkService.java
  6. +1
    -1
      bubble-server/src/main/resources/META-INF/bubble/bubble.properties
  7. +12
    -1
      bubble-server/src/main/resources/ansible/roles/algo/tasks/algo_firewall.yml
  8. +2
    -2
      bubble-server/src/main/resources/ansible/roles/mitmproxy/tasks/main.yml
  9. +0
    -1
      bubble-server/src/main/resources/bubble/rule/RequestModifierRule_icon.js.hbs
  10. +4
    -1
      bubble-server/src/main/resources/bubble/rule/social/block/JsUserBlockerRuleDriver.js.hbs
  11. +64
    -20
      bubble-server/src/main/resources/bubble/rule/social/block/site/LI.js.hbs
  12. +1
    -7
      bubble-server/src/main/resources/models/apps/user_block/li/bubbleApp_userBlock_li_data.json
  13. +10
    -0
      bubble-server/src/main/resources/models/apps/user_block/li/bubbleApp_userBlock_li_matchers.json
  14. +1
    -0
      bubble-server/src/main/resources/packer/node-roles.txt
  15. +14
    -0
      bubble-server/src/main/resources/packer/roles/firewall/files/bubble_sshd.conf
  16. +5
    -0
      bubble-server/src/main/resources/packer/roles/firewall/files/jail.local
  17. +21
    -2
      bubble-server/src/main/resources/packer/roles/firewall/tasks/main.yml
  18. +5
    -2
      bubble-server/src/main/resources/packer/roles/firewall/tasks/rules.yml
  19. +19
    -0
      bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_api.py
  20. +7
    -9
      bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_request.py
  21. +89
    -0
      bubble-server/src/main/resources/packer/roles/tarpit/files/bubble_http_tarpit.py
  22. +87
    -0
      bubble-server/src/main/resources/packer/roles/tarpit/files/bubble_ssh_tarpit.py
  23. +6
    -0
      bubble-server/src/main/resources/packer/roles/tarpit/files/supervisor_http_tarpit.conf
  24. +6
    -0
      bubble-server/src/main/resources/packer/roles/tarpit/files/supervisor_ssh_tarpit.conf
  25. +54
    -0
      bubble-server/src/main/resources/packer/roles/tarpit/tasks/main.yml
  26. +1
    -1
      bubble-web

+ 19
- 0
bubble-server/src/main/java/bubble/dao/account/AccountSshKeyDAO.java ファイルの表示

@@ -4,8 +4,11 @@
*/
package bubble.dao.account;

import bubble.dao.bill.AccountPlanDAO;
import bubble.dao.cloud.BubbleNetworkDAO;
import bubble.model.account.Account;
import bubble.model.account.AccountSshKey;
import bubble.model.bill.AccountPlan;
import bubble.model.cloud.AnsibleInstallType;
import bubble.model.cloud.BubbleNetwork;
import bubble.server.BubbleConfiguration;
@@ -14,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.io.File;
import java.util.List;

import static bubble.ApiConstants.HOME_DIR;
import static org.cobbzilla.util.io.FileUtil.touch;
@@ -25,6 +29,8 @@ import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx;
public class AccountSshKeyDAO extends AccountOwnedEntityDAO<AccountSshKey> {

@Autowired private AccountDAO accountDAO;
@Autowired private AccountPlanDAO accountPlanDAO;
@Autowired private BubbleNetworkDAO networkDAO;
@Autowired private BubbleConfiguration configuration;

public AccountSshKey findByAccountAndHash(String accountUuid, String hash) {
@@ -90,6 +96,19 @@ public class AccountSshKeyDAO extends AccountOwnedEntityDAO<AccountSshKey> {

@Override public void delete(String uuid) {
final AccountSshKey key = findByUuid(uuid);

// remove from any AccountPlans that reference it
final List<AccountPlan> accountPlans = accountPlanDAO.findByAccountAndSshKey(key.getAccount(), key.getUuid());
for (AccountPlan plan : accountPlans) {
accountPlanDAO.update(plan.setSshKey(null));
}

// remove from any BubbleNetworks that reference it
final List<BubbleNetwork> bubbleNetworks = networkDAO.findByAccountAndSshKey(key.getAccount(), key.getUuid());
for (BubbleNetwork network : bubbleNetworks) {
networkDAO.update(network.setSshKey(null));
}

super.delete(uuid);
if (key.installSshKey()) refreshInstalledKeys();
}


+ 4
- 0
bubble-server/src/main/java/bubble/dao/bill/AccountPlanDAO.java ファイルの表示

@@ -63,6 +63,10 @@ public class AccountPlanDAO extends AccountOwnedEntityDAO<AccountPlan> {

public AccountPlan findByNetwork(String networkUuid) { return findByUniqueField("network", networkUuid); }

public List<AccountPlan> findByAccountAndSshKey(String account, String keyUuid) {
return findByFields("account", account, "sshKey", keyUuid);
}

public List<AccountPlan> findByAccountAndNotDeleted(String account) {
return findByFields("account", account, "deleting", false, "deleted", null);
}


+ 4
- 0
bubble-server/src/main/java/bubble/dao/cloud/BubbleNetworkDAO.java ファイルの表示

@@ -92,6 +92,10 @@ public class BubbleNetworkDAO extends AccountOwnedEntityDAO<BubbleNetwork> {
return findByUniqueFields("name", name, "domain", domainUuid);
}

public List<BubbleNetwork> findByAccountAndSshKey(String account, String keyUuid) {
return findByFields("account", account, "sshKey", keyUuid);
}

@Override public void delete(String uuid) {
final BubbleNetwork network = findByUuid(uuid);
if (network == null) return;


+ 2
- 1
bubble-server/src/main/java/bubble/model/app/AppMatcher.java ファイルの表示

@@ -44,7 +44,8 @@ import static org.cobbzilla.wizard.model.crypto.EncryptedTypes.ENC_PAD;
public class AppMatcher extends IdentifiableBase implements AppTemplateEntity, HasPriority {

public static final String[] VALUE_FIELDS = {
"fqdn", "urlRegex", "template", "enabled", "priority", "connCheck", "requestCheck", "requestModifier"
"fqdn", "urlRegex", "userAgentRegex", "template", "enabled", "priority",
"connCheck", "requestCheck", "requestModifier"
};
public static final String[] CREATE_FIELDS = ArrayUtil.append(VALUE_FIELDS, "name", "site", "rule");



+ 3
- 1
bubble-server/src/main/java/bubble/service/cloud/StandardNetworkService.java ファイルの表示

@@ -290,7 +290,9 @@ public class StandardNetworkService implements NetworkService {
prepareLaunchFiles(nn, computeDriver, node, progressMeter, network, sageKey, account, plan, sageNode, automation, errors, sshKeyFile);

// run ansible
final String sshArgs = "-o UserKnownHostsFile=/dev/null "
final String sshArgs
= "-p 1202 "
+ "-o UserKnownHostsFile=/dev/null "
+ "-o StrictHostKeyChecking=no "
+ "-o PreferredAuthentications=publickey "
+ "-i " + abs(sshKeyFile);


+ 1
- 1
bubble-server/src/main/resources/META-INF/bubble/bubble.properties ファイルの表示

@@ -1 +1 @@
bubble.version=Adventure 1.1.3
bubble.version=Adventure 1.2.1

+ 12
- 1
bubble-server/src/main/resources/ansible/roles/algo/tasks/algo_firewall.yml ファイルの表示

@@ -3,7 +3,7 @@
#
# Insert additional firewall rules to allow required services to function
# Insert them all on rule_num 5, and insert them in reverse order here:
- name: Allow SSH
- name: Allow SSH tarpit
iptables:
chain: INPUT
protocol: tcp
@@ -11,6 +11,17 @@
ctstate: NEW
syn: match
jump: ACCEPT
comment: Accept new SSH tarpit connections
become: yes

- name: Allow SSH
iptables:
chain: INPUT
protocol: tcp
destination_port: 1202
ctstate: NEW
syn: match
jump: ACCEPT
comment: Accept new SSH connections
become: yes



+ 2
- 2
bubble-server/src/main/resources/ansible/roles/mitmproxy/tasks/main.yml ファイルの表示

@@ -18,7 +18,7 @@
- name: Ensure mitmproxy user owns all mitmproxy files
shell: chown -R mitmproxy /home/mitmproxy/mitmproxy

- name: Install mitmproxy1 supervisor conf file
- name: Install mitm8888 supervisor conf file
template:
src: supervisor_mitmproxy.conf.j2
dest: /etc/supervisor/conf.d/mitm8888.conf
@@ -28,7 +28,7 @@
vars:
port: 8888

- name: Install mitmproxy2 supervisor conf file
- name: Install mitm9999 supervisor conf file
template:
src: supervisor_mitmproxy.conf.j2
dest: /etc/supervisor/conf.d/mitm9999.conf


+ 0
- 1
bubble-server/src/main/resources/bubble/rule/RequestModifierRule_icon.js.hbs ファイルの表示

@@ -263,7 +263,6 @@ function {{JS_PREFIX}}_chase_redirects (a, removeParams, regex, groups, callback
body: JSON.stringify(follow_body)
}
const follow_url = is_regex ? {{JS_PREFIX}}_follow_and_apply_regex_url : {{JS_PREFIX}}_follow_url;
console.log('>>>>>>>>>>>> requesting follow_url ('+follow_url+') with request_opts: '+JSON.stringify(request_opts));
fetch(follow_url, request_opts)
.then(response => is_regex ? response.json() : response.text())
.then(data => {


+ 4
- 1
bubble-server/src/main/resources/bubble/rule/social/block/JsUserBlockerRuleDriver.js.hbs ファイルの表示

@@ -72,8 +72,11 @@ function {{JS_PREFIX}}_uuidv4() {

const {{JS_PREFIX}}_create_block_img = function(size) {
const img = document.createElement('img');
img.style.all = 'revert';
img.style.fontSize = 'x-small';
img.src = {{JS_PREFIX}}_asset_img_url('icon');
img.width = typeof size !== 'undefined' ? size : 24;
img.width = typeof size !== 'undefined' && size !== null ? size : 24;
img.style.width = img.width+'px';
return img;
}



+ 64
- 20
bubble-server/src/main/resources/bubble/rule/social/block/site/LI.js.hbs ファイルの表示

@@ -3,6 +3,14 @@

const {{JS_PREFIX}}_site_host = location.protocol + '//' + window.location.hostname + '/';

function {{JS_PREFIX}}_mobile() {
const html = Array.from(document.getElementsByTagName('html'));
if (html.length !== 0) {
return html[0].className && html[0].className.indexOf(' mobile ') !== -1;
}
return false;
}

const {{JS_PREFIX}}_jail = document.createElement('div');
{{JS_PREFIX}}_jail.style.display = 'none';

@@ -16,7 +24,9 @@ function {{JS_PREFIX}}_apply_blocks(blocked_users) {
} else {
}
}
const articles = Array.from(document.getElementsByClassName('feed-shared-update-v2'));
const articles = {{JS_PREFIX}}_mobile()
? Array.from(document.getElementsByClassName('feed-item'))
: Array.from(document.getElementsByClassName('feed-shared-update-v2'));
if (articles === null || articles.length === 0) {
console.warn('No articles found, not filtering');
return;
@@ -30,7 +40,7 @@ function {{JS_PREFIX}}_is_valid_author_name(name) {

function {{JS_PREFIX}}_author_from_href(linkId, callback) {
if (typeof linkId === 'undefined' || linkId === null || linkId.length === 0) {
console.log('author_from_href: invalid link ID: '+linkId);
// console.log('author_from_href: invalid link ID: '+linkId);
return;
}
const link = document.getElementById(linkId);
@@ -47,12 +57,17 @@ function {{JS_PREFIX}}_author_from_href(linkId, callback) {
}
if (h.endsWith('/')) h = h.substring(0, h.length - 1);
let profile_type = null;

const mobile = {{JS_PREFIX}}_mobile();
if (mobile && h.startsWith('mwlite/')) {
h = h.substring('mwlite/'.length);
}
if (h.startsWith('in/')) {
profile_type = 'in/';
} else if (h.startsWith('company/')) {
profile_type = 'company/';
} else {
// console.log("author_from_href: skipping (not in/ or company/) href: "+href);
// console.log("author_from_href: skipping (not in/ or company/) href: "+href+', h='+h);
return;
}
const slashPos = h.indexOf('/');
@@ -121,26 +136,62 @@ function {{JS_PREFIX}}_create_block_control(article, authorName, articleLink) {
blockSpan.appendChild(document.createTextNode('\u00A0\u00A0'));
blockSpan.appendChild(blockLink);
blockSpan.id = 'blockSpan_'+{{JS_PREFIX}}_uuidv4();
console.log('adding block control on '+authorName);
// console.log('adding block control on '+authorName);
return blockSpan;
}

function {{JS_PREFIX}}_hash_url(url) { return btoa(url).replaceAll('=', ''); }

function {{JS_PREFIX}}_is_ad(article) {
const mobile = {{JS_PREFIX}}_mobile();
return (mobile && article.getAttribute('data-is-sponsored') && article.getAttribute('data-is-sponsored') !== "false")
|| (article.innerHTML.indexOf('<span>Promoted</span>') !== -1)
|| (article.innerHTML.indexOf('<span dir="ltr">Promoted</span>') !== -1);
}

function {{JS_PREFIX}}_find_append_span(link) {
const mobile = {{JS_PREFIX}}_mobile();
if (!mobile) {
let authorSpans = Array.from(link.getElementsByClassName('feed-shared-actor__name'));
if (authorSpans.length > 0) {
return authorSpans[0];
} else {
return Array.from(link.getElementsByTagName('span'))
.find(s => s.getAttribute('dir') === 'ltr' || s.getAttribute('data-entity-type'));
}
} else {
const ltrSpan = Array.from(link.getElementsByTagName('span'))
.find(s => s.getAttribute('dir') === 'ltr' || s.getAttribute('data-entity-type'));
if (ltrSpan) return ltrSpan;
if (link.className && link.className.indexOf('profile-link') !== -1) {
return link.lastChild;
}
}
}

function {{JS_PREFIX}}_consider_block(articles, blocked_users) {
const mobile = {{JS_PREFIX}}_mobile();
if (articles && articles.length && articles.length > 0) {
for (let i=0; i<articles.length; i++) {
const article = articles[i];
if ({{JS_PREFIX}}_is_ad(article)) {
{{JS_PREFIX}}_tally_author_block({{PAGE_PREFIX}}_msg_or_default({{JS_PREFIX}}_messages, 'web_advertOrOtherBlock', 'ad/other'));
{{JS_PREFIX}}_remove_article_from_dom(article);
continue;
}
const firstEval = {{JS_PREFIX}}_mark_evaluated(article);
if ({{JS_PREFIX}}_includes_block_keyword(article, firstEval)) {
{{JS_PREFIX}}_remove_article_from_dom(article);
continue;
}
const articleLinks = Array.from(article.getElementsByTagName('a'));
const articleLinks = mobile
? Array.from(article.getElementsByTagName('a')).filter(a => !a.hasAttribute('aria-hidden'))
: Array.from(article.getElementsByTagName('a'));
// console.log('consider_block: found '+articleLinks.length+' articleLinks');
for (let j=0; j<articleLinks.length; j++) {
const articleLink = articleLinks[j];
if (typeof articleLink === 'undefined' || articleLink === null || typeof articleLink.href === 'undefined') {
console.log('consider_block: skipping invalid articleLink: '+JSON.stringify(articleLink));
// console.log('consider_block: skipping invalid articleLink: '+JSON.stringify(articleLink));
continue;
}
if (typeof articleLink.id === 'undefined' || articleLink.id === null || articleLink.id.length === 0) {
@@ -163,7 +214,7 @@ function {{JS_PREFIX}}_consider_block(articles, blocked_users) {

const realLink = document.getElementById(linkId);
if (realLink === null) {
console.log('consider_block: link with id '+linkId+' seems to have disappeared from the document');
// console.log('consider_block: link with id '+linkId+' seems to have disappeared from the document');
return;
}

@@ -174,26 +225,19 @@ function {{JS_PREFIX}}_consider_block(articles, blocked_users) {
{{JS_PREFIX}}_remove_article_from_dom(article);

} else if (!seenBefore) {
let appendToSpan = null;
let authorSpans = Array.from(realLink.getElementsByClassName('feed-shared-actor__name'));
if (authorSpans.length > 0) {
appendToSpan = authorSpans[0];
} else {
appendToSpan = Array.from(realLink.getElementsByTagName('span'))
.find(s => s.getAttribute('dir') === 'ltr' || s.getAttribute('data-entity-type'));
if (typeof appendToSpan === 'undefined') {
console.log('consider_block: found no span to attach block control for author: '+author);
return;
}
let appendToSpan = {{JS_PREFIX}}_find_append_span(realLink);
if (!appendToSpan) {
// console.log('consider_block: no span found to append to for author '+author);
return;
}

let b = {{JS_PREFIX}}_create_block_control(article, author, realLink);
if (b !== null) {
console.log('consider_block: inserting span='+b.id+' for article by '+author);
// console.log('consider_block: inserting span='+b.id+' for article by '+author);
appendToSpan.parentNode.appendChild(b);
{{JS_PREFIX}}_tally_allow();
} else {
console.log('consider_block: create_block_control returned null for author '+author)
// console.log('consider_block: create_block_control returned null for author '+author)
}
}
});


+ 1
- 7
bubble-server/src/main/resources/models/apps/user_block/li/bubbleApp_userBlock_li_data.json ファイルの表示

@@ -1,12 +1,6 @@
[{
"name": "UserBlocker",
"children": {
"AppData": [{
"site": "LinkedIn",
"template": true,
"matcher": "LIMatcher",
"key": "kw:_<span>Promoted</span>",
"data": "true"
}]
"AppData": []
}
}]

+ 10
- 0
bubble-server/src/main/resources/models/apps/user_block/li/bubbleApp_userBlock_li_matchers.json ファイルの表示

@@ -10,6 +10,16 @@
"fqdn": "www.linkedin.com",
"urlRegex": "/feed/",
"rule": "li_user_blocker"
}, {
"name": "LIMobileMatcher",
"site": "LinkedIn",
"template": true,
"requestCheck": true,
"requestModifier": true,
"fqdn": "www.linkedin.com",
"urlRegex": "/",
"userAgentRegex": "Mobi|Android",
"rule": "li_user_blocker"
}]
}
}]

+ 1
- 0
bubble-server/src/main/resources/packer/node-roles.txt ファイルの表示

@@ -1,5 +1,6 @@
common
firewall
tarpit
nginx
algo
mitmproxy


+ 14
- 0
bubble-server/src/main/resources/packer/roles/firewall/files/bubble_sshd.conf ファイルの表示

@@ -0,0 +1,14 @@
Port 1202
LoginGraceTime 10
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no
X11Forwarding no
PermitUserEnvironment no
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
KexAlgorithms curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com

+ 5
- 0
bubble-server/src/main/resources/packer/roles/firewall/files/jail.local ファイルの表示

@@ -0,0 +1,5 @@
[sshd]
mode = aggressive
port = 1202
logpath = %(sshd_log)s
backend = %(sshd_backend)s

+ 21
- 2
bubble-server/src/main/resources/packer/roles/firewall/tasks/main.yml ファイルの表示

@@ -90,15 +90,34 @@
dest: /usr/local/bin/bubble_peer_manager.py
owner: root
group: root
mode: 0555
mode: 0550
when: fw_enable_admin

- name: Install supervisor conf file for port manager
- name: Install supervisor conf file for peer manager
copy:
src: supervisor_bubble_peer_manager.conf
dest: /etc/supervisor/conf.d/bubble_peer_manager.conf
owner: root
group: root
mode: 0550
when: fw_enable_admin

- name: Install SSH hardening settings
copy:
src: bubble_sshd.conf
dest: /etc/ssh/sshd_config.d/bubble_sshd.conf
owner: root
group: root
mode: 0400

- name: Install SSH fail2ban settings
copy:
src: jail.local
dest: /etc/fail2ban/jail.local
owner: root
group: root
mode: 0400

- include: rules.yml

- supervisorctl:


+ 5
- 2
bubble-server/src/main/resources/packer/roles/firewall/tasks/rules.yml ファイルの表示

@@ -17,15 +17,18 @@
comment: Allow related and established connections
become: yes

- name: Allow SSH
- name: Allow SSH on ports 22 and 1202
iptables:
chain: INPUT
protocol: tcp
destination_port: 22
destination_port: "{{ item }}"
ctstate: NEW
syn: match
jump: ACCEPT
comment: Accept new SSH connections
with_items:
- 22
- 1202
become: yes
when: fw_enable_ssh



+ 19
- 0
bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_api.py ファイルの表示

@@ -64,6 +64,7 @@ LOCAL_IPS = []
for local_ip in subprocess.check_output(['hostname', '-I']).split():
LOCAL_IPS.append(local_ip.decode())

TARPIT_PORT = 8080

VPN_IP4_CIDR = IPNetwork(wireguard_network_ipv4)
VPN_IP6_CIDR = IPNetwork(wireguard_network_ipv6)
@@ -469,6 +470,24 @@ def health_check_response(flow):
flow.response.stream = lambda chunks: [b'OK\n']


def tarpit_response(flow, host):
# if bubble_log.isEnabledFor(DEBUG):
# bubble_log.debug('health_check_response: special bubble health check request, responding with OK')
response_headers = nheaders.Headers()
response_headers[HEADER_LOCATION] = 'http://'+host+':'+str(TARPIT_PORT)+'/admin/index.php'
if flow.response is None:
flow.response = http.HTTPResponse(http_version='HTTP/1.1',
status_code=301,
reason='Moved Permanently',
headers=response_headers,
content=b'')
else:
flow.response.headers = nheaders.Headers()
flow.response.headers = response_headers
flow.response.status_code = 301
flow.response.reason = 'Moved Permanently'


def include_request_headers(path):
return '/followAndApplyRegex' in path



+ 7
- 9
bubble-server/src/main/resources/packer/roles/mitmproxy/files/bubble_request.py ファイルの表示

@@ -32,7 +32,7 @@ from mitmproxy.net.http import headers as nheaders
from bubble_api import bubble_matchers, bubble_activity_log, \
CTX_BUBBLE_MATCHERS, CTX_BUBBLE_SPECIAL, CTX_BUBBLE_ABORT, CTX_BUBBLE_LOCATION, \
CTX_BUBBLE_PASSTHRU, CTX_BUBBLE_FLEX, CTX_BUBBLE_REQUEST_ID, add_flow_ctx, parse_host_header, \
is_bubble_special_path, is_bubble_health_check, health_check_response, \
is_bubble_special_path, is_bubble_health_check, health_check_response, tarpit_response,\
is_bubble_request, is_sage_request, is_not_from_vpn, is_flex_domain
from bubble_config import bubble_host, bubble_host_alias
from bubble_flex import new_flex_flow
@@ -168,11 +168,10 @@ class Rerouter:
return None

elif is_not_from_vpn(client_addr):
# todo: add to fail2ban
if bubble_log.isEnabledFor(WARNING):
bubble_log.warning('bubble_handle_request: returning 404 for non-VPN client='+client_addr+', url='+log_url+' host='+host)
bubble_activity_log(client_addr, server_addr, 'http_abort_non_vpn', fqdns)
add_flow_ctx(flow, CTX_BUBBLE_ABORT, 404)
bubble_log.warning('bubble_handle_request: sending to tarpit: non-VPN client='+client_addr+', url='+log_url+' host='+host)
bubble_activity_log(client_addr, server_addr, 'http_tarpit_non_vpn', fqdns)
tarpit_response(flow, host)
return None

if is_bubble_special_path(path):
@@ -230,11 +229,10 @@ class Rerouter:
# bubble_activity_log(client_addr, server_addr, 'http_no_matcher_response', log_url)

elif is_http and is_not_from_vpn(client_addr):
# todo: add to fail2ban
if bubble_log.isEnabledFor(WARNING):
bubble_log.warning('bubble_handle_request: returning 404 for non-VPN client='+client_addr+', server_addr='+server_addr)
bubble_activity_log(client_addr, server_addr, 'http_abort_non_vpn', [server_addr])
add_flow_ctx(flow, CTX_BUBBLE_ABORT, 404)
bubble_log.warning('bubble_handle_request: sending to tarpit: non-VPN client='+client_addr)
bubble_activity_log(client_addr, server_addr, 'http_tarpit_non_vpn', [server_addr])
tarpit_response(flow, host)
return None

else:


+ 89
- 0
bubble-server/src/main/resources/packer/roles/tarpit/files/bubble_http_tarpit.py ファイルの表示

@@ -0,0 +1,89 @@
#!/usr/bin/python3
#
# Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
#
# Adapted from: https://nullprogram.com/blog/2019/03/22/
#
import asyncio
import random
import os
import sys

import logging
from logging import INFO, DEBUG, WARNING, ERROR, CRITICAL

from pathlib import Path

TARPIT_LOG = '/var/log/bubble/http_tarpit.log'
TARPIT_LOG_LEVEL_FILE = '/home/tarpit/http_tarpit_log_level.txt'
TARPIT_LOG_LEVEL_ENV_VAR = 'HTTP_TARPIT_LOG_LEVEL'
DEFAULT_TARPIT_LOG_LEVEL = 'INFO'
TARPIT_LOG_LEVEL = None

TARPIT_PORT_FILE = '/home/tarpit/http_tarpit_port.txt'
TARPIT_PORT_ENV_VAR = 'HTTP_TARPIT_PORT'
DEFAULT_TARPIT_PORT = '8080'

tarpit_log = logging.getLogger(__name__)

try:
TARPIT_LOG_LEVEL = Path(TARPIT_LOG_LEVEL_FILE).read_text().strip()
except IOError:
print('error reading log level from '+TARPIT_LOG_LEVEL_FILE+', checking env var '+TARPIT_LOG_LEVEL_ENV_VAR, file=sys.stderr, flush=True)
TARPIT_LOG_LEVEL = os.getenv(TARPIT_LOG_LEVEL_ENV_VAR, DEFAULT_TARPIT_LOG_LEVEL)

TARPIT_NUMERIC_LOG_LEVEL = getattr(logging, TARPIT_LOG_LEVEL.upper(), None)
if not isinstance(TARPIT_NUMERIC_LOG_LEVEL, int):
print('Invalid log level: ' + TARPIT_LOG_LEVEL + ' - using default '+DEFAULT_TARPIT_LOG_LEVEL, file=sys.stderr, flush=True)
TARPIT_NUMERIC_LOG_LEVEL = getattr(logging, DEFAULT_TARPIT_LOG_LEVEL.upper(), None)

try:
with open(TARPIT_LOG, 'w+') as f:
logging.basicConfig(format='%(asctime)s - [%(module)s:%(lineno)d] - %(levelname)s: %(message)s', filename=TARPIT_LOG, level=TARPIT_NUMERIC_LOG_LEVEL)
except IOError:
logging.basicConfig(format='%(asctime)s - [%(module)s:%(lineno)d] - %(levelname)s: %(message)s', stream=sys.stdout, level=TARPIT_NUMERIC_LOG_LEVEL)

tarpit_log = logging.getLogger(__name__)

if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('tarpit initialized, default log level = '+logging.getLevelName(TARPIT_NUMERIC_LOG_LEVEL))

TARPIT_PORT = 8080
try:
TARPIT_PORT = int(Path(TARPIT_PORT_FILE).read_text().strip())
except IOError:
print('error reading port from '+TARPIT_PORT_FILE+', checking env var '+TARPIT_PORT_ENV_VAR, file=sys.stderr, flush=True)
TARPIT_PORT = int(os.getenv(TARPIT_PORT_ENV_VAR, DEFAULT_TARPIT_PORT))

TRAP_COUNT = 0


async def handler(_reader, writer):
global TRAP_COUNT
TRAP_COUNT = TRAP_COUNT + 1
peer_addr = writer.get_extra_info('socket').getpeername()[0]
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('trapped '+peer_addr+' - trap count: ' + str(TRAP_COUNT))
writer.write(b'HTTP/1.1 200 OK\r\n')
try:
while True:
header = random.randint(0, 2**32)
value = random.randint(0, 2**32)
await asyncio.sleep(3 + (header % 4))
writer.write(b'X-WOPR-%x: %x\r\n' % (header, value))
await writer.drain()
except ConnectionResetError:
TRAP_COUNT = TRAP_COUNT - 1
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('dropped '+peer_addr+' - trap count: ' + str(TRAP_COUNT))
pass


async def main():
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('starting HTTP tarpit on port '+str(TARPIT_PORT))
server = await asyncio.start_server(handler, '0.0.0.0', TARPIT_PORT)
async with server:
await server.serve_forever()

asyncio.run(main())

+ 87
- 0
bubble-server/src/main/resources/packer/roles/tarpit/files/bubble_ssh_tarpit.py ファイルの表示

@@ -0,0 +1,87 @@
#!/usr/bin/python3
#
# Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
#
# Adapted from: https://nullprogram.com/blog/2019/03/22/
#
import asyncio
import random
import os
import sys

import logging
from logging import INFO, DEBUG, WARNING, ERROR, CRITICAL

from pathlib import Path

TARPIT_LOG = '/var/log/bubble/ssh_tarpit.log'
TARPIT_LOG_LEVEL_FILE = '/home/tarpit/ssh_tarpit_log_level.txt'
TARPIT_LOG_LEVEL_ENV_VAR = 'SSH_TARPIT_LOG_LEVEL'
DEFAULT_TARPIT_LOG_LEVEL = 'INFO'
TARPIT_LOG_LEVEL = None

TARPIT_PORT_FILE = '/home/tarpit/ssh_tarpit_port.txt'
TARPIT_PORT_ENV_VAR = 'SSH_TARPIT_PORT'
DEFAULT_TARPIT_PORT = '22'

tarpit_log = logging.getLogger(__name__)

try:
TARPIT_LOG_LEVEL = Path(TARPIT_LOG_LEVEL_FILE).read_text().strip()
except IOError:
print('error reading log level from '+TARPIT_LOG_LEVEL_FILE+', checking env var '+TARPIT_LOG_LEVEL_ENV_VAR, file=sys.stderr, flush=True)
TARPIT_LOG_LEVEL = os.getenv(TARPIT_LOG_LEVEL_ENV_VAR, DEFAULT_TARPIT_LOG_LEVEL)

TARPIT_NUMERIC_LOG_LEVEL = getattr(logging, TARPIT_LOG_LEVEL.upper(), None)
if not isinstance(TARPIT_NUMERIC_LOG_LEVEL, int):
print('Invalid log level: ' + TARPIT_LOG_LEVEL + ' - using default '+DEFAULT_TARPIT_LOG_LEVEL, file=sys.stderr, flush=True)
TARPIT_NUMERIC_LOG_LEVEL = getattr(logging, DEFAULT_TARPIT_LOG_LEVEL.upper(), None)

try:
with open(TARPIT_LOG, 'w+') as f:
logging.basicConfig(format='%(asctime)s - [%(module)s:%(lineno)d] - %(levelname)s: %(message)s', filename=TARPIT_LOG, level=TARPIT_NUMERIC_LOG_LEVEL)
except IOError:
logging.basicConfig(format='%(asctime)s - [%(module)s:%(lineno)d] - %(levelname)s: %(message)s', stream=sys.stdout, level=TARPIT_NUMERIC_LOG_LEVEL)

tarpit_log = logging.getLogger(__name__)

if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('tarpit initialized, default log level = '+logging.getLevelName(TARPIT_NUMERIC_LOG_LEVEL))

TARPIT_PORT = 8080
try:
TARPIT_PORT = int(Path(TARPIT_PORT_FILE).read_text().strip())
except IOError:
print('error reading port from '+TARPIT_PORT_FILE+', checking env var '+TARPIT_PORT_ENV_VAR, file=sys.stderr, flush=True)
TARPIT_PORT = int(os.getenv(TARPIT_PORT_ENV_VAR, DEFAULT_TARPIT_PORT))

TRAP_COUNT = 0


async def handler(_reader, writer):
global TRAP_COUNT
TRAP_COUNT = TRAP_COUNT + 1
peer_addr = writer.get_extra_info('socket').getpeername()[0]
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('trapped '+peer_addr+' - trap count: ' + str(TRAP_COUNT))
try:
while True:
val = random.randint(0, 2 ** 32)
await asyncio.sleep(6 + (val % 4))
writer.write(b'%x\r\n' % val)
await writer.drain()
except ConnectionResetError:
TRAP_COUNT = TRAP_COUNT - 1
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('dropped '+peer_addr+' - trap count: ' + str(TRAP_COUNT))
pass


async def main():
if tarpit_log.isEnabledFor(INFO):
tarpit_log.info('starting SSH tarpit on port '+str(TARPIT_PORT))
server = await asyncio.start_server(handler, '0.0.0.0', TARPIT_PORT)
async with server:
await server.serve_forever()

asyncio.run(main())

+ 6
- 0
bubble-server/src/main/resources/packer/roles/tarpit/files/supervisor_http_tarpit.conf ファイルの表示

@@ -0,0 +1,6 @@

[program:http_tarpit]
stdout_logfile = /dev/null
stderr_logfile = /dev/null
command=sudo -u tarpit /home/tarpit/bubble_http_tarpit.py
stopsignal=QUIT

+ 6
- 0
bubble-server/src/main/resources/packer/roles/tarpit/files/supervisor_ssh_tarpit.conf ファイルの表示

@@ -0,0 +1,6 @@

[program:ssh_tarpit]
stdout_logfile = /dev/null
stderr_logfile = /dev/null
command=/home/tarpit/bubble_ssh_tarpit.py
stopsignal=QUIT

+ 54
- 0
bubble-server/src/main/resources/packer/roles/tarpit/tasks/main.yml ファイルの表示

@@ -0,0 +1,54 @@
#
# Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
#
- name: Create tarpit user
user:
name: tarpit
comment: tarpit user
shell: /bin/false
system: yes
home: /home/tarpit
groups: bubble-log

- name: Copy bubble_ssh_tarpit script
copy:
src: bubble_ssh_tarpit.py
dest: /home/tarpit/bubble_ssh_tarpit.py
owner: tarpit
group: tarpit
mode: 0500

- name: Copy bubble_http_tarpit script
copy:
src: bubble_http_tarpit.py
dest: /home/tarpit/bubble_http_tarpit.py
owner: tarpit
group: tarpit
mode: 0500

- name: Install ssh tarpit supervisor conf file
copy:
src: supervisor_ssh_tarpit.conf
dest: /etc/supervisor/conf.d/ssh_tarpit.conf
owner: root
group: root
mode: 0400

- name: Install http tarpit supervisor conf file
copy:
src: supervisor_http_tarpit.conf
dest: /etc/supervisor/conf.d/http_tarpit.conf
owner: root
group: root
mode: 0400

- name: Allow HTTP tarpit port
iptables:
chain: INPUT
protocol: tcp
destination_port: 8080
ctstate: NEW
syn: match
jump: ACCEPT
comment: Accept new connections on HTTP tarpit port
become: yes

+ 1
- 1
bubble-web

@@ -1 +1 @@
Subproject commit 0748d749df4bddac2ed011f2f256e9ca7f2a7e31
Subproject commit 66d46695a64ff58934560c4b35aa43a0ab32fbe2

読み込み中…
キャンセル
保存