commit 3ddbd81e687705e8ca9493c97dddb6795e4a4f25 Author: Felix Schueren Date: Tue Nov 4 21:10:31 2025 +0100 initial diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..51fb87e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM debian:latest +RUN apt update && apt --yes install openssh-server python3 sudo iproute2 iputils-ping traceroute vim && mkdir -p /root/.ssh/ && mkdir -p /run/sshd && rm -f /etc/ssh/ssh_host* +RUN apt --yes install dnsmasq udhcpc less tcpdump +RUN apt --yes install telnet netcat-openbsd wget curl man net-tools +# RUN apt install --yes nginx python3-pip +# RUN pip3 install 'uvicorn[standard] fastapi' + +# COPY authorized_keys /root/.ssh/authorized_keys +# COPY setup-linux.sh /usr/local/bin/sshd_with_setup.sh +# CMD ["/usr/sbin/sshd", "-D"] +CMD ["/usr/local/bin/setup.sh"] \ No newline at end of file diff --git a/config/authorized_keys b/config/authorized_keys new file mode 100644 index 0000000..0de69c2 --- /dev/null +++ b/config/authorized_keys @@ -0,0 +1,2 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6Gc/53b0ZBGL/ORF5hIa61hTPTAsrjnkxXl3wawsHT felix@home +cert-authority,no-pty,command="" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCvYzwz1f8HNV9EsZT/vkZ/HO9OG3XiMxOddQO46cCZIHIbQU0N9ga4/XxVWYvuqmtlWu1/eykfZoqXj7BSENj43q1d4UWSDevUexkBOoy61BCBwJoMW3io77R+DZodqCacNaUHqP+46kxaIcckYMq5t2mlbXtJsv3Yr55P5HofoN144LXjNhlr16rrjcxs1Ht5kTPtv7sCx/f4okBnKVmpAq3IcuZjgAnz89qb4iXJasLlFYTDC0GGJV0zzzThah52vnS4C5vyNr6hL9xLYesNlrO9ni7jET7Szu2E8V6xUDkF4x6Cxnl2U7jtfTcSf/pt8SEoivMUlV8B785Pj0APbRxWfpxEgGtQhDc5SxaMnIpQ5mqOSkH+LVtI3LMHIHRZfzKUgVTgA5qkqjgpf13I7dXSCg9PvZv7yesT9mdBhmXiUvvwZUG3AQ9CoG78jJLorhi1AdlAtxJ2qNiWJO+70/6LQJBfuZtTAv9KU7L1nckSslY26WOOWj21l0IQjCE= root@lair diff --git a/config/dnsmasq.conf b/config/dnsmasq.conf new file mode 100644 index 0000000..1eac972 --- /dev/null +++ b/config/dnsmasq.conf @@ -0,0 +1,3 @@ +interface=eth2 +domain=sshworkshop.local +dhcp-range=set:workshop,192.168.0.10,192.168.0.200,12h diff --git a/example.clab.yml b/example.clab.yml new file mode 100644 index 0000000..6512d82 --- /dev/null +++ b/example.clab.yml @@ -0,0 +1,85 @@ +# topology documentation: http://containerlab.dev/lab-examples/single-srl/ +name: sshworkshop +topology: + kinds: + nokia_srlinux: + type: ixrd3 + image: ghcr.io/nokia/srlinux + arista_ceos: + image: ceos-lab:4.32.0.1F + linux: + image: workshop-debian:v1 + network-mode: none + binds: + - ./names-hashes.yml:/etc/workshopnames.yml:ro + - ./config/authorized_keys:/root/.ssh/authorized_keys:ro + - ./setups/linux.sh:/usr/local/bin/setup.sh:ro + memory: 256MB + cpu-set: 4-7 + nodes: + # a1: + # kind: arista_ceos + br-clab-intonly: + kind: bridge + br-ext: + kind: bridge + linux-gw: + kind: linux + # network-mode: "bridge" + binds: + - ./config/dnsmasq.conf:/etc/dnsmasq.conf:ro + - ./setups/gateway.sh:/usr/local/bin/setup.sh:ro + - ./names-hashes.yml:/etc/workshopnames.yml:ro + jumphost1: + kind: linux + binds: + - ./setups/jumphost.sh:/usr/local/bin/setup.sh:ro + - ./names-hashes.yml:/etc/workshopnames.yml:ro + memory: 4GB + cpu-set: 2-3 + jumphost2: + kind: linux + binds: + - ./setups/jumphost.sh:/usr/local/bin/setup.sh:ro + memory: 1GB + webserver1: + kind: linux + # network-mode: "bridge" + binds: + - ./namepicker/webserver.py:/usr/local/bin/webserver.py:ro + - ./setups/webserver.sh:/usr/local/bin/setup.sh:ro + memory: 1GB + webserver2: + kind: linux + # network-mode: "bridge" + binds: + - ./namepicker/webserver.py:/usr/local/bin/webserver.py:ro + - ./setups/webserver.sh:/usr/local/bin/setup.sh:ro + memory: 1GB + linux1: + kind: linux + linux2: + kind: linux + linux3: + kind: linux + linux4: + kind: linux + linux5: + kind: linux + linux6: + kind: linux + + links: + # - endpoints: ["a1:eth1","br-clab:eth1"] + - endpoints: [ "linux-gw:eth1", "br-ext:gw1" ] + - endpoints: [ "linux-gw:eth2", "br-clab-intonly:gw2" ] + - endpoints: [ "linux-gw:eth3", "jumphost1:eth0" ] + - endpoints: [ "jumphost1:eth1", "webserver1:eth1" ] + - endpoints: [ "jumphost1:eth2", "jumphost2:eth0" ] + - endpoints: [ "jumphost2:eth1", "webserver2:eth1" ] + - endpoints: [ "linux1:eth0", "br-clab-intonly:l1" ] + - endpoints: [ "linux2:eth0", "br-clab-intonly:l2" ] + - endpoints: [ "linux3:eth0", "br-clab-intonly:l3" ] + - endpoints: [ "linux4:eth0", "br-clab-intonly:l4" ] + - endpoints: [ "linux5:eth0", "br-clab-intonly:l5" ] + - endpoints: [ "linux6:eth0", "br-clab-intonly:l6" ] diff --git a/namepicker/.gitignore b/namepicker/.gitignore new file mode 100644 index 0000000..505a3b1 --- /dev/null +++ b/namepicker/.gitignore @@ -0,0 +1,10 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv diff --git a/namepicker/.python-version b/namepicker/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/namepicker/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/namepicker/README.md b/namepicker/README.md new file mode 100644 index 0000000..e69de29 diff --git a/namepicker/main.py b/namepicker/main.py new file mode 100644 index 0000000..574dce2 --- /dev/null +++ b/namepicker/main.py @@ -0,0 +1,91 @@ +from bs4 import BeautifulSoup +import httpx, lxml # need lxml as bs4 parser +from pprint import pp +import pandas as pd +from typing import Set +import random +from io import StringIO + + +def fetch_names_table() -> pd.DataFrame: + """Grab the list of most common names across the last century from the american social security agency SSA + and parse it out as a pandas dataframe""" + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36", + "Accept-Language": "en-US,en;q=0.9,de;q=0.8", + } + page = httpx.get('https://www.ssa.gov/OACT/babynames/decades/century.html', headers=headers) + soup = BeautifulSoup(page.content, 'lxml') + table = soup.find('table') + with StringIO(str(table)) as f: + df = pd.read_html(f)[0] + return df + +def make_names_set() -> Set[str]: + """extract the male & female names from the SSA table and lowercase them into a single set (to be 100% unique, even if + they should already be from the source material)""" + my_names = fetch_names_table() + return set([ n.lower() for n in [ n for n in my_names['Females']['Name'] ] + [ n for n in my_names['Males']['Name'] ] if ' ' not in n ]) + +def get_randomized_names_list() -> list[str]: + """takes the unique (ordered) set of common names and returns a shuffled version as a list to be used for + sequentially assigning randomized user names to containers""" + names = list(make_names_set()) + random.shuffle(names) + return(names) + + + +def sha512_crypt(password: str, salt: bytes = None) -> str: + """ + Produce a SHA‑512 crypt‑style hash (like ``crypt.crypt(..., METHOD_SHA512)``) + using only ``hashlib``. + + The output format matches the traditional ``$6$salt$hash`` string. + """ + # Generate a 16‑byte salt if none is provided + if salt is None: + salt = os.urandom(16) + + # Encode password and salt as bytes + pwd_bytes = password.encode("utf-8") + # ``crypt`` uses a base64 variant; ``base64`` with ``urlsafe_b64encode`` + # and stripping padding gives the same character set. + salt_str = base64.urlsafe_b64encode(salt).rstrip(b"=").decode("ascii") + + # Perform the SHA‑512 hash + hasher = hashlib.sha512() + hasher.update(pwd_bytes + salt) + digest = hasher.digest() + + # Encode the digest in the same base64 variant + hash_str = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii") + + # Return the full crypt‑style string + return f"$6${salt_str}${hash_str}" + +def create_password(username: str) -> str: + """generate a crypted pw hash for this username""" + return sha512_crypt(f'{username}123!') + +def my_password(password) -> str: + salt = os.urandom(8) + pwd_hash = sha512_crypt.using(salt=salt, rounds=1000).hash(password) + return pwd_hash + +if __name__ == "__main__": + import hashlib + import os + import base64 + import yaml + from xkcd_passwords import generate_password + from passlib.hash import sha512_crypt + from pathlib import Path + with Path('../names.yml').open('r') as yaml_file: + names = yaml.safe_load(yaml_file) + # names = get_randomized_names_list() + # name_pw_map = { n: (sha512_crypt(pwd:= generate_password(separator="", capitalize=True).strip())) + ',' + pwd for n in names} + name_pw_map = { n: (my_password(pwd:= generate_password(separator="", capitalize=True).strip())) + ',' + pwd for n in names} + with Path('names.yml').open('w') as yaml_file: + yaml.dump(name_pw_map, yaml_file) + pp(name_pw_map, width=200, compact=True) diff --git a/namepicker/names.yml b/namepicker/names.yml new file mode 100644 index 0000000..dd9a620 --- /dev/null +++ b/namepicker/names.yml @@ -0,0 +1,200 @@ +aaron: $6$rounds=1000$foobar$/AkayIb/twFQqLVp5MnQeJp8p1Pt2sOfWzhq6wTNnqnMsQawo0NYlxDuWnBhFXbT6GDTCrs38JgmyDwqOO6CD.,SharingClarkReflectedMunich +abigail: $6$rounds=1000$foobar$UhKfDtIT9UdQ0M93H3z61XSiECAuTZOGgm/s7lFxFSHP57DmRN1zpGpKjS/DgaQgaAHumyJ7V5n9qvUU.6plz0,PraiseBurnerHeavyBalls +adam: $6$rounds=1000$foobar$Mzfo9xERe9r7d5uHDxg/RejsM1hgHx2M9GBkwvgrFwgMXrQDZUk22nfRgRNMgKrnEAi2nJSZV6DtOzAMtCu1E1,ElectoralBelgiumPredictedVoted +alan: $6$rounds=1000$foobar$AYjIzlSVFZZ2Im9RUl0VfjBvPRoEd/s/wKDscsO060VbL5qde77I6Td7f8WSbziFbXpP0r/z0fjh50cU/oVHq1,CurrenciesMotorcyclesRiversideNeighbors +albert: $6$rounds=1000$foobar$.MHQgb9DObm52j1F6waW55weIdatBlX8G0FSHt2fTD0xZESkBK7moLT5owdJ0kX7bRXf8T8OKuWSylbSuQZtU/,AttachmentsThatsOperatorExchanges +alexander: $6$rounds=1000$foobar$aucI7SNenyzzV0nAMIRQaJpUPaJxGNzHD2ZmULQcL3O3UF.Qxrjk5TkI4BUzHL/mOXVUp6VHcNCYbYpN51CUI0,ChannelsBatteriesMonitorsButtons +alexis: $6$rounds=1000$foobar$0lXeslOv.yiv3rHQ2ROgMjsjasY0S3.IHlrRVnlq5syXkF6GZL4Unmt57zjx3.BXw6NJbaEIkNNgcXKs0FUTU0,WasherObligationsVentureEffective +alice: $6$rounds=1000$foobar$HWEGkfh7Xynli0Z9NMsZ8YsMYV5f6nXgW.EY7suTxWihGAFESjBWVAyVLMO.q2nXPDVgzx0P7rlrYCBn9/T0S1,CorrespondingBooleanFighterFever +amanda: $6$rounds=1000$foobar$llJk1qXcc.H8Z1NUzZU9rxKBb24aTeYOig5fwN/cEfDGcmJNHshvO4tj2EZK9c9MGbVS/B9GcyM/dVphpZq7O0,AstrologyTransitionBreastPriority +amber: $6$rounds=1000$foobar$kzAnfDhoqxTqwWCRyfmQuPv2UuwkzQlefUV32TXCIah4xPWISz3n8w7WG5urbEPk1KNERRR2CJZUOvvuRzGM4/,LendingJapanHazardousTopless +amy: $6$rounds=1000$foobar$/F9S4x8y4PqRzjxZ03XV.tS79GMJD51c5GRDp0E/oNXpv29UvToBCjuICMgCReTadpn63jofggHhi0vaOTaDD.,NorthwestPopularityAuthorsBrunei +andrea: $6$rounds=1000$foobar$yhnd7hV0N2VrturKbgSLFWRJXl2vSto.iyJAqX4eZUrp42iKgnUpsGlPhKD2jrWoeDU9JVscfjsbg8fL3Znjl/,BondageDigitSwitchedCoordinate +andrew: $6$rounds=1000$foobar$hZOg2iXrd1PI/iMIZNppA.3TMuKD0sU4Q5.Uq8HoyaMgPY6Y/u64jxkLdXE.1V6bqsdNVxE3adSejBEwEnCIO/,PromisedFallingConfusionLegitimate +angela: $6$rounds=1000$foobar$nKTzEw5gEoG6kM3hprpe4uSwAfv423VshKtgQu5rS7UWvusf1pcVlCGN7/XuesUZgmTarp1XvEOLOcbvni5tJ0,ConsortiumClustersConcentrateAdjusted +ann: $6$rounds=1000$foobar$iFmHys2UgeoZF/MkXv3S12zuf.SjGhOmqVQONUel4LFhRi36OoWgPYPgcSWEuYQ2cB9s61G5ZCVR.PWbTKs7X1,PointsVoicesForestSalem +anna: $6$rounds=1000$foobar$4ylTQV1a/Igy8IA4x0mFTvEgzawD.xaQAL7YYipulffBeU65aYpY78pYw79gwhte8388CsUBaXylA8j92oyeh1,DeploymentCreekSelectiveMagic +anthony: $6$rounds=1000$foobar$zrvdq2Zyb9zlTOhy.S.da1DgC1C5f9DDmj7MyHTcX94u4KPvL68via1u/rEIXhTwLRUVOWNb2cqwn0VQ/wVF2.,EncounterGonnaDefendReflects +arthur: $6$rounds=1000$foobar$FJ70cmgrLHvzBnth6LcWpQQO4GdXEeAgioEXaBejMqfQuPbVHlwELY9EPBst8BpUSZ4w0jiX3.gFm003HXZ31/,RhythmDatabasesNuclearShelf +ashley: $6$rounds=1000$foobar$/ydqEj0Z3b93BD/3vpOWra6uMgqhUeyU2swWNAG07VqNEK/O3xMB1ox1OVObcOBBDjfKb8zDdJFk7EI8afKsR0,FragranceMissileBlockedCollapse +austin: $6$rounds=1000$foobar$TTg.68QCBwGYMwl.SN7uHWtqxvUvlAGjyRdwiQI.bSIWeyURLfpR2CP/bcjj5/zk1Dcl.c0xcz6Y1esNOaXl2/,IncurredDarknessChrisWednesday +barbara: $6$rounds=1000$foobar$nHEPpRd7bz6ptGCwe6juScyZVWY8BZ4.ef.G94tZbZ3yTJIuEUJ4dWpdis7A/IAUNaYf.gZPS2uM4TY4Bh2VN0,FossilJungleCentresEspecially +benjamin: $6$rounds=1000$foobar$VY2j5Eq/uHagoLu96htQIKmSEYXsbHJYzxUZuzi980ikEzv99BNCBWuVqV0nsUMiJWuEbGg/IO77DXizlkXA70,BriefsSuitedPrivacyEverywhere +betty: $6$rounds=1000$foobar$tt04W6hjkM2rMOylOPF7s6LliRia8jbNWF9Yxr7ry/bq9zEyO/DYekD8aJ4OcCmzonfsc15IFTUkaZ4UAjXq50,ScoringSubmittedKennedyOrders +beverly: $6$rounds=1000$foobar$pJydQK07eW5rfXN793bGZaYHJa0058BHpau6d5T1OxqgSgK0uACz6AkmbLPWnjZRPllaahwkG5ywkp3dO9BOA.,FilesFarmingWomenDistinguished +billy: $6$rounds=1000$foobar$IFmnvlmRScNa/NGVPvuZEh7bLih8bvvO8u0ru6dlckP1pO5yULYY8YqCIZnyiJWWyX0abJnzHlMdcPRWV0pqq1,SubstantialDesignCamcordersEnclosed +bobby: $6$rounds=1000$foobar$pBYqaWlWxcaBaYyO6isdxcQE0cBJbaMfQtmn7EvyqL2Je.IzM1HE2TEoRsI2vC45zhQHRW32FVpq9yhWvchOW0,SizesPenaltyPossibilityCauses +bradley: $6$rounds=1000$foobar$dJW1ccajrfQHmhy1Mh.n9otKxKBqkBD7MO4o3mmUYU85cg7iRmtUng284vMAdlcb.Z19nSAizk1pymclwWHfN1,BunnyProposeOutreachNeither +brandon: $6$rounds=1000$foobar$DP1TWnFX0GVRAJDqQ5EzPiJy/CaKXpm1T/dpMDAnaxuSEOcynLGGS6.RkT.5oIEgUXJqh0HWS.zZnWbLSXVaT.,CatholicTimelineBroadwayFunded +brenda: $6$rounds=1000$foobar$Qk6NlaWP.TFeE4fer2yjO61F3ISMSD3rNIWJpfgFnabp488JR5Dg7.IiQ5eZzkXMwEhptCY.CTNCc8rf8dXaT0,HolidayCellularHeightsIndigenous +brian: $6$rounds=1000$foobar$bmNJ7KlKRXryyZ7pFQdy7pw7c5BMgbs3XYXLnTKbJ/yau2tthUcYqxhikTluUcV1XvVi4BsBN/PXsPmD8VOpm/,AndrewsEnsuringNeverthelessReproductive +brittany: $6$rounds=1000$foobar$r0VbZk8xxrHdc36tWiXIZXZGUTgr/uC0KTfU4zCTlH6UXVPN/kgrqP81hjc1FH3bI10s/OyMY89tjupcX6OIn1,ExportCookiesUpsetScenario +bruce: $6$rounds=1000$foobar$5v8o9rIQuDKSitje8b2BoYqbjRLzG3l08e2GuskE.V1l8ASuNkqJwOlDtBuOU1j1CAWvX9BNzhUNsAR9wE.CF.,IraqiVacuumFilterComics +bryan: $6$rounds=1000$foobar$vY/tPZoGd.lWlXMrbvjxCl4CQFUlD0KzMYzrPFO1FHUMt8fuNdQFNDppBrfhu3DGpX0EFoNy8OiERKZXPVzOC0,AmericanTwikiDiegoWitch +caleb: $6$rounds=1000$foobar$97Lhk3wwiHOVm7uJe2YmhLHApgEHUQyXZJvHnKcWXr2ldMz2AjF2gkwM6ikVorL8H61Xb1ir5emPOhH9RZdz01,MixingImagineAnniversaryRespect +carl: $6$rounds=1000$foobar$YtOEVcz9hAFGNKIa2bvFJsLIgAWiWmTaClrgI8s/6q95Dl.F9an1/mb733NhDbrryomoS/pwzWp7OnDYPPzmb/,MileageEquallyAppropriationsMemphis +carol: $6$rounds=1000$foobar$eb8iLX1Ddi1N099fCBXkG6RDw3tlPEoqpbmCWKyIxs1UrX9waUtLpUHmkGUVFZeZzAMWBri0idUCaCbgzT6YJ1,CumulativeSecondaryTradesSurgeon +carolyn: $6$rounds=1000$foobar$ei6HbC0TrYTaZ4ffdvwGKdF5TPLA.tE.2TQVRmWUkpUvzSWsrAcs4iHxgRejxIY/RvlKWOsx1fYwmb313mmYQ.,PresentsKnifeBlocksParcel +catherine: $6$rounds=1000$foobar$vwLeYxyiKuWfzZ3KWWIksBO56tSWddHSQTfMFZV5mlvk97e/j9ucagfoQ9ziix2r58OHsF4YErJYatbG11U.F.,InterviewWristStateCancel +charles: $6$rounds=1000$foobar$R3DZja1PktqUJXM8tFHS9uhnvaMplliVO1LxHVrVLpoE7mRUyfAso9hOWCVYjZD50vp45PwCJ24uD8//A4pMQ/,StreamsShippedPlayersEcological +charlotte: $6$rounds=1000$foobar$24DuYssFKs8W.D2jDnPPLRM/NQZmrHnMwmh.QcOsdwi1CLusHDwdjlhkk9AI5UxSxQQ7qZsiIeSe/SQ6m6b970,BlinkEvaluationsInterestedFacing +cheryl: $6$rounds=1000$foobar$eDyZgB.FTwmECGimV/.MWwqhgmBnscC9NkSFivi8S8K5WmMbQ0pCcZlIzXJ7GA/D6FJ6RVIr5pC/M7XO5GpPB/,PublicityWithoutLendingSharp +christian: $6$rounds=1000$foobar$Cvc2LROxHb030sK2rcVSbvCtwF2b.nwqqZT0ur6m/Be2SvLat1gJNmj9007g1Ip9Hw4LSm25QjoU1e6mWUJOc1,AnalystsLikedSpecialsCharming +christina: $6$rounds=1000$foobar$20G99E34Rv80qBsKnCcNoMHcSiRpUyp8xLk19E1ng1Qw12BSC8Jzu2s8NaKuf54VTcSr4TnozGfzwTb1EZw2j.,MinuteTerminalFocalDemonstration +christine: $6$rounds=1000$foobar$ewh1LAFzNOGJH53rM567un1peZZIIt2VmTy7dCcyCbFd1m6qHY7.SMXuSgUyzme2K6qI3Kgh0lnaDwsnp3jkG.,IncludingTerrorConditionalVictims +christopher: $6$rounds=1000$foobar$4vlg5pzHuTHc1r/eT6uGyz9WsslBY9fpzqBvRlDLoCCS0Sjpu7cesWphDIQUaMfQH7.zFpwlJWWf/s7OH6K230,AccessoryOriginalBouquetGonna +cynthia: $6$rounds=1000$foobar$ioV1Gzp7ndTfz5/0Casfc9zAqw8mtn2khBcww/SWJWMaKIWR7s1FS8GGbxjpAL/8oL3aHISz4X9wV4H8AL48I.,EducationalDisplayedApartmentMoney +daniel: $6$rounds=1000$foobar$z0fARmuDlfkd9eDWED3xV.Jjmf/mgzsvNwVPUqiliqfTOQheBkisyvwmJ1llUkN2UFujwoNo.498iocoX1ZQj.,PasswordStolenActualCalled +danielle: $6$rounds=1000$foobar$Ib/LynVECJDXAW9A.KrCJ9gyafBHJtXBbrZFHm9BKp4W9p7j.B0KaV7ot5ax217ryUEmNaQcoNIhe01Uy2Ko20,JoinedMotivatedRequestsInformal +david: $6$rounds=1000$foobar$VJ9VObrcYD0vkuNOB7cpVCsVm/w.ojaB6W/ZAq1bIGa6HLsrhPLjC3BKCxWNOQ1rKabLEJvBO6wzfmhXPExxn.,EitherRetailPowersProducers +deborah: $6$rounds=1000$foobar$HsJxBydJgCzJtNzXJ3IJxlo8WpHQNP73YoW7QqXLYhfCtWrmg.rXBGx.HykZoagyHDhLqnzGVe9j6P0y9WRD11,DesireBeatsMostlyToyota +debra: $6$rounds=1000$foobar$S9dwuvus7aWDfW/XF.98nfep5WT1kBI5WRtc.zf7qHQjeZif1l9Sf9bN/4gCYNoFKKlJNP8AJb3bEddZuNFsi1,UsefulTelecomDozensMedicare +denise: $6$rounds=1000$foobar$.QVTcRGBfTq5DxVl100xisE4dnospPkV5Sl7FRB2ZrDA8IIwF//wvcpm8Tmi0iCso7yHD1sT/KjPprwrtjUZB1,OscarDeliversStationeryDriver +dennis: $6$rounds=1000$foobar$N0I4cm.LGZ89NP/YclZazsTbsjQ92AGIE6tRS8JRLXPMcM.0d4sHVktyjVoqgSfovk0rinEqOe1rH8wfF/cii.,DiscoveryActivityLifestyleFurnishings +diana: $6$rounds=1000$foobar$hBc04w5TLX7WT9dpOLDgKNNDYJ3AW79jFG/ANvF3r6yyWVThixaNvnK/n87k2mNpjFlu8qi8kz6xbP60pKkc70,KinaseProperStoppingTries +diane: $6$rounds=1000$foobar$2Xn7plTfRFqqKzlx59Xmqp0BGSPJF3E4582QJVpHXt7T9Q0cMRAXlkQa7fSnPOfSnW08eiEdbsYjXrQg7EXSU/,SignedPocketFluidOther +donald: $6$rounds=1000$foobar$86dh/jaWHjTfaqzth.8pI3VzbtQCslpmwb.s2hj.vMJ3.sHWpVv6lXY2aHiFIEQ3AogXiDsuGYWjnZofd8BVg.,DecemberVanillaFunctionContracting +donna: $6$rounds=1000$foobar$eIJrg4IwLlxdQqH0Qz5W3biJfb6mX1MPX1FsBWSh8Iu8CE3s6/NahnWuhLUt494wl9m0ApCLcToRot4jfMD7d1,UrbanPreviewJelsoftThree +dorothy: $6$rounds=1000$foobar$9uk23Aw2BtwxkeHTXyWJhKroz5juIJcy3sehIzKY.Cr/k7FsMkEGRwyGjPcTq/PeqcoZP4x7yEvlXxknbqMD21,PracticesYahooThoughtsTreating +douglas: $6$rounds=1000$foobar$Rci6WVofcz6ccVdqOjOb0dhPrDueBJGM9VsDIU4kXyNu0ZmE6aYN9CFv6TWZpwveKR.6cRcJBNsSN5YbbOzCJ/,TrademarkConfidentialDefendExamining +dylan: $6$rounds=1000$foobar$ydtHRpF33b1yS8xREXQ9d0br2eAbDIzi07Cc0SSJczL9cnD1wgx4XS881ZRgZ.oLaAyWLIgiK1QxJYLAjeVhL1,ReducedVistaOrangeThrows +edward: $6$rounds=1000$foobar$c30Qkpejpj13HuykbeCvJkJGzMkUwutiVL2AQlXI0x1rm29De8ZrMSa9mMxVmohp4jsvGKmBkvtfwlVv4YTfv.,FreebsdCelebsPraiseIncident +elijah: $6$rounds=1000$foobar$QDXDDS0CVwtI4I7VSAqn68Mm1y6rcekH5uqtKALW65BymCctN.kKNYYqxRKTz8wwXZ/bjQldCsbjaoMoBa30E.,ReviewedHerselfExcitementDisplays +elizabeth: $6$rounds=1000$foobar$/9WY5moeRZUFWsHpLmqBxYJ26FHpLInapJyf9RncSZS1AzAL92QUYymIKJxp.6v5Bhk60SkR4d4yNQ3vN4Qql.,ImportSupervisorControversyKnows +emily: $6$rounds=1000$foobar$1TUj4iPqJDALiGfZhuXAh9QEUrh3blO1Ea0JSO2Do1QDPBesgr4MoR0EHq/D58diAlcatsKu4f5SR6knOsXYY/,TreasureUsingEmployeesCollecting +emma: $6$rounds=1000$foobar$P7O7Ewa0ALQl7lPZ4WJHu3cbfC6NpewkabJ5LT2199hsyPkYtX.vnFNmb98IFNWh5GH.X9zLKnRu9PEZ5NBDa/,ShallHungryRecruitmentProjectors +eric: $6$rounds=1000$foobar$zJCfoM1tb55oLoRydZPZ04lmJuI63yKeucl3.XuCwLBHxK/eff8l8eitdcZg.jMYweRv/ZtJG6aONmr9xUlvG1,LightweightMarkedUniversalUruguay +ethan: $6$rounds=1000$foobar$4.1AxIXjXiE0EM.hXQDDGbXsB0xXj6N99YRCZ323bWeC9RAImM71ymOlETzrpfruiepzN9TvIMzFklYEvTHa7/,IndicatedDomainsLeatherSisters +evelyn: $6$rounds=1000$foobar$quFgEwXnpBrYa5T8sjonjloWtcG1phHs/3V34dS4QmhcxEmkyw1RTuLv.rBRob1ORGHsKfpMDJuqMOKuFRmDl1,DollsPricesRangersProbably +frances: $6$rounds=1000$foobar$ybdXl6wAyetHGqvS5BUBRvQCdly5kFDQiO/jgLtxWWeezYJtttlnAjDhRr7qMSDRFTkgCgnpJ8EV4CD366Akd.,SatinBeneathDecorEddie +frank: $6$rounds=1000$foobar$m4q47tMc2cw3yBbo5xu3PVaKf.cLHBb8XFHp8OavKCtfvl.mhs4SoO4kdVO1X3Q8OA/LDbo5/42IqDBIGWaxU1,VolvoIntegrityWivesChocolate +gabriel: $6$rounds=1000$foobar$sX5noayEPiYZCtJySyUj5ClMY2cINHTD3iQ/CRtuL/LvhlZavpalu0wRmHsYD3J4QYCllEi1qPj/LYeTffKML0,ModeratorAnniversaryPartnerOttawa +gary: $6$rounds=1000$foobar$DeUvPns4c2gsch0El3Op08FUn6YL7gOy53PyWyI7iaHxNJR1jxVj1R5IqYYcrAhvW4m17B/ToLImuQ9MfxODQ0,CumshotsOldestSignatureStudent +george: $6$rounds=1000$foobar$4PNQdUA1NPu0sD9SHw2y/aUKe1q8TSJl6GjdcGIRzOUdmNa93B1hiZ5D4S/TCa7pSkwdzwPIDuai6xP3KT2050,DifferentOxygenExhibitionsAspects +gerald: $6$rounds=1000$foobar$l1gcMCRbLRy.YnCLmlLxH1DenjVAoylvgvNbj4fFVLt.9Di7iqnbZxP0EvPK4IQBRt.HlsRYRqMtoxeKx4w4V1,ColleaguesHomelessAndreasStylus +gloria: $6$rounds=1000$foobar$dCVFMRHzY4WLp4W8kGXKjwBW2Pw5Ppj.qZc97MOPgtYQA7I02q9OAkhd3vqYD6e3Hjw4be5ho4QEAny3d8qX/.,PassageOccurredDeathMixture +grace: $6$rounds=1000$foobar$wjZuT.44uUm82sAuKyOn09DU3zUaCcqUCwQpW3aPOF.hSh8dhVwMci9T8Xu2z8uQ37os5xhBW8bJRDH/aqRaP0,LendingInsightCommonlySynthesis +gregory: $6$rounds=1000$foobar$bSeSPWH58KDRuW89Qy2.GJ58Ehb/qiP34wUB64nAu2/ephF3uAuedyDj1Xzf.9LX5gZWyLq/snwjDR4yOZ3zw1,CraftsFeedbackReplicationAdvise +hannah: $6$rounds=1000$foobar$26HVQutL6BEPPoiX5KVEt/e3yKa3NE.4grd69xXGprzfqrv32cib9VnrcLDp8Sl.oqG0YrvXkoOzB0sb4c8W90,NaturallyKatrinaHardwoodPurchase +harold: $6$rounds=1000$foobar$VJmWFmUdpvyZCG3wC3mU3r.SZt2lpHD.RM96zj7llllPk15x6OmLWKQveBlKfAVdTZd1RQaSCG9KQndR0dLny0,GeorgiaEnableMultipleDevelopment +heather: $6$rounds=1000$foobar$RXMqG/foeXyS/QFjPPkGafhd9Ndg4THpvzUZi0t1JO.mJiSHpJhokxrcqBvxXK5Y1ypB0arHRMrcrV0KqnQKi/,ReplicationFemalesWisconsinStranger +helen: $6$rounds=1000$foobar$wcE2OOgqXvw8QrN/9Oc75kuGPYaVcpiUnPgzNq7/IPEa95XIKQnOvAT0kWBfETmGbIh6c1KzJrUCFhvhMvMh./,ExpandIllnessNortonCommunicate +henry: $6$rounds=1000$foobar$ZXHBdq1jLKVN3dLvIKhs15czMX7.6aw2Tmh6NJbgRdWyR7H2.NWEzzBQ4pW.qr559FqNRMtgJxkG5oYi6kp4E1,SupervisorBoardsSpeechesTrader +isaac: $6$rounds=1000$foobar$/yPFmCYOayJ1bue0/MdOMTrALkTupJxfcoXIq3GgP0C6i6o1NH95YfS0apzbc4N/z9O7kAzmz09xA4Oit/5xM/,SolarisThehunAcuteEmotions +isabella: $6$rounds=1000$foobar$kxPylvgvk/cH87h.ETTlgkhKN/p7uP7PqgQhXHNerN/8N2NbnIIAI3Fiw26clGIs59.j5Y8ZYfJQJxm0nfJs8.,MatchedWelcomeMissileBoutique +jack: $6$rounds=1000$foobar$vmPMFMf9GgGpr1C9sP5DQeIDrfG0yeLXLCydlbWAKsiq1jWTME0GRV1XBJnmPOQ9y/hjpIAcEimRQxrJYjwmx/,DefeatSuicideEnvironmentsHistory +jacob: $6$rounds=1000$foobar$cywGcMOwPH4BIvX4AzT6WSftQZ7elobKoKehnw1LL2/WFbSMCmWAu6SbJ5tgEms2.DK9CvUgsAI0jEiMG1usd.,DependingPhysicallyCrosswordRetired +jacqueline: $6$rounds=1000$foobar$/A/jBcXA4yhOR0.Hby7fhw1gtNzt57bP7M7O9Cu/cgKFyjwhxlk1Cpv/qsy2oa2CqVUKOJplptLPrB.cSWI6O1,TelephoneAndaleCalgaryExercise +james: $6$rounds=1000$foobar$vN6xnsOzBWJzNCrY2m3uSBH28D4vQlgrO5RWr1ky/OJBe/YpO0vF.e0UfbirOe0JVXnfxdgX.7zL.bumnFiH80,NewsletterBrokerGlassesGreetings +janet: $6$rounds=1000$foobar$AqIvk0am6orIyhN9A55947kTYlolJY3/JxScrP2EdtV7qXsQ8Rq//nRhjzLCPirFUTJUOfF1RiXwKVtIVzwyC1,CommitteesExercisesDisclaimerSaturn +janice: $6$rounds=1000$foobar$jv3HQehrn6lHebt2mWbt2b0AEz2bQ58nx1Xo4GA5XgEAqrEALOxS.1xdNb2Xn6LHNxc3hsCFm0bAawC/WRbtF0,SquirtingDocumentationConferencingPoints +jason: $6$rounds=1000$foobar$Y8/5Aageu3O2ro3qtG61Poz/U71/nojYpBudzTT/e2My5tu2UJn2wAVJMNmH4Z49LivF0ChjOlzNAvtxZf6EZ1,StoneAfricanProjectorsFlashers +jean: $6$rounds=1000$foobar$98Ff9mHfZLfz/wzobS0ZXJIr5KkDPRRpu7Gcd3fHZwbEnvOKQP6jXXgmCMLPY58dMobfeI8R25JN//kG5qJwd0,KodakAssureInflationPurchases +jeffrey: $6$rounds=1000$foobar$zG1O5LJtfewMm5zUmPXctU/4sbgDK57cF.WlgduAmI/LnZ2f/MSHVVnsY5UPZP9uCDJvcXYCbiZjRH5cRTqGv1,ChapelBillingSundayZones +jennifer: $6$rounds=1000$foobar$mjO21lHABXhW/hJXmk49jObqt2SDZKIA863YH7c.RB9FVko39yDQCwoyCGpByDUHwhxn1HrcWl27AUweKkmbf0,RadioMobilesQualificationsBanned +jeremy: $6$rounds=1000$foobar$6NQcuRoIGSBfcvnB0HlI0GkWXP/hb/0PEgTPp.wPtRMUUaBVb9YR6vPFPjSo1DSO4p9i6rzFX7mreTBfMkB9n/,CarbonSatisfyLengthCharacterized +jerry: $6$rounds=1000$foobar$Qa1K4djQMj3tZ9HHhxHditEasDYuWMiK0Ley7EI3beiqLVAKn5KqAEsfPP1eCHhDhetvhDlQYZbEfaH3vogSZ1,SatisfyTextbookCitiesProvide +jesse: $6$rounds=1000$foobar$.uIL0f/tfN.MZPOwvx/bFvzvs4L0LBA8VlFgsYS8/2Q8yWbUpcbaYY5FGnlpSTR64mwmFvUvUOB98dI1BlZam/,ElsewhereEnquiriesNorfolkNorfolk +jessica: $6$rounds=1000$foobar$kc4Vw/dYvQgIcNjzDBTjDiPyq50x4fXhVq9PFqqybnA9wD.J0rrvgX7bt3RsQ4iShifb1Srzc.XL3MVyKDuDf0,NutritionalCommentaryTraditionsGenerate +joan: $6$rounds=1000$foobar$qLpDkNDbt5AKSPcavbKgatkYmn9yt0za8LAlvDIZ6aW1ZpxAAa5AQf7U7lQ3TWVehUqc1qbkmSHT5IwNL1Iii1,LatinoAmbassadorTerroristsChristmas +joe: $6$rounds=1000$foobar$Hy2.efxWznqMd06gel2YIxiJwkQdC6feMWmbBF0qjBTtrTvzqdbzQEv.ZsFrMDjMkI4Q0IXqbz/uzoniJDO4E/,MightyGlanceBeginningBasin +john: $6$rounds=1000$foobar$CALZbkLjLXZ86E7l7DESmPYcTvnTeS9iW0JsjRECguqVNHEzXrtvNJlleBPKsc/W0.rlqYbDM/xqQp2Hlu4.G0,GraphicKeywordIndicatePeripheral +jonathan: $6$rounds=1000$foobar$TnaGpoItP9gQwVV7FhE3Qm1aLF1AakwjolJF6N8JKUZlGqg1X2QHIYqWCdYjMZp0LPi2AtRDUffnHUiuEGhmv0,MountainBasketInsuredApril +jordan: $6$rounds=1000$foobar$Jhk95fCwEEqaTRHGr/cT84YHyHHSizbacPNA27XuxkpGYzfokVHY3vFSzTqz2Zxwm70aRwnKtoal0O.uLVTRL1,SpecificsCastleProgrammeMerchant +jose: $6$rounds=1000$foobar$ue/l54A4adVXRIqOAmg5eirN88OdyZBDpkGFglEIriHHEnUyJg08RwWUEENBdrUHErM/vRVSwXvrRIQYq.wnm/,BoatingCertificatesAdministrativeRequires +joseph: $6$rounds=1000$foobar$LNi3/WbHXDofLtsp7ltFSWtaXaVPqrjRcIeb4MI3.LW1gachiyUToQIJdrI8FRflDCyNGIGjLJzS4FzyDfi/w/,DelightSuckingEntitiesPoison +joshua: $6$rounds=1000$foobar$9RnY.un4pDhY1PZ1LjCfuz2dr3KjvakEj3cuyN516kxxOHLfjm/84LlJ.iA8lkJ4TP5jA7dG0b22IxNBqAn6G1,SenseSublimePlatePsychological +joyce: $6$rounds=1000$foobar$Gce70wR74OxkLX6E1g2jU.uIRQHfSHYhy/.VHeGJXCVIgLWYq0DseBe4TJJIne3pv08kb7V7MWh9wk3LWZYdR1,GovernancePreventionEnormousCornell +juan: $6$rounds=1000$foobar$jWA/ff.2xDNSd1R3ChK.Iwks2TH1G3ih9zYdA35dhpQ7QxoOrXmCEBJYKKiPWYcem73ak/liFx/KgFy.7Aas.1,LocatorPlateAnimationPicks +judith: $6$rounds=1000$foobar$JEwcLIBp5qOSWx5yR0nOtTl5AU0/h.SRGaA6ba5ljSpgo76TU7UDWfXCdd.wC1SlJuzzz.fg/UjbkXkoLIfp90,OpinionsClubsSubstantialCooked +judy: $6$rounds=1000$foobar$xk8R7yMEtYBFC.9/Z02nTu8LF3W.4bEwWb7FcLmafld73Ond/s58unND0KYiuIU40NpyrRDpTtL2WG1VH6SJt.,BlastUntitledIcelandIncurred +julia: $6$rounds=1000$foobar$ZoYUMw//tA0z2U7j.XP9C9KaH4/7/hnklTwL8aXCzdA8VGSuGe1dS/zLhLIn8ggNCElqIvFH5XAFpX8kIOnvn.,TeachAppearsFarmersNoted +julie: $6$rounds=1000$foobar$xjjgIe8Y06SAw88XMnkFnutipODmc3n9iioT0HkXdDP3wrvr6c72r0HhCFweAxlCCwK3us16efU04oIcOaUiy1,ActuallyLargeAttackedBeginner +justin: $6$rounds=1000$foobar$3KULwSRQA/W2A1cOAUjggSXA7s0vXaT4VXHDlByYz3xr1nnKV746hjzobsuWOjv4gy6IV8bWN7jMGSZgW.tfb/,ExperimentalSurprisingRickySound +karen: $6$rounds=1000$foobar$Dhfog2IbO3IjgwQgxloCH/.mh.oDDHwpbugDwvRyphOMf3KRZzMgiY.E7CqIOE9mstz7sGYv.MyxW8yoXtXTg/,ToothSponsoredChildrenRenew +katherine: $6$rounds=1000$foobar$2kRV7jpklePwRyM4cD5d7L5GvM1CQMMMW/MBpJg1s6TBSyIrj8QQRbrlPMM8CiUEQxlNUPS4e1YDQzVDPSYG80,CanvasSuddenHypotheticalCompounds +kathleen: $6$rounds=1000$foobar$9MM2/glyIQsClTLGYYY.DWZws2q019pZ2SW5gYtfqYraVAoqSFywy8phRv5Vqd5PN/vb2AMk4VyGsVmN2mTiw.,BraceletsExecutionSmileTribe +kathryn: $6$rounds=1000$foobar$qgx7SfIZ7CTb5Ti/XXmDG76fsu18UYTl15SGZCgFY1fy/HAfIo2rPXPgpZVYvo906ic87MqfCmPf4ggM3ft8m/,MinimumAlcoholAdditionInnocent +kathy: $6$rounds=1000$foobar$SthJO1C4LX44P9npuC8f8/KmYP4mrJjdbetqaTUhl5ogaoWuqAks5bkisrmHogYfAXjPu14MajW3fVYtwMzwP1,RespiratorySyriaChiefResearchers +kayla: $6$rounds=1000$foobar$j4lJCouYUhpwwoiEIkkPvGFDFUDTVoaV4tU6YOcX81PZEajddMI4WYJsAdHbsUZK1JVTrHC/ymPFfj2nz26M..,VintageResultCertificatesBaseline +keith: $6$rounds=1000$foobar$zaB/Q/mQZ8I4ONeH2PsU3plRU4u9n7ifQwZAA6eBcLqLvwIjAgw2zyGP8vXwHhXa7z86U.40PZR5Dv6QjbI1w/,ElectronicsElsewhereLatitudeCodes +kelly: $6$rounds=1000$foobar$WdZWjNR4FRLJOXDlSgVsl6lKeqa4N8C.gmJASZktJUxlc.eDNK.hjE2cky5lpILBHCXxylKU6fsgYDxYtVwef0,BreathingManualPerformerDestroyed +kenneth: $6$rounds=1000$foobar$9E9McHIjksSKD8InDAuu6Ko/8ktYt3yf/723X2rtSi8f6kWh7sBB0ajcbtDd9E3KceMSGXjOMyrBwfZWzR1wu0,SkirtHistoricSpellingCoins +kevin: $6$rounds=1000$foobar$eFDYRw/SRRCDtS4t.O9uLFbmIMyLs6Vf3gb/GERcC61x/VurkvG/N/7gKqkm0peqrOKUMteuRkO.g3WbcOKFt1,VariedExaminesEncyclopediaCologne +kimberly: $6$rounds=1000$foobar$FYsnjOtaknayjCBHfnqOozqflFW9v3Iw9qx.DWcfhPyaQZm8magBw/B4F164Z504kriRqe.07Q2a.kA1vfZZ81,StartingCriticalCelebrationMilitary +kyle: $6$rounds=1000$foobar$gqFrdCKNJ6IzRflj2iXHWGXYFcfQVBv/zWDpcpO2v9anZpX8mXP5NGuZfhGLg0k3v5jFHTDCNoCdvhzGliVNj/,ConflictsDairyQuantitiesAccessing +larry: $6$rounds=1000$foobar$MmG75IMq0d5rxB5uM1IkohMrGtcix46Hiw3N.rYwl2CkYwrui1ocNDGAbZL5tu5K2AOUGxhxwiA64SKDpsrhC.,PossiblyCourtesyCouplesCurrencies +laura: $6$rounds=1000$foobar$CXtGB73yHksduDdZzSbVP0/GGXWBVj.FQLgSQ0xCSRFzPjozbl6Mm8VgvFgOSVmwB99Voj0qJ77WINqITxra21,SoughtContractingOperationalManage +lauren: $6$rounds=1000$foobar$Z44g13AGHMKZlt0/UyYYQZLsT0EmKk.ab8UWyw9pFnJ3XLirrbm6mSlJ1fCEiFRPJE.savg7hm29yDw9l7rtP1,StakeInspectionCompetitionExplore +lawrence: $6$rounds=1000$foobar$vKzBWziCM/KOesRiVcSg7d7pVPHIGQFqtArDqJUi/z5H34RNesa6yAxVXsBlsGoXXgZ6/jozKtNX7TiWoxjRW0,WeblogsOuterDividendDebian +liam: $6$rounds=1000$foobar$baraX1GgmxaCqJ2wgRVNKqrWlMn/5uigEVU2kPAQrrdJyyhOP5vBCTK.TeXcFHG4qZucNxVXzFPCwjOVllLzr.,EcologicalLessonCopyrightedMemphis +linda: $6$rounds=1000$foobar$XlTDcXZ6iCEG0.ypfCyyS/MqvJYTyEVwvUQ2A8oh6yI6yRnhRHez.64imB/1Z5ApCJbBT4ko0NBpQajUalQDM/,BackedArabiaComputingBerkeley +lisa: $6$rounds=1000$foobar$0ssY8DjbZLmgQy4G9vkqGG38mPkIYkLV.DNpe19.IrDbwwjOU6K5Szy.Rs6OIITzUWaxWLxvp6JVdKLFFwPKM1,ControversialAllocatedRegardsPowers +logan: $6$rounds=1000$foobar$mn6wftx2vX2FReMiGqzwohAHm6axyvEUsXbrGE2AM.tAur1nS5JD9rU.1q8XW0bHacb2Le2xMz6c6cUWkHwhH.,PlasticOutputStretchReleases +lori: $6$rounds=1000$foobar$1l84xwpehd3FK7VSShgq0S3XB3kGq74IvZuhzGeiDrWLvTvq/AWPAJjRCMHx9umeqGI3n16ka5us928nRGsir/,PresentAnymoreTonightIncidence +lucas: $6$rounds=1000$foobar$RllqlAfXnEOr19nRkFV5QOtud/Th2GzK4ReFELbsJkCIndvFS/yVSuqsigR5Oztqdaj28Ilj8WmDPbvvBN7Dc.,ConsequenceAmazingNewcastleWatts +luke: $6$rounds=1000$foobar$940LbqROjEznDfumnbdw91a4rfVjliOe4/HJt9UnoldvmVaOvq3I.YNfBMyx8Yttp28DcVZrjmv689QRChTpl/,BallsThesaurusPaperTrustee +madison: $6$rounds=1000$foobar$gapX3kpy3HnXpl7wAEWUTz6En3OLsGT4qe1h5OaR99Iyex.X2QIODDSuKQJKzSSAoeVkMznj/AaorYhGc2oSJ.,ViolenceTermsStrongAssign +margaret: $6$rounds=1000$foobar$kASM2TTLmWOvrWr1D3eT/M0pRMRKOs4qiQl8pA0tLPQzMPysc.tuel28MwWamXmZ2C7DUoQDRxpc1S1fYFPu4.,ChaptersStartsPlainsFourth +maria: $6$rounds=1000$foobar$GpqbwJnwCMJAO6Cixn2FRDwWkduE3Thes85HqkVnEw3sYxQPz3mJkyFhNmT5Qkflcyrv1yjO.XLuqrAJPsu7v1,ResortVariablesBidderPlanners +marilyn: $6$rounds=1000$foobar$2n8ew.pOthJrQfoWiydX4t0emqe3UaAiRaKeEbtF1dSJq5mWDvQeEFVk/Gg1xUsBj3jkJgw3USdCeP19z1i9v1,PlateInnovationsYugoslaviaIrish +mark: $6$rounds=1000$foobar$LrkszyNP3Akb.fS0xvvTkka7SQDyrEabOtGwqCeF1qdVge8isLwpSVKvl5fl2.9lUWzAw2PJ9IFdA3gJVmjM21,CongressSagemTraceReprints +martha: $6$rounds=1000$foobar$DuDTtZLJ061zGRRoPB6jRFbQNl5VxOgx1KmiS2Av7b4NHXrLEw1pWVcnZr6RcqwcT.KG3hxSUdYxgKBinyWQQ/,DependLargerOperatedNewest +mary: $6$rounds=1000$foobar$vbDYt4ru7qp8a0ZpP0GTA1YBqcwuSwGyYfYDLD0HzJsoP9kI9JMfb31q5.Q.Ejn9IcGppszcWzXUDfy/WEnh1/,InfectionSuicideFixesHearing +mason: $6$rounds=1000$foobar$xtYSU5FvMS2PAQyY7t.BiiwdTeYHpu86c5py.BqDQZQ7UiVBtRKtQkUuLX.Bjvfh93thNcfFXDla6TjwesEmn/,RoyaltyMinimalFourthRecreational +matthew: $6$rounds=1000$foobar$ocmZUTtwEntGNV7SW.7ldrxMPQfoNPk7g/mwxT7WrId0um/IFLwQRIXGLcRQumZr7rx02X3zBuLF/z.voxsdh/,SublimePotterConfiguringDanny +megan: $6$rounds=1000$foobar$ISvkPiWVMzub9ZBZmrKl8lK/D3Zh.yzwPZREt9cVs.50fUgvcaPV4KAY.CHqBsK5rjq2TDsd/9ubIXKJYdWFT.,ScannedExemptIllustrationsSquirting +melissa: $6$rounds=1000$foobar$MKMbVKnBM0d0cQhNYwbRIf0eD3reOKtdC1FCPhG1scFhtA1MubF877BxlnkIUNJHb.Pi7qtfYSLbO0jaZJqxt.,ForecastsChargesRecordFinance +michael: $6$rounds=1000$foobar$bc1.xAOvZB1IUahkKBiQEORRSYxtS0odgn78JM2Zdv3Ok5yzTxQaucqhkNhFMK/mK8W.vzxUO.M678BNEcQsI1,AdobeIngredientsGlobeDeclare +michelle: $6$rounds=1000$foobar$mENSc1yikBj3ZiXkW1qGoSrvGxyQZqfgUme.1DUWkvxA2ETaxJIDrXVSCEeCutC5YiaQl9wKSsoHliOLPFBww.,TightGeneticsStrokeSmaller +nancy: $6$rounds=1000$foobar$mXlNWgxoIB4Ukc1Kv/Y4vCO0ipuV.OhLcLPPHF3FGo7bhgGCdTzzI7xJUFzLrkXGm.9mpclXcAFdxXlY/m4Cu/,FavourEssentiallyMobilesPrecisely +natalie: $6$rounds=1000$foobar$A.g4TkLKKjMlebEtIUeiwin/.VHeyYmoV.9LrroULBzJ8q.HkHUJN1FD9HTHWRDfQiQYjIRIn3/V33.wQlrEO1,KennedyFiltersFacialNodes +nathan: $6$rounds=1000$foobar$n8MRulpmZ1oQR3mp9ffEFr6EG5NAtsQ2osnrM2NDTZlsGMT9UwJ9V9ry0u8aCAbQOX7V8WkvQDbWPwwBxX05V/,TravelersAggressiveMuslimsAnswers +nicholas: $6$rounds=1000$foobar$44hKtISXPPbYjBHMNrrt3L5OP8TPcEEqsmZcpii3qhuy.htpyIvuVEbRXBsKNVOeFcR1uJFY1vXovZljeVvm5.,ParticularPlymouthDivingPittsburgh +nicole: $6$rounds=1000$foobar$0KrE6cbQ0QI/RzDLG2jeWp2KshUVSW5k5YwDI2wPB/fCxmnyK8OL4WY0GOWK827Qs06v4vY3e5j8xrdB6e6px1,KeyboardDozenAcceptsJacob +noah: $6$rounds=1000$foobar$oB1XvsNDywFHvQ.tI4hHvQfB16u5LqkRjaeU1Z49rpvFQwzWGeYogfFOI/4JXBomwec/RmUX6GAfQl6C9LXjn1,ShortlyHollywoodRouterTrial +olivia: $6$rounds=1000$foobar$Os45sGP6GArpMRSnvw5IfqCabbPId8FMCu7Ge4ceQ.CmTo/ph/ul5oapaoLJu.cYBUNadRRzUjYiA/ew4LVWe.,IndustriesCaymanSonicLuther +pamela: $6$rounds=1000$foobar$yYLpRFR1lApm55bVO/RvmzPcXnldfk5DQlRZzaf54lmT4Vv.V6thgRlUNsDe.ZEMvCVDDUHeqqhdliJ8afnAA.,ChambersTrucksNotreSatisfy +patricia: $6$rounds=1000$foobar$iyvHuvnSS4mf2VC0dxEzE4JKttF7R/akDvBxGtIdJqrzm2i.u5YCcDj.Cob0KS7zhZ5zfHy4jP0UPOnkxtZ6r0,SubmissionsAssociationTransitWhereas +patrick: $6$rounds=1000$foobar$TpVIjY43m6WMzy7ZLxGI3Q2O2pF1SOYBUjV7TBezrLKn531V1UE/wRIq93kveoYcdErkjvuGqah0anGfbUC4B0,FundraisingMalawiSecretProzac +paul: $6$rounds=1000$foobar$PWChlv..YaJoVRPLNvwwrJmQ6RrGK101q4Vd3i7q9iuHzNRDC5bybTPbis3JcAC006vBfAaRPrydXTGirhKpR.,ThemselvesJapaneseThirtyAdaptor +peter: $6$rounds=1000$foobar$B2EuWhsFOssmN2Rzm0LdCrSevaa0Z4JD1Ag7jKk5Cd5p/SjWiay0bPOwR.3hSHmFW5yhPjMviB7hrS3OzEKtX/,AccountAmountSlopeCattle +rachel: $6$rounds=1000$foobar$jBzJI0F4zHKGpdrOjaoGKjMj56sBB49uKRZ/9u63hlqo8oczAPa6lfqLGINfgvWrfAHFBDrmiLmq1KPWYG/oP0,ReviewedShadowBeaverKentucky +randy: $6$rounds=1000$foobar$ewRjuVFtJYImbZkUIm8co8HB6H1goX1t0J/H2c6AO/I281ws0tK0uYh0Hhc5XxnVX8f5RcvYUmkJ37TR3A5LN0,StephenHandsRentalsConditions +raymond: $6$rounds=1000$foobar$LLGUD351Bz0VER9K4YXsH/7jnszzd7m6iqzfAGbuYRiENkBJA0.WZD3sRi2cBDzOsdtl91YtuRo0jWigq/YcX.,RailwayMightHeadquartersDemonstrate +rebecca: $6$rounds=1000$foobar$AEaejdY6xmBZgloll46J7zPAWPWhb.Wd27sHRv7hugIvRNrPxM9Fqrsb.ZpClp/dJNWxMnvgugrvP1Lu87gP31,BootsImmigrantsCreativityBiography +richard: $6$rounds=1000$foobar$s954H7sB0.PUlMBB/zH80FBlwh6Xs1c26pit3LfWbn83QCfNTs7oTktk.tHgZlh/Oq.KQZn5BzbZgGnoBOdAw.,PresentedStrictlyOperationalUniverse +robert: $6$rounds=1000$foobar$qt5L6s3JtPT7igjzh99XEbmojghs2ssmIBFz7MXM2/R7Hn5fcwx0fF2x9ozeQwERuKz202k9yeM83pCL1mCsN/,EditionBraceletNativeRalph +roger: $6$rounds=1000$foobar$WvrnIEejdQxGoiL0zH7zaaHFl7WV11O6hJnH6w19RIZ2zbfHjDyHm.kNdfZQK2XeZ0n1ibUd1hEMRANtVJaqI1,GnomePursuantBravePodcasts +ronald: $6$rounds=1000$foobar$2gfnmtcLM5ugGbtyiW7B4SC0t9EbXNUI7Dn//Ugumm9E09jV6cyBle.R5dwmlbR3ilnMZ/jnb11xaomnQ84w41,BoliviaMarieDetectedDistinct +ruth: $6$rounds=1000$foobar$ZaQg9KvNMKwQXM2WrPMrt6Kpm6frROERXfGt21CZOQe66uUJGjN7qJgNFZcZk0YEXE7Bdi9kdE4QBpgf9xyCn.,DeltaMilesIllustratedPolished +ryan: $6$rounds=1000$foobar$pP1vGnX6JTeCb08kpiz.J275pZglzAm79wJUwYrhoKH/BPSBrrgrP80CR4dTuCra5kpevv2tzTraw1DtoF2Le1,KnivesNudistSystemMessage +samantha: $6$rounds=1000$foobar$GpPpOtbum46lshxY3pt10GDydFIDEEng5loxGTR37SWhCjJz1P1Nr4NPZbCrcsbrgpTz5RWgHscDatdqXPuk51,PercentBingoEmissionsContinent +samuel: $6$rounds=1000$foobar$HYLAgSkvwnrv9MIFiZVqBM0.lF89I1t1F/KpnV7AMMe4I76mDe.aptEUJl0yOvDdrhuIRSWafzPhokPk9yc2e1,CalledPendingHeadingBeverages +sandra: $6$rounds=1000$foobar$S8xQR/B2gERfHz02RHPRw4YAnN9X.IhW607/8HHEwOrX7s/vihfyrmE.U4O4pK075HLPNnn52cSgSVPQcapnU1,SamoaAttractionAccommodateSonic +sara: $6$rounds=1000$foobar$BCeAnZZmS3Ufk6hW67PeA5P0az4sCKNejp5AcXE8pFCXXyQbtvvRy/Vf45kGP9CTlrbK9v1sFWT4SbARmSjv6/,ProvidingAdvantageEntireLanes +sarah: $6$rounds=1000$foobar$YGcqyDl99HMrGB3bJ3qrieDUNFkOfinTcMa.CHqeVF1DySJ0tHxGhZXQ5mgxO5h2MDjBxpa.f3kqEuFe72kjX1,PeterPublicAbstractDescending +scott: $6$rounds=1000$foobar$Vrq6NKaL/xOYDrdJjSxfLyN8qYpuTCrolCf1A50RqkFmxBk8PXg.mmVXeTURndLGrwFB9CjTHtdU4T8nkgnUF1,TeacherUpcomingAdvancementMetallic +sean: $6$rounds=1000$foobar$6dnZ5CqQD.BlGfzL1YEWuzmFsAR1yCXPYyY5F7PnYNdOg5u9jvYlPBTkBAZOENMIoo3DVsErIXeMRU2WOTt/V.,CarriersSquadArubaConsolidation +sharon: $6$rounds=1000$foobar$noogFI7R39yAvo4LETAlLgTTxWgkx5jZ05ygZWmD5HQvQAtdK6/CjxdRXdcl8XPurUDusTS7Xs/QISwehLGQn1,HighwayMagneticParticipationProvisions +shirley: $6$rounds=1000$foobar$LE6JSJFh43xH0oWXitF.zaJ/cUv7.zvYacW9j9L11zF7Qv9u1qY6iIMdZGigHsdYimvYG.HLBbY6fUs5tzeGJ1,GlassesMiniatureApproximatelyPermissions +sophia: $6$rounds=1000$foobar$FTyhbu8311bYDcUcPxMrj5BDUWOCQuY2e0kcgakSWC6htrXorbizIFhU5dosH8XNAspljEh2IjdfZL9jV.AGW0,FrequentlyAssistsAssignedArbor +stephanie: $6$rounds=1000$foobar$YSIqsFh1QwEUQ/PdcQE1Xkt04vjOkqlld7gN5uPQ4iRITfZQ7j50piiAq8hjLOYzgS4UunZRJKFBPw1/DsIXM0,ComplianceFarmingLimitRentals +stephen: $6$rounds=1000$foobar$P/SaadViSKuHFZUZgyS/UGKZ71R5duneeGNCXNYLYyjJq2..ogDkORaOFEwGRjNaUBoegTiQdxIXktQBKXm9d0,GamespotConnectorHighwaysCourts +steven: $6$rounds=1000$foobar$hKEC5RxQwZbs3SNDxKUILamePPSSvlI9lU4Woe0HUyOwqpXvZw4sCsTtHY/.O.7Thrbh.frYp/.I7nbN4qR8h0,ChoosePleaseEpinionsSalem +susan: $6$rounds=1000$foobar$ElmgjhK8HpP.J8HEpCFfY9lKihY00dXbifqX0P2T1vWTtRfdhcg37BDbnU48/8SSq1/If5FzxlTBGRx2uN9DP.,CloudsInstallingUtilityLuxembourg +teresa: $6$rounds=1000$foobar$YsR6m5aIzW9WNaQVgZ2OcqAOsr8RIXrAGgjLTxqtcw0tAlE1xU0UkWE4rf5QwUvLGcnLqKwDKGTpcERu4qAZw/,FotosAddressesFlowerInvasion +terry: $6$rounds=1000$foobar$N0gquC4y9JnanNZDG1L4p0Q2KHvZnyEw6MfhjtK1QJKtG5a/PW4O67/xFKdaFBC87TdQScsmFWjP0glI8dIuU1,ChangelogRationalMountedSponsorship +theresa: $6$rounds=1000$foobar$VAU6fB65ZG2VfgBt9PrMb8DX57qynJU8kUWAX7DQn3jo7UpBYdp99DsjfW0oWG05dY6Ndmd4brTfU4k0x8zvl0,BrutalAppreciatedAucklandPositions +thomas: $6$rounds=1000$foobar$.Hk4uVMnVmh4KzkdpZ/z8ToSkdCIYNPTcUVmzA9wr.8u/HGOlGzLSqpLufrSWU3d2IFr/SnjneOSlVfmPuxLO/,WarrantiesDeutschlandRepublicDistances +tiffany: $6$rounds=1000$foobar$ITse2sfHeVYSMjG6xY3fyznwPXwFeKZeBjvw1Bg5N4g6G8Fjo7ffktzoIxX3VKKTVK6l3iRr6dksKNuEYjQrV.,ConstraintsAnotherWhetherModules +timothy: $6$rounds=1000$foobar$4DSzxNENU17W38nE1ljHyfRLalkATEd0kvMxsR9VIZ5YpDU6ochdwcLMZJn4l6zrILf5rwuMHdFV6u7M.n.q//,OriginalTroopsLevelPsychology +tyler: $6$rounds=1000$foobar$axMaCayjttUIaSLyWHuLnhtshqIO0DgZLTsj/4H39TS5h91udUHqpRxieu39bN6o4JpOHhjY0GTnj.W0wONhX0,TamilRolandAccordanceResponse +victoria: $6$rounds=1000$foobar$EQRrMnXIsJKgH055Gjh1qQw0Z7ejNF0Z1Y91siXjd.0ii1Ihb54XeSIFGFpDi3F9Ik2QECC/0BbizvzuzGk010,TerraceCompilationCachedBangbus +vincent: $6$rounds=1000$foobar$pxZlJYT94LtvdR0KmzvQ113M0mP5fCKhVHUQOFjEQvXaz0.CSZ9LDhDmNQ6fxc5aR/GeQ70VPAF1lu5pMf0V71,ArrestChristianExpansysComposite +virginia: $6$rounds=1000$foobar$y64JbpwXDJyVYjZBXEtMIHn.L/Kutzrleqh8OQVIPqjC6u4EavVjo8Vj502HC1mgP5m2vT.JpwdnhzBcGw6C60,TicketMoneyGroundImperial +walter: $6$rounds=1000$foobar$YhrbkGUGLCpZ31/IvoOLPmiqCSl21hvtrX2VfJIAfKWVyUrjdZ.OdO4l1F/g2iz3W/Z/kuU/7bxi3rNoEP7G1.,CopperCoastConfusedRecent +wayne: $6$rounds=1000$foobar$Fgsekrc29M0xcJoWOXRKSUxWgSDPOZVXzEjF53JviXg2hMcsnyp2.haAwEmyT3PqBBgp79cufCU6d8g2E60mZ0,SignatureWidespreadCrossingBookmark +william: $6$rounds=1000$foobar$fnaFDubi8rG7yfiqhKpmtFTLHGR2qy3.oy8hy2S1wAcjQcivs2EbbhaVaUj9XtBybVUJLhGDnjo8m1I11Ct8U0,ObtainedHendersonCentrePayroll +willie: $6$rounds=1000$foobar$/Pw0tVuAwAoTszWwOXtQYSRj.7qNFqc2C21yL0V0HNcZCJOQ60q4x.PNCy8TOzSG9rxzrSrb.7Lc4z9bAcP2J0,SchemaWroteConnectivityInfinite +zachary: $6$rounds=1000$foobar$HpbILboobvEJg28.j.QWvwxRcEtffycGBKGEHAx1llYWa7kr4KmsBPsVTTxojj0pPe/Mc.zv1jOBFtE2Vbxjx/,PipelinePanelEmeraldIncluded diff --git a/namepicker/peg-creator.py b/namepicker/peg-creator.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/namepicker/peg-creator.py @@ -0,0 +1 @@ + diff --git a/namepicker/pyproject.toml b/namepicker/pyproject.toml new file mode 100644 index 0000000..05e3688 --- /dev/null +++ b/namepicker/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "namepicker" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [] diff --git a/namepicker/webserver.py b/namepicker/webserver.py new file mode 100644 index 0000000..d5d68af --- /dev/null +++ b/namepicker/webserver.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +""" +Simple HTTP server that works on both IPv4 and IPv6. +It prints the client address (src) and the local address (dst) for each request. +""" + +import logging +import socket +from http.server import BaseHTTPRequestHandler, HTTPServer +from socketserver import TCPServer + + +## wrangled together by stackoverflow & duck.ai GPT-OSS 120B to add dualstack support +class S(BaseHTTPRequestHandler): + def _set_response(self): + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + + def do_GET(self): + logging.info( + "GET request,\nPath: %s\nHeaders:\n%s\n", + self.path, + self.headers, + ) + self._set_response() + self.wfile.write("
".encode())
+
+        # echo request info
+        self.wfile.write(
+            f"GET request for {self.path} from {self.headers}\n".encode()
+        )
+        self.wfile.write(
+            f"client (src_ip, src_port)   = {self.client_address}\n".encode()
+        )
+        self.wfile.write(
+            f"local  (dst_ip, dst_port)   = {self.request.getsockname()}\n".encode()
+        )
+        self.wfile.write("
".encode()) + + def do_POST(self): + length = int(self.headers.get('Content-Length', 0)) + post_data = self.rfile.read(length) + + logging.info( + "POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n", + self.path, + self.headers, + post_data.decode('utf-8', errors='replace') + ) + self._set_response() + self.wfile.write(f"POST request for {self.path}".encode()) + +def make_dualstack_server(host: str, port: int, handler): + """ + Return a TCPServer that listens on both IPv4 and IPv6. + The socket is created, configured and bound **once** by us, + then handed to TCPServer. We only call `server_activate()`. + """ + # 1️⃣ IPv6 socket + sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + + # 2️⃣ Enable dual‑stack (IPv4‑mapped IPv6) on platforms where the + # default is IPv6‑only (macOS, Windows) + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + + # 3️⃣ Reuse address – convenient during development + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + # 4️⃣ Bind the socket ourselves + sock.bind((host, port)) + + # 5️⃣ Build the server **without** the automatic bind/activate step + server = TCPServer( + server_address=None, # not used because we bind manually + RequestHandlerClass=handler, + bind_and_activate=False, + ) + server.socket = sock # replace the internal socket + # server.server_address is optional; we set it for nice repr() + server.server_address = (host, port) + + # 6️⃣ Only activate (listen) – do NOT call server_bind() again + server.server_activate() + return server + + + +# ---------------------------------------------------------------------- +# Main entry point +# ---------------------------------------------------------------------- +def run(host: str = "", port: int = 8080): + """ + Starts the HTTP server. + * host = '' → listen on all interfaces (IPv4 & IPv6) + * host = '::' → same as '' but explicit IPv6 + * host = '0.0.0.0' → IPv4 only (still works with dual‑stack socket) + """ + logging.basicConfig(level=logging.INFO, + format='%(asctime)s %(levelname)s %(message)s') + httpd = make_dualstack_server(host, port, S) + + logging.info(f"Starting HTTP server on {host or '::'}:{port} (dual‑stack)") + try: + httpd.serve_forever() + except KeyboardInterrupt: + logging.info("Keyboard interrupt – shutting down") + finally: + httpd.server_close() + logging.info("Server stopped") + + +if __name__ == '__main__': + import sys + if len(sys.argv) == 3: + # usage: script.py + run(host=sys.argv[1], port=int(sys.argv[2])) + elif len(sys.argv) == 2: + # usage: script.py + run(port=int(sys.argv[1])) + else: + run() \ No newline at end of file diff --git a/namepicker/xkcd_passwords.py b/namepicker/xkcd_passwords.py new file mode 100644 index 0000000..d99bb3f --- /dev/null +++ b/namepicker/xkcd_passwords.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +""" +XKCD “correct-horse-battery-staple” password generator. + +Features +-------- +* Uses the official EFF large word list (7776 words, ~12.9 bits/word). +* Caches the list in the user's cache directory. +* Fully type‑annotated, no external dependencies beyond the standard library. +* Optional extra digit / symbol and customizable separator. +""" + +import hashlib +import os +import random +import sys +import urllib.request +from pathlib import Path +from typing import List + +# ---------------------------------------------------------------------- +# 1️⃣ Load / cache the word list +# ---------------------------------------------------------------------- +WORDLIST_URL = ( + # "https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt" + "https://github.com/first20hours/google-10000-english/raw/refs/heads/master/google-10000-english.txt" +) +CACHE_DIR = Path(os.getenv("XDG_CACHE_HOME", Path.home() / ".cache")) / "xkcd_passgen" +CACHE_DIR.mkdir(parents=True, exist_ok=True) +# WORDLIST_PATH = CACHE_DIR / "eff_large_wordlist.txt" +WORDLIST_PATH = CACHE_DIR / "google-10000-english.txt" + + +def _download_wordlist() -> None: + """Download the EFF word list to the cache directory.""" + print("Downloading word list …", file=sys.stderr) + with urllib.request.urlopen(WORDLIST_URL) as resp, open( + WORDLIST_PATH, "wb" + ) as out: + out.write(resp.read()) + + +def _load_wordlist() -> List[str]: + """Return the list of words (one per line, stripped).""" + if not WORDLIST_PATH.is_file(): + _download_wordlist() + words = [] + with open(WORDLIST_PATH, "r", encoding="utf-8") as f: + for line in f: + # The file format is: 12345 word + line = line.strip() + if " " in line: + parts = line.strip().split() + if len(parts) == 2: + words.append(parts[1]) + else: + if len(line) > 4: + words.append(line) + if not words: + raise RuntimeError("Failed to load any words from the word list.") + return words + + +# Load once at import time – cheap after the first run +WORDLIST = _load_wordlist() + + +# ---------------------------------------------------------------------- +# 2️⃣ Core password generator +# ---------------------------------------------------------------------- +def generate_password( + num_words: int = 4, + separator: str = " ", + capitalize: bool = False, + add_digit: bool = False, + add_symbol: bool = False, + rng: random.Random | None = None, +) -> str: + """ + Return a password consisting of *num_words* random words. + + Parameters + ---------- + num_words: + Number of words to concatenate (default 4 → ~52 bits of entropy). + separator: + String placed between words (default space). Use ``''`` for a + “passphrase” without spaces. + capitalize: + Capitalise the first word (adds ~1 bit of entropy). + add_digit: + Append a random decimal digit (adds ~3.3 bits). + add_symbol: + Append a random symbol from ``!@#$%^&*()-_=+[]{};:,.?`` (adds ~5 bits). + + Returns + ------- + str + The generated password. + """ + rng = rng or random.SystemRandom() # cryptographically strong RNG + + chosen = [rng.choice(WORDLIST) for _ in range(num_words)] + + if capitalize: + chosen = [c.capitalize() for c in chosen] + # chosen[0] = chosen[0].capitalize() + + password = separator.join(chosen) + + if add_digit: + password += str(rng.randint(0, 9)) + + if add_symbol: + symbols = "!@#$%^&*()-_=+[]{};:,.?" + password += rng.choice(symbols) + + return password + + +# ---------------------------------------------------------------------- +# 3️⃣ Helper to compute entropy (optional but nice to have) +# ---------------------------------------------------------------------- +def password_entropy(num_words: int, add_digit: bool, add_symbol: bool) -> float: + """ + Approximate entropy in bits for the given configuration. + + The EFF list has 7776 words → log₂(7776) ≈ 12.9 bits per word. + """ + bits_per_word = 12.9 + entropy = num_words * bits_per_word + if add_digit: + entropy += 3.32 # log₂(10) + if add_symbol: + entropy += 5.0 # log₂(≈32 symbols) + return entropy + + +# ---------------------------------------------------------------------- +# 4️⃣ Command‑line interface (optional) +# ---------------------------------------------------------------------- +def _cli() -> None: + import argparse + + parser = argparse.ArgumentParser( + description="Generate XKCD-style passphrases." + ) + parser.add_argument( + "-n", "--num-words", type=int, default=4, + help="Number of words (default: 4)." + ) + parser.add_argument( + "-s", "--separator", default=" ", + help="String placed between words (default: space)." + ) + parser.add_argument( + "-c", "--capitalize", action="store_true", + help="Capitalize the first word." + ) + parser.add_argument( + "-d", "--digit", action="store_true", + help="Append a random digit." + ) + parser.add_argument( + "-S", "--symbol", action="store_true", + help="Append a random symbol." + ) + args = parser.parse_args() + + pwd = generate_password( + num_words=args.num_words, + separator=args.separator, + capitalize=args.capitalize, + add_digit=args.digit, + add_symbol=args.symbol, + ) + print(pwd) + # print(f"≈ {password_entropy(args.num_words, args.digit, args.symbol):.1f} bits of entropy") + + +if __name__ == "__main__": + _cli() diff --git a/presentation/.crossnote/config.js b/presentation/.crossnote/config.js new file mode 100644 index 0000000..80613c4 --- /dev/null +++ b/presentation/.crossnote/config.js @@ -0,0 +1,15 @@ +({ + katexConfig: { + "macros": {} +}, + + mathjaxConfig: { + "tex": {}, + "options": {}, + "loader": {} +}, + + mermaidConfig: { + "startOnLoad": false +}, +}) \ No newline at end of file diff --git a/presentation/.crossnote/head.html b/presentation/.crossnote/head.html new file mode 100644 index 0000000..079058b --- /dev/null +++ b/presentation/.crossnote/head.html @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/presentation/.crossnote/parser.js b/presentation/.crossnote/parser.js new file mode 100644 index 0000000..0f6b5a9 --- /dev/null +++ b/presentation/.crossnote/parser.js @@ -0,0 +1,12 @@ +({ + // Please visit the URL below for more information: + // https://shd101wyy.github.io/markdown-preview-enhanced/#/extend-parser + + onWillParseMarkdown: async function(markdown) { + return markdown; + }, + + onDidParseMarkdown: async function(html) { + return html; + }, +}) \ No newline at end of file diff --git a/presentation/.crossnote/style.less b/presentation/.crossnote/style.less new file mode 100644 index 0000000..cf7e2ab --- /dev/null +++ b/presentation/.crossnote/style.less @@ -0,0 +1,8 @@ + +/* Please visit the URL below for more information: */ +/* https://shd101wyy.github.io/markdown-preview-enhanced/#/customize-css */ + +.markdown-preview.markdown-preview { + // modify your style here + // eg: background-color: blue; +} diff --git a/presentation/README.md b/presentation/README.md new file mode 100644 index 0000000..cf3ceda --- /dev/null +++ b/presentation/README.md @@ -0,0 +1,2 @@ +# how to build +```docker run --rm -v $PWD:/home/marp/app/ -e LANG=$LANG marpteam/marp-cli presentation.md``` diff --git a/presentation/img/adam-winger-GIFlfKX23rc-unsplash.jpg b/presentation/img/adam-winger-GIFlfKX23rc-unsplash.jpg new file mode 100644 index 0000000..18dff96 Binary files /dev/null and b/presentation/img/adam-winger-GIFlfKX23rc-unsplash.jpg differ diff --git a/presentation/img/artbreeder-composer-2024-07-14T22_49_42.899Z.jpeg b/presentation/img/artbreeder-composer-2024-07-14T22_49_42.899Z.jpeg new file mode 100644 index 0000000..e992baa Binary files /dev/null and b/presentation/img/artbreeder-composer-2024-07-14T22_49_42.899Z.jpeg differ diff --git a/presentation/img/artbreeder-composer-2024-07-15T00_14_43.169Z.jpeg b/presentation/img/artbreeder-composer-2024-07-15T00_14_43.169Z.jpeg new file mode 100644 index 0000000..70450a8 Binary files /dev/null and b/presentation/img/artbreeder-composer-2024-07-15T00_14_43.169Z.jpeg differ diff --git a/presentation/img/artbreeder-image-2024-07-15T00_14_32.493Z.jpeg b/presentation/img/artbreeder-image-2024-07-15T00_14_32.493Z.jpeg new file mode 100644 index 0000000..5d33302 Binary files /dev/null and b/presentation/img/artbreeder-image-2024-07-15T00_14_32.493Z.jpeg differ diff --git a/presentation/img/artbreeder-image-2025-10-25T11_33_45.435Z.jpeg b/presentation/img/artbreeder-image-2025-10-25T11_33_45.435Z.jpeg new file mode 100644 index 0000000..f63b04d Binary files /dev/null and b/presentation/img/artbreeder-image-2025-10-25T11_33_45.435Z.jpeg differ diff --git a/presentation/img/bgp-unnumbered-example-eos-cfg.png b/presentation/img/bgp-unnumbered-example-eos-cfg.png new file mode 100644 index 0000000..47852de Binary files /dev/null and b/presentation/img/bgp-unnumbered-example-eos-cfg.png differ diff --git a/presentation/img/bgp-unnumbered-leaf3-ping.png b/presentation/img/bgp-unnumbered-leaf3-ping.png new file mode 100644 index 0000000..d1ffa64 Binary files /dev/null and b/presentation/img/bgp-unnumbered-leaf3-ping.png differ diff --git a/presentation/img/bgp-unnumbered-playbook-excerpt.png b/presentation/img/bgp-unnumbered-playbook-excerpt.png new file mode 100644 index 0000000..1356027 Binary files /dev/null and b/presentation/img/bgp-unnumbered-playbook-excerpt.png differ diff --git a/presentation/img/bgp-unnumbered-show-interface-mapping.png b/presentation/img/bgp-unnumbered-show-interface-mapping.png new file mode 100644 index 0000000..a83975f Binary files /dev/null and b/presentation/img/bgp-unnumbered-show-interface-mapping.png differ diff --git a/presentation/img/cajeo-zhang-EbOAPSCwjnU-unsplash.jpg b/presentation/img/cajeo-zhang-EbOAPSCwjnU-unsplash.jpg new file mode 100644 index 0000000..9617867 Binary files /dev/null and b/presentation/img/cajeo-zhang-EbOAPSCwjnU-unsplash.jpg differ diff --git a/presentation/img/diagram.png b/presentation/img/diagram.png new file mode 100644 index 0000000..644564e Binary files /dev/null and b/presentation/img/diagram.png differ diff --git a/presentation/img/felix_evpn_design_2.drawio.png b/presentation/img/felix_evpn_design_2.drawio.png new file mode 100644 index 0000000..cb898d4 Binary files /dev/null and b/presentation/img/felix_evpn_design_2.drawio.png differ diff --git a/presentation/img/jonatan-david-K7MkzR7rBmM-unsplash.jpg b/presentation/img/jonatan-david-K7MkzR7rBmM-unsplash.jpg new file mode 100644 index 0000000..185980e Binary files /dev/null and b/presentation/img/jonatan-david-K7MkzR7rBmM-unsplash.jpg differ diff --git a/presentation/presentation.md b/presentation/presentation.md new file mode 100644 index 0000000..e28ecde --- /dev/null +++ b/presentation/presentation.md @@ -0,0 +1,78 @@ +--- +theme: jobs +_class: lead +size: 16:9 +paginate: true +#footer: "Felix EVPN Demo" +marp: true +backgroundColor: #fff +backgroundImage: url('https://marp.app/assets/hero-background.svg') +--- + +# **EVPN Demo** + +2024-07-17 Munich +Felix Schüren + +--- +![bg left:66% contain](./img/felix_evpn_design_2.drawio.png) +## lab diagram reference +Lo0 numbering from left to right, starting at ```.1``` +management via hostname, prefix is ```clab-f3-``` + +- Spines + - 10.0.255/24 +- Leaves + - 10.0.254/24 + +--- + + + +![bg left:33% contain](./img/felix_evpn_design_2.drawio.png) +- basic Spine/Leaf architecture +- border/service leaf + - keep spine config as simple as possible + - help humans +- eBGP underlay + - I come from a service provider background, BGP is second nature +- bgp unnumbered instead of /31 + - easier debugging of BGP sessions etc as you can see the Interface name in BGP :) + - no IPAM for underlay transfer links + +--- +![bg](./img/bgp-unnumbered-playbook-excerpt.png) +![bg](./img/bgp-unnumbered-example-eos-cfg.png) + +--- +![bg contain](./img/bgp-unnumbered-show-interface-mapping.png) +![bg contain](./img/bgp-unnumbered-leaf3-ping.png) + +--- +MLAG: querlink noetig, aber weniger EVPN-Routen, vendor-spezifisch +EVPN-Multihoming: kein querlink, aber mehr Routen, ... + +underlay/overlay via AVD fertigmachen + +service deployment via ansible oder so (VLANs, VNIs, ...) + +v4-less underlay? 5549 nexthop rewriting mit interface-peering, damit baut sich ein neues leaf quasi umsonst + +weitere leaf(s) dazubauen im Betrieb + +eAPI, eAPI-Docs raussuchen + +show commands rund um evpn (welche mac wo, aktueller Zustand, etc) + + +tmux auf einem arista-switch installieren und casual anmoderieren + +habe mir ueberlegt, mal eben prometheus/loki etc drumrumzubauen, aber Zeit, ... \ No newline at end of file diff --git a/presentation/ssh-advanced.md b/presentation/ssh-advanced.md new file mode 100644 index 0000000..2127601 --- /dev/null +++ b/presentation/ssh-advanced.md @@ -0,0 +1,89 @@ +--- +theme: uncover +_class: lead +size: 16:9 +paginate: true +footer: "SSH advanced uses - Felix" +marp: true +backgroundColor: #fff +backgroundImage: url('https://marp.app/assets/hero-background.svg') +--- +# **SSH advanced uses** + +Cool things you can do with **ssh** beyond the basics +(**s**ecure **sh**ell, though nobody calls it that) + +--- +## TOC +1. [Port forwarding](#port-forwarding) +2. [ProxyJump](#proxyjump) +3. [Certificates](#certificates) +4. [X11 forwarding](#x11) +5. [outdated hosts](#modern-client-old-hosts) +6. Control channel reuse +7. ssh_config best practices & tricks +--- +# Port forwarding + +--- +# ProxyJump +The problem: *remote-server* can only be accessed from *jumphost*, so you need to connect to *jumphost* first and then connect to *remote-server*. + + +openssh from ca 2016 on allows you to do +```ssh -J user@jumphost root@remote-server``` + + +--- +# Certificates + +Awesome! + +--- + +## create CA +```ssh-keygen -f my_ssh_cert_authority``` + + +--- + +## sign a pubkey +```ssh-keygen -V +1h -s my_ssh_cert_authority -I felix_via_cert my_test_user.pub``` + +This creates ```my_test_user-cert.pub```, get this back to the user requesting access. + +--- +### inspect a signed cert +```ssh-keygen -L -f my_test_user-cert.pub``` +``` +my_test_user-cert.pub: + Type: ssh-ed25519-cert-v01@openssh.com user certificate + Public key: ED25519-CERT SHA256:VJyz194XhAw4HcMZ5uboj/35ZJyC9yNLP0lLtjiKCX8 + Signing CA: RSA SHA256:5Fs780JRzis+3lEreIZGoo+Ao7hKX8ksUU58cI58AyQ (using rsa-sha2-512) + Key ID: "felix_via_cert" + Serial: 0 + Valid: from 2024-07-14T13:46:00 to 2024-07-14T14:47:34 + Principals: (none) + Critical Options: (none) + Extensions: + permit-X11-forwarding + permit-agent-forwarding + permit-port-forwarding + permit-pty + permit-user-rc +``` + +--- +## use your shiny new cert + +for openssh based systems, place the $IDENTITY-cert.pub file next to the $IDENTITY file. Make sure it has the correct permissions (of 0600), and it will be used automatically when you specify to use $IDENTITY. + +--- +# X11 +- check out x2go + +--- +# modern client, old hosts +Increasingly often, I get old (older than 2016 or so) kit that does not let me connect \ No newline at end of file diff --git a/presentation/ssh-basics.html b/presentation/ssh-basics.html new file mode 100644 index 0000000..e8a2eea --- /dev/null +++ b/presentation/ssh-basics.html @@ -0,0 +1,281 @@ +**SSH 101 with Felix**
+

SSH 101 with Felix

+

Things everybody assumes you already know about ssh
+(secure shell, though nobody calls it that)

+

Felix Schüren
+2025-11-09 DENOG17 Essen

+

Background illustration created with Artbreeder

+
SSH 101 - Felix
+
+
+

Intro

+
    +
  • Which of you... +
      +
    • use ssh already?
    • +
    • use port forwarding?
    • +
    • use ProxyJump?
    • +
    • use ssh certificates?
    • +
    • use Windows? MacOS? Linux? Something else?
    • +
    +
  • +
+
+
+

Timeline ideas

+
    +
  • passwordless login
  • +
  • port forwarding
  • +
  • jumphosts, agent forwarding, proxyjump
  • +
  • host_keys / mitm
  • +
  • ssh_config
  • +
+
+
+

ideas

+
    +
  • Im laufenden Lab auf einem jumphost die host_keys ändern, um Warnungen zu produzieren
  • +
  • agent_forwarding abgreifen und mich als einen der User einloggen
  • +
  • Hinterher aufzeigen (frei nach "I've been here the whole time!"), dass man cert-based logins benutzt hat
  • +
  • ggfs Stacy Fakename, Joanna Fakename als Easter eggs?
  • +
  • wie baue ich eine gute ssh_config
  • +
+
+
+
    +
  1. How?
  2. +
  3. Host Keys
  4. +
  5. Key-based auth
  6. +
  7. SSH Agent
  8. +
  9. Agent forwarding
  10. +
+

Illustration created with Artbreeder

+
+
+

how?

+

The essence behind SSH is asymmetric cryptography
+(public/private key cryptography)

+
    +
  1. you give others a "servant key" (public key) +
      +
    • can only be used to encrypt data
    • +
    +
  2. +
  3. you keep the master key (private key) +
      +
    • this decrypts the data that was encrypted with 1.
    • +
    • keep this very secure, never give it away.
    • +
    +
  4. +
+
+
+

host keys

+

Very similar concept to user/client keys, the host/server also has a private/public key pair

+
    +
  • live in /etc/ssh/, typically called ssh_host_$TYPE_key
  • +
  • be mindful of this when templating/cloning VMs or containers
  • +
+
+
+

known_hosts and you

+
The authenticity of host 'schmargonrog.example.com' can't be established.
+ED25519 key fingerprint is SHA256:H49twPDi0au3WObFIUmrbUSqc2j8uYzb2BCDigttvbw.
+This key is not known by any other names.
+Are you sure you want to continue connecting (yes/no/[fingerprint])?
+
+
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
+Someone could be eavesdropping on you right now (man-in-the-middle attack)!
+It is also possible that a host key has just been changed.
+The fingerprint for the ED25519 key sent by the remote host is
+SHA256:s7Z+oc2AUQUqNH91OPkqzL0VXbe2fAoF+p+robhrCv0.
+Add correct host key in /root/.ssh/known_hosts to get rid of this message.
+Offending ECDSA key in /root/.ssh/known_hosts:71
+  remove with:
+  ssh-keygen -f "/root/.ssh/known_hosts" -R "clab-f2-spine2"
+[...]
+Host key verification failed.
+
+
+
+

how does it keep track of known hosts?

+ +

~/.ssh/known_hosts (personal) or /etc/ssh/ssh_known_hosts (global)

+
    +
  • lists hostname, ssh key type and public key for hosts you have previously connected to
    server1.example.com ssh-ed25519 AAAAC3NzaC1lZ...
    +server1.example.com ssh-rsa AAAAB3NzaC1yc2E...
    +server1.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLX...
    +
    +
  • +
  • some Linux distros default to HashKnownHosts yes, which replaces the hostname with a hashed version
    |1|32Q6...=|Ul+EoN...= ssh-ed25519 AAAAC3NzaC1l...
    +
    +
  • +
+
+
+

To recap:

+
    +
  • temp pub/priv keys used to establish connection & share the session secret +
      +
    • from this point on, all comms on the ssh session are protected by this session secret
    • +
    +
  • +
  • known_hosts important to prevent man-in-the-middle attacks
  • +
+ +

Photo by Adam Winger on Unsplash

+
+
+

Part 2

+

The Two Pillars

+ +
    +
  • Key based auth
  • +
  • ssh agent
  • +
+ +

Photo by Cajeo Zhang on Unsplash

+
+
+

Key-based authentication

+
    +
  • A private (secret, only for you) key +
      +
    • never give this to anyone else
    • +
    • never copy this to another machine
    • +
    +
  • +
  • A public (everyone can have it) key +
      +
    • can be generated from the private key
    • +
    +
  • +
  • typically, id_rsa and id_rsa.pub +
      +
    • or id_ed25519, id_dsa, ...
    • +
    +
  • +
+
+
+

how to generate a key pair

+
    +
  • ssh-keygen -t rsa -b 4096 +
      +
    • will create ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub
    • +
    +
  • +
  • ssh-keygen -t ed25519 +
      +
    • id_ed25519, essentially, it's id_$TYPE
    • +
    +
  • +
  • please, do set a passphrase for the key. I will explain how to make it not annoying.
  • +
+
+
+

and now? aka authorized_keys

+ +

You now put the public key into the ~/.ssh/authorized_keys file of the user/host combination you want to have access to.

+
# cat ~/.ssh/authorized_keys
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6Gc/53b0ZBGL/ORF5hIa61hTPTAsrjnkxXl3wawsHT felix@home
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEx6jIZeHFxh1NDJOoHGQLg0ViqqFNKGd5ofTRvHb4Fh backuppc@backup
+
+ +

(Remember, do not give your private key to anyone. Never.)

+
+
+

example login

+

having added the contents of my ~/.ssh/id_ed25519.pub file to the user root on my server db.example.com, I can now do

+
ssh -i ~/.ssh/id_ed25519 root@db.example.com
+
+

My local SSH client will ask me for my passphrase (to unlock the private key into memory), then use the private key to authenticate me, allowing me to log in.

+
+
+

SSH agent

+
    +
  • Stores unlocked private keys in memory
  • +
  • only enter your passphrase when adding the key to the in-memory store
  • +
  • configurable lifetime +
      +
    • I would recommend 8-10 hours, so that during normal working hours you enter your passphrase once per day.
    • +
    +
  • +
  • Painless passwordless logins!
  • +
  • can either manually add keys to it using ssh-add or set AddKeysToAgent yes in your ssh config
  • +
+
+
+

Agent forwarding

+
    +
  • Exposes your private key cache (the "agent") to the system you're connecting to +
      +
    • SSH_AUTH_SOCK environment variable
    • +
    +
  • +
+
# echo $SSH_AUTH_SOCK
+/tmp/ssh-XXXXiA23DL/agent.1337770
+
+
    +
  • allows you to access your private keys from an intermediary system +
      +
    • but everyone with root rights on the intermediary can use your SSH_AUTH_SOCK!
    • +
    +
  • +
+
+
+

Finished

+ +
    +
  • keep your private key very private
  • +
  • be very careful with agent forwarding
  • +
  • be paranoid with known_hosts
  • +
  • check out the manpage of the ssh client config: man ssh_config
  • +
  • visit the advanced workshop
  • +
+ +

Photo by Jonatan David on Unsplash

+
+
+

Teaser: Ich wollte eh auf der DENOG Workshop fuer ssh machen, gebe hier mal Teaser, Rest koennt ihr dann euch selber angucken, ...

+

Praxis-Fails einbauen - neue hostkeys, passwort eingeben muessen, passphrase, ... jetzt muss ich meinen private key auf den jumphost kopieren, nein doch nicht, .... teaser-problem fuer proxyjump

+

optional proxyjump, falls ich zu schnell durchgehe bzw generell mal fragen, ob weiteres Interesse besteht

+

fuer lab: ein ssh-container-labyrinth, proxyjumps aussenrum und dann zum Schluss mit einem einzelnen ssh-kommando dateien aus der Mitte nach aussen... oder so.

+
+
+

https://www.openssh.com/legacy.html
+KexAlgorithms: the key exchange methods that are used to generate per-connection keys
+HostkeyAlgorithms: the public key algorithms accepted for an SSH server to authenticate itself to an SSH client
+Ciphers: the ciphers to encrypt the connection
+MACs: the message authentication codes used to detect traffic modification
+ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 user@legacyhost
+ssh -oHostKeyAlgorithms=+ssh-dss user@legacyhost

+
+

This is aimed at people using SSH in their daily work - it does not deal with details of the SSH protocol itself.

pubkey can also check signatures, but that's less important in ssh use.

This enables one-way secure communication - others can now send data to you that +can not be read or changed on the way to you. That's great, but you want to communicate bi-directionally...

Now both parties (client and server) have a way to receive encrypted messages, and as mentionend use this to share a session secret (for efficiency and speed) for running sessions. +Under the hood, SSH will use temporary pub/priv keys to establish the connection, then switch to symmetric encryption where the shared secret is randomly generated between the +SSH parties and transmitted securely through the magic of pub/priv crypto.

you will have seen a message popping up when you connect to a new host for the first time, displaying some form of

Another message you might have seen is this one:

In both cases, you should think about what is going on. First case is pretty normal, you're connecting to a host that you have no previous information on. It's usually okay to say yes here. For the extra paranoid, verify the host key fingerprint through a secure side channel (phone? DNS? photo? etc...) before continuing the connection. + +Don't follow the second message blindly say yes, think about *why* the host key might have changed. Did you recently do a dist-upgrade, did someone reinstall the machine without backing up/restoring the host keys, or was there a major breaking change with ssh during a system upgrade run? In any case, be vigilant.

If you said "yes" on the prior page, how does your local ssh client actually save this information? There are two spaces, with the global one usually not being used.

(this is what happens when you answer "yes" on the first connection to a host and )

This makes some deleting entries a lot harder than before, and it might break some tab-completion magic that might use the known_hosts file

You might want to roll out the known_hosts centrally.

Right, that's the basic how. Let's get to some things that make your life so much easier, yet that are often misunderstood.

The most widely used yet often misunderstood features of SSH. This is why you are here.

One of the core failures of the whole SSH ecosystem is the default naming convention for private/public keys. Accidentally cat'ing your private key is way too easy. + +rsa, dsa, ed25519 are different variants/types of encryption. When in doubt, use RSA. + +(I would always go with ED25519 for modern deployments, though it might not work on systems that are older than 2020ish)

Securing your private key with a passphrase (which just means "long password") is a good precaution, it means somebody that gets access to your private key cannot immediately use it.

Okay, now you have your key, and you can give the public side of it to others. If others want to securely encrypt something that **only** you can decrypt, they use your **public** key to encrypt. You then use your **private** key to decrypt.

In order to use this for authentication instead of a password, the SSH server will essentially encrypt something with your public key and ask you to send the decrypted text back (to prove that you are indeed in posession of the private key part)

*READABLE* && pubkey → *CRYPTED* && privkey → *READABLE*

Yes, this feels identical to logging in with a password, except I was encouraged to make my passphrase longer and more annoying to type. Great job, you've made using ssh more annoying.

Should probably more accurately be called the SSH private key cache. It's well integrated into openssh, and normal ssh connection attempts will try to use your local ssh agent automatically.

On macos, you can use the system keychain to store the passphrase, where they will live together with all the other saved passwords, protected by a master password. It's ```UseKeychain yes +``` in your .ssh/config, and I would recommend to use that if you're on a mac.

On windows, putty has "pageant" to serve as private key cache, it works identically, though by default does not set a lifetime (you do need to re-enter passphrase after rebooting)

This is a must-have if you're serious about using key-based auth. You can get by with using keys without passphrase, but please don't, it carries a heavy price in terms of identity theft and potential damage to your reputation. As a sidenote, with more and more machines ending up in an MDM (mobile device management) "friendly rootkit" scenario, your private keys, passphrases and logins etc are essentially all compromised (to your MDM admins), making (digital) identity inside your company all but meaningless, but that is a topic for another talk... or hit me up later, I will talk at length about the problems I see with MDM :)

you have essentially compromised your keys to every admin of the intermediary system. Be very, very careful where you agent forward to. Come visit my advanced SSH workshop to learn a better way.

This covers what I consider the SSH 101

\ No newline at end of file diff --git a/presentation/ssh-basics.md b/presentation/ssh-basics.md new file mode 100644 index 0000000..eb82973 --- /dev/null +++ b/presentation/ssh-basics.md @@ -0,0 +1,341 @@ +--- +theme: sparta +_class: lead +size: 16:9 +paginate: true +_footer: "SSH 101 - Felix" +marp: true +transition: fade +lang: en +backgroundColor: #fff +xbackgroundImage: url('https://marp.app/assets/hero-background.svg') +backgroundImage: ![bg](./img/artbreeder-composer-2024-07-15T00_14_43.169Z.jpeg) +--- + + + +![bg left:33% h:105% brightness:.7 opacity:.5](./img/artbreeder-image-2025-10-25T11_33_45.435Z.jpeg) + +
SSH essentials
+
Stuff everybody assumes you already know about ssh
+
Felix Schüren
+
2025-11-09
+
DENOG 17, Essen
+ +--- +# Intro +## about me +I've been doing networks for over 30 years by now. And DevOps stuff before we called it that. +## about you +- Which of you... + * use ssh already? + * use port forwarding? + * use ProxyJump? + * use ssh certificates? + * use Windows? MacOS? Linux? Something else? + +--- +# Clients +## OpenSSH +- Linux, Mac, Windows + - just +## but I need a GUI +- A well-maintained ssh_config is usually better. And if you have too many hosts, you want to use ansible etc anyway. + +--- +# Timeline ideas +- clients + +- passwordless login +- port forwarding +- jumphosts, agent forwarding, proxyjump +- host_keys / mitm +- ssh_config +- vs code +- identity files (multiple identities) + +--- +# ~/.ssh permissions etc + + +--- +# ideas +- Im laufenden Lab auf einem jumphost die host_keys ändern, um Warnungen zu produzieren +- agent_forwarding abgreifen und mich als einen der User einloggen +- Hinterher aufzeigen (frei nach "I've been here the whole time!"), dass man cert-based logins benutzt hat +- ggfs Stacy Fakename, Joanna Fakename als Easter eggs? +- wie baue ich eine gute ssh_config +--- +![bg right](./img/artbreeder-composer-2024-07-14T22_49_42.899Z.jpeg) + +1. [How?](#how) +2. [Host Keys](#host-keys) +3. [Key-based auth](#key-based-authentication) +4. [SSH Agent](#ssh-agent) +5. [Agent forwarding](#agent-forwarding) + +Illustration created with Artbreeder + +--- +# SSH Agent +## Windows +``` +Set-Service ssh-agent -StartupType Automatic +Start-Service ssh-agent +``` +## Mac +To add your ssh keys to MacOS Keychain, use `UseKeychain yes` in your ssh_config. + +## Linux +Generally, ssh- agent "just works". If not, simply + +--- +# ssh_config +- on Windows: + `%USERPROFILE%\.ssh\config` + - `PS C:\Users\felix>` `notepad.exe .\.ssh\config` +- on Mac/Linux: `~/.ssh/config` + +--- +# SSH Agent cont'd + +``` +root@clab:~/labs/ssh-workshop# ssh-agent +SSH_AUTH_SOCK=/tmp/ssh-TzLLcOVtmlcY/agent.342525; export SSH_AUTH_SOCK; +SSH_AGENT_PID=342526; export SSH_AGENT_PID; +echo Agent pid 342526; +``` +-> `eval $(ssh-agent -s)` + + +--- +# Never use `ForwardAgent yes`. +*ANYONE* with access to the SSH_AUTH_SOCK file can use the socket. (Generally, root. The directory is locked down as far as it will go, but root...) + + + +--- +# ssh identities +- ssh_config: +``` +IdentityFile ~/.ssh/my-special-key +IdentitiesOnly yes +``` +--- +# how? +The essence behind SSH is asymmetric cryptography +(public/private key cryptography) + +1. you give others a "servant key" (public key) + - can only be used to encrypt data +2. you keep the master key (private key) + - this decrypts the data that was encrypted with 1. + - keep this **very secure**, never give it away. + + + +--- +# host keys +Very similar concept to user/client keys, the host/server also has a private/public key pair +- server (`sshd`) keys live in ```/etc/ssh/```, typically called ```ssh_host_$TYPE_key``` +- they are the "identity" of this particular ssh instance, they should be different on different machines +- be mindful of this when templating/cloning VMs or containers + + +--- +## known_hosts and you + +``` +The authenticity of host 'schmargonrog.example.com' can't be established. +ED25519 key fingerprint is SHA256:H49twPDi0au3WObFIUmrbUSqc2j8uYzb2BCDigttvbw. +This key is not known by any other names. +Are you sure you want to continue connecting (yes/no/[fingerprint])? +``` + +``` +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! +Someone could be eavesdropping on you right now (man-in-the-middle attack)! +It is also possible that a host key has just been changed. +The fingerprint for the ED25519 key sent by the remote host is +SHA256:s7Z+oc2AUQUqNH91OPkqzL0VXbe2fAoF+p+robhrCv0. +Add correct host key in /root/.ssh/known_hosts to get rid of this message. +Offending ECDSA key in /root/.ssh/known_hosts:71 + remove with: + ssh-keygen -f "/root/.ssh/known_hosts" -R "clab-f2-spine2" +[...] +Host key verification failed. +``` + + +--- +## how does it keep track of known hosts? + +```~/.ssh/known_hosts``` (personal) or ```/etc/ssh/ssh_known_hosts``` (global) +- lists hostname, ssh key type and public key for hosts you have previously connected to + ``` + server1.example.com ssh-ed25519 AAAAC3NzaC1lZ... + server1.example.com ssh-rsa AAAAB3NzaC1yc2E... + server1.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLX... + ``` + +- some Linux distros default to ```HashKnownHosts yes```, which replaces the hostname with a hashed version + ``` + |1|32Q6...=|Ul+EoN...= ssh-ed25519 AAAAC3NzaC1l... + ``` + + +--- +![bg left 70%](./img/adam-winger-GIFlfKX23rc-unsplash.jpg) + +To recap: +- temp pub/priv keys used to establish connection & share the session secret + - from this point on, all comms on the ssh session are protected by this session secret +- ```known_hosts``` important to prevent man-in-the-middle attacks + + + +Photo by Adam Winger on Unsplash + +--- +# Part 2 +### The Two Pillars +![bg right](./img/cajeo-zhang-EbOAPSCwjnU-unsplash.jpg) +- Key based auth +- ssh agent + +Photo by Cajeo Zhang on Unsplash + +--- +# Key-based authentication + +- A private (secret, **only for you**) key + - **never** give this to anyone else + - **never** copy this to another machine +- A public (everyone can have it) key + - can be generated from the private key +- typically, ```id_rsa``` and ```id_rsa.pub``` + - or ```id_ed25519```, ```id_dsa```, ... + +--- +## how to generate a key pair +- ```ssh-keygen -t rsa -b 4096``` + - will create ```~/.ssh/id_rsa``` and ```~/.ssh/id_rsa.pub``` +- ```ssh-keygen -t ed25519``` + - →```id_ed25519```, essentially, it's ```id_$TYPE``` +- please, do set a passphrase for the key. I will explain how to make it not annoying. + + + +--- +## and now? aka ```authorized_keys``` + +You now put the **public** key into the ```~/.ssh/authorized_keys``` file of the user/host combination you want to have access to. + +``` +# cat ~/.ssh/authorized_keys +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6Gc/53b0ZBGL/ORF5hIa61hTPTAsrjnkxXl3wawsHT felix@home +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEx6jIZeHFxh1NDJOoHGQLg0ViqqFNKGd5ofTRvHb4Fh backuppc@backup +``` + + + +(Remember, do not give your private key to anyone. Never.) + + +--- +## example login +having added the contents of my ```~/.ssh/id_ed25519.pub``` file to the user ```root``` on my server ```db.example.com```, I can now do +``` +ssh -i ~/.ssh/id_ed25519 root@db.example.com +``` +My local SSH client will ask me for my passphrase (to unlock the private key into memory), then use the private key to authenticate me, allowing me to log in. + + + +--- + +# SSH agent +- Stores unlocked private keys in memory +- only enter your passphrase when adding the key to the in-memory store +- configurable lifetime + - I would recommend 8-10 hours, so that during normal working hours you enter your passphrase once per day. +- Painless passwordless logins! +- can either manually add keys to it using ```ssh-add``` or set ```AddKeysToAgent yes``` in your ssh config + + + + + + + +--- +# Agent forwarding +- Exposes your private key cache (the "agent") to the system you're connecting to + - ```SSH_AUTH_SOCK``` environment variable +``` +# echo $SSH_AUTH_SOCK +/tmp/ssh-XXXXiA23DL/agent.1337770 +``` +- allows you to access your private keys from an intermediary system + - but everyone with root rights on the intermediary can use your SSH_AUTH_SOCK! + + + +--- +![bg left 70%](./img/jonatan-david-K7MkzR7rBmM-unsplash.jpg) +# Finished + +- keep your private key very private +- be very careful with agent forwarding +- be paranoid with ```known_hosts``` +- check out the manpage of the ssh client config: ```man ssh_config``` +- visit the advanced workshop + + +Photo by Jonatan David on Unsplash + + + +--- +Teaser: Ich wollte eh auf der DENOG Workshop fuer ssh machen, gebe hier mal Teaser, Rest koennt ihr dann euch selber angucken, ... + + +Praxis-Fails einbauen - neue hostkeys, passwort eingeben muessen, passphrase, ... jetzt muss ich meinen private key auf den jumphost kopieren, nein doch nicht, .... teaser-problem fuer proxyjump + +optional proxyjump, falls ich zu schnell durchgehe bzw generell mal fragen, ob weiteres Interesse besteht + +fuer lab: ein ssh-container-labyrinth, proxyjumps aussenrum und dann zum Schluss mit einem einzelnen ssh-kommando dateien aus der Mitte nach aussen... oder so. + +--- +https://www.openssh.com/legacy.html +KexAlgorithms: the key exchange methods that are used to generate per-connection keys +HostkeyAlgorithms: the public key algorithms accepted for an SSH server to authenticate itself to an SSH client +Ciphers: the ciphers to encrypt the connection +MACs: the message authentication codes used to detect traffic modification +`ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 user@legacyhost` +`ssh -oHostKeyAlgorithms=+ssh-dss user@legacyhost` diff --git a/setups/gateway.sh b/setups/gateway.sh new file mode 100755 index 0000000..d70ab7d --- /dev/null +++ b/setups/gateway.sh @@ -0,0 +1,45 @@ +#!/bin/bash +dpkg-reconfigure openssh-server + +# the "northbound" interface connecting the gateway to the "real" server/clab hypervisor (NOT management-interface!) +ip addr replace dev eth1 10.192.40.2/29 + +# the "southbound" interface towards all the clients that we are DHCP server for +ip addr replace dev eth2 192.168.0.1/24 + +# "eastbound" gw<->jumphost1 eth3 and 172.16.200/23 behind jumphost1 +ip addr replace dev eth3 172.16.202.32/31 +# ip route replace 172.16.200/23 via 172.16.202.33 + +echo "nameserver 192.168.0.1" > /etc/resolv.conf + + +# ---------------------------------------------------------------------- +# Run the long pipeline in the background, but start it only after 60 s. +# The rest of the script continues immediately. +# ---------------------------------------------------------------------- + +( + # wait 60 seconds first + sleep 60 + # cat /var/lib/misc/dnsmasq.leases | while read line; do loctet=$(echo $line | cut -f 4 -d . | cut -f 1 -d " "); printf "${line}\t"; sed -n "$loctet{p;q}" < <(tr ":" "," < /etc/workshopnames.yml | cut -f 1,3 -d, | tr , "\t") ; done | cut -b 30- | sort -b -k 2,2V | column -t + cat /var/lib/misc/dnsmasq.leases | cut -d' ' -f3-4 | + while read -r line; do + loctet=$(echo "$line" | cut -d'.' -f4 | cut -d' ' -f1) + printf '%s\t' "$line" + sed -n "${loctet}{p;q}" < <( + tr ':' ',' < /etc/workshopnames.yml | + cut -d',' -f1,3 | + tr ',' '\t' + ) + done | + # cut -b30- | + sort -b -k2,2V | + column -t +) > /online-users.txt & # ← background the whole subshell + + +# launch dnsmasq (automatically backgrounds) +dnsmasq --no-daemon + +# /usr/sbin/sshd -D diff --git a/setups/jumphost.sh b/setups/jumphost.sh new file mode 100755 index 0000000..d19dc2c --- /dev/null +++ b/setups/jumphost.sh @@ -0,0 +1,59 @@ +#!/bin/bash +dpkg-reconfigure openssh-server +create_user_from_shared_names_list () { + my_last_octet=$1 + my_username_pwhash=$(sed -n "${my_last_octet}{p;q}" /etc/workshopnames.yml) + my_username="${my_username_pwhash%%:*}" + my_pwhash_pwd="${my_username_pwhash#*: }" + my_pwhash=${my_pwhash_pwd%%,*} + my_pwd=${my_pwhash_pwd#*,} + useradd -m ${my_username} -p "${my_pwhash}" -s /bin/bash + # su - ${my_username} -c 'mkdir -p .ssh' + echo "$my_pwd" > /home/${my_username}/PASSWORD +} + + +case $(hostname) in + jumphost1) + useradd -m devops -s /bin/bash + # the "westbound" interface gw<->jumphost1 + ip addr replace dev eth0 172.16.202.33/31 + ip route replace 192.168/16 via 172.16.202.32 + + # the "southbound" interface towards webserver1 (also named webserver, defaults to ipv4 in /etc/hosts) + ip addr replace dev eth1 172.16.200.1/24 + ip addr replace dev eth1 fd4c:00a6:b6a7::ae/127 + + # eastbound jumphost1-eth2<->eth0-jumphost2 + ip addr replace dev eth2 172.16.202.34/31 + printf "172.16.202.35\tjumphost2\n" >> /etc/hosts + printf "172.16.200.11\twebserver webserver-ipv4 webserver1 webserver1-ipv4\n" >> /etc/hosts + printf "fd4c:00a6:b6a7::af\twebserver-ipv6 webserver1-ipv6\n" >> /etc/hosts + + for user_no in $(seq 10 200); do + create_user_from_shared_names_list ${user_no} + done + ;; + + jumphost2) + # jumphost2 will have a forward-only authorized keys + # cert-authority,no-pty,command="" + useradd -m dbadmin + mkdir -p /home/dbadmin/.ssh + echo "cert-authority,no-pty,command=\"\" " > /home/dbadmin/.ssh/authorized_keys + # the "westbound" interface jumphost1-eth2<->eth0-jumphost2 + ip addr replace dev eth0 172.16.202.35/31 + + # the "southbound" interface towards webserver2 (which I want to name "webserver" as well in order to confuse known_hosts. Also, defaults to ipv6 in /etc/hosts) + ip addr replace dev eth1 172.16.201.1/24 + ip addr replace dev eth1 fd4c:00a6:b6a7::ce/127 + + printf "172.16.202.34\tjumphost1\n" >> /etc/hosts + printf "fd4c:00a6:b6a7::cf\twebserver webserver-ipv6 webserver2 webserver2-ipv6\n" >> /etc/hosts + printf "172.16.201.12\twebserver-ipv4 webserver2-ipv4\n" >> /etc/hosts + ;; +esac + +# launch dnsmasq (automatically backgrounds) + +/usr/sbin/sshd -D diff --git a/setups/linux.sh b/setups/linux.sh new file mode 100755 index 0000000..56ffdbb --- /dev/null +++ b/setups/linux.sh @@ -0,0 +1,17 @@ +#!/bin/bash +dpkg-reconfigure openssh-server +# my_username=$(shuf -n 1 /etc/workshopnames.yml | cut -b 3-) +udhcpc -i eth0 -x hostname:$(hostname) -F $(hostname) +# once we have our IP, create the matching user. +my_last_octet=$(ip -4 a s dev eth0 | grep / | cut -f 4 -d . | cut -f 1 -d /) +my_username_pwhash=$(sed -n "${my_last_octet}{p;q}" /etc/workshopnames.yml) +my_username="${my_username_pwhash%%:*}" +my_pwhash_pwd="${my_username_pwhash#*: }" +my_pwhash=${my_pwhash_pwd%%,*} +my_pwd=${my_pwhash_pwd#*,} +useradd -m ${my_username} -p "${my_pwhash}" -s /bin/bash +su - ${my_username} -c 'mkdir .ssh' +echo "$my_pwd" > /home/${my_username}/PASSWORD +# cp /root/.ssh/authorized_keys /home/${my_username}/.ssh/ && chown ${my_username}:${my_username} /home/${my_username}/.ssh/authorized_keys +printf "172.16.202.33\tjumphost1\n" >> /etc/hosts +/usr/sbin/sshd -D diff --git a/setups/webserver.sh b/setups/webserver.sh new file mode 100755 index 0000000..5041732 --- /dev/null +++ b/setups/webserver.sh @@ -0,0 +1,30 @@ +#!/bin/bash +dpkg-reconfigure openssh-server +useradd -m nginx +case $(hostname) in + webserver1) + # the "northbound" interface towards jumphost1-eth1<->eth1-webserver1 + ip addr replace dev eth1 172.16.200.11/24 + ip addr replace dev eth1 fd4c:00a6:b6a7::af/127 + # create a test file to view/download + su - nginx -c 'echo "welcome!" > /home/nginx/webserver1.txt' + # spawn python3 http server (with ipv6 support) + # su - nginx -c 'python3 -m http.server 31337 --bind ::' + (su - nginx -c 'python3 /usr/local/bin/webserver.py 31337') & + ;; + + webserver2) + # the "northbound" interface towards jumphost2-eth1<->eth1-webserver2 + ip addr replace dev eth1 172.16.201.12/24 + ip addr replace dev eth1 fd4c:00a6:b6a7::cf/127 + # create a test file to view/download + su - nginx -c 'echo "welcome (again)!" > /home/nginx/webserver2.txt' + su - nginx -c 'echo "...is a lie." > /home/nginx/CAKE' + # spawn python3 http server (with ipv6 support) + # su - nginx -c 'python3 -m http.server 41337 --bind ::' + (su - nginx -c 'python3 /usr/local/bin/webserver.py 41337') & + ;; +esac + +# service started in case above +/usr/sbin/sshd -D