<![CDATA[Etherarp]]>https://etherarp.net/https://etherarp.net/favicon.pngEtherarphttps://etherarp.net/Ghost 3.1Fri, 26 Jun 2020 12:30:31 GMT60<![CDATA[Caddy Cheatsheat]]>https://etherarp.net/caddy-cheatsheat/5eba3263cc68b80001bd7d42Tue, 12 May 2020 05:25:54 GMT

Server options

Serve requests only $CADDYHOST environment variable


Bind to IP address $CADDYBIND


Define the web server root

root /srv/caddy/{$CADDYROOT}

Log requests to stdout

log / stdout "{>X-Forwarded-For} {remote} - {user} [{when}] \"{method} {uri} {proto}\" {status} {size} \"{>Referer}\" \"{>User-Agent}\""

TLS options

Set up Lets Encrypt with email $CADDYEMAIL


Configure TLS with local certificate/key

tls cert.pem key.pem {
    protocols tls1.2 tls1.3

Configure TLS with client authentication

tls cert.pem key.pem {
    clients require ca.crt

Redirect HTTP to HTTPS

redir {
if {scheme} is http
/ https://{host}{uri}

Access & Authentication

HTTP Basic auth

basicauth /private {$BASICUSER} {$BASICPASS}

Allow directory browsing under /private

browse /private

Expose Caddy runtime stats at /private/stats

expvar /private/stats

Redirect HTTP to HTTPS

redir {
    if {scheme} is http
    / https://{host}{uri}

Github Oauth2 Login

redir 302 {
    if {path} is /
    / /login
login {
    github client_id={$GITHUB_CLIENT_ID},client_secret={$GITHUB_CLIENT_SECRET}
    redirect_check_referer false
    success_url /home.html
    logout_url /logout
jwt {
    path /
    redirect https://{host}/login
    allow sub {$GITHUB_ALLOWED_USERS}


Apply templating only to /private/templates

# See https://caddyserver.com/v1/docs/template-actions
templates /private/templates

Dynamically render /public/*.md as markdown (css optional)

markdown /public {
ext .md
css markdown.css

HTTP options

Add file extensions to unqualified requests - try until matched

ext .html .txt 

Add/Modify the response headers

header / X-Frame-Options "DENY"

Enable Compression

gzip {
level 4
not /proxy

Generate an error page

errors {
404 error/404.html
* error/catchall.html

Proxy Options

Simple reverse proxy

proxy /proxy https://checkip.amazonaws.com

Reverse proxy with auth header

proxy /transmission http://localhost:9091 {
header_upstream Authorization "Basic dXNlcjpwYXNzd29yZAo="


Define a snippet

(snippet_name) {
import snippet_name
<![CDATA[Built-in Firewall for Systemd Services]]>https://etherarp.net/built-in-firewall-for-systemd-services/5e06d47b0b5e2f0001ff0e50Sat, 28 Dec 2019 04:10:35 GMTSystemd includes a feature to restrict which IP addresses can communicate with a service. The neat thing is that these rules operate independently of any iptables configuration, providing an extra layer of security.

Restricting SSH to a local subnet

As an example, let's restrict sshd to the subnet. Run systemctl edit sshd.service and add the following.


Then run systemctl daemon-reload; systemctl restart sshd

<![CDATA[Connecting Network Namespaces with veth]]>https://etherarp.net/connecting-network-namespaces-with-veth/5db666587e1897000199e95dSat, 14 Sep 2019 10:13:29 GMT

This post will look at how network namespaces can be connected together.

Basic use of Network Namespaces

Network namespaces restrict a process from "seeing" the network interfaces, IP addresses, routes, and firewall entries from the rest of the system. Network namespaces are managed via the iproute2 utility

Create Network namespace

ip netns add foo 
ip netns add bar 

Attach an interface to the network namespace

ip link set dev enp3s1f0 netns foo

Configure newly attached interface

ip netns exec foo ip l set dev enp3s1f0 up;
ip netns exec foo ip a add dev enp3s1f0;
ip netns exec foo ip r add default via;

Run a process inside the namespace

ip netns exec foo nc -lkp 8080 <<< OK

Connecting between system and namespaces

In this example, we will use the newly created veth pair to connect the system to network namespace foo. Veth is a type of virtual ethernet interface that is always created as a pair. Veth can be thought of as a 'virtual crossover cable', it creates two virtual NICs that are connected

Defining a veth pair

ip link add veth1_left type veth peer veth1_right;

Create a bridge interface on the system

ip link add bridge0 type bridge;
ip link set bridge0 up;
ip addr add dev bridge0;

Attach the left veth interface to the bridge

ip link set veth0_left master bridge0 up ;

Attach the right veth interface to the network namespace

# ip link set dev veth0_right netns foo;

Bring up the right interface inside the namespace

ip netns exec foo ip link set veth0_right name eth0;
ip netns exec foo ip link set dev eth0 up;
ip netns exec foo ip addr add dev eth0;

Connecting between namespaces

Connecting one network namespace to another follows the same process as connecting a network namespace with the host. Create a veth pair and attach each side to the appropriate namespace

Create a veth pair

ip link add veth1_left type veth peer veth1_right

Attach the left veth to the foo namespace

ip link set veth1_left netns foo

Configure ip for the foo namespace

ip netns exec foo ip l set veth1_left name eth1;
ip netns exec foo ip l set eth1 up;
ip netns exec foo ip a add dev eth1;
ip netns exec foo ip r add via

Attach the right veth to the bar namespace

ip link set veth1_right netns bar

Configure IP for the bar namespace

ip netns exec bar ip l set veth1_right name eth0;
ip netns exec bar ip l set eth0 up;
ip netns exec bar ip a add dev eth0;
ip netns exec bar ip r add via'

Routing accross namespaces

Recall that we created a namespace (foo) that has both a veth link from the default namespace (host) and a veth link to a second namespace (bar). As a proof concept, let's configure connectivity between the system and the remote namespace

Create a static route on the system

ip route add via

Verify connectivity

traceroute -n
traceroute to (, 30 hops max, 60 byte packets
 1  0.053 ms  0.013 ms  0.011 ms
 2  0.019 ms  0.014 ms  0.012 ms

Now, the system should be able to reach (network namespace 'bar') routing via network namespace 'foo'. Connectivity can be verified with a ping or traceroute.

<![CDATA[Sending Emails with cURL]]>https://etherarp.net/sending-emails-with-curl/5db666587e1897000199e95cSun, 08 Sep 2019 01:54:24 GMTCurl is a command line utility found on most modern Linux distributions used to retrieve content from  the internet. However, it can do a lot more than that and supports a plethora of protocols, including TFTP, SFTP, and SMTP.

Sending an Email

[rohan@desktop ~]$ echo "Subject: Sending an Email with Curl!

Just wanted to check you were able to receive this email, sent over the curl command.
Has it gone into spam?.

" | curl -s --ssl smtp://$SERVER --mail-from $FROM --mail-rcpt $TO --upload-file /dev/stdin --user rohan@example.etherarp.net
Enter host password for user 'rohan@example.etherarp.net':
[rohan@desktop ~]$

Let's see if the email was delivered (nb: the address has been changed to prevent spam)

Email sent with Curl

Securing the credentials

In this example, the --user field did not specify a password, this results in a prompt. If you don't want to enter it manually, you can either hardcode it somewhere (bad) or use GPG encryption. With GPG, the gpg-agent can be configured to store credentials in memory. This way, you only need to enter the password once. Configuring gpg is out of the scope of this article

[rohan@desktop ~]$ curl -s --ssl smtp://$SERVER --mail-from $FROM --mail-rcpt $TO --upload-file $EMAIL --user rohan@example.etherarp.net:$(pass email/rohan@example.etherarp.net 2>/dev/null)
[rohan@desktop ~]$
<![CDATA[Verify TLS Servers with Random Art]]>https://etherarp.net/verify-tls-servers-with-random-art/5db666587e1897000199e95bSat, 07 Sep 2019 13:01:04 GMT

SSH Public Key infrastructure does not typically use certificates or certificate authorities, it pins the public keys directly, with a trust model based on TOFO (trust-on-first-use). When you first connect to an SSH server, it asks you to trust the host key and displays a visual representation of the key, like this

SSH random art for Github.com server

The public key cryptography SSH uses is very similar to what's used with TLS/SSL so it should be straightforward to generate the random art for an HTTPS server. The main difference with TLS is that certificates are verified rather than keys; certificates are generated from keys. So if a website you trust starts showing a certificate error, it will be useful to know whether or not the underlying key has changed.

My script is pretty simple and has the following steps:

  1. openssl s_client connects to the server and verifies it against a CA bundle (this is optional). The server certificate is printed
  2. openssl x509 extracts the public key from the certificate
  3. ssh-keygen -vi converts the public key from the standard TLS format -m PKCS8 to the SSH format
  4. ssh-keygen -vl prints the random art

Trying it out

Trying my script out

Final notes

The output is based on the public/private key pair used to generate the certificate signing request, not the certificate itself. So two certificates will give the same output even if they have a totally different common name and are signed by a completely different certificate authority.

This script could be used to check if a server is still secured with the same public key after its certificate has changed, or to detect man-in-the-middle attacks

<![CDATA[Process Privilege Escalation with SUID]]>https://etherarp.net/linux-suid/5db666587e1897000199e95aThu, 29 Aug 2019 09:38:32 GMTWhen an executable with the suid bit is run, it will always run as the user who owns the file, irrespective of the current user. A familiar example is the ping utility. Ping must run as the root user because it opens raw IP sockets, so it has the suid bit set. When an ordinary user runs ping, the process is running as root because /bin/ping is owned by root.

Examining the permissions of /bin/ping

To view the permissions of a file, the stat -c "%a" command is used

root@b6386528aa87:/# stat -c %a /bin/ping
root@b6386528aa87:/# stat -c %a /bin/bash

Looking at the /bin/ping file, we can see it has an extra permission bit (4), which bash does not have. This is the suid bit.

Adding and removing the suid bit

The chmod utility can be used to add the suid bit to an executable file. Let's look at what happens when we remove suid from ping and add it to the whoami executable

root@b6386528aa87:/# chmod +s /usr/bin/whoami
root@b6386528aa87:/# chmod -s /bin/ping
root@b6386528aa87:/# sudo -u nobody ping -c1
ping: Lacking privilege for raw socket.
root@b6386528aa87:/# sudo -u nobody whoami

After removing setuid from ping, we cannot ping as an unprivileged user. Conversely, after adding the suid bit, whoami reports root even when running as nobody.

Finding all SUID binaries

The find command can be used to search for all the executables with the SUID permission (-perm -4000). In the following example, find is passing its output to ls -ldb. The -user root can be used to restrict the search to files owned by root.

root@b6386528aa87:/# find / -perm -4000 -exec ls -ldb {} \; 2>/dev/null
-rwsr-xr-x. 1 root root 40000 Mar 29 2015 /bin/mount
-rwsr-xr-x. 1 root root 70576 Oct 28 2014 /bin/ping
-rwsr-xr-x. 1 root root 61392 Oct 28 2014 /bin/ping6
-rwsr-xr-x. 1 root root 40168 Feb 24 2017 /bin/su
-rwsr-xr-x. 1 root root 27416 Mar 29 2015 /bin/umount
-rwsr-xr-x. 1 root root 53616 Feb 24 2017 /usr/bin/chfn
-rwsr-xr-x. 1 root root 44464 Feb 24 2017 /usr/bin/chsh
-rwsr-xr-x. 1 root root 75376 Feb 24 2017 /usr/bin/gpasswd
-rwsr-xr-x. 1 root root 39912 Feb 24 2017 /usr/bin/newgrp
-rwsr-xr-x. 1 root root 54192 Feb 24 2017 /usr/bin/passwd

Disabling SUID Globally

Warning: This may break things
The SUID permission can be ignored globally by using the -o nosuid flag when mounting root. This will break things like sudo or su or services that drop privileges.

rohan@localhost:~$ sudo mount / -o remount,nosuid
rohan@localhost:~$ sudo whoami
sudo: effective uid is not 0, is /usr/bin/sudo on a file system with the 'nosuid' option set or an NFS file system without root privileges?

Disabling SUID in Containers

The SUID capability can be dropped in containers via the --cap-drop=setuid option. See this page for more information https://www.redhat.com/en/blog/secure-your-containers-one-weird-trick

Replacing SUID with Granular Capabilities

The SUID permission does not provide granular privilege escalation. When a binary (for instance, /bin/ping) is elevated to root, it can do anything and everything, such as writing to system directories, installing kernel modules, or messing with hardware. This is poor security practice as it violates the principle of least privilege.

Going back to the example of /bin/ping, it runs as root because it requires the cap_net_raw privileged capability. So rather than elevating it to root, we can tell the kernel to grant that capability to that executable, even if invoked by an unprivileged user.

[root@centos7 ~]# chmod u-s /bin/ping
[root@centos7 ~]# sudo -u nobody ping -c1 localhost
ping: socket: Operation not permitted
[root@centos7 ~]# setcap cap_net_raw+p /bin/ping
[root@centos7 ~]# sudo -u nobody ping -c1 localhost
PING localhost ( 56(84) bytes of data.
64 bytes from localhost ( icmp_seq=1 ttl=64 time=0.045 ms

That's all for now. Thanks for reading!

<![CDATA[Network Isolation of Services with Systemd]]>https://etherarp.net/network-isolation-of-services-with-systemd/5db666587e1897000199e959Sun, 18 Aug 2019 05:42:00 GMTThis tutorial will look at how network namespaces can be defined in systemd service unit definitions. This example will at running the Nginx service inside a separate network namespace that has its own physical interface. Like most of my tutorials, this will be done on a Fedora system.

What are Network Namespaces?

Network namespaces are an important component of containerization in Linux. A network namespace (netns) allows a running process to see different network interfaces, routes, and firewall rules from the rest  of the system. There are a number of use cases for network namespaces, such as running multiple servers, testing/staging environments and providing isolation of services.

Creating a Network Namespace

We begin by creating a generic systemd service for creating a named network namespace. I add the following to /usr/lib/systemd/system/netns@.service. In systemd, the @ mean the service takes a parameter which is passed to the unit via %i. E.g, we can run sudo systemctl start netns@webserver.service.

Unit to set up the interface

Next, we create a definition in /usr/lib/systemd/system/attach-enp3s1f0@.service. This service associates the enp3s1f0 with a specified network namespace. It also sets up addresses within  the network namespace. In iproute2, the command to run a process within a  specified network namespace is ip netns exec $namespace $command.

Running a process inside a network namespace

As a simple test, I define /usr/lib/systemd/system/webserver.service which runs a simple TCP server over netcat inside the netns. Notice the JoinsNamespaceOf=netns@webserver.service option, stating that the service join the network namespace of an already running service. I added some extra privilege constraint such as ProtectSystem=true and CapabilityBoundingSet= which are unrelated to network namespaces - these are for another post :)

Trying it out

After setting the above files, I run the following commands

$ sudo systemctl start \
  netns@webserver.service \
  attach-enp3s1f0@webserver.service \

Now the enp3s1f0 interface has disappeared from the system, as it's no longer in the default namespace

$sudo ip l show dev enp3s1f0
Device "enp3s1f0" does not exist.

Let's see if its reachable

$ curl

When a service is running in an alternate network namespace, it is possible to use the service's port on the host system, over all  interfaces.

$ /usr/bin/nc --send-only --exec "/usr/bin/echo Foo" -lkp 8080 &
[1] 8556
$ curl
$ curl

Services running in an alternate network namespace are unaffected by local firewall rules on the host system.

$ sudo iptables -t raw -I PREROUTING -p tcp --dport 8080 -j DROP
$ curl --max-time=3
curl: (28) Connection timed out after 3001 milliseconds
$ curl

Associating the nginx service with the network namespace

I add PrivateNetworking=true to the [Service] section and the following lines to the [Unit] section of /usr/lib/systemd/system/nginx.service


I then run sudo systemctl daemon-reload; sudo systemctl start nginx.service.

Let's test it out

$ curl
<!doctype html>
  <title>Nginx Server</title>
  <h1>It Works!</h1>


A couple of final things to note. Network namespaces are a form of  specific isolation - they only concern networking, not filesystems, user  rights, etc. These partial isolation systems can be joined to form  general isolation, this is how Docker and containerd operate. Systemd and the related technologies on a modern Linux system are extremely powerful and there's a lot of isolation that can be done within systemd service definitions, such as privilege and capability dropping, which will be covered in more detail in a later post.

Standalone use of network namespaces can be useful for situations  when the only form of isolation required is on the networking side. For example, running a service (such as the transmission-daemon bittorrent) client over a VPN, without requiring a VPN on the rest of the system.

I hope you found this post informative

<![CDATA[Generating Memorable Hostnames for a Subnet]]>https://etherarp.net/generating-memorable-hostnames/5db666587e1897000199e958Wed, 14 Aug 2019 11:20:35 GMTDownload
# File containing list of words (seperated by NL)
# Define the network range printed
# Iterate and print
readarray words < <(printf "%s%s\n" $(shuf $wordList))
let "z=0"; 
for ((i=$netwMin; i<$netwMax; i++)); do   
  for ((y=$hostMin; y<$hostMax; y++)); do
   printf "%s\t%s" "$subnet.$i.$y" "${words[$z]}";    
   let "z++"; 

Modifying the network ranges

Edit these to modify the network, by default, it generates


Output	BoyRecognize	CharacterGrowth	SituationShot	ScienceDinner	HeavySend	WhenPrevent	ViewMarriage	SourceProvide	PerformMuch	AnotherDead	MusicPay	AirLast	LittleMouth	IfWonder	TeamShow	DirectionWall	CompanyThink	OrderOne	NeedHear	HighRadio	WhichCommercial	MilitaryDark	FamilyRemove	WhoseJust	OfficialProperty	ImageMember	FingerEarly	PerThree	SomeonePossible	IncreaseCoach	TreatAccording	BuildAlone	SexualThough	InvestmentOld	SaveSeries	TrainingMachine	OpportunityMessage	ResourceDiscover	BehaviorChoose	CourtIts	LikelyViolence	CanItself	FollowSeveral	EnjoyConference	FactorStandard	PushAbout	BeforeGive	WillHair	OrganizationFirm	AnyDifferent	ShootReceive	ShortThus	StreetForm	RaceItem	LotWithout	MomentNorth	CellRecent	HopePopulation	HundredAge	StudentTrip	BeatChance	SoonSport	ClassTelevision	RelationshipOther	DoOutside	RecentlyOf	ModernDie	ParticularBudget	ThanLeader	ChurchCheck	EventNational	RealAction	CenturyEvidence	ConcernOur	BoxTest	GovernmentPeople	ParentKnow	BetweenOfficer	CongressQuestion	DirectorSo	PositiveSchool	AlongCold	DevelopThing	OrAdmit	DetermineHelp	UnderstandCouple	AroundWater	MagazinePolitical	PassWhite	BehindHard	SiteWhat	NewsLocal	BackNever	ValueBit	BaseTrouble	TakeGoal	MethodInclude	DegreeNation	LieWhether	ImproveHow	LateAnimal	ReportFoot	LoseKind	InterestingBank	AttorneyOthers	WellAll	ThenPiece	HangNetwork	EightFuture	KnowledgeNatural	EndReally	TruthLand	ProveBecause	OnInside	ForeignAs	WhyInto	GrowDemocrat	MakePerson	BestSpeech	StateIndividual	RelateSouthern	WeaponTalk	WalkPoor	BloodSing	BillionGeneral	TooQuality	CareAlthough	EnvironmentEnergy	HourImagine	QuiteDifference	SummerLoss	DifficultUpon	FederalKey	PracticeProtect	SeasonAway	MotherThroughout	NoteLeg	WaitMyself	PullScore	SitNear	ReachResearch	BornThousand	SocialRemember	PositionSpecial	DespiteRich	EveryTogether	StyleSay	PastFirst	MindMiddle	PainHospital	PutClaim	TrialCatch	WeekAllow	EasyStore	DropWorker	AgreeSomebody	WithCost	QuicklyHeat	ExistGood	ThemLater	TownCamera	LowPersonal	ExecutiveMeeting	DescribeFood	FarExplain	ProduceCharge	ManagementTheory	TenWhole	AlwaysProduction	ActivityPartner	AlmostEffect	CivilAcross	StepWould	KitchenAgency	BusinessCapital	YoungFront	ContinueMy	TryIndustry	BabyLook	CreateReturn	HoweverDecide	HistoryYes	SuccessHouse	TableHusband	AndLong	ShareHead	IdentifySubject	ClearLegal	DiscussWorry	LetWord	SortDeep	SixPolitics	WeProblem	YearStar	FormerEat	ReflectBook	ResponseAddress	RoleFinal	StockSometimes	OnlyFall	TermYet	GreenShake	ReasonPrivate	HimDream	WhoLeave	BuyLife	DetailCall	ConsiderBlack	CurrentOil	NumberSuffer	SincePaper	ManageMeasure	DrawTonight	GirlSecurity	DoctorAdd	FiveEconomy	LearnSurface	StructureSimilar	TimeSerious	GlassAbility	WeightTrade	MarketReality	UnderOnce	ClearlyWrong	AgainstDuring	PlayerCar	StartBelieve	CulturalComputer	MemoryElection	WhileArgue	OwnerProbably	TraditionalPresent	ToughSupport	CouldHuman	ThatEither	MovieLead	FinancialPoint	WantEffort	RoomName	MrSister	LeftMajor	NearlyAccount	EveningEverything	EnterLetter	DeathSide	GreatEver	HoldStation	ProfessionalGo	EnoughPlan	CampaignField	DecisionDefense	SuccessfulSection	IdeaEveryone	RatherReduce	SexEntire	BrotherAt	NewApply	RuleHuge	NightAuthor	SeatCover	ToAudience	JobCompare	OccurSenior	AvailableTop	SomePolice	StuffMrs	ExpertDown	FreeProfessor	IChoice	HimselfPublic	AgreementCard	LayBegin	EspeciallyHand	UsuallyAppear	SingleAgent	TheirAbove	ItPlay	WomanRate	InformationNice	CertainlyCollege	RaiseArm	FullHere	WayCut	FinishCome	EdgeEast	TellTree	AnythingMean	HeartMore	UpSuch	CultureCommunity	SeekOnto	LanguageVoice	ColorMan	SenseEverybody	PatternSee	CarryPlant	ConditionTurn	WideBe	DemocraticDecade	KidPurpose	PowerThere	CenterWestern	ExactlyPressure	CustomerKeep	ResponsibilityCity	TowardWin	MentionSure	SomethingBecome	WorldAnyone	SpecificCrime	ArticleBody	ChangeNot	FindMust	WifeBad	BeyondBig	FineStatement	WindowTreatment	BreakExample	DealPretty	CareerAlso	AbleDoor	FourWrite	CollectionCase	PartyShould	ThreatPerformance	PictureAmerican	DevelopmentMaintain	EnvironmentalNothing	ThirdMatter	ExperienceLess	RiseSystem	EachEye	ResultWork	DiseasePrice	HomeHappen	FearStory	StrongSecond	IndeedRed	FactWest	AgainCause	SpendTeach	BetterLawyer	LawAnalysis	ListenVarious	BeautifulPeriod	FatherRecord	StudyKill	CountryBoard	SuddenlyAffect	SignRisk	HaveSound	WarAccept	YouFeel	CancerReligious	BarSong	InvolveLine	SouthFast	SocietySon	DesignMaterial	BlueForget	OhExpect	CourseStay	GuessMiss	FigureRemain	ByAsk	WatchOwn	UsAmong	MonthPainting	RangePhysical	NatureApproach	DayBill	CupVote	DrugOperation	SetCentral	MediaWriter	FilmTravel	TheEducation	PartPM	HappyShe	StandChild	FewSpring	OfficeInstead	PopularSea	SpeakVisit	GroundLove	LevelThemselves	SignificantServe	AnswerAct	OverYard	ThoseWind	FlyNo	OpenGame	InterestYourself	PrepareYour	TwoHis	FillHealth	NewspaperIn	OptionRight	AdministrationOffer	GetAttention	PeaceGeneration	TaxSimply	ForwardInternational	SkillEconomic	HotShoulder	BuildingLike	AreaToday	OffThrough	SpaceTV	VictimRun	ImpactRead	ThrowMany	MeetImportant	ScientistMission	ProjectContain	ProductBall	PresidentNow	FinallyHer	StopOut	SellFail	DriveMoney	WishStaff	FireBag	HeAhead	TaskTechnology	LargeMinute	OkFocus	WearProgram	ActuallySoldier	LaughType	BedEmployee	WhereDaughter	ChairOften	RegionEstablish	SceneBring	ControlSimple	TendGun	PageMight	GasFish	PatientThought	MorningHit	CloseFloor	ChallengeMedical	LeastMain	MoveTeacher	VeryMay	AgoRepresent	ForData	NextMost	ProcessAssume	SizeService	ThisList	HotelFeeling	AdultCitizen	SevenAlready	BothPerhaps	PhoneAuthority	SameGarden	LightSkin	BenefitHalf	PlaceNecessary	RevealN'T	ElseArtist	AmountSafe	TheyParticularly	MovementFrom	CommonHerself	RoadCertain	TrueFace	ParticipantMaybe	InterviewUntil	GroupStage	ConsumerNotice	InstitutionCandidate	ArtJoin	IssueEven	AttackFight	WithinStrategy	WhomPolicy	RockWhatever	FundStill	MillionDiscussion	YeahDog	RequireLive	ButMajority	NorRealize	NoneSuggest	RespondGuy	SeemUse	ReadyMe	FriendThank	PickDebate	SmallSmile	TheseIndicate	AvoidModel	ArriveIncluding


1000 most common words in English - https://www.ef.co.nz/english-resources/english-vocabulary/top-1000-words/

<![CDATA[Send IM when site goes offline]]>https://etherarp.net/send-im-when-site-goes-offline/5db666587e1897000199e957Tue, 13 Aug 2019 06:41:11 GMTThis tutorial will show how I used a Python script, Systemd timers, and the XMPP protocol to send myself an instant message when my website goes offline.

Receiving an IM notification about an unreachable website

The Python Script

The script uses the Python requests library to contact the server. If the server is unreachable, returns an http error, or fails to respond within the timeout (5s for my site) the script runs the sendxmpp command (the configuration of which will be covered later in this article).

The script takes two arguments
1. The URL to monitor
2. The address to send the message to

Systemd User Timers

Systemd is a major component of most modern Linux distributions, replacing older utilities such as cron and init. Systemd is used to run services and schedule tasks.

Services can be defined and activated on a per-user basis allowing automation without using the root account.

In my distribution, Fedora, user-specific systemd configuration files reside in $HOME/~.config/systemd/user. You may need to create the directory.

Begin by defining a definition in $HOME/.config/systemd/user/check-website.service. This is what calls the python script.

Don't forget to substitute the example.etherarp.net values with ones applicable to you!.

The next required file is $HOME/.config/systemd/user/check-website.timer which specifies that check-website.service be run at regular intervals

This timer definition states the service will run every ten minutes, provided the system has been up for fifteen minutes (it will run immediately if the timer is manually started)

Run the following commands to activate the timer

systemctl --user daemon-reload
systemctl --user enable check-website.timer
systemctl --user start  check-website.timer

Configure the XMPP client

I decided to use the old Perl based sendxmpp command as my XMPP client because it is extremely simple.

It does have a couple of fairly major flaws:

  • It doesn't understand DNS SRV records
  • Fedora had SSL verification issues (which I fixed)

First, in my home directory, I created a ~/.sendxmpprc file with the following entries specific to my (test) XMPP server

username: test
component: example.etherarp.net
jserver: example.etherarp.net:5222
password: Password!

When I tried running `sendxmpp` on Fedora, I got a fatal SSL error as it couldn't find a CA bundle. I fixed it by editing
/usr/share/perl5/vendor_perl/XML/Stream.pm and looking for the line matching $self->{SIDS}->{default}->{ssl_ca_path} = '';
and changing '' to '/etc/pki/tls/certs/ca-bundle.crt'

On the topic of SSL, the Python script calls sendxmpp with the -t flag which mandates the use of TLS. Remove this flag if you're using a plaintext server.

Configure the XMPP Server

I created a test XMPP server using the prosody package running on a Debian in a Docker container. As this was a quick test, I set it up interactively rather than using a Dockerfile.

I copied a valid SSL certificate/key/dh2048 to it and placed the following in /etc/prosody/prosody.cfg.lua

I started prosody, and then used prosodyctl adduser test@example.etherarp.net and prosodyctl adduser webmaster@example.etherarp.net commands to generate the user and service XMPP accounts.

Then, on my workstation, I set example.etherarp.net to the address of the container in /etc/hosts. I connected over Pidgin client and logged into both accounts, adding and accepting both as 'buddies' of one and other.  

I then verified messages could be sent between them

<![CDATA[Github Login on Caddy]]>https://etherarp.net/github-login-on-caddy/5db666587e1897000199e956Sat, 13 Jul 2019 12:15:00 GMTThis post covers how to configure Github based login for the Caddy webserver. This allows single sign on using Oauth2. The Caddy plugins http.jwt and http.login are required.

Create an Oauth2 App in Github

Log into your Github account and open Developer Settings.
Create a new Oauth2 App, set https://<domain>/login/github as the Authorization Callback URL

Install Caddy

Using Oauth2 requires the http.login and http.jwt plugins, which are not included by default. There are a number of ways to build Caddy - I used the Dockerfile below. To build, run the following command docker build . -t local/caddy. You will need a Caddyfile and index.html in your working directory.

Once Caddy has been built, it can be started using docker-compose.

Configure Caddy

This is the Caddyfile. All requests to / are redirected to /login. On successful Oauth2 verification, the user is redirected to /internal/index.html. The sub statement specifies the authorized Github accounts (by username)

Trying it out

In a private window, I open up my site, and as expected, it redirects to the login page

The sign in link points to a login page on the Github.com domain

The first time you log in, you will have to associate the Oauth2 App with your Github account; only associate your account with sites you consider trustworthy!

Upon authorization, you will be redirected to the site's internal area.

<![CDATA[Firewalld Tutorial]]>https://etherarp.net/firewalld-readme/5db666587e1897000199e955Sat, 06 Jul 2019 09:23:40 GMT


Install and enable Firewalld

$ apt-get install firewalld firewall-cmd
$ systemctl enable firewalld
$ systemctl start firewalld

Check if firewalld is active

$ firewall-cmd --state

Make current rules persistent

$ firewall-cmd --runtime-to-permanent
$ firewall-cmd --reload

Reload the firewall

$ firewall-cmd --reload


Get zone of interface

$ firewall-cmd --get-zone-of-interface tun0

Remove an interface from a zone

$ firewall-cmd --zone=external \
               --remove-interface tun0

Add an interface to a zone

$ firewall-cmd --zone=internal \ 
               --add-interface tun0


Get zone names

$ firewall-cmd --get-zones
FedoraServer FedoraWorkstation block dmz drop external home internal public trusted work

Get default zone

$ firewall-cmd --get-default-zone

Change the default zone

$ firewall-cmd --set-default-zone external

Adding a service to a zone

$ firewall-cmd --zone=home \
               --add-service ssh

Adding a source IP to a zone

$ firewall-cmd --zone=home \

Adding a source MAC to a zone

$ firewall-cmd --zone=trusted \
               --add-source 5a:c2:5c:02:f3:e9

Get active zones

$ firewall-cmd --get-active-zones
 interfaces: ens3
 interfaces: tun0

Describe all zones and rules

$ firewall-cmd --list-all-zones
external (active)
  target: default
  icmp-block-inversion: no
  interfaces: ens3
  services: openvpn
  masquerade: yes
  rich rules:

home (active)
  target: default
  icmp-block-inversion: no
  services: cockpit ssh
  masquerade: no
  rich rules:

internal (active)
  target: default
  icmp-block-inversion: no
  interfaces: tun0
  services: http https ssh cockpit dns
  masquerade: no
  rich rules: 

Using the drop and trusted zone

Firewalld includes a special zone known as trusted that will unconditionally allow traffic.
“Trusted” sources or interfaces can be added to this zone.

$ firewall-cmd --zone trusted \
               --add-source \

$ firewall-cmd --zone trusted \
               --add-interface virbr3

Firewalld also includes a drop zone which can be used to unconditionally drop traffic from specific interfaces or source addresses

$ firewall-cmd --zone drop \

Clear services from zones

Firewalld is preinstalled with a default configuration for every zone. For example, the home zone includes services such as samba and mDNS. I prefer starting with a clean slate. The command below will clear every service from every zone. Be careful not to lock yourself out!

for zone in $(firewall-cmd --get-zones); do firewall-cmd \
--list-services --zone=$zone | xargs -n1 firewall-cmd \
--zone=$zone --remove-service; done

Rich rules

Allow any traffic from source address

This rule allows any traffic with a source address of
This rule only applies to sources/interfaces in the internal zone

$ firewall-cmd --zone=internal \
               --add-rich-rule 'rule 
                                source address= 

Allow service from source address

This rule allows SSH if the source address is
This rule only applies to sources/interfaces in the external zone

$ firewall-cmd --zone=external \
               --add-rich-rule 'rule 
                                service name=ssh 
                                source address= 

Allow traffic to destination address and port

This rule allows traffic to port 1194/udp if the destination address is
This rule applies to the default zone

$ firewall-cmd --add-rich-rule 'rule 
                                protocol=udp port=1194
                                destination address= 

Allow a service with rate-limit

$ firewall-cmd --add-rich-rule='rule 
                                service name=ssh 
                                limit value=10/m 

Accept a service and log (with ratelimit)

$ firewall-cmd --add-rich-rule='rule 
                                service name=ssh 
                                log prefix=ssh 
                                limit value=3/m 


Define a new ipset

$ firewall-cmd --permanent \
               --new-ipset china 
               --type hash:net 

Add an ipset from XML file

$ firewall-cmd --permanent \ 
               --new-ipset-from-file=china.netset.xml \
               --name=china \

Add ipset entries from file

$ firewall-cmd --permanent \
               --ipset china \
               --add-entries-from-file ip2location_country_cn.netset

Block sources by ipset

$ firewall-cmd --permanent \
               --zone=drop \
               --source ipset:china

Find the XML file containing ipset

$ firewall-cmd --permanent --path-ipset china


Time-limited rules

Using the timeout option, it is possible to set a maximum lifetime of a rule; the rules below will be automatically deleted after 60s

$ firewall-cmd --timeout 60 \
               --add-service https

$ firewall-cmd --timeout 60 \
               --add-rich-rule 'rule family=ipv4 
                                source address= 

Adding traditional iptables rules

$ firewall-cmd --permanent \
               --direct \
               --add-rule ipv4 filter FORWARD 1 -i tun+ -d -j DROP

Configure NAT masquerading

$ firewall-cmd --zone=external \

Configure port forwarding

 $ firewall-cmd --zone=external \
                --add-forward-port 'port=8080:proto=tcp:toport=80:toaddr=' 
<![CDATA[Protect your infrastructure with SSH Jump Hosts]]>https://etherarp.net/ssh-jump-hosts/5db666587e1897000199e912Tue, 25 Jun 2019 12:37:00 GMTSSH includes a feature known as ProxyJump. ProxyJump allows an SSH connection to be used as a transparent proxy for a subsequent SSH connection. In other words, allowing you to "jump" through one server to reach another. These ProxyJumps can be chained together.

This sounds a bit confusing, but it's really just an extension of the SSH LocalForward option. To illustrate, suppose we have Host1 which shares an internal network with Host2. If we wanted to access Host2, we could do the following:

SSH ProxyJump simplifies this process, so that we can do the above in one line ssh host2user@host2 -J host1

Defining Jump Hosts in ~/.ssh/config

This feature is most powerful when ProxyJumps are defined in ~/.ssh/config because any SSH features (like port-forwarding or SCP) can be used through a jump host.

With this definition, every SSH connection to 'host2' will go through host1. Resolution of the hostname will be performed on host1. If custom options for host1 (such as a different port or user) are required, add an above entry in ~/.ssh/config

We can also add wildcard entries, like this

Chaining ProxyJump Entries

Host1 is reachable. Host2 is only reachable via Host1. Host3 is only reachable via Host2

Note: Hostname resolution is performed on the jump host. 'host2' must resolve correctly on host1, and host3 must resolve correctly on host2.
This problem can be solved by adding IP addresses as the hostnames in your local ~/.ssh/config.

Creating a locked down account for JumpHosts

Add the above to /etc/ssh/sshd_config to restrict the user sshjumpsa as a jump host service account. The PermitOpen statement is a whitelist of allowed upstream  destinations. The match is string/regex based, so adding a hostname does  not permit requests that use the corresponding IP.

To create the service account, use the following commands:

Locked Down Access in ~/.ssh/authorized_keys

It is also possible to define locked-down access for a particular public key in ~/.ssh/authorized_keys.

In the example below, the public key can open ssh connections to host2 and host3:22; access is only allowed from (the  from= statement is optional)


<![CDATA[Migrating the Blog (Ghost+Caddy+Docker)]]>https://etherarp.net/migrating-the-blog/5db666587e1897000199e954Tue, 25 Jun 2019 12:00:00 GMTThis blog is hosted using Ghost, a NodeJS based content management system. It is composed of three containers - the backend, the database and the proxy.

The proxy frontend will be handled with Caddy. Caddy is a very elegent web server, with a sucicent configuration syntax, automatic LetsEncrypt, and security focused default settings.

This post is a refinement of Setting up Ghost 2.0 With Cloudflare (November 24th, 2018)

Copying the volumes

All the persistent data on the old server is stored in docker volumes, which are directories on the local file system. First, shutdown the containers with docker-compose down and scp. The main working directory will be /srv/etherarp

Creating the new Docker compose file

This docker-compose file defines the three containers, db, www, ghost. The Ghost and Mariadb backend containers are in an internal-only network, while the Caddy frontend has two networks, internal and external.

Variables (in the form of ${vars}) are sourced from /srv/etherarp.net/.env. This contains the SQL credentials, SSL certificate paths, and external address bindings. The Docker host will host a number of origin servers, so each web facing container is bound to its own specific IPv6 address. Public access is provided through Cloudflare.

Configuring the Caddyfile

Configuring a systemd service

The stack can be controlled as a service using systemd. The systemd unit creates and destroys the IPv6 addresses, and calls Docker-compose. The preferred_lft 0 option is used when adding the address to prevent it being used as a default source address.


Next, firewalld needs to be configured to allow access to https from the Cloudflare edge. This is achieved with the above script. To allow public https access for a different address, the following firewalld rich rule can be used.

firewall-cmd --add-rich-rule \
"rule family=ipv6 port 
protocol=tcp port=443 
destination address=$addr 

I also add and remove additional rules to tightly restrict both in and outbound traffic. This is done both on the system and via the cloud provider console. On the topic of firewalls, it's a good time to check the Cloudflare settings for the site. I make sure the admin interface/API isn't accessible to the public internet

Wrapping up

At this point, all that's left to do is run systemctl start etherarp.service; systemctl enable etherarp.service.

To check it's working, we can run
curl --resolve etherarp.net:[$ip6addr] https://etherarp.net.

Once the local instance is tested and working, it's time for the moment of truth, via Cloudflare, the DNS records are changed to the new server. If you're able to read this post, it means everything went smoothly.

<![CDATA[Dynamic DNS with Route53]]>https://etherarp.net/dynamic-dns-with-route53/5db666587e1897000199e952Fri, 21 Jun 2019 08:51:08 GMTWhat is Route53

Amazon Route53 offers hosting for public DNS as well as private DNS for use within the cloud.

I really like the Amazon AWS API. I prefer it to Cloudflare for Dynamic DNS because Route53 provides granual API access (you can generate a key that can only update a single domain) while Cloudflare has a single API key unrestricted privileges.

The Script

IAM policies for Service account

Begin by going to the IAM console at https://console.aws.amazon.com/iam/home and click on Create Policy. Add the following JSON

Then create a group and attach the policy to the group. Finally, create a user, assign it to the group, and download its credentials.

Creating a RESTful API

I have a number of small devices (like routers) that don't have a proper python environment. I want a dynamic dns solution that can be accessed purely with curl. Using Flask, it is possible to host the python script as a service!.

Clients connect to it, and the script runs on the server, updating the records with the clients ip.

Securing it

I'm still new to Flask, so didn't have time to learn how to set up HTTP basic authentication. I used an ad-hoc solution, the API endpoint is defined by an environment variable, which can be set to some random string.

Access control can also be set up with a reverse proxy (e.g. nginx/caddy) but this is outside of the scope of this tutorial.

The service also uses TLS (https). To easily generate LetsEncrypt certs for your local environment, see my tutorial

<![CDATA[Encrypting Files using an RSA Public Key]]>https://etherarp.net/encrypting-files-using-an-rsa-public-key/5db666587e1897000199e951Sat, 01 Jun 2019 09:04:11 GMTObtaining a public key from a remote server

First, let's obtain the public key of a running SSH server.
The ssh-keyscan utility is used for this, with the -t flag specifying RSA key. We also need to convert it into a suitable format, this involves two things. First, stripping the hostname field added by ssh-keygen,
then using ssh-keygen to convert to the standard pkcs8 format

Encrypting a file against a public key

With the exception of very small files (less than ~2kb), RSA cannot be used directly for encryption. Instead,
RSA is used to encrypt/decrypt a shared passphase, used for a subsequent symmetric algorithm such as AES-256.

With that in mind, let's make a function to directly encrypt a small file using RSA

Putting it together

For this demo, I have a Docker containing running SSH.
It has a local ncat server that decrypts incoming requests using its RSA public key.

$ Get-SSHPublicKey $peer > $peer.pub.pem
# SSH-2.0-OpenSSH_6.7p1 Debian-5+deb8u8
$ Encrypt-WithRSA $peer.pub.pem <<< "Hello World" | cat >/dev/tcp/$peer/2222
$ docker logs ssh-demo
[ ok ] Starting OpenBSD Secure Shell server: sshd.
Hello World