checking in all work done so far because what if my SSD dies?
This commit is contained in:
parent
b2109646f3
commit
9c528c4908
9
TODO
9
TODO
@ -13,6 +13,13 @@
|
|||||||
|
|
||||||
|
|
||||||
sshkeys:
|
sshkeys:
|
||||||
-need to verify keys via GPG signature. we also need to have a more robust way of updating pubkeys - categorization
|
-need to verify keys via GPG signature. we also need to have a more robust way of updating pubkeys - categorization, role
|
||||||
-write API to get pubkeys, hostkeys? really wish DBs supported nesting
|
-write API to get pubkeys, hostkeys? really wish DBs supported nesting
|
||||||
-separate by algo, but this is easy to do (split on space, [0])
|
-separate by algo, but this is easy to do (split on space, [0])
|
||||||
|
|
||||||
|
snippet: create mtree with libarchive, bsdtar -cf /tmp/win.mtree --one-file-system --format=mtree --options='mtree:sha512,mtree:indent' /path/*
|
||||||
|
probably need to package https://packages.debian.org/source/stretch/freebsd-buildutils to get fmtree for reading
|
||||||
|
|
||||||
|
-net, add ipxe - write flask app that determines path based on MAC addr
|
||||||
|
|
||||||
|
-net, add shorewall templater
|
119
git/remotehooks.py
Executable file
119
git/remotehooks.py
Executable file
@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import ast # Needed for localhost cmd strings
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
modules = {}
|
||||||
|
try:
|
||||||
|
import git
|
||||||
|
modules['git'] = True
|
||||||
|
except ImportError:
|
||||||
|
import subprocess
|
||||||
|
modules['git'] = False
|
||||||
|
try:
|
||||||
|
import paramiko
|
||||||
|
import socket
|
||||||
|
modules['ssh'] = True
|
||||||
|
except ImportError:
|
||||||
|
modules['ssh'] = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
repos = {}
|
||||||
|
repos['bdisk'] = {'remotecmds': {'g.rainwreck.com': {'gitbot': {'cmds': ['git -C /var/lib/gitbot/clonerepos/BDisk pull',
|
||||||
|
'git -C /var/lib/gitbot/clonerepos/BDisk pull --tags',
|
||||||
|
'asciidoctor /var/lib/gitbot/clonerepos/BDisk/docs/manual/HEAD.adoc -o /srv/http/bdisk/index.html']}}}}
|
||||||
|
repos['test'] = {'remotecmds': {'g.rainwreck.com': {'gitbot': {'cmds': ['echo $USER']}}}}
|
||||||
|
repos['games-site'] = {'remotecmds': {'games.square-r00t.net':
|
||||||
|
{'gitbot':
|
||||||
|
{'cmds': ['cd /srv/http/games-site && git pull']}}}}
|
||||||
|
repos['aif-ng'] = {'cmds': [['asciidoctor', '/opt/git/repo.checkouts/aif-ng/docs/README.adoc', '-o', '/srv/http/aif/index.html']]}
|
||||||
|
|
||||||
|
def execHook(gitinfo = False):
|
||||||
|
if not gitinfo:
|
||||||
|
gitinfo = getGitInfo()
|
||||||
|
repo = gitinfo['repo'].lower()
|
||||||
|
print('Executing hooks for {0}:{1}...'.format(repo, gitinfo['branch']))
|
||||||
|
print('This commit: {0}\nLast commit: {1}'.format(gitinfo['currev'], gitinfo['oldrev']))
|
||||||
|
# Execute local commands first
|
||||||
|
if 'cmds' in repos[repo].keys():
|
||||||
|
for cmd in repos[repo]['cmds']:
|
||||||
|
print('\tExecuting {0}...'.format(' '.join(cmd)))
|
||||||
|
subprocess.call(cmd)
|
||||||
|
if 'remotecmds' in repos[repo].keys():
|
||||||
|
for host in repos[repo]['remotecmds'].keys():
|
||||||
|
if 'port' in repos[repo]['remotecmds'][host].keys():
|
||||||
|
port = int(repos[repo]['remotecmds'][host]['port'])
|
||||||
|
else:
|
||||||
|
port = 22
|
||||||
|
for user in repos[repo]['remotecmds'][host].keys():
|
||||||
|
print('{0}@{1}:'.format(user, host))
|
||||||
|
if paramikomodule:
|
||||||
|
ssh = paramiko.SSHClient()
|
||||||
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
ssh.connect(host, username = user, port = port)
|
||||||
|
try:
|
||||||
|
for cmd in repos[repo]['remotecmds'][host][user]['cmds']:
|
||||||
|
print('\tExecuting \'{0}\'...'.format(cmd))
|
||||||
|
stdin, stdout, stderr = ssh.exec_command(cmd)
|
||||||
|
stdout = stdout.read().decode('utf-8')
|
||||||
|
stderr = stderr.read().decode('utf-8')
|
||||||
|
print(stdout)
|
||||||
|
if stderr != '':
|
||||||
|
print(stderr)
|
||||||
|
except paramiko.AuthenticationException:
|
||||||
|
print('({0}@{1}) AUTHENTICATION FAILED!'.format(user, host))
|
||||||
|
except paramiko.BadHostKeyException:
|
||||||
|
print('({0}@{1}) INCORRECT HOSTKEY!'.format(user, host))
|
||||||
|
except paramiko.SSHException:
|
||||||
|
print('({0}@{1}) FAILED TO ESTABLISH SSH!'.format(user, host))
|
||||||
|
except socket.error:
|
||||||
|
print('({0}@{1}) SOCKET CONNECTION FAILURE! (DNS, timeout/firewall, etc.)'.format(user, host))
|
||||||
|
else:
|
||||||
|
for cmd in repos[repo]['remotecmds'][host][user]['cmds']:
|
||||||
|
try:
|
||||||
|
print('\tExecuting \'{0}\'...'.format(cmd))
|
||||||
|
subprocess.call(['ssh', '{0}@{1}'.format(user, host), cmd])
|
||||||
|
except:
|
||||||
|
print('({0}@{1}) An error occurred!'.format(user, host))
|
||||||
|
|
||||||
|
def getGitInfo():
|
||||||
|
refs = sys.argv[1].split('/')
|
||||||
|
gitinfo = {}
|
||||||
|
if refs[1] == 'tags':
|
||||||
|
gitinfo['branch'] = False
|
||||||
|
gitinfo['tag'] = refs[2]
|
||||||
|
elif refs[1] == 'heads':
|
||||||
|
gitinfo['branch'] = refs[2]
|
||||||
|
gitinfo['tag'] = False
|
||||||
|
gitinfo['repo'] = os.environ['GL_REPO']
|
||||||
|
gitinfo['user'] = os.environ['GL_USER']
|
||||||
|
clientinfo = os.environ['SSH_CONNECTION'].split()
|
||||||
|
gitinfo['ssh'] = {'client': {'ip': clientinfo[0], 'port': clientinfo[1]},
|
||||||
|
'server': {'ip': clientinfo[2], 'port': clientinfo[3]},
|
||||||
|
'user': os.environ['USER']
|
||||||
|
}
|
||||||
|
if os.environ['GIT_DIR'] == '.':
|
||||||
|
gitinfo['dir'] = os.environ['PWD']
|
||||||
|
else:
|
||||||
|
#gitinfo['dir'] = os.path.join(os.environ['GL_REPO_BASE'], gitinfo['repo'], '.git')
|
||||||
|
gitinfo['dir'] = os.path.abspath(os.path.expanduser(os.environ['GIT_DIR']))
|
||||||
|
if gitmodule:
|
||||||
|
# This is preferred, because it's a lot more faster and a lot more flexible.
|
||||||
|
#https://gitpython.readthedocs.io/en/stable
|
||||||
|
gitobj = git.Repo(gitinfo['dir'])
|
||||||
|
commits = list(gitobj.iter_commits(gitobj.head.ref.name, max_count = 2))
|
||||||
|
else:
|
||||||
|
commits = subprocess.check_output(['git', 'rev-parse', 'HEAD..HEAD^1']).decode('utf-8').splitlines()
|
||||||
|
gitinfo['oldrev'] = re.sub('^\^', '', commits[1])
|
||||||
|
gitinfo['currev'] = re.sub('^\^', '', commits[0])
|
||||||
|
return(gitinfo)
|
||||||
|
#sys.exit(0)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
execHook()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
69
git/remotehooks2.py
Executable file
69
git/remotehooks2.py
Executable file
@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
# Can we use paramiko for remotecmds?
|
||||||
|
try:
|
||||||
|
import paramiko
|
||||||
|
import socket
|
||||||
|
has_ssh = True
|
||||||
|
except ImportError:
|
||||||
|
has_ssh = False
|
||||||
|
# Can we use the python git module?
|
||||||
|
try:
|
||||||
|
import git # "python-gitpython" in Arch; https://github.com/gitpython-developers/gitpython
|
||||||
|
has_git = True
|
||||||
|
except ImportError:
|
||||||
|
has_git = False
|
||||||
|
|
||||||
|
|
||||||
|
class repoHooks(object):
|
||||||
|
def __init__(self):
|
||||||
|
with open(os.path.join(os.environ['HOME'],
|
||||||
|
'.gitolite',
|
||||||
|
'local',
|
||||||
|
'hooks',
|
||||||
|
'repo-specific',
|
||||||
|
'githooks.json'), 'r') as f:
|
||||||
|
self.cfg = json.loads(f.read())
|
||||||
|
self.repos = list(self.cfg.keys())
|
||||||
|
self.env = os.environ.copy()
|
||||||
|
if 'GIT_DIR' in self.env.keys():
|
||||||
|
del(self.env['GIT_DIR'])
|
||||||
|
self.repo = self.env['GL_REPO']
|
||||||
|
|
||||||
|
def remoteExec(self):
|
||||||
|
for _host in self.repos[self.repo]['remotecmds'].keys():
|
||||||
|
if len(_host.split(':')) == 2:
|
||||||
|
_server, _port = [i.strip() for i in _host.split(':')]
|
||||||
|
else:
|
||||||
|
_port = 22
|
||||||
|
_server = _host.split(':')[0]
|
||||||
|
_h = self.repos[self.repo]['remotecmds'][_host]
|
||||||
|
for _user in _h.keys():
|
||||||
|
_u = _h[_user]
|
||||||
|
if has_ssh:
|
||||||
|
_ssh = paramiko.SSHClient()
|
||||||
|
_ssh.load_system_host_keys()
|
||||||
|
_ssh.missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
_ssh.connect(_server,
|
||||||
|
int(_port),
|
||||||
|
_user)
|
||||||
|
for _cmd in _h.keys():
|
||||||
|
pass # DO STUFF HERE
|
||||||
|
else:
|
||||||
|
return() # no-op; no paramiko
|
||||||
|
|
||||||
|
def localExec(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def main():
|
||||||
|
h = repoHooks()
|
||||||
|
if h.repo not in h.repos:
|
||||||
|
return()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
27
git/sample.githooks..json
Normal file
27
git/sample.githooks..json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# remotehooks.py should go in your <gitolite repo>/local/hooks/repo-specific directory,
|
||||||
|
# along with the (uncommented) format of this file configured for your particular hooks
|
||||||
|
# "cmds" is a list of commands performed locally on the gitolite server,
|
||||||
|
# "remotecmds" contains a recursive directory of commands to run remotely
|
||||||
|
|
||||||
|
{
|
||||||
|
"<REPO_NAME>": {
|
||||||
|
"remotecmds": {
|
||||||
|
"<HOST_OR_IP_ADDRESS>": {
|
||||||
|
"<USER>": {
|
||||||
|
"cmds": [
|
||||||
|
"<COMMAND_1>",
|
||||||
|
"<COMMAND_2>"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"<REPO2_NAME>": {
|
||||||
|
"cmds": [
|
||||||
|
[
|
||||||
|
"<LOCAL_COMMAND_1>",
|
||||||
|
"<LOCAL_COMMAND_2>"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
1
mumble/.gitignore
vendored
1
mumble/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
/docs
|
/docs
|
||||||
|
/testcertimport.py
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
-add lsChans()
|
-add lsChans()
|
||||||
-lsACL? lsBans? edit these?
|
-lsACL? lsBans? edit these?
|
||||||
-find out some way to use the DBus/ICE/RPC interface instead? then we can get rid of the restart
|
-find out some way to use the ICE/GRPC interface completely
|
||||||
-- NOTE: Arch murmur package currently disables ice at compile-time. https://bugs.archlinux.org/task/55958
|
|
||||||
|
-i need to learn way more about GRPC:
|
||||||
|
https://wiki.mumble.info/wiki/GRPC
|
||||||
|
https://github.com/mumble-voip/mumble/issues/1196
|
||||||
|
https://grpc.io/docs/tutorials/basic/python.html
|
||||||
|
7
mumble/grpctest.py
Executable file
7
mumble/grpctest.py
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import grpc
|
||||||
|
from grpc.tools import protoc
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
conn = grpc.
|
@ -1,5 +1,8 @@
|
|||||||
[ICE]
|
[MURMUR]
|
||||||
|
# This section controls some general settings.
|
||||||
|
|
||||||
|
# The host of the Murmur server. This will be used to determine where to connect to
|
||||||
|
# for interaction for whichever interface you choose.
|
||||||
# Examples:
|
# Examples:
|
||||||
# fqdn.domain.tld
|
# fqdn.domain.tld
|
||||||
# 127.0.0.1
|
# 127.0.0.1
|
||||||
@ -7,6 +10,40 @@
|
|||||||
# ::1
|
# ::1
|
||||||
host = localhost
|
host = localhost
|
||||||
|
|
||||||
|
# The type of interface to use. Currently, only "ice" and "grpc" are supported.
|
||||||
|
# "ice" is the default.
|
||||||
|
connection = "ice"
|
||||||
|
|
||||||
|
|
||||||
|
[GRPC]
|
||||||
|
# The GRPC interface is intended to (potentially) replace the ICE and DBUS interfaces.
|
||||||
|
# However, it's currently considered "experimental" - both upstream in Mumble/Murmur,
|
||||||
|
# and in this project. It's faster and more secure than Ice, however, if you've
|
||||||
|
# enabled TLS transport in your murmur.ini. It requires you to build murmur explicitly
|
||||||
|
# with grpc support, however.
|
||||||
|
|
||||||
|
# The port GRPC is running on.
|
||||||
|
port = 50051
|
||||||
|
|
||||||
|
# One of udp or tcp. You probably want to use tcp.
|
||||||
|
proto = tcp
|
||||||
|
|
||||||
|
# You probably will need to change this.
|
||||||
|
# If you need a copy, you can get the most recent at:
|
||||||
|
# https://github.com/mumble-voip/mumble/blob/master/src/murmur/MurmurRPC.proto
|
||||||
|
# If you leave this empty ("proto = "), we will attempt to fetch the slice from the remote
|
||||||
|
# instance ("MURMUR:host" above).
|
||||||
|
spec = /usr/local/lib/optools/mumble/murmurRPC.proto
|
||||||
|
|
||||||
|
# The maximum size for GRPC Messages (in KB)
|
||||||
|
# You're probably fine with the default.
|
||||||
|
max_size = 1024
|
||||||
|
|
||||||
|
|
||||||
|
[ICE]
|
||||||
|
# Ice is on its way out, but is currently the stable interface and most widely
|
||||||
|
# supported across versions.
|
||||||
|
|
||||||
# The port ICE is running on
|
# The port ICE is running on
|
||||||
port = 6502
|
port = 6502
|
||||||
|
|
||||||
@ -18,7 +55,7 @@ proto = tcp
|
|||||||
# https://github.com/mumble-voip/mumble/blob/master/src/murmur/Murmur.ice
|
# https://github.com/mumble-voip/mumble/blob/master/src/murmur/Murmur.ice
|
||||||
# If you leave this empty ("slice = "), we will attempt to fetch the slice from the remote
|
# If you leave this empty ("slice = "), we will attempt to fetch the slice from the remote
|
||||||
# instance ("host" above).
|
# instance ("host" above).
|
||||||
slice = /usr/local/lib/optools/mumble/murmur.ice
|
spec = /usr/local/lib/optools/mumble/murmur.ice
|
||||||
|
|
||||||
# The maximum size for ICE Messages (in KB)
|
# The maximum size for ICE Messages (in KB)
|
||||||
# You're probably fine with the default.
|
# You're probably fine with the default.
|
||||||
@ -26,52 +63,12 @@ max_size = 1024
|
|||||||
|
|
||||||
|
|
||||||
[AUTH]
|
[AUTH]
|
||||||
|
# If both read and write are populated, write will be used preferentially.
|
||||||
|
|
||||||
# The Ice secret for read-only operations.
|
# The Ice secret for read-only operations.
|
||||||
# Set to a blank string if you want to only make a write-only connection.
|
# Can be a blank string if you specify a write connection (see below).
|
||||||
read =
|
read =
|
||||||
|
|
||||||
# The Ice secret for write-only operations.
|
# The Ice secret for read+write operations.
|
||||||
# Set to a blank string if you want to only make a read-only connection.
|
# Set to a blank string if you want to only make a read-only connection.
|
||||||
write =
|
write =
|
||||||
|
|
||||||
[TUNNEL]
|
|
||||||
# NOTE: TO USE SSH TUNNELING, YOU MUST HAVE THE "sshtunnel" PYTHON MODULE INSTALLED.
|
|
||||||
# If enabled, we will bind the remote port to the host and port given in the [ICE] section.
|
|
||||||
# So you probably want to use localhost/127.0.0.1/::1 up there.
|
|
||||||
|
|
||||||
# If this is enabled, we will try to initiate an SSH tunnel to the remote server,
|
|
||||||
# and use the Ice interface through that. Probably only works with TCP Ice instances.
|
|
||||||
# "enable" should be true or false. If blank, assume true. It's a VERY GOOD IDEA
|
|
||||||
# to use this feature, as it greatly heightens the security.
|
|
||||||
enable = true
|
|
||||||
|
|
||||||
# The remote host to bind a port with. In most cases, this is going to be the host
|
|
||||||
# that your Murmur instance is running on.
|
|
||||||
host = your.murmur.server.tld
|
|
||||||
|
|
||||||
# The remote user to auth as. If blank, use the current (local) username.
|
|
||||||
user =
|
|
||||||
|
|
||||||
# The port for SSH. In most cases, 22 is what you want. You can leave it blank,
|
|
||||||
# we'll use the default in that case.
|
|
||||||
port = 22
|
|
||||||
|
|
||||||
# The authentication method. Currently supported methods are "key" and "passphrase".
|
|
||||||
# Key is recommended (and the default). See:
|
|
||||||
# https://sysadministrivia.com/news/hardening-ssh-security#auth_client
|
|
||||||
# (and/or a multitude of other resources) on how to set up pubkey auth for SSH.
|
|
||||||
auth = key
|
|
||||||
|
|
||||||
# If "auth" is "password", enter the password here. If password auth is used
|
|
||||||
# and no password is provided, you will be prompted to enter it.
|
|
||||||
passphrase =
|
|
||||||
|
|
||||||
# If "auth" is "key", enter the path to the *private* (not public) key here.
|
|
||||||
# If none is provided, we'll use the default of ~/.ssh/id_rsa.
|
|
||||||
# Note that if your key is password-protected, you should enable "key_passphrase".
|
|
||||||
key = ~/.ssh/id_rsa
|
|
||||||
|
|
||||||
# Should we (securely) prompt for a key_passphrase? This is REQUIRED if your key
|
|
||||||
# is password-protected and you're using key authentication. Can be "true" or "false".
|
|
||||||
key_passphrase = false
|
|
||||||
|
@ -25,7 +25,9 @@ class IceMgr(object):
|
|||||||
if self.args['verbose']:
|
if self.args['verbose']:
|
||||||
import pprint
|
import pprint
|
||||||
self.getCfg()
|
self.getCfg()
|
||||||
self.connect()
|
if self.cfg['MURMUR']['connection'] == '':
|
||||||
|
self.cfg['MURMUR']['connection'] == 'ice'
|
||||||
|
self.connect(self.cfg['MURMUR']['connection'])
|
||||||
|
|
||||||
def getCfg(self):
|
def getCfg(self):
|
||||||
_cfg = os.path.join(os.path.abspath(os.path.expanduser(self.args['cfgfile'])))
|
_cfg = os.path.join(os.path.abspath(os.path.expanduser(self.args['cfgfile'])))
|
||||||
@ -42,69 +44,13 @@ class IceMgr(object):
|
|||||||
self.cfg[section][option] = _parser.get(section, option)
|
self.cfg[section][option] = _parser.get(section, option)
|
||||||
return()
|
return()
|
||||||
|
|
||||||
def sshTunnel(self):
|
def connect(self, ctxtype):
|
||||||
try:
|
ctxtype = ctxtype.strip().upper()
|
||||||
from sshtunnel import SSHTunnelForwarder,create_logger
|
if ctxtype.lower() not in ('ice', 'grpc'):
|
||||||
except ImportError:
|
raise ValueError('You have specified an invalid connection type.')
|
||||||
raise ImportError('You must install the sshtunnel Python module to use SSH tunneling!')
|
_cxcfg = self.cfg[ctxtype]
|
||||||
import time
|
self.cfg[ctxtype]['spec'] = os.path.join(os.path.abspath(os.path.expanduser(self.cfg[ctxtype]['spec'])))
|
||||||
_sshcfg = self.cfg['TUNNEL']
|
# ICE START
|
||||||
# Do some munging to make this easier to deal with.
|
|
||||||
if _sshcfg['user'] == '':
|
|
||||||
_sshcfg['user'] = getpass.getuser()
|
|
||||||
if _sshcfg['port'] == '':
|
|
||||||
_sshcfg['port'] = 22
|
|
||||||
else:
|
|
||||||
_sshcfg['port'] = int(_sshcfg['port'])
|
|
||||||
if _sshcfg['auth'].lower() == 'passphrase':
|
|
||||||
if _sshcfg['passphrase'] == '':
|
|
||||||
_sshcfg['passphrase'] = getpass.getpass(('What passphrase should ' +
|
|
||||||
'we use for {0}@{1}:{2}? (Will not ' +
|
|
||||||
'echo back.)\nPassphrase: ').format(
|
|
||||||
_sshcfg['user'],
|
|
||||||
_sshcfg['host'],
|
|
||||||
_sshcfg['port'])).encode('utf-8')
|
|
||||||
else:
|
|
||||||
_sshcfg['passphrase'] = _sshcfg['passphrase'].encode('utf-8')
|
|
||||||
_sshcfg['key'] = None
|
|
||||||
else:
|
|
||||||
if _sshcfg['key'] == '':
|
|
||||||
_sshcfg['key'] = '~/.ssh/id_rsa'
|
|
||||||
_key = os.path.abspath(os.path.expanduser(_sshcfg['key']))
|
|
||||||
# We need to get the passphrase for the key, if it's set.
|
|
||||||
if _sshcfg['key_passphrase'].lower() == 'true':
|
|
||||||
_keypass = getpass.getpass(('What is the passphrase for {0}? ' +
|
|
||||||
'(Will not be echoed back.)\nPassphrase: ').format(_key)).encode('utf-8')
|
|
||||||
else:
|
|
||||||
_keypass = None
|
|
||||||
# To pring debug info, just add "logger=create_logger(loglevel=1)" to the params.
|
|
||||||
self.ssh = SSHTunnelForwarder(_sshcfg['host'],
|
|
||||||
ssh_pkey = _key,
|
|
||||||
ssh_private_key_password = _keypass,
|
|
||||||
ssh_username = _sshcfg['user'],
|
|
||||||
ssh_port = _sshcfg['port'],
|
|
||||||
local_bind_address = ('127.0.0.1', ),
|
|
||||||
remote_bind_address = (self.cfg['ICE']['host'],
|
|
||||||
int(self.cfg['ICE']['port'])),
|
|
||||||
set_keepalive = 3.0)
|
|
||||||
self.ssh.start()
|
|
||||||
if self.args['verbose']:
|
|
||||||
print('Configured tunneling for {0}:{1}({2}:{3}) => {4}:{5}'.format(
|
|
||||||
_sshcfg['host'],
|
|
||||||
_sshcfg['port'],
|
|
||||||
self.cfg['ICE']['host'],
|
|
||||||
self.cfg['ICE']['port'],
|
|
||||||
self.ssh.local_bind_address[0],
|
|
||||||
self.ssh.local_bind_address[1]))
|
|
||||||
#self.cfg['ICE']['port'] = int(self.ssh.local_bind_ports[0])
|
|
||||||
self.cfg['ICE']['port'] = int(self.ssh.local_bind_port)
|
|
||||||
self.cfg['ICE']['host'] = self.ssh.local_bind_address[0]
|
|
||||||
time.sleep(3)
|
|
||||||
return()
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
if self.cfg['TUNNEL']['enable'].lower() == 'true':
|
|
||||||
self.sshTunnel()
|
|
||||||
_props = {'ImplicitContext': 'Shared',
|
_props = {'ImplicitContext': 'Shared',
|
||||||
'Default.EncodingVersion': '1.0',
|
'Default.EncodingVersion': '1.0',
|
||||||
'MessageSizeMax': str(self.cfg['ICE']['max_size'])}
|
'MessageSizeMax': str(self.cfg['ICE']['max_size'])}
|
||||||
@ -155,7 +101,7 @@ class IceMgr(object):
|
|||||||
_slicefile.close()
|
_slicefile.close()
|
||||||
os.remove(_filepath)
|
os.remove(_filepath)
|
||||||
else: # A .ice file was explicitly defined in the cfg
|
else: # A .ice file was explicitly defined in the cfg
|
||||||
_slicedir.append(os.path.join(os.path.abspath(os.path.expanduser(self.cfg['ICE']['slice']))))
|
_slicedir.append(self.cfg[ctxtype]['spec'])
|
||||||
Ice.loadSlice('', _slicedir)
|
Ice.loadSlice('', _slicedir)
|
||||||
import Murmur
|
import Murmur
|
||||||
self.conn = {}
|
self.conn = {}
|
||||||
|
49
net/addr/app/dnsinfo.py
Normal file
49
net/addr/app/dnsinfo.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# https://gist.github.com/akshaybabloo/2a1df455e7643926739e934e910cbf2e
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
|
import dns # apacman -S python-dnspython
|
||||||
|
import ipwhois # apacman -S python-ipwhois
|
||||||
|
import whois # apacman -S python-ipwhois
|
||||||
|
|
||||||
|
class netTarget(object):
|
||||||
|
def __init__(self, target):
|
||||||
|
self.target = target
|
||||||
|
|
||||||
|
|
||||||
|
##!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
#import pprint
|
||||||
|
#import dns
|
||||||
|
#import whois
|
||||||
|
#import ipwhois
|
||||||
|
#
|
||||||
|
#d = 'sysadministrivia.com' # A/AAAA
|
||||||
|
#d = 'autoconfig.sysadministrivia.com' # CNAME
|
||||||
|
#
|
||||||
|
#records = {'whois': None,
|
||||||
|
# 'ptr': None,
|
||||||
|
# 'allocation': None}
|
||||||
|
#
|
||||||
|
#def getWhois(domain):
|
||||||
|
# _w = whois.whois(d)
|
||||||
|
# records['whois'] = dict(_w)
|
||||||
|
# return()
|
||||||
|
#
|
||||||
|
#def getIps(domain):
|
||||||
|
# addrs = []
|
||||||
|
# for t in ('A', 'AAAA'):
|
||||||
|
# answers = dns.resolver.query(domain, t)
|
||||||
|
# for a in answers:
|
||||||
|
# try:
|
||||||
|
# addrs.append(a.address)
|
||||||
|
# except:
|
||||||
|
# pass
|
||||||
|
# return(addrs)
|
||||||
|
#
|
||||||
|
#def getPtr(addrs):
|
||||||
|
# for a in addrs:
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
#print(getIps(d))
|
||||||
|
##pprint.pprint()
|
210
net/dhcp/dhcpcdump.py
Executable file
210
net/dhcp/dhcpcdump.py
Executable file
@ -0,0 +1,210 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# See RFC 2131, Figure 1 and Table 1 (section 2)
|
||||||
|
# Much thanks to https://github.com/igordcard/dhcplease for digging into dhcpcd
|
||||||
|
# source for the actual file structure (and providing inspiration).
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
## DEFINE SOME PRETTY STUFF ##
|
||||||
|
class color(object):
|
||||||
|
PURPLE = '\033[95m'
|
||||||
|
CYAN = '\033[96m'
|
||||||
|
DARKCYAN = '\033[36m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
YELLOW = '\033[93m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
BOLD = '\033[1m'
|
||||||
|
UNDERLINE = '\033[4m'
|
||||||
|
END = '\033[0m'
|
||||||
|
|
||||||
|
class packetParser(object):
|
||||||
|
def __init__(self, data):
|
||||||
|
## Set the segment labels and struct formats
|
||||||
|
self.fmt = collections.OrderedDict()
|
||||||
|
# In the below, 'cnt' is how large (in octets) the field is.
|
||||||
|
# 'fmt' is a struct format string (https://docs.python.org/3/library/struct.html#format-characters)
|
||||||
|
# "op" through "hops" (incl.) may actually be '8B' instead of '8c'.
|
||||||
|
self.fmt['op'] = {'cnt': 8, 'fmt': '8c'} # this will always be \x02
|
||||||
|
self.fmt['htype'] = {'cnt': 8, 'fmt': '8c'} # this will always be \x01
|
||||||
|
self.fmt['hlen'] = {'cnt': 8, 'fmt': '8c'}
|
||||||
|
self.fmt['hops'] = {'cnt': 8, 'fmt': '8c'}
|
||||||
|
self.fmt['xid'] = {'cnt': 32, 'fmt': '8I'}
|
||||||
|
self.fmt['secs'] = {'cnt': 16, 'fmt': '8H'}
|
||||||
|
self.fmt['flags'] = {'cnt': 16, 'fmt': '8H'}
|
||||||
|
# "ciaddr" through "giaddr" (incl.) may actually be '4c' instead of '4B'.
|
||||||
|
self.fmt['ciaddr'] = {'cnt': 4, 'fmt': '4B'}
|
||||||
|
self.fmt['yiaddr'] = {'cnt': 4, 'fmt': '4B'}
|
||||||
|
self.fmt['siaddr'] = {'cnt': 4, 'fmt': '4B'}
|
||||||
|
self.fmt['giaddr'] = {'cnt': 4, 'fmt': '4B'}
|
||||||
|
# "chaddr" through "file" (incl.) may actually be <#>c instead of <#>B.
|
||||||
|
self.fmt['chaddr'] = {'cnt': 16, 'fmt': '16B'} # first 6 bytes used for MAC addr of client
|
||||||
|
self.fmt['sname'] = {'cnt': 64, 'fmt': '64B'} # server host name (via BOOTP)
|
||||||
|
self.fmt['file'] = {'cnt': 128, 'fmt': '128B'} # the boot filename (for BOOTP)
|
||||||
|
# OPTIONS - RFC 2132
|
||||||
|
# Starting at octet 320 (so, f.seek(319, 0)) to the end of the message are
|
||||||
|
# DHCP options. It's a variable-length field so it makes things tricky
|
||||||
|
# for us. But it's at *least* 312 octets long per the RFC?
|
||||||
|
# It probably starts with a magic.
|
||||||
|
#self.dhcp_opts = {'idx': 324, 'cnt': 4, 'fmt': '4c'}
|
||||||
|
#self.dhcp_opts = {'idx': 324, 'cnt': 4, 'fmt': None}
|
||||||
|
self.opts = {'magic': b'\x63\x82\x53\x63',
|
||||||
|
'struct': {'idx': 324, 'cnt': 4, 'fmt': '4B'},
|
||||||
|
'size': 0,
|
||||||
|
'bytes': b'\00'}
|
||||||
|
## Convert the data into a bytes object because struct.unpack() wants a stream
|
||||||
|
self.buf = BytesIO(data)
|
||||||
|
|
||||||
|
def getStd(self):
|
||||||
|
self.reconstructed_segments = collections.OrderedDict()
|
||||||
|
_idx = 0 # add to this with the 'cnt' value for each iteration.
|
||||||
|
for k in self.fmt.keys():
|
||||||
|
print('Segment: ' + k) # TODO: remove, this stuff goes in the printer
|
||||||
|
pkt = struct.Struct(self.fmt[k]['fmt'])
|
||||||
|
self.buf.seek(_idx, 0)
|
||||||
|
try:
|
||||||
|
self.reconstructed_segments[k] = pkt.unpack(self.buf.read(self.fmt[k]['cnt']))
|
||||||
|
except struct.error as e:
|
||||||
|
# Some DHCP implementations are... broken.
|
||||||
|
# I've noticed it mostly in Verizon Fi-OS gateways/WAPs/routers.
|
||||||
|
print('Warning({0}): {1}'.format(k, e))
|
||||||
|
self.buf.seek(_idx, 0)
|
||||||
|
_truesize = len(self.buf.read(self.fmt[k]['cnt']))
|
||||||
|
print('Length of bytes read: {0}'.format(_truesize))
|
||||||
|
# But sometimes it's... kind of fixable?
|
||||||
|
if k == 'file' and _truesize < self.fmt[k]['cnt']:
|
||||||
|
self.buf.seek(_idx, 0)
|
||||||
|
self.fmt[k] = {'cnt': _truesize, 'fmt': '{0}B'.format(_truesize)}
|
||||||
|
pkt = struct.Struct(self.fmt[k]['fmt'])
|
||||||
|
print('Struct format size automatically adjusted.')
|
||||||
|
try:
|
||||||
|
self.reconstructed_segments[k] = pkt.unpack(self.buf.read(self.fmt[k]['cnt']))
|
||||||
|
except struct.error as e2:
|
||||||
|
# yolo.
|
||||||
|
print('We still couldn\'t populate {0}; filling with a nullbyte.'.format(k))
|
||||||
|
print('Error (try #2): {0}'.format(e2))
|
||||||
|
print('We read {0} bytes.'.format(_truesize))
|
||||||
|
print('fmt: {0}'.format(self.fmt[k]['fmt']))
|
||||||
|
self.reconstructed_segments[k] = b'\00'
|
||||||
|
_idx += self.fmt[k]['cnt']
|
||||||
|
self.buf.seek(_idx, 0)
|
||||||
|
# Finally, check for opts. If they exist, populate.
|
||||||
|
_optbytes = len(self.buf.read())
|
||||||
|
if _optbytes >= 1:
|
||||||
|
self.opts['size'] = _optbytes
|
||||||
|
self.buf.seek(_idx, 0)
|
||||||
|
self.opts['bytes'] = self.buf.read() # read to the end
|
||||||
|
return()
|
||||||
|
|
||||||
|
def getOpts(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.buf.close()
|
||||||
|
|
||||||
|
def parseArgs():
|
||||||
|
args = argparse.ArgumentParser()
|
||||||
|
_deflease = '/var/lib/dhcpcd/'
|
||||||
|
args.add_argument('-l', '--lease',
|
||||||
|
metavar = '/path/to/lease/dir/or_file.lease',
|
||||||
|
default = _deflease,
|
||||||
|
dest = 'leasepath',
|
||||||
|
help = ('The path to the directory of lease files or specific lease file. ' +
|
||||||
|
'If a directory is provided, all lease files found within will be ' +
|
||||||
|
'parsed. Default: {0}{1}{2}').format(color.BOLD,
|
||||||
|
_deflease,
|
||||||
|
color.END))
|
||||||
|
args.add_argument('-n', '--no-color',
|
||||||
|
action = 'store_false',
|
||||||
|
dest = 'color',
|
||||||
|
help = ('If specified, suppress color formatting in output.'))
|
||||||
|
args.add_argument('-d', '--dump',
|
||||||
|
metavar = '/path/to/dumpdir',
|
||||||
|
default = False,
|
||||||
|
dest = 'dump',
|
||||||
|
help = ('If provided, dump the parsed leases to this directory (in ' +
|
||||||
|
'addition to printing). It will dump with the same filename ' +
|
||||||
|
'and overwrite any existing file with the same filename, so ' +
|
||||||
|
'do NOT use the same directory as your dhcpcd lease files! ' +
|
||||||
|
'({0}-l/--lease{1}). The directory will be created if it does ' +
|
||||||
|
'not exist').format(color.BOLD,
|
||||||
|
color.END))
|
||||||
|
args.add_argument('-p', '--pretty',
|
||||||
|
action = 'store_true',
|
||||||
|
dest = 'prettyprint',
|
||||||
|
help = ('If specified, include color formatting {0}in the dump ' +
|
||||||
|
'file(s){1}').format(color.BOLD, color.END))
|
||||||
|
return(args)
|
||||||
|
|
||||||
|
def getLeaseData(fpath):
|
||||||
|
if not os.path.isfile(fpath):
|
||||||
|
raise FileNotFoundError('{0} does not exist'.format(fpath))
|
||||||
|
with open(fpath, 'rb') as f:
|
||||||
|
_data = f.read()
|
||||||
|
return(_data)
|
||||||
|
|
||||||
|
def iterLease(args):
|
||||||
|
# If the lease path is a file, just operate on that.
|
||||||
|
# If it's a directory, iterate (recursively) through it.
|
||||||
|
leases = {}
|
||||||
|
if not os.path.lexists(args['leasepath']):
|
||||||
|
raise FileNotFoundError('{0} does not exist'.format(args['leasepath']))
|
||||||
|
if os.path.isfile(args['leasepath']):
|
||||||
|
_pp = packetParser(getLeaseData(args['leasepath']))
|
||||||
|
# TODO: convert the hex vals to their actual vals... maybe?
|
||||||
|
_keyname = re.sub('^(dhcpcd-)?(.*)\.lease$',
|
||||||
|
'\g<2>',
|
||||||
|
os.path.basename(args['leasepath']))
|
||||||
|
leases[_keyname] = leaseParse(_pp, args)
|
||||||
|
else:
|
||||||
|
# walk() instead of listdir() because whotf knows when some distro like
|
||||||
|
# *coughcoughUbuntucoughcough* will do some breaking change like creating
|
||||||
|
# subdirs based on iface name or something.
|
||||||
|
for _, _, files in os.walk(args['leasepath']):
|
||||||
|
if not files:
|
||||||
|
continue
|
||||||
|
files = [i for i in files if i.endswith('.lease')] # only get .lease files
|
||||||
|
for i in files:
|
||||||
|
_args = args.copy()
|
||||||
|
_fpath = os.path.join(args['leasepath'], i)
|
||||||
|
_keyname = re.sub('^(dhcpcd-)?(.*)\.lease$', '\g<2>', os.path.basename(_fpath))
|
||||||
|
_dupeid = 0
|
||||||
|
# JUST in case there are multiple levels of dirs in the future
|
||||||
|
# that have files of the sama name
|
||||||
|
while _keyname in leases.keys():
|
||||||
|
# TODO: convert the hex vals to their actual vals... maybe?
|
||||||
|
_keyname = re.sub('^$',
|
||||||
|
'\g<1>.{0}'.format(_dupeid),
|
||||||
|
_keyname)
|
||||||
|
_dupeid += 1
|
||||||
|
_pp = packetParser(getLeaseData(_fpath))
|
||||||
|
leases[_keyname] = leaseParse(_pp, _args, fname = _fpath)
|
||||||
|
return(leases)
|
||||||
|
|
||||||
|
def leaseParse(pp, args, fname = False):
|
||||||
|
# Essentially just a wrapper function.
|
||||||
|
# Debugging output...
|
||||||
|
if fname:
|
||||||
|
print(fname)
|
||||||
|
pp.getStd()
|
||||||
|
pp.getOpts()
|
||||||
|
if args['dump']:
|
||||||
|
pass # TODO: write to files, creating dump dir if needed, etc.
|
||||||
|
pp.close()
|
||||||
|
# do pretty-printing (color-coded segments, etc.) here
|
||||||
|
return(pp.reconstructed_segments)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
args = vars(parseArgs().parse_args())
|
||||||
|
args['leasepath'] = os.path.abspath(os.path.expanduser(args['leasepath']))
|
||||||
|
if not os.path.lexists(args['leasepath']):
|
||||||
|
exit('{0} does not exist!'.format(args['leasepath']))
|
||||||
|
leases = iterLease(args)
|
||||||
|
# just print for now until we write the parser/prettyprinter
|
||||||
|
print(list(leases.keys()))
|
7
net/ssh/hostkeymanager/app/__init__.py
Normal file
7
net/ssh/hostkeymanager/app/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
|
|
||||||
|
from app import views
|
||||||
|
|
||||||
|
app.config.from_object('config')
|
41
net/ssh/hostkeymanager/app/manage.py
Executable file
41
net/ssh/hostkeymanager/app/manage.py
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
# This is ugly as fuck. TODO: can we do this more cleanly?
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
|
||||||
|
import config
|
||||||
|
|
||||||
|
class DBmgr(object):
|
||||||
|
def __init__(self, args = None):
|
||||||
|
self.DB = config.DB
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def keyChk(self):
|
||||||
|
# Is it a pubkey file?
|
||||||
|
if os.path.isfile(os.path.abspath(os.path.expanduser(self.args['key']))):
|
||||||
|
with open(os.path.abspath(os.path.expanduser(self.args['key'])), 'r') as f:
|
||||||
|
self.args['key'] = f.read()
|
||||||
|
self.args['key'] = self.args['key'].strip()
|
||||||
|
|
||||||
|
|
||||||
|
def add(self, key, host, role):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def argParse():
|
||||||
|
args = argparse.ArgumentParser()
|
||||||
|
args.add_argument('-k',
|
||||||
|
'--key',
|
||||||
|
dest = 'key',
|
||||||
|
default = None,
|
||||||
|
type = 'str',
|
||||||
|
|
||||||
|
return(args)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args -
|
||||||
|
d = DBmgr(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
0
net/ssh/hostkeymanager/app/models.py
Normal file
0
net/ssh/hostkeymanager/app/models.py
Normal file
4
net/ssh/hostkeymanager/app/templates/about.html
Normal file
4
net/ssh/hostkeymanager/app/templates/about.html
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{% extends "base.html" %}{% block title %}r00t^2 SSH Key Repository || About{% endblock %}{% block body %}<div class="jumbotron">
|
||||||
|
<h1>About</h1></div>
|
||||||
|
<p>This is a tool to deliver SSH public keys (or, optionally, host keys) to SSH's authentication system in a safe and secure manner.</p>
|
||||||
|
{% endblock %}
|
35
net/ssh/hostkeymanager/app/templates/base.html
Normal file
35
net/ssh/hostkeymanager/app/templates/base.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
<!-- Bootstrap core CSS -->
|
||||||
|
<!-- Thanks, https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xii-facelift and
|
||||||
|
https://scotch.io/tutorials/getting-started-with-flask-a-python-microframework -->
|
||||||
|
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<!--<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">-->
|
||||||
|
<!-- Custom styles for this template -->
|
||||||
|
<link href="https://getbootstrap.com/examples/jumbotron-narrow/jumbotron-narrow.css" rel="stylesheet">
|
||||||
|
<!--<link href="https://getbootstrap.com/docs/4.0/examples/offcanvas/offcanvas.css" rel="stylesheet">-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header clearfix">
|
||||||
|
<nav>
|
||||||
|
<ul class="nav nav-pills pull-right">
|
||||||
|
<li role="presentation"><a href="/">Home</a></li>
|
||||||
|
<li role="presentation"><a href="/about">About</a></li>
|
||||||
|
<li role="presentation"><a href="/usage">Usage</a></li>
|
||||||
|
<!-- the following opens in a new tab/window/whatever. the line after opens in the same tab/window/etc. -->
|
||||||
|
<!-- <li role="presentation"><a href="https://square-r00t.net/" target="_blank">r00t^2</a></li> -->
|
||||||
|
<li role="presentation"><a href="https://square-r00t.net/">r00t^2</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
<footer class="footer">
|
||||||
|
<p><sub>The code for this page is released under the <a href="https://www.gnu.org/licenses/gpl-3.0.en.html#content">GPL 3.0 License</a>. It can be found <a href="https://git.square-r00t.net/OpTools/tree/net">here</a>.</sub></p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<!-- /container -->
|
||||||
|
</body>
|
||||||
|
</html>
|
38
net/ssh/hostkeymanager/app/templates/html.html
Normal file
38
net/ssh/hostkeymanager/app/templates/html.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<h2>Client/Browser Information</h2>
|
||||||
|
<p>This is information that your browser sends with its connection.</p>
|
||||||
|
<p>
|
||||||
|
<ul>
|
||||||
|
<li><b>Client IP:</b> <a href="https://ipinfo.io/{{ visitor['ip'] }}">{{ visitor['ip'] }}</a></li>
|
||||||
|
<li><b>Browser:</b> {{ '<a href="{0}">{1}</a>'.format(browsers[visitor['client']['browser']][0],
|
||||||
|
browsers[visitor['client']['browser']][1])|safe
|
||||||
|
if visitor['client']['browser'] in browsers.keys()
|
||||||
|
else visitor['client']['browser'].title()
|
||||||
|
if visitor['client']['browser'] is not none
|
||||||
|
else '(N/A)' }}</li>
|
||||||
|
<li><b>Language/Locale:</b> {{ visitor['client']['language'] or '(N/A)' }}</li>
|
||||||
|
{%- set alt_os = alts[visitor['client']['os']] if visitor['client']['os'] in alts.keys() else '' %}
|
||||||
|
<li><b>Operating System:</b> {{ '<a href="{0}">{1}</a>{2}'.format(os[visitor['client']['os']][0],
|
||||||
|
os[visitor['client']['os']][1],
|
||||||
|
alt_os)|safe
|
||||||
|
if visitor['client']['os'] in os.keys()
|
||||||
|
else visitor['client']['os'].title()
|
||||||
|
if visitor['client']['os'] is not none
|
||||||
|
else '(N/A)' }}</li>
|
||||||
|
<li><b>User Agent:</b> {{ visitor['client']['str'] }}</li>
|
||||||
|
<li><b>Version:</b> {{ visitor['client']['version'] or '(N/A)' }}</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<h2>Request Headers</h2>
|
||||||
|
<p>These are headers sent along with the request your browser sends for the page's content.</p>
|
||||||
|
<p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>{% for k in visitor['headers'].keys()|sort(case_sensitive = True) %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ k }}</td>
|
||||||
|
<td>{{ visitor['headers'][k] if visitor['headers'][k] != '' else '(N/A)' }}</td>
|
||||||
|
</tr>{% endfor %}
|
||||||
|
</table>
|
||||||
|
</p>
|
6
net/ssh/hostkeymanager/app/templates/index.html
Normal file
6
net/ssh/hostkeymanager/app/templates/index.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{% extends "base.html" %}{% block title %}r00t^2 Client Info Revealer{% endblock %}{% block body %}<div class="jumbotron">
|
||||||
|
<h1>Client Info Revealer</h1>
|
||||||
|
<p class="lead">A tool to reveal client-identifying data sent to webservers</p>
|
||||||
|
</div>
|
||||||
|
{% include 'html.html' if not params['json'] else 'json.html' %}
|
||||||
|
{% endblock %}
|
1
net/ssh/hostkeymanager/app/templates/json.html
Normal file
1
net/ssh/hostkeymanager/app/templates/json.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<pre>{{ json }}</pre>
|
51
net/ssh/hostkeymanager/app/templates/usage.html
Normal file
51
net/ssh/hostkeymanager/app/templates/usage.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{% extends "base.html" %}{% block title %}r00t^2 Client Info Revealer || Usage{% endblock %}{% block body %}<div class="jumbotron">
|
||||||
|
<h1>Usage</h1></div>
|
||||||
|
<h2>Parameters</h2>
|
||||||
|
<p>You can control how this page displays/renders. By default it will try to "guess" what you want; e.g. if you access it in Chrome, it will display this page but if you fetch via Curl, you'll get raw JSON. The following parameters control this behavior.</p>
|
||||||
|
<p><i><b>Note:</b> "Enabled" parameter values can be one of <b>y</b>, <b>yes</b>, <b>1</b>, or <b>true</b>. "Disabled" parameter values can be one of <b>n</b>, <b>no</b>, <b>0</b>, or <b>false</b>. The parameter names are case-sensitive but the values are not.</i></p>
|
||||||
|
<p><ul>
|
||||||
|
<li><b>json:</b> Force rendering in JSON format
|
||||||
|
<ul>
|
||||||
|
<li>It will display it nicely if you're in a browser, otherwise it will return raw/plaintext JSON.</li>
|
||||||
|
<li>Use <b>raw</b> if you want to force raw plaintext JSON output.</li>
|
||||||
|
</ul></li>
|
||||||
|
<li><b>html:</b> Force rendering in HTML
|
||||||
|
<ul>
|
||||||
|
<li>It will render HTML in clients that would normally render as JSON (e.g. curl, wget).</li>
|
||||||
|
</ul></li>
|
||||||
|
<li><b>raw:</b> Force output into a raw JSON string
|
||||||
|
<ul>
|
||||||
|
<li>Pure JSON instead of HTML or formatted JSON. This is suitable for API usages if your client is detected wrongly (or you just want to get the raw JSON).</li>
|
||||||
|
<li>Overrides all other tags.</li>
|
||||||
|
<li>Has no effect for clients that would normally render as JSON (curl, wget, etc.).</li>
|
||||||
|
</ul></li>
|
||||||
|
<li><b>tabs:</b> Indentation for JSON output
|
||||||
|
<ul>
|
||||||
|
<li>Accepts a positive integer.</li>
|
||||||
|
<li>Default is 4 for "desktop" browsers (if <b>json</b> is enabled), and no indentation otherwise.</li>
|
||||||
|
</ul></li>
|
||||||
|
</ul></p>
|
||||||
|
<h2>Examples</h2>{% set scheme = 'https' if request.is_secure else 'http'%}
|
||||||
|
<p><table>
|
||||||
|
<tr>
|
||||||
|
<th>URL</th>
|
||||||
|
<th>Behavior</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ scheme }}://{{ request.headers['host'] }}/">{{ scheme }}://{{ request.headers['host'] }}/</a></td>
|
||||||
|
<td>Displays HTML and "Human" formatting if in a graphical browser, otherwise returns a raw, unformatted JSON string.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ scheme }}://{{ request.headers['host'] }}/?raw=1">{{ scheme }}://{{ request.headers['host'] }}/?raw=1</a></td>
|
||||||
|
<td>Renders a raw, unformatted JSON string if in a graphical browser, otherwise no effect. All other parameters ignored (if in a graphical browser).</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ scheme }}://{{ request.headers['host'] }}/?html=1">{{ scheme }}://{{ request.headers['host'] }}/?html=1</a></td>
|
||||||
|
<td>Forces HTML rendering on non-graphical clients.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ scheme }}://{{ request.headers['host'] }}/?json=1&tabs=4">{{ scheme }}://{{ request.headers['host'] }}/?json=1&tabs=4</a></td>
|
||||||
|
<td>Returns JSON indented by 4 spaces for each level (you can leave "json=1" off if it's in a non-graphical browser, unless you specified "html=1").</td>
|
||||||
|
</tr>
|
||||||
|
</table></p>
|
||||||
|
{% endblock %}
|
57
net/ssh/hostkeymanager/app/views.py
Normal file
57
net/ssh/hostkeymanager/app/views.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import json
|
||||||
|
import re
|
||||||
|
from flask import render_template, make_response, request
|
||||||
|
from app import app
|
||||||
|
|
||||||
|
@app.route('/', methods = ['GET']) #@app.route('/')
|
||||||
|
def index():
|
||||||
|
hostkeys = None # TODO: hostkeys go here. dict?
|
||||||
|
# First we define interactive browsers
|
||||||
|
_intbrowsers = ['camino', 'chrome', 'firefox', 'galeon',
|
||||||
|
'kmeleon', 'konqueror', 'links', 'lynx']
|
||||||
|
# Then we set some parameter options for less typing later on.
|
||||||
|
_yes = ('y', 'yes', 'true', '1', True)
|
||||||
|
_no = ('y', 'no', 'false', '0', False, 'none')
|
||||||
|
# http://werkzeug.pocoo.org/docs/0.12/utils/#module-werkzeug.useragents
|
||||||
|
# We have to convert these to strings so we can do tuple comparisons on lower()s.
|
||||||
|
params = {'json': str(request.args.get('json')).lower(),
|
||||||
|
'html': str(request.args.get('html')).lower(),
|
||||||
|
'raw': str(request.args.get('raw')).lower()}
|
||||||
|
if request.user_agent.browser in _intbrowsers:
|
||||||
|
if params['html'] == 'none':
|
||||||
|
params['html'] = True
|
||||||
|
if params['json'] == 'none':
|
||||||
|
params['json'] = False
|
||||||
|
elif params['json'] in _yes:
|
||||||
|
params['json'] = True
|
||||||
|
for k in params.keys():
|
||||||
|
if params[k] in _no:
|
||||||
|
params[k] = False
|
||||||
|
else:
|
||||||
|
params[k] = True
|
||||||
|
# Set the tabs for JSON
|
||||||
|
try:
|
||||||
|
params['tabs'] = int(request.args.get('tabs'))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
if request.user_agent.browser in _intbrowsers or params['html']:
|
||||||
|
params['tabs'] = 4
|
||||||
|
else:
|
||||||
|
params['tabs'] = None
|
||||||
|
j = json.dumps(hostkeys, indent = params['tabs'])
|
||||||
|
if (request.user_agent.browser in _intbrowsers and params['html'] and not params['raw']) or \
|
||||||
|
(request.user_agent.browser not in _intbrowsers and params['html']):
|
||||||
|
return(render_template('index.html', hostkeys = hostkeys))
|
||||||
|
else:
|
||||||
|
if visitor['client']['browser'] in _intbrowsers.keys() and not params['raw']:
|
||||||
|
return(render_template('json.html',
|
||||||
|
json = j,
|
||||||
|
params = params))
|
||||||
|
return(j)
|
||||||
|
|
||||||
|
@app.route('/about', methods = ['GET'])
|
||||||
|
def about():
|
||||||
|
return(render_template('about.html'))
|
||||||
|
|
||||||
|
@app.route('/usage', methods = ['GET'])
|
||||||
|
def usage():
|
||||||
|
return(render_template('usage.html'))
|
8
net/ssh/hostkeymanager/config.py
Normal file
8
net/ssh/hostkeymanager/config.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# config.py
|
||||||
|
|
||||||
|
# Flask debugging - DISABLE FOR PRODUCTION ENVIRONMENTS
|
||||||
|
#DEBUG = True
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
# Path to your Sqlite3 DB
|
||||||
|
DB = '/var/local/db/optools/ssh_keys.sqlite3'
|
4
net/ssh/hostkeymanager/run.py
Normal file
4
net/ssh/hostkeymanager/run.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from app import app
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run()
|
18
net/ssh/hostkeymanager/uwsgi.ini
Normal file
18
net/ssh/hostkeymanager/uwsgi.ini
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[uwsgi]
|
||||||
|
plugin = python
|
||||||
|
py-autoreload = 1
|
||||||
|
#uid = http
|
||||||
|
#gid = http
|
||||||
|
socket = /run/uwsgi/netinfo.sock
|
||||||
|
chown-socket = http:http
|
||||||
|
processes = 4
|
||||||
|
master = 1
|
||||||
|
base = /usr/local/lib/optools/net/ssh
|
||||||
|
chdir = %(base)
|
||||||
|
#mount = /=%(base)/run.py
|
||||||
|
wsgi-file = %(base)/run.py
|
||||||
|
chmod-socket = 660
|
||||||
|
callable = app
|
||||||
|
cgi-helper =.py=python
|
||||||
|
logto = /var/log/uwsgi/%n.log
|
||||||
|
vacuum
|
Loading…
Reference in New Issue
Block a user