Skip to content

Commit

Permalink
fix(dns_disruption): forward mitm logs (#635)
Browse files Browse the repository at this point in the history
* fix(dns_disruption): forward mitm logs

* Update injector/python_test.go

Co-authored-by: Philip Thompson <[email protected]>

Co-authored-by: Philip Thompson <[email protected]>
  • Loading branch information
luphaz and ptnapoleon authored Jan 16, 2023
1 parent f69fdba commit fb1701a
Show file tree
Hide file tree
Showing 18 changed files with 977 additions and 110 deletions.
1 change: 1 addition & 0 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ go.uber.org/zap,go.uber.org/zap/internal/bufferpool,MIT
go.uber.org/zap,go.uber.org/zap/internal/color,MIT
go.uber.org/zap,go.uber.org/zap/internal/exit,MIT
go.uber.org/zap,go.uber.org/zap/zapcore,MIT
go.uber.org/zap,go.uber.org/zap/zaptest/observer,MIT
golang.org/x/crypto,golang.org/x/crypto/ed25519,BSD-3-Clause
golang.org/x/crypto,golang.org/x/crypto/ed25519/internal/edwards25519,BSD-3-Clause
golang.org/x/mod,golang.org/x/mod/semver,BSD-3-Clause
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ generate-chaosdogfood-protobuf:
generate-mock:
go install github.com/vektra/mockery/v2@latest
go generate ./...
# First re-generate header, it should complain as just (re)generated mocks does not contains them
-$(MAKE) header-check
# Then, re-generate header, it should succeed as now all files contains headers as expected, and command return with an happy exit code
$(MAKE) header-check

release:
Expand Down
98 changes: 71 additions & 27 deletions bin/injector/dns_disruption_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,30 @@
Taken from https://github.com/Crypt0s/FakeDns/blob/085737893532ae1c11717807cf3928e989029391/fakedns.py
"""

import json
import logging

class CustomJsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
super(CustomJsonFormatter, self).format(record)
output = {k: v for k, v in record.__dict__.items() if k not in ["msg", "args", "levelname", "levelno"]}
return json.dumps(output, skipkeys=True)


cf = CustomJsonFormatter()
sh = logging.StreamHandler()
sh.setFormatter(cf)

logger = logging.getLogger("dns.disruption.resolver")
logger.addHandler(sh)
logger.setLevel(level=logging.INFO)

# This isn't the most elegent way - i could possibly support both versions of python,
# but people should really not use Python 2 anymore.
import sys
vnum = sys.version.split()[0]
if int(vnum[0]) < 3:
print("Python 2 support has been deprecated. Please run FakeDNS using Python3!")
logger.error("Python 2 support has been deprecated. Please run FakeDNS using Python3!")
sys.exit(1)

import binascii
Expand Down Expand Up @@ -234,7 +252,7 @@ def make_packet(self):
self.pointer + self.type + self.dnsclass + self.ttl + \
self.length + self.data
except Exception as e: #(TypeError, ValueError):
print("[!] - %s" % str(e))
logger.error("[!] unable to make packet", extra={'error': e})

# All classes need to set type, length, and data fields of the DNS Response
# Finished
Expand Down Expand Up @@ -329,13 +347,13 @@ def __init__(self, query, txt_record):

@staticmethod
def get_domain(dns_record):
domain = dns_record
ret_domain=[]
for x in domain.split('.'):
st = "{:02x}".format(len(x))
ret_domain.append( st.decode("hex"))
ret_domain.append(x)
return "".join(ret_domain)
domain = dns_record
ret_domain=[]
for x in domain.split('.'):
st = "{:02x}".format(len(x))
ret_domain.append( st.decode("hex"))
ret_domain.append(x)
return "".join(ret_domain)

class SOA(DNSResponse):
def __init__(self, query, config_location):
Expand Down Expand Up @@ -414,7 +432,7 @@ def __init__(self, query):
self.rranswers = b"\x00\x00"
self.length = b"\x00\x00"
self.data = b"\x00"
print(">> Built NONEFOUND response")
logger.debug("Built NONEFOUND response")


class Rule (object):
Expand Down Expand Up @@ -489,17 +507,17 @@ def match(self, req_type, domain, addr):
# Error classes for handling rule issues
class RuleError_BadRegularExpression(Exception):
def __init__(self,lineno):
print("\n!! Malformed Regular Expression on rulefile line #%d\n\n" % lineno)
logger.error("\n!! Malformed Regular Expression on rulefile line #%d\n\n", lineno)


class RuleError_BadRuleType(Exception):
def __init__(self,lineno):
print("\n!! Rule type unsupported on rulefile line #%d\n\n" % lineno)
logger.error("\n!! Rule type unsupported on rulefile line #%d\n\n", lineno)


class RuleError_BadFormat(Exception):
def __init__(self,lineno):
print("\n!! Not Enough Parameters for rule on rulefile line #%d\n\n" % lineno)
logger.error("\n!! Not Enough Parameters for rule on rulefile line #%d\n\n", lineno)


class RuleEngine2:
Expand All @@ -512,8 +530,8 @@ def _replace_self(self, ips):
try:
self_ip = socket.gethostbyname(socket.gethostname())
except socket.error:
print(">> Could not get your IP address from your " \
"DNS Server.")
logger.warning("Could not get your IP address from your " \
"DNS Server.")
self_ip = '127.0.0.1'
ips[ips.index(ip)] = self_ip
return ips
Expand Down Expand Up @@ -604,7 +622,7 @@ def __init__(self, file_):
# increment the line number
lineno += 1

print(">> Parsed %d rules from %s" % (len(self.rule_list),file_))
logger.info("Parsed %d rules from %s", len(self.rule_list), file_)


def match(self, query, addr):
Expand All @@ -627,15 +645,15 @@ def match(self, query, addr):

response = CASE[query.type](query, response_data)

print(">> Matched Request - " + query.domain.decode())
logger.info("Matched Request - %s", query.domain.decode())
return response.make_packet()

# if we got here, we didn't match.
# Forward a request that we didnt have a rule for to someone else

# if the user said not to forward requests, and we are here, it's time to send a NONEFOUND
if args.noforward:
print(">> Don't Forward %s" % query.domain.decode())
logger.info("Don't Forward - %s", query.domain.decode())
return NONEFOUND(query).make_packet()
try:
s = socket.socket(type=socket.SOCK_DGRAM)
Expand All @@ -651,13 +669,12 @@ def match(self, query, addr):
s.sendto(query.data, addr)
data = s.recv(1024)
s.close()
print("Unmatched Request " + query.domain.decode())
logger.info("Forwarded Request - %s", query.domain.decode())
return data
except socket.error as e:
# We shouldn't wind up here but if we do, don't drop the request
# send the client *something*
print(">> Error was handled by sending NONEFOUND")
print(e)
logger.debug("Error was handled by sending NONEFOUND", extra={'error': e})
return NONEFOUND(query).make_packet()


Expand All @@ -683,7 +700,7 @@ def respond(data, addr, s):

# Capture Control-C and handle here
def signal_handler(signal, frame):
print('Exiting...')
logger.info('Exiting...')
sys.exit(0)


Expand All @@ -693,6 +710,20 @@ def signal_handler(signal, frame):
parser.add_argument(
'-c', dest='path', action='store', required=True,
help='Path to configuration file')

parser.add_argument(
'--log-context-disruption-name', dest='disruptionName', action='store', required=True,
help="Disruption name to add to all logs")
parser.add_argument(
'--log-context-disruption-namespace', dest='disruptionNamespace', action='store', required=True,
help="Disruption namespace to add to all logs")
parser.add_argument(
'--log-context-target-name', dest='targetName', action='store', required=True,
help="Target name to add to all logs")
parser.add_argument(
'--log-context-target-node-name', dest='targetNodeName', action='store', required=True,
help="Target node name to add to all logs")

parser.add_argument(
'-i', dest='iface', action='store', default='0.0.0.0', required=False,
help='IP address you wish to run FakeDns with - default all')
Expand Down Expand Up @@ -723,16 +754,28 @@ def signal_handler(signal, frame):

args = parser.parse_args()

# Add disruption related fields to all logs to ease log filtering and troubleshooting
def log_default_fields(record: logging.LogRecord):
record.disruptionName = args.disruptionName
record.disruptionNamespace = args.disruptionNamespace
record.targetName = args.targetName
record.targetNodeName = args.targetNodeName
# we expect the log level to be in level not levelname, and lowercased
record.level = record.levelname.lower()
return record

sh.addFilter(log_default_fields)

# if non-authoritative is set to true, it'll cancel out the default authoritative setting
# this is a not-very-coherent way to pull this off but we'll be changing the behavior of FakeDNS soon so it's OK
args.authoritative = True ^ args.non_authoritative

# Default config file path.
path = args.path
if not os.path.isfile(path):
print('>> Please create a "dns.conf" file or specify a config path: ' \
'./fakedns.py [configfile]')
exit()
logger.error('Please create a "dns.conf" file or specify a config path: ' \
'./fakedns.py [configfile]')
exit(1)

rules = RuleEngine2(path)
rule_list = rules.rule_list
Expand All @@ -743,12 +786,13 @@ def signal_handler(signal, frame):
try:
server = ThreadedUDPServer((interface, int(port)), UDPHandler)
except socket.error:
print(">> Could not start server -- is another program on udp:{0}?".format(port))
logger.error("Could not start server -- is another program on udp:%d?", port)
exit(1)

logger.info("Fake DNS server started on udp:%d?", port)

server.daemon = True

# Tell python what happens if someone presses ctrl-C
signal.signal(signal.SIGINT, signal_handler)
server.serve_forever()
server_thread.join()
10 changes: 9 additions & 1 deletion cli/injector/dns_disruption.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,15 @@ var dnsDisruptionCmd = &cobra.Command{

// create injectors
for _, config := range configs {
inj, err := injector.NewDNSDisruptionInjector(hostRecordPairs, injector.DNSDisruptionInjectorConfig{Config: config})
inj, err := injector.NewDNSDisruptionInjector(
hostRecordPairs,
injector.DNSDisruptionInjectorConfig{
Config: config,
DisruptionName: disruptionName,
DisruptionNamespace: disruptionNamespace,
TargetName: targetName,
},
)
if err != nil {
log.Fatalw("error initializing the DNS injector", "error", err)
}
Expand Down
66 changes: 64 additions & 2 deletions eventnotifier/slack/mock_slack_notifier_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion eventnotifier/slack/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const (
infoNotAvailable = "n/a"
)

//go:generate mockery --name=slackNotifier --inpackage --case=underscore --testonly
//go:generate mockery --name=slackNotifier --inpackage --case=underscore --testonly --disable-version-string --with-expecter
type slackNotifier interface {
PostMessage(channelID string, options ...slack.MsgOption) (string, string, error)
GetUserByEmail(email string) (*slack.User, error)
Expand Down
Loading

0 comments on commit fb1701a

Please sign in to comment.