adding aif scripts/config
This commit is contained in:
parent
2ab99f0f22
commit
e03be139ef
62
aif/cfgs/base.xml
Normal file
62
aif/cfgs/base.xml
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<aif xmlns:aif="https://aif.square-r00t.net"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://aif.square-r00t.net aif.xsd">
|
||||
<storage>
|
||||
<disk device="/dev/sda" diskfmt="gpt">
|
||||
<part num="1" start="0%" size="10%" fstype="ef00" />
|
||||
<part num="2" start="10%" size="100%" fstype="8300" />
|
||||
</disk>
|
||||
<mount source="/dev/sda2" target="/mnt/aif" order="1" />
|
||||
<mount source="/dev/sda1" target="/mnt/aif/boot" order="2" />
|
||||
</storage>
|
||||
<network hostname="aiftest.square-r00t.net">
|
||||
<iface device="auto" address="auto" netproto="ipv4" />
|
||||
</network>
|
||||
<system timezone="EST5EDT" locale="en_US.UTF-8" chrootpath="/mnt/aif" reboot="1">
|
||||
<users rootpass="!" />
|
||||
<service name="sshd" status="1" />
|
||||
<service name="cronie" status="1" />
|
||||
<service name="haveged" status="1" />
|
||||
</system>
|
||||
<pacman command="apacman -S">
|
||||
<repos>
|
||||
<repo name="core" enabled="true" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||
<repo name="extra" enabled="true" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||
<repo name="community" enabled="true" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||
<repo name="multilib" enabled="true" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||
<repo name="testing" enabled="false" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||
<repo name="multilib-testing" enabled="false" siglevel="default" mirror="file:///etc/pacman.d/mirrorlist" />
|
||||
<repo name="archlinuxfr" enabled="false" siglevel="Optional TrustedOnly" mirror="http://repo.archlinux.fr/$arch" />
|
||||
</repos>
|
||||
<mirrorlist>
|
||||
<mirror>http://mirror.us.leaseweb.net/archlinux/$repo/os/$arch</mirror>
|
||||
<mirror>http://mirrors.advancedhosters.com/archlinux/$repo/os/$arch</mirror>
|
||||
<mirror>http://ftp.osuosl.org/pub/archlinux/$repo/os/$arch</mirror>
|
||||
<mirror>http://arch.mirrors.ionfish.org/$repo/os/$arch</mirror>
|
||||
<mirror>http://mirrors.gigenet.com/archlinux/$repo/os/$arch</mirror>
|
||||
<mirror>http://mirror.jmu.edu/pub/archlinux/$repo/os/$arch</mirror>
|
||||
</mirrorlist>
|
||||
<software>
|
||||
<package name="sed" repo="core" />
|
||||
<package name="python" />
|
||||
<package name="openssh" />
|
||||
<package name="vim" />
|
||||
<package name="vim-plugins" />
|
||||
<package name="haveged" />
|
||||
<package name="byobu" />
|
||||
<package name="etc-update" />
|
||||
<package name="cronie" />
|
||||
<package name="mlocate" />
|
||||
<package name="mtree-git" />
|
||||
</software>
|
||||
</pacman>
|
||||
<bootloader type="grub" target="/boot" efi="true" />
|
||||
<scripts>
|
||||
<script uri="https://aif.square-r00t.net/cfgs/scripts/pkg/python.sh" order="1" execution="pkg" />
|
||||
<script uri="https://aif.square-r00t.net/cfgs/scripts/pkg/apacman.py" order="2" execution="pkg" />
|
||||
<script uri="https://aif.square-r00t.net/cfgs/scripts/post/sshsecure.py" order="1" execution="post" />
|
||||
<script uri="https://aif.square-r00t.net/cfgs/scripts/post/sshkeys.py" order="2" execution="post" />
|
||||
<script uri="https://aif.square-r00t.net/cfgs/scripts/post/configs.py" order="3" execution="post" />
|
||||
</scripts>
|
||||
</aif>
|
92
aif/scripts/pkg/apacman.py
Normal file
92
aif/scripts/pkg/apacman.py
Normal file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from urllib.request import urlopen
|
||||
|
||||
pkg = 'https://aif.square-r00t.net/cfgs/files/apacman.tar.xz'
|
||||
local_pkg = '/tmp/apacman.tar.xz'
|
||||
|
||||
conf_options = {}
|
||||
conf_options['apacman'] = {'enabled': ['needed', 'noconfirm', 'noedit', 'progress', 'purgebuild', 'skipcache', 'keepkeys'],
|
||||
'disabled': [],
|
||||
'values': {'tmpdir': '"/var/tmp/apacmantmp-$UID"'}}
|
||||
conf_options['pacman'] = {'enabled': [],
|
||||
'disabled': [],
|
||||
'values': {'UseSyslog': None, 'Color': None, 'TotalDownload': None, 'CheckSpace': None, 'VerbosePkgLists': None}}
|
||||
|
||||
def downloadPkg(pkgfile, dlfile):
|
||||
# Prep the destination
|
||||
os.makedirs(os.path.dirname(dlfile), exist_ok = True)
|
||||
# Download the pacman package
|
||||
with urlopen(pkgfile) as url:
|
||||
with open(dlfile, 'wb') as f:
|
||||
f.write(url.read())
|
||||
return()
|
||||
|
||||
def installPkg(pkgfile):
|
||||
# Install it
|
||||
subprocess.run(['pacman', '-Syyu']) # Installing from an inconsistent state is bad, mmkay?
|
||||
subprocess.run(['pacman', '--noconfirm', '--needed', '-S', 'base-devel'])
|
||||
subprocess.run(['pacman', '--noconfirm', '-Rdd', 'gcc']) # Required for multilib-devel
|
||||
subprocess.run(['pacman', '--noconfirm', '--needed', '-S', 'multilib-devel'])
|
||||
subprocess.run(['pacman', '--noconfirm', '--needed', '-U', pkgfile])
|
||||
return()
|
||||
|
||||
def configurePkg(opts, pkgr):
|
||||
cf = '/etc/{0}.conf'.format(pkgr)
|
||||
# Configure it
|
||||
shutil.copy2(cf, '{0}.bak.{1}'.format(cf, int(datetime.datetime.utcnow().timestamp())))
|
||||
with open(cf, 'r') as f:
|
||||
conf = f.readlines()
|
||||
for idx, line in enumerate(conf):
|
||||
l = line.split('=')
|
||||
opt = l[0].strip('\n').strip()
|
||||
if len(l) > 1:
|
||||
val = l[1].strip('\n').strip()
|
||||
# enabled options
|
||||
for o in opts['enabled']:
|
||||
if re.sub('^#?', '', opt).strip() == o:
|
||||
if pkgr == 'apacman':
|
||||
conf[idx] = '{0}=1\n'.format(o)
|
||||
elif pkgr == 'pacman':
|
||||
conf[idx] = '{0}\n'.format(o)
|
||||
# disabled options
|
||||
for o in opts['disabled']:
|
||||
if re.sub('^#?', '', opt).strip() == o:
|
||||
if pkgr == 'apacman':
|
||||
conf[idx] = '{0}=0\n'.format(o)
|
||||
elif pkgr == 'pacman':
|
||||
conf[idx] = '#{0}\n'.format(o)
|
||||
# values
|
||||
for o in opts['values']:
|
||||
if opts['values'][o] is not None:
|
||||
if re.sub('^#?', '', opt).strip() == o:
|
||||
if pkgr == 'apacman':
|
||||
conf[idx] = '{0}={1}\n'.format(o, opts['values'][o])
|
||||
elif pkgr == 'pacman':
|
||||
conf[idx] = '{0} = {1}\n'.format(o, opts['values'][o])
|
||||
else:
|
||||
if re.sub('^#?', '', opt).strip() == o:
|
||||
conf[idx] = '{0}\n'.format(o)
|
||||
with open(cf, 'w') as f:
|
||||
f.write(''.join(conf))
|
||||
|
||||
def finishPkg():
|
||||
# Finish installing (optional deps)
|
||||
for p in ('apacman-deps', 'apacman-utils', 'git', 'customizepkg-scripting', 'pkgfile', 'rsync'):
|
||||
subprocess.run(['apacman', '--noconfirm', '--needed', '-S', p])
|
||||
|
||||
def main():
|
||||
downloadPkg(pkg, local_pkg)
|
||||
installPkg(local_pkg)
|
||||
for tool in ('pacman', 'apacman'):
|
||||
configurePkg(conf_options[tool], tool)
|
||||
finishPkg()
|
||||
return()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
3
aif/scripts/pkg/python.sh
Normal file
3
aif/scripts/pkg/python.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
pacman --needed --noconfirm -S python
|
136
aif/scripts/post/configs.py
Normal file
136
aif/scripts/post/configs.py
Normal file
@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import pwd
|
||||
import subprocess
|
||||
|
||||
def byobu(user = 'root'):
|
||||
homedir = os.path.expanduser('~{0}'.format(user))
|
||||
subprocess.run(['byobu-enable'])
|
||||
b = '{0}/.byobu'.format(homedir)
|
||||
# The keybindings, and general enabling
|
||||
confs = {'backend': 'BYOBU_BACKEND=tmux\n',
|
||||
'color': 'BACKGROUND=k\nFOREGROUND=w\nMONOCHROME=0', # NOT a typo; the original source I got this from had no end newline.
|
||||
'color.tmux': 'BYOBU_DARK="\#333333"\nBYOBU_LIGHT="\#EEEEEE"\nBYOBU_ACCENT="\#75507B"\nBYOBU_HIGHLIGHT="\#DD4814"\n',
|
||||
'datetime.tmux': 'BYOBU_DATE="%Y-%m-%d "\nBYOBU_TIME="%H:%M:%S"\n',
|
||||
'keybindings': 'source $BYOBU_PREFIX/share/byobu/keybindings/common\n',
|
||||
'keybindings.tmux': 'unbind-key -n C-a\nset -g prefix ^A\nset -g prefix2 ^A\nbind a send-prefix\n',
|
||||
'profile': 'source $BYOBU_PREFIX/share/byobu/profiles/common\n',
|
||||
'profile.tmux': 'source $BYOBU_PREFIX/share/byobu/profiles/tmux\n',
|
||||
'prompt': '[ -r /usr/share/byobu/profiles/bashrc ] && . /usr/share/byobu/profiles/bashrc #byobu-prompt#\n',
|
||||
'.screenrc': None,
|
||||
'.tmux.conf': None,
|
||||
'.welcome-displayed': None,
|
||||
'windows': None,
|
||||
'windows.tmux': None}
|
||||
for c in confs.keys():
|
||||
with open('{0}/{1}'.format(b, c), 'w') as f:
|
||||
if confs[c] is not None:
|
||||
f.write(confs[c])
|
||||
else:
|
||||
f.write('')
|
||||
# The status file- add some extras, and remove the session string which is broken apparently.
|
||||
# Holy shit I wish there was a way of storing compressed text in plaintext besides base64.
|
||||
statusconf = ["# status - Byobu's default status enabled/disabled settings\n", '#\n', '# Override these in $BYOBU_CONFIG_DIR/status\n',
|
||||
'# where BYOBU_CONFIG_DIR is XDG_CONFIG_HOME if defined,\n', '# and $HOME/.byobu otherwise.\n', '#\n',
|
||||
'# Copyright (C) 2009-2011 Canonical Ltd.\n', '#\n', '# Authors: Dustin Kirkland <kirkland@byobu.org>\n', '#\n',
|
||||
'# This program is free software: you can redistribute it and/or modify\n', '# it under the terms of the GNU ' +
|
||||
'General Public License as published by\n', '# the Free Software Foundation, version 3 of the License.\n', '#\n',
|
||||
'# This program is distributed in the hope that it will be useful,\n', '# but WITHOUT ANY WARRANTY; without even the ' +
|
||||
'implied warranty of\n', '# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n', '# GNU General Public License ' +
|
||||
'for more details.\n', '#\n', '# You should have received a copy of the GNU General Public License\n', '# along with this ' +
|
||||
'program. If not, see <http://www.gnu.org/licenses/>.\n', '\n', "# Status beginning with '#' are disabled.\n", '\n', '# Screen has ' +
|
||||
'two status lines, with 4 quadrants for status\n', 'screen_upper_left="color"\n', 'screen_upper_right="color whoami hostname ' +
|
||||
'ip_address menu"\n', 'screen_lower_left="color logo distro release #arch session"\n', 'screen_lower_right="color network #disk_io ' +
|
||||
'custom #entropy raid reboot_required updates_available #apport #services #mail users uptime #ec2_cost #rcs_cost #fan_speed #cpu_temp ' +
|
||||
'battery wifi_quality #processes load_average cpu_count cpu_freq memory #swap disk #time_utc date time"\n', '\n', '# Tmux has one ' +
|
||||
'status line, with 2 halves for status\n', 'tmux_left=" logo #distro release arch #session"\n', '# You can have as many tmux right ' +
|
||||
'lines below here, and cycle through them using Shift-F5\n', 'tmux_right=" network disk_io #custom #entropy raid reboot_required ' +
|
||||
'#updates_available #apport services #mail #users uptime #ec2_cost #rcs_cost #fan_speed #cpu_temp #battery #wifi_quality processes ' +
|
||||
'load_average cpu_count cpu_freq memory #swap disk whoami hostname ip_address time_utc date time"\n', '#tmux_right="network ' +
|
||||
'#disk_io #custom entropy raid reboot_required updates_available #apport #services #mail users uptime #ec2_cost #rcs_cost fan_speed ' +
|
||||
'cpu_temp battery wifi_quality #processes load_average cpu_count cpu_freq memory #swap #disk whoami hostname ip_address #time_utc ' +
|
||||
'date time"\n', '#tmux_right="network #disk_io custom #entropy raid reboot_required updates_available #apport #services #mail users ' +
|
||||
'uptime #ec2_cost #rcs_cost #fan_speed #cpu_temp battery wifi_quality #processes load_average cpu_count cpu_freq memory #swap #disk ' +
|
||||
'#whoami #hostname ip_address #time_utc date time"\n', '#tmux_right="#network disk_io #custom entropy #raid #reboot_required ' +
|
||||
'#updates_available #apport #services #mail #users #uptime #ec2_cost #rcs_cost fan_speed cpu_temp #battery #wifi_quality #processes ' +
|
||||
'#load_average #cpu_count #cpu_freq #memory #swap whoami hostname ip_address #time_utc disk date time"\n']
|
||||
with open('{0}/status'.format(b), 'w') as f:
|
||||
f.write(''.join(statusconf))
|
||||
# The statusrc file is another lengthy one.
|
||||
statusrc = ["# statusrc - Byobu's default status configurations\n", '#\n', '# Override these in $BYOBU_CONFIG_DIR/statusrc\n',
|
||||
'# where BYOBU_CONFIG_DIR is XDG_CONFIG_HOME if defined,\n', '# and $HOME/.byobu otherwise.\n', '#\n', '# Copyright (C) ' +
|
||||
'2009-2011 Canonical Ltd.\n', '#\n', '# Authors: Dustin Kirkland <kirkland@byobu.org>\n', '#\n', '# This program is free software: ' +
|
||||
'you can redistribute it and/or modify\n', '# it under the terms of the GNU General Public License as published by\n',
|
||||
'# the Free Software Foundation, version 3 of the License.\n', '#\n', '# This program is distributed in the hope that it will be ' +
|
||||
'useful,\n', '# but WITHOUT ANY WARRANTY; without even the implied warranty of\n', '# MERCHANTABILITY or FITNESS FOR A PARTICULAR ' +
|
||||
'PURPOSE. See the\n', '# GNU General Public License for more details.\n', '#\n', '# You should have received a copy of the GNU ' +
|
||||
'General Public License\n', '# along with this program. If not, see <http://www.gnu.org/licenses/>.\n', '\n', '# Configurations that ' +
|
||||
'you can override; if you leave these commented out,\n', '# Byobu will try to auto-detect them.\n', '\n', '# This should be auto-detected ' +
|
||||
'for most distro, but setting it here will save\n', '# some call to lsb_release and the like.\n', '#BYOBU_DISTRO=Ubuntu\n', '\n',
|
||||
'# Default: depends on the distro (which is either auto-detected, either set\n', '# via $DISTRO)\n', '#LOGO="\\o/"\n', '\n', '# Abbreviate ' +
|
||||
'the release to N characters\n', '# By default, this is disabled. But if you set RELEASE_ABBREVIATED=1\n', '# and your lsb_release is ' +
|
||||
'"precise", only "p" will be displayed\n', '#RELEASE_ABBREVIATED=1\n', '\n', '# Default: /\n', '#MONITORED_DISK=/\n', '\n', '# Minimum ' +
|
||||
'disk throughput that triggers the notification (in kB/s)\n', '# Default: 50\n', '#DISK_IO_THRESHOLD=50\n', '\n', '# Default: eth0\n',
|
||||
'#MONITORED_NETWORK=eth0\n', '\n', '# Unit used for network throughput (either bits per second or bytes per second)\n', '# Default: ' +
|
||||
'bits\n', '#NETWORK_UNITS=bytes\n', '\n', '# Minimum network throughput that triggers the notification (in kbit/s)\n', '# Default: 20\n',
|
||||
'#NETWORK_THRESHOLD=20\n', '\n', '# You can add an additional source of temperature here\n', '#MONITORED_TEMP=/proc/acpi/thermal_zone/' +
|
||||
'THM0/temperature\n', '\n', '# Default: C\n', '#TEMP=F\n', '\n', '#SERVICES="eucalyptus-nc|NC eucalyptus-cloud|CLC eucalyptus-walrus ' +
|
||||
'eucalyptus-cc|CC eucalyptus-sc|SC"\n', '\n', '#FAN=$(find /sys -type f -name fan1_input | head -n1)\n', '\n', '# You can set this to 1 ' +
|
||||
'to report your external/public ip address\n', '# Default: 0\n', '#IP_EXTERNAL=0\n', '\n', '# The users notification normally counts ssh ' +
|
||||
"sessions; set this configuration to '1'\n", '# to instead count number of distinct users logged onto the system\n', '# Default: 0\n',
|
||||
'#USERS_DISTINCT=0\n', '\n', '# Set this to zero to hide seconds int the time display\n', '# Default 1\n', '#TIME_SECONDS=0\n']
|
||||
with open('{0}/statusrc'.format(b), 'w') as f:
|
||||
f.write(''.join(statusrc))
|
||||
setPerms(user, b)
|
||||
return()
|
||||
|
||||
def vim():
|
||||
vimc = ['\n', 'set nocompatible\n', 'set number\n', 'syntax on\n', 'set paste\n', 'set ruler\n', 'if has("autocmd")\n',' au BufReadPost * if ' +
|
||||
'line("\'\\"") > 1 && line("\'\\"") <= line("$") | exe "normal! g\'\\"" | endif\n', 'endif\n', '\n', '" bind F3 to insert a timestamp.\n', '" In ' +
|
||||
'normal mode, insert.\n', 'nmap <F3> i<C-R>=strftime("%c")<CR><Esc>\n', '\n', 'set pastetoggle=<F2>\n', '\n', '" https://stackoverflow.com/' +
|
||||
'questions/27771616/turn-off-all-automatic-code-complete-in-jedi-vim\n', 'let g:jedi#completions_enabled = 0\n', 'let g:jedi#show_call_' +
|
||||
'signatures = "0"\n']
|
||||
with open('/etc/vimrc', 'a') as f:
|
||||
f.write(''.join(vimc))
|
||||
setPerms('root', '/etc/vimrc')
|
||||
return()
|
||||
|
||||
def bash():
|
||||
bashc = ['\n', 'alias vi=/usr/bin/vim\n', 'export EDITOR=vim\n', '\n', 'if [ -f ~/.bashrc ];\n', 'then\n', ' source ~/.bashrc\n', 'fi \n',
|
||||
'if [ -d ~/bin ];\n', 'then\n', ' export PATH="$PATH:~/bin"\n', 'fi\n', '\n', 'alias grep="grep --color"\n',
|
||||
'alias egrep="egrep --color"\n', '\n', 'alias ls="ls --color=auto"\n', 'alias vi="/usr/bin/vim"\n', '\n', 'export HISTTIMEFORMAT="%F %T "\n',
|
||||
'export PATH="${PATH}:/sbin:/bin:/usr/sbin"\n']
|
||||
with open('/etc/bash.bashrc', 'a') as f:
|
||||
f.write(''.join(bashc))
|
||||
setPerms('root', '/etc/bash.bashrc')
|
||||
return()
|
||||
|
||||
def mlocate():
|
||||
subprocess.run(['updatedb'])
|
||||
return()
|
||||
|
||||
def setPerms(user, path):
|
||||
uid = pwd.getpwnam(user).pw_uid
|
||||
gid = pwd.getpwnam(user).pw_gid
|
||||
pl = pathlib.PurePath(path).parts
|
||||
for basedir, dirs, files in os.walk(path):
|
||||
os.chown(basedir, uid, gid)
|
||||
if os.path.isdir(basedir):
|
||||
os.chmod(basedir, 0o755)
|
||||
elif os.path.isfile(basedir):
|
||||
os.chmod(basedir, 0o644)
|
||||
for f in files:
|
||||
os.chown(os.path.join(basedir, f), uid, gid)
|
||||
os.chmod(os.path.join(basedir, f), 0o644)
|
||||
return()
|
||||
|
||||
def main():
|
||||
byobu()
|
||||
vim()
|
||||
bash()
|
||||
mlocate()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
31
aif/scripts/post/sshkeys.py
Normal file
31
aif/scripts/post/sshkeys.py
Normal file
@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import pwd
|
||||
from urllib.request import urlopen
|
||||
|
||||
keysfile = 'https://square-r00t.net/ssh/all'
|
||||
|
||||
def copyKeys(keystring, user = 'root'):
|
||||
uid = pwd.getpwnam(user).pw_uid
|
||||
gid = pwd.getpwnam(user).pw_gid
|
||||
homedir = os.path.expanduser('~{0}'.format(user))
|
||||
sshdir = '{0}/.ssh'.format(homedir)
|
||||
authfile = '{0}/authorized_keys'.format(sshdir)
|
||||
with open(authfile, 'a') as f:
|
||||
f.write(keystring)
|
||||
os.makedirs(sshdir, mode = 0o700, exist_ok = True)
|
||||
for basedir, dirs, files in os.walk(sshdir):
|
||||
os.chown(basedir, uid, gid)
|
||||
os.chmod(basedir, 0o700)
|
||||
for f in files:
|
||||
os.chown(os.path.join(basedir, f), uid, gid)
|
||||
os.chmod(os.path.join(basedir, f), 0o600)
|
||||
return()
|
||||
|
||||
def main():
|
||||
with urlopen(keysfile) as keys:
|
||||
copyKeys(keys.read().decode('utf-8'))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
188
aif/scripts/post/sshsecure.py
Normal file
188
aif/scripts/post/sshsecure.py
Normal file
@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Pythonized automated way of running https://sysadministrivia.com/news/hardening-ssh-security
|
||||
|
||||
import datetime
|
||||
import glob
|
||||
import os
|
||||
import pwd
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
conf_options = {}
|
||||
conf_options['sshd'] = {'KexAlgorithms': 'curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256',
|
||||
'Protocol': '2',
|
||||
'HostKey': ['/etc/ssh/ssh_host_ed25519_key',
|
||||
'/etc/ssh/ssh_host_rsa_key'],
|
||||
'PermitRootLogin': 'prohibit-password',
|
||||
'PasswordAuthentication': 'no',
|
||||
'ChallengeResponseAuthentication': 'no',
|
||||
'PubkeyAuthentication': 'yes',
|
||||
'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,hmac-ripemd160-etm@openssh.com,' +
|
||||
'umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,hmac-ripemd160,umac-128@openssh.com'}
|
||||
# Uncomment if this is further configured
|
||||
#conf_options['sshd']['AllowGroups'] = 'ssh-user'
|
||||
|
||||
conf_options['ssh'] = {'Host': {'*': {'KexAlgorithms': 'curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256',
|
||||
'PubkeyAuthentication': 'yes',
|
||||
'HostKeyAlgorithms': 'ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ssh-ed25519,ssh-rsa'}}}
|
||||
# Uncomment below if Github still needs diffie-hellman-group-exchange-sha1 sometimes.
|
||||
#conf_options['ssh']['Host']['github.com'] = {'KexAlgorithms': 'curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256,' +
|
||||
# 'diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1'}
|
||||
|
||||
|
||||
def hostKeys(buildmoduli):
|
||||
# Starting haveged should help lessen the time load, but not much.
|
||||
if os.path.lexists('/usr/bin/haveged'):
|
||||
# We could use psutil here, but then that's a python dependency we don't need.
|
||||
# We could parse the /proc directory, but that's quite unnecessary. pgrep's installed by default on Arch.
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
if subprocess.run(['pgrep', 'haveged'], stdout = devnull).returncode != 0:
|
||||
subprocess.run(['haveged'], stdout = devnull)
|
||||
#Warning: The moduli stuff takes a LONG time to run. Hours.
|
||||
if buildmoduli:
|
||||
subprocess.run(['ssh-keygen', '-G', '/etc/ssh/moduli.all', '-b', '4096', '-q'])
|
||||
subprocess.run(['ssh-keygen', '-T', '/etc/ssh/moduli.safe', '-f', '/etc/ssh/moduli.all', '-q'])
|
||||
if os.path.lexists('/etc/ssh/moduli'):
|
||||
os.rename('/etc/ssh/moduli', '/etc/ssh/moduli.old')
|
||||
os.rename('/etc/ssh/moduli.safe', '/etc/ssh/moduli')
|
||||
os.remove('/etc/ssh/moduli.all')
|
||||
for suffix in ('', '.pub'):
|
||||
for k in glob.glob('/etc/ssh/ssh_host_*key{0}'.format(suffix)):
|
||||
os.rename(k, '{0}.old.{1}'.format(k, int(datetime.datetime.utcnow().timestamp())))
|
||||
subprocess.run(['ssh-keygen', '-t', 'ed25519', '-f', '/etc/ssh/ssh_host_ed25519_key', '-q', '-N', ''])
|
||||
subprocess.run(['ssh-keygen', '-t', 'rsa', '-b', '4096', '-f', '/etc/ssh/ssh_host_rsa_key', '-q', '-N', ''])
|
||||
# We currently don't use this, but for simplicity's sake let's return the host keys.
|
||||
hostkeys = {}
|
||||
for k in ('ed25519', 'rsa'):
|
||||
with open('/etc/ssh/ssh_host_{0}_key.pub'.format(k), 'r') as f:
|
||||
hostkeys[k] = f.read()
|
||||
return(hostkeys)
|
||||
|
||||
def config(opts, t):
|
||||
special = {'sshd': {}, 'ssh': {}}
|
||||
# We need to handle these directives a little differently...
|
||||
special['sshd']['opts'] = ['Match']
|
||||
special['sshd']['filters'] = ['User', 'Group', 'Host', 'LocalAddress', 'LocalPort', 'Address']
|
||||
# These are arguments supported by each of the special options. We'll use this to verify entries.
|
||||
special['sshd']['args'] = ['AcceptEnv', 'AllowAgentForwarding', 'AllowGroups', 'AllowStreamLocalForwarding', 'AllowTcpForwarding',
|
||||
'AllowUsers', 'AuthenticationMethods', 'AuthorizedKeysCommand', 'AuthorizedKeysCommandUser',
|
||||
'AuthorizedKeysFile', 'AuthorizedPrincipalsCommand', 'AuthorizedPrincipalsCommandUser', 'AuthorizedPrincipalsFile',
|
||||
'Banner', 'ChrootDirectory', 'ClientAliveCountMax', 'ClientAliveInterval', 'DenyGroups', 'DenyUsers', 'ForceCommand',
|
||||
'GatewayPorts', 'GSSAPIAuthentication', 'HostbasedAcceptedKeyTypes', 'HostbasedAuthentication',
|
||||
'HostbasedUsesNameFromPacketOnly', 'IPQoS', 'KbdInteractiveAuthentication', 'KerberosAuthentication', 'MaxAuthTries',
|
||||
'MaxSessions', 'PasswordAuthentication', 'PermitEmptyPasswords', 'PermitOpen', 'PermitRootLogin', 'PermitTTY',
|
||||
'PermitTunnel', 'PermitUserRC', 'PubkeyAcceptedKeyTypes', 'PubkeyAuthentication', 'RekeyLimit', 'RevokedKeys',
|
||||
'StreamLocalBindMask', 'StreamLocalBindUnlink', 'TrustedUserCAKeys', 'X11DisplayOffset', 'X11Forwarding', 'X11UseLocalHost']
|
||||
special['ssh']['opts'] = ['Host', 'Match']
|
||||
special['ssh']['args'] = ['canonical', 'exec', 'host', 'originalhost', 'user', 'localuser']
|
||||
cf = '/etc/ssh/{0}_config'.format(t)
|
||||
shutil.copy2(cf, '{0}.bak.{1}'.format(cf, int(datetime.datetime.utcnow().timestamp())))
|
||||
with open(cf, 'r') as f:
|
||||
conf = f.readlines()
|
||||
conf.append('\n\n# Added per https://sysadministrivia.com/news/hardening-ssh-security\n\n')
|
||||
confopts = []
|
||||
# Get an index of directives pre-existing in the config file.
|
||||
for line in conf[:]:
|
||||
opt = line.split()
|
||||
if opt:
|
||||
if not re.match('^(#.*|\s+.*)$', opt[0]):
|
||||
confopts.append(opt[0])
|
||||
#print(confopts)
|
||||
# We also need to modify the config file- comment out starting with the first occurrence of the
|
||||
# specopts, if it exists. This is why we make a backup.
|
||||
commentidx = None
|
||||
for idx, i in enumerate(conf):
|
||||
if re.match('^({0})\s+.*$'.format('|'.join(special[t]['opts'])), i):
|
||||
commentidx = idx
|
||||
break
|
||||
if commentidx is not None:
|
||||
idx = commentidx
|
||||
while idx <= (len(conf) - 1):
|
||||
conf[idx] = '#{0}'.format(conf[idx])
|
||||
idx += 1
|
||||
# Now we actually start replacing/adding some major configuration.
|
||||
for o in opts.keys():
|
||||
if o in special[t]['opts'] or isinstance(opts[o], dict):
|
||||
# We need to put these at the bottom of the file due to how they're handled by sshd's config parsing.
|
||||
continue
|
||||
# We handle these a little specially too- they're for multiple lines sharing the same directive.
|
||||
# Since the config should be explicit, we remove any existing entries specified that we find.
|
||||
else:
|
||||
if o in confopts:
|
||||
#print('commenting out old {0}'.format(o))
|
||||
# If I was more worried about recursion, or if I was appending here, I should use conf[:].
|
||||
# But I'm not. So I won't.
|
||||
for idx, opt in enumerate(conf):
|
||||
if re.match('^{0}(\s.*)?\n$'.format(o), opt):
|
||||
#l = opt.split()
|
||||
#conf[idx] = '#{0} {1}'.format(l[0].strip, l[1].strip)
|
||||
#print('old {0}: {1}'.format(o, conf[idx]))
|
||||
conf[idx] = '#{0}'.format(opt)
|
||||
#print('new {0}: {1}'.format(o, conf[idx]))
|
||||
# Here we handle the "multiple-specifying" options- notably, HostKey.
|
||||
if isinstance(opts[o], list):
|
||||
for l in opts[o]:
|
||||
if l is not None:
|
||||
conf.append('{0} {1}\n'.format(o, l))
|
||||
else:
|
||||
conf.append('{0}\n'.format(o))
|
||||
else:
|
||||
# So it isn't something we explicitly save until the end (such as a Match or Host),
|
||||
# and it isn't something that's specified multiple times.
|
||||
if opts[o] is not None:
|
||||
conf.append('{0} {1}\n'.format(o, opts[o]))
|
||||
else:
|
||||
conf.append('{0}\n'.format(o))
|
||||
# NOW we can add the Host/Match/etc. directives.
|
||||
for o in opts.keys():
|
||||
if isinstance(opts[o], dict):
|
||||
for k in opts[o].keys():
|
||||
conf.append('{0} {1}\n'.format(o, k))
|
||||
for l in opts[o][k].keys():
|
||||
if opts[o][k][l] is not None:
|
||||
conf.append('\t{0} {1}\n'.format(l, opts[o][k][l]))
|
||||
else:
|
||||
conf.append('\t{0}\n'.format(l))
|
||||
with open(cf, 'w') as f:
|
||||
f.write(''.join(conf))
|
||||
return()
|
||||
|
||||
def clientKeys(user = 'root'):
|
||||
uid = pwd.getpwnam(user).pw_uid
|
||||
gid = pwd.getpwnam(user).pw_gid
|
||||
homedir = os.path.expanduser('~{0}'.format(user))
|
||||
sshdir = '{0}/.ssh'.format(homedir)
|
||||
os.makedirs(sshdir, mode = 0o700, exist_ok = True)
|
||||
if not os.path.lexists('{0}/id_ed25519'.format(sshdir)) and not os.path.lexists('{0}/id_ed25519.pub'.format(sshdir)):
|
||||
subprocess.run(['ssh-keygen', '-t', 'ed25519', '-o', '-a', '100',
|
||||
'-f', '{0}/id_ed25519'.format(sshdir), '-q', '-N', ''])
|
||||
if not os.path.lexists('{0}/id_rsa'.format(sshdir)) and not os.path.lexists('{0}/id_rsa.pub'.format(sshdir)):
|
||||
subprocess.run(['ssh-keygen', '-t', 'rsa', '-b', '4096', '-o', '-a', '100',
|
||||
'-f', '{0}/id_rsa'.format(sshdir), '-q', '-N', ''])
|
||||
for basedir, dirs, files in os.walk(sshdir):
|
||||
os.chown(basedir, uid, gid)
|
||||
os.chmod(basedir, 0o700)
|
||||
for f in files:
|
||||
os.chown(os.path.join(basedir, f), uid, gid)
|
||||
os.chmod(os.path.join(basedir, f), 0o600)
|
||||
if 'pubkeys' not in globals():
|
||||
pubkeys = {}
|
||||
pubkeys[user] = {}
|
||||
for k in ('ed25519', 'rsa'):
|
||||
with open('{0}/id_{1}.pub'.format(sshdir, k), 'r') as f:
|
||||
pubkeys[user][k] = f.read()
|
||||
return(pubkeys)
|
||||
|
||||
def main():
|
||||
#Warning: The moduli stuff takes a LONG time to run. Hours.
|
||||
buildmoduli = True
|
||||
hostKeys(buildmoduli)
|
||||
for t in ('sshd', 'ssh'):
|
||||
config(conf_options[t], t)
|
||||
clientKeys()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in New Issue
Block a user