SSH - Legacy Devices

Intro

sometime, one have to access to old and legacy devices. they may do not support the current ciphers and key algorithms, so, we have to modify the “.ssh/config” File or provide some additional cli arguments.

If you have todo this regualary, you may wanna extend the current parameters with the legacy ones like this:

Backup old config

you never know ;)

mv /etc/ssh/ssh_config /etc/ssh/ssh_config-$(date "+%s")

Install Updated Version

you have to copy/paste as root

Apibench

Benchmark API’s

i like to work with fastapi. “FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints.”. i also know that scripting languages like python are ways slower than compiled languages like c, c++, rust, …

why not build a little “hello world” api, running it on localhost, and then do a benchmark …

Rust

let’s start with rust.

Code

src/main.rs

Knot

KNOT DNS

some information related to knot dns / knot-dnsutils. Tested with ‘knotd (Knot DNS), version 3.3.3’ running on OpenBSD 7.5.

Install Knot

pkg_add knot

Build Config

we’re configure this server as “slave” which get’s it’s config from a Primary Nameserver

# /etc/knot/knot.conf 

server:
    rundir: "/var/run/knot"
    user: _knot:_knot
    automatic-acl: on
    listen: [ xx.xx.xx.xx@53, xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx@53 ]

log:
  - target: syslog
    any: info

database:
    storage: "/var/db/knot"

key:
  - id: mykey
    algorithm: hmac-sha256
    secret: xXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx=

remote:
  - id: primary
    address: [ xx.xx.xx.xx@53, xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx@53 ] # IP Address of Primary Nameserver
    key: mykey

template:
  # default
  - id: default
    storage: "/var/db/knot"
    file: "%s.zone"
    global-module: mod-stats
    semantic-checks: on

zone:

  # Slave Zones
  - domain: your-domain.ch
    master: primary
  - domain: your-other-domain.de
    master: primary
  - domain: your-last-domain.com
    master: primary

enable and start service

rcctl enable knot
rcctl restart knot

zone backup

folder="/tmp/knot"
mkdir $folder
chown -R _knot $folder
knotc zone-backup +backupdir $folder

Build query File

cat ${folder}/zonefiles/stoege.net.zone |awk "{print \$1,\$3}" |grep -E "(NS|DS|A|AAAA|PTR|MX|SOA)$" |\
  sort -u -R > ${folder}/queries.txt 

StressTests (from a Debian Box)

apt install knot-dnsutils
cd /tmp
scp root@45.32.159.233:/tmp/knot/queries.txt .

5k Queries

kxdpgun -i queries.txt 45.32.159.233
using interface ens18, XDP threads 1, UDP, native mode
thread#00: sent 5010, received 5010
total queries:     5010 (1002 pps)
total replies:     5010 (1002 pps) (100%)
average DNS reply size: 63 B
average Ethernet reply rate: 842459 bps (0.84 Mbps)
responded NOERROR:   5010
duration: 5 s

100k Queries

time kxdpgun -t 20 -Q 5000 -i queries.txt -b 20 -p 8853 45.32.159.233
using interface ens18, XDP threads 1, UDP, native mode
thread#00: sent 100020, received 0
total queries:     100020 (5001 pps)
total replies:     0 (0 pps) (0%)
average DNS reply size: 0 B
average Ethernet reply rate: 24 bps (0.00 Mbps)
duration: 20 s

real	0m22.052s
user	0m0.092s
sys	0m0.183s

khost – Simple DNS lookup utility¶

# khost stoege.net
stoege.net. has IPv4 address 159.69.214.12
stoege.net. has IPv6 address 2a01:4f8:c0c:fff7::2
stoege.net. mail is handled by 10 ideo.noflow.ch.
# khost stoege.net -t SOA
stoege.net. start of authority is ns1.noflow.ch. hostmaster.noflow.ch. 2024052701 3600 900 1209600 1800

kdig – Advanced DNS lookup utility¶

# kdig stoege.net A    
;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 57426
;; Flags: qr rd ra; QUERY: 1; ANSWER: 1; AUTHORITY: 0; ADDITIONAL: 0

;; QUESTION SECTION:
;; stoege.net.         		IN	A

;; ANSWER SECTION:
stoege.net.         	1800	IN	A	159.69.214.12

;; Received 44 B
;; Time 2024-07-10 19:27:20 CEST
;; From 108.61.10.10@53(UDP) in 1.4 ms

short answer

# kdig +short stoege.net AAAA
2a01:4f8:c0c:fff7::2

output in json

# kdig +json stoege.net AAAA
{
  "dateString": "2024-07-10T19:28:01+0200",
  "dateSeconds": 1720632481,
  "msgLength": 56,
  "ID": 27609,
  "QR": 1,
  "Opcode": 0,
  "AA": 0,
  "TC": 0,
  "RD": 1,
  "RA": 1,
  "AD": 0,
  "CD": 0,
  "RCODE": 0,
  "QDCOUNT": 1,
  "ANCOUNT": 1,
  "NSCOUNT": 0,
  "ARCOUNT": 0,
  "QNAME": "stoege.net.",
  "QTYPE": 28,
  "QTYPEname": "AAAA",
  "QCLASS": 1,
  "QCLASSname": "IN",
  "answerRRs": [
    {
      "NAME": "stoege.net.",
      "TYPE": 28,
      "TYPEname": "AAAA",
      "CLASS": 1,
      "CLASSname": "IN",
      "TTL": 1800,
      "rdataAAAA": "2a01:4f8:c0c:fff7::2",
      "RDLENGTH": 16,
      "RDATAHEX": "2A0104F80C0CFFF70000000000000002"
    }
  ]
}

Any Comments ?

sha256: 4034db839fb307e487b0188f378a9bc142ededf7de783788811c270f126f03f5

Debian

Patch OpenSSH Only

apt install --only-upgrade  openssh-client openssh-server openssh-sftp-server

Time Zone

timedatectl set-timezone Europe/Zurich

-> set symlink: /etc/localtime -> ../usr/share/zoneinfo/Europe/Zurich

Fix Sudo Stuff

use ‘sudo -i’ and keep SSH_AUTH_SOCK if set

apt update
apt install sudo
usermod -aG sudo stoege
echo "Defaults env_keep+=SSH_AUTH_SOCK" > /etc/sudoers.d/ssh_auth_sock
echo "%sudo ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/group_sudo_nopass

Any Comments ?

sha256: 7e5018c28bea4357e2f6703ec2876e92798e020801a61e46d6e3698151bc5a38

Sound Selector MacOS

Sound Selector for MacOS

on MacOS, you can switch the input and output source on “system setting/sound”. i’d like todo this on the cli.

SwitchAudio

there is a litte tool called switchaudio. it can list, and also set the input/output device. let’s build a small wrapper around.

brew install switchaudio-osx

Usage

List Sound Devices

stoege@mac ~ % sound.sh

1: Externe Kopfhörer
2: Externes Mikrofon
3: Jabra Link 400
4: Mac mini-Lautsprecher
5: SRS-XB33
6: USB Audio Device

Set Sound Device

Mariadb

Install MariaDB on OpenBSD

Wanna install and Operate MariaDB on OpenBSD? Here a few hints …

Install Package

pkg_add mariadb-server mariadb-client
root@puffy /tmp# pkg_add mariadb-server                                                                                                                                    
quirks-7.14 signed on 2024-06-15T18:27:56Z
mariadb-server-10.9.8p0v1:lzo2-2.10p2: ok
mariadb-server-10.9.8p0v1:snappy-1.1.10p1: ok
mariadb-server-10.9.8p0v1:mariadb-client-10.9.8v1: ok
mariadb-server-10.9.8p0v1:p5-FreezeThaw-0.5001p0: ok
mariadb-server-10.9.8p0v1:p5-MLDBM-2.05p0: ok
mariadb-server-10.9.8p0v1:p5-Net-Daemon-0.49: ok
mariadb-server-10.9.8p0v1:p5-PlRPC-0.2020p0: ok
mariadb-server-10.9.8p0v1:p5-Math-Base-Convert-0.11p0: ok
mariadb-server-10.9.8p0v1:p5-Clone-0.46: ok
mariadb-server-10.9.8p0v1:p5-Module-Runtime-0.016p0: ok
mariadb-server-10.9.8p0v1:p5-Params-Util-1.102: ok
mariadb-server-10.9.8p0v1:p5-SQL-Statement-1.414: ok
mariadb-server-10.9.8p0v1:p5-DBI-1.643p0: ok
mariadb-server-10.9.8p0v1:p5-DBD-MariaDB-1.23: ok
mariadb-server-10.9.8p0v1:libxml-2.12.7: ok
mariadb-server-10.9.8p0v1: ok
Running tags: ok
The following new rcscripts were installed: /etc/rc.d/mysqld
See rcctl(8) for details.
New and changed readme(s):
	/usr/local/share/doc/pkg-readmes/mariadb-server

add MariaDB Tests

https://mariadb.com/kb/en/mariadb-test-overview/

SSHChat

SSH Chat

how to run your own SSH Chat Server

Setup

# add go
pkg_add go

# add user 'sshchat'
adduser

# switch user
su - sshchat
ftp https://github.com/shazow/ssh-chat/archive/v1.10.tar.gz
tar zxf v1.10.tar.gz
cd ssh-chat-1.10/
make build

# back to root
exit
cp /home/sshchat/ssh-chat-1.10/ssh-chat /usr/local/bin/

sshchat - ipfile

manage whiteliste ip in dedicated file

# create folder
mkdir -p /etc/pf.d

# sample file
echo "127.0.0.1" > /etc/pf.d/sshchat

# set permission
chmod 600 /etc/pf.d/sshchat

pf.conf

update pf.conf appropriate

PyProject 1

Sample PyProject from: https://github.com/volfpeter/motorhead/tree/main

PyProject

poetry init
poetry add motor pydantic
poetry add mkdocs-material mkdocstrings[python] mypy ruff poethepoet pytest pytest-asyncio pytest-random-order --group dev
[project]
name = "motorhead"
description = "Async MongoDB with vanilla Pydantic v2+ - made easy."
readme = "README.md"
license = { text = "MIT" }
authors = [
    { name = "Peter Volf", email = "do.volfp@gmail.com" },
]
requires-python = ">=3.10"
dependencies = ["pydantic", "motor"]
classifiers = [
    "Intended Audience :: Information Technology",
    "Operating System :: OS Independent",
    "Programming Language :: Python :: 3",
    "Development Status :: 4 - Beta",
    "Topic :: Internet",
    "Topic :: Software Development :: Libraries",
    "Topic :: Software Development",
    "Typing :: Typed",
    "Environment :: Web Environment",
    "Framework :: FastAPI",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Topic :: Internet :: WWW/HTTP",
]

[project.urls]
homepage = "https://github.com/volfpeter/motorhead"
documentation = "https://volfpeter.github.io/motorhead"
tracker = "https://github.com/volfpeter/motorhead/issues"

[tool.poetry]
name = "motorhead"
version = "0.2403.0"
description = "Async MongoDB with vanilla Pydantic v2+ - made easy."
authors = ["Peter Volf <do.volfp@gmail.com>"]
readme = "README.md"
packages = [{include = "motorhead"}]

[tool.poetry.dependencies]
python = "^3.10"
motor = "^3.1.0"
pydantic = "^2.1.0"

[tool.poetry.group.dev.dependencies]
mkdocs-material = "^9.5.19"
mkdocstrings = {extras = ["python"], version = "^0.25.0"}
mypy = "^1.10.0"
ruff = "^0.4.2"
poethepoet = "^0.26.0"
pytest = "^8.2.0"
pytest-asyncio = "^0.23.6"
pytest-docker = "^3.1.1"
pytest-random-order = "^1.1.1"

[tool.mypy]
strict = true
show_error_codes = true
exclude = ["tree_app"]

[[tool.mypy.overrides]]
module = ["motor.*"]
ignore_missing_imports = true

[tool.ruff]
line-length = 108
lint.exclude = [
    ".git",
    ".mypy_cache",
    ".pytest_cache",
    ".ruff_cache",
    ".venv",
    "dist",
    "docs",
]
lint.select = [
    "E",  # pycodestyle errors
    "W",  # pycodestyle warnings
    "F",  # pyflakes
    "I",  # isort
    "S",  # flake8-bandit - we must ignore these rules in tests
    "C",  # flake8-comprehensions
    "B",  # flake8-bugbear
]

[tool.ruff.lint.per-file-ignores]
"tests/**/*" = ["S101"]  # S101: use of assert detected

[tool.pytest.ini_options]
addopts = "--random-order"

[tool.poe.tasks]
serve-docs = "mkdocs serve"
check-format = "ruff format --check ."
lint = "ruff check ."
mypy = "mypy ."
format = "ruff format ."
lint-fix = "ruff . --fix"
test = "python -m pytest tests --random-order"

static-checks.sequence = ["lint", "check-format", "mypy"]
static-checks.ignore_fail = "return_non_zero"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Any Comments ?

sha256: 889b2e95d218175c3503451a55e2c17af3760750a9b2d6e52559b7f336bee043

Borgbackup

Prerequisite

  • you need a remote Borg Server (Unix/Linux Machine with Borg installed)
  • valid User and Key for SCP Transfer
  • SSH Key -> /backup/id_ed25519

Create Local Folder

test -d /backup || (mkdir /backup; chmod 700 /backup)

Borg Backup Script

cat << 'EOF2' > /backup/borg.sh
#!/usr/bin/env bash

# BorgBackup Script, v1.0, 2024-04-09, by @stoege

# Remote server details
REMOTE_USER="borguser"
REMOTE_HOST="your.remote.borg.server"
REMOTE_REPO="mysamplerepo"

# Local directory to backup
LOCAL_DIR="/"

# List of directories to exclude
EXCLUDE_DIRS=(
    "*/.cache/"
    "/tmp"
    "/restore"
)

# Set PassPhrase for the Backup Encryption (run: pwgen 32 1)
export BORG_PASSPHRASE="your-super-strong-password"
export BORG_RSH='ssh -i /backup/id_ed25519'


# Function to perform full backup
perform_full_backup() {

    # Construct exclude options
    exclude_opts=""
    for dir in "${EXCLUDE_DIRS[@]}"; do
        exclude_opts+="--exclude $dir "
    done

    # Run BorgBackup command
    borg create \
        --verbose \
        --stats \
        --progress \
        --compression lz4 \
        $REMOTE_USER@$REMOTE_HOST:$REMOTE_REPO::'{hostname}-{now:%Y-%m-%d_%H:%M:%S}' \
        $LOCAL_DIR \
        $exclude_opts \
    || borg init -e repokey-blake2 $REMOTE_USER@$REMOTE_HOST:$REMOTE_REPO
}

# Function to perform restore
perform_restore() {

    # Create Restore
    test -d /restore || mkdir /restore

    # Change Dir
    cd /restore
 
    # Run BorgBackup command to restore specific directory
    borg extract \
        --verbose \
        --progress \
        $REMOTE_USER@$REMOTE_HOST:$REMOTE_REPO::$1 \
        $2
}

# Function to list all backups
list_backups() {

    # Run BorgBackup command to list all archives
    borg list $REMOTE_USER@$REMOTE_HOST:$REMOTE_REPO

}


# Function to rotate backups
rotate_backups() {
    # Run BorgBackup command to prune archives based on retention policy
    borg prune \
        --verbose \
        --list \
        --glob-archives "${hostname}*" \
        --keep-hourly=2 \
        --keep-daily=2 \
        --keep-weekly=2 \
        --keep-monthly=2 \
        $REMOTE_USER@$REMOTE_HOST:$REMOTE_REPO
}

# Install on Host
install_myself() {

  # Folder
  f="/backup"

  # Create Directory, copy File
  test -d ${f} || (mkdir ${f}; chmod 700 ${f})
  cp $0 ${f}/
  
  # Inform User
  cat << EOF

# to install in Crontab:
crontab -e
5 6,12,18 * * * cd ${f}; $0 backup >> /var/log/borgbackup.log 2>&1

EOF

}

# Help
show_help() {
  echo "$0 [ backup | list | rotate | install | restore BACKUPNAME /etc ]"
  exit 1
}


# Main script
echo "Starting BorgBackup..."

# Check if BorgBackup is installed
if ! command -v borg &> /dev/null; then
    echo "Error: BorgBackup is not installed. Please install BorgBackup."
    exit 1
fi

# Check if parameter is provided
if [ $# -eq 0 ]; then
  show_help
fi

# Perform action based on parameter
case "$1" in
    "backup")
        echo "Performing full backup..."
        perform_full_backup
        rotate_backups
        ;;
    "restore")
        if [ $# -lt 3 ]; then
            echo "Error: Please specify a Backup Set and directory to restore."
            echo "$0 BACKUP_SET /FOLDER/TO/RESTORE"
            exit 1
        fi
        echo "Performing restore for directory '$3' on set '$2'"
        perform_restore $2 $3
        ;;
    "list")
        echo "Listing all backups..."
        list_backups
        ;;
    "rotate")
        echo "Rotating backups..."
        rotate_backups
        ;;
    "install")
        echo "Install Scripts"
        install_myself
        ;;
    *)
        show_help
        ;;
esac

# Check backup status
if [ $? -eq 0 ]; then
    echo "Action completed successfully."
else
    echo "Action failed."
fi

# Finally done
exit 0

EOF2
chmod 700 /backup/borg.sh

Execute It

Create Backup

/backup/borg.sh backup

List Backup

/backup/borg.sh list

Restore Folder

/backup/borg.sh restore hostname-date /etc"

Any Comments ?

sha256: 3adc039f17d2b87ef48b8e9d200c53675b430603c048d4879aacb2dabb3ce37f

Python MTR

Setup Project

Poetry

poetry init
poetry add twisted
poetry add twisted-mtr

Build Python Script

cat << 'EOF' > main.py
#!/usr/bin/env python3

'''
    An example usage for twisted-mtr which initiates multiple async traceroutes
    to multiple IPv4 and IPv6 target IP addresses at the same time. You will
    need to set your source IP addresses correctly and have a working dual
    IPv4/IPv6 networking stack to run this example.

'''

import sys
import signal
import logging
import ipaddress
from twisted.internet import reactor
from twisted_mtr import logger, errors, mtr, utils


log = logger.get_logger('trace', level=logging.DEBUG)


if __name__ == '__main__':

    log.info(f'Starting up...')

    # Find mtr-packet in your path
    mtr_binary_name = 'mtr-packet'
    mtr_binary_path = utils.find_binary(mtr_binary_name)

    # Replace with your local IPv4 address
    # Note that if you set this to an IP address not available on your system
    # your traceroutes will simply all time out
    local_ipv4 = ipaddress.IPv4Address('10.1.2.3')

    # Replace with your local IPv6 address
    # Note that if you set this to an IP address not available on your system
    # your traceroutes will simply all time out
    local_ipv6 = ipaddress.IPv6Address('2404:1:2:3:4:5:6:7')

    # Create the TraceRoute Twisted process object
    app_mtr = mtr.TraceRoute(
        mtr_binary_path=mtr_binary_path,
        local_ipv4=local_ipv4,
        local_ipv6=local_ipv6
    )

    # Bind to the Twisted tractor with the mtr-packet binary
    reactor.spawnProcess(app_mtr, mtr_binary_name, [mtr_binary_path], {})

    # Sets to track the traceroutes that have been dispatched and completed
    requested = set()
    completed = set()

    # Success callback
    def _test_traceroute_callback(timestamp, target_ip, protocol, port, hops):
        log.info(f'Completed traceroute started at {timestamp} to: '
                 f'{target_ip} ({protocol}:{port})')
        completed.add(str(target_ip))
        for (hop_num, hop_ip, microseconds) in hops:
            log.info(f' - {hop_num} {hop_ip} {microseconds}')
        if requested == completed:
            log.info('All traces complete, stopping reactor')
            reactor.stop()

    # Error callback
    def _test_trace_error(counter, joined_request, error, extra):
        log.error(f'Error running traceroute: {error}')
        reactor.stop()

    # Queue up our traceroutes
    target_ip = utils.parse_ip('8.1.1.1')  # No route after a few hops to test
    requested.add(str(target_ip))
    app_mtr.trace(_test_traceroute_callback, _test_trace_error, target_ip)

    target_ip = utils.parse_ip('8.8.8.8')
    requested.add(str(target_ip))
    app_mtr.trace(_test_traceroute_callback, _test_trace_error, target_ip, protocol='udp', port=53)

    target_ip = utils.parse_ip('1.1.1.1')
    requested.add(str(target_ip))
    app_mtr.trace(_test_traceroute_callback, _test_trace_error, target_ip, protocol='tcp', port=53, ttl=3)

    target_ip = utils.parse_ip('2404:6800:4015:802::200e')
    requested.add(str(target_ip))
    app_mtr.trace(_test_traceroute_callback, _test_trace_error, target_ip)

    target_ip = utils.parse_ip('2606:4700::6810:7b60')
    requested.add(str(target_ip))
    app_mtr.trace(_test_traceroute_callback, _test_trace_error, target_ip)

    # Polite hook for control+c to abort the traceroutes before they complete
    def signal_handler(sig, frame):
        sys.stdout.write('\n')  # put the ^C on its own line
        log.info(f'Caught keyboard interrupt, shutting down...')
        reactor.stop()

    signal.signal(signal.SIGINT, signal_handler)

    # Start the Twisted reactor event loop
    log.info(f'Starting event loop...')
    reactor.run()

    # If we reach here the reactor has been stopped, all done
    log.info(f'Goodbye')
EOF

don’t forget to set your local_ipv4 and local_ipv6 address correctly