-
Notifications
You must be signed in to change notification settings - Fork 0
/
rss.xml
1449 lines (1021 loc) · 73 KB
/
rss.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<rss version="2.0" xml:base="https://zakaria.org">
<channel>
<title>zakaria's web log</title>
<description/>
<link>https://zakaria.org/posts/</link>
<item>
<link>https://zakaria.org/posts/resident-ssh-keys-on-windows.html</link>
<title>YubiKey resident SSH keys on Windows+WSL</title>
<pubDate>Wed, 23 Mar 2022 00:00:00 +0000</pubDate>
<description>
<h1 id="yubikey-resident-ssh-keys-on-windows-wsl">YubiKey resident SSH keys on Windows+WSL</h1>
<p>This is a guide that documents how to use YubiKey resident SSH keys on Windows, with passthrough to WSL2 via <code>npiperelay</code> and <code>socat</code>.</p>
<p>It's important to understand that with this setup, WSL is using the ssh-agent service running on Windows, not WSL. However, because of the magic of Unix pipes+sockets, you shouldn't notice any functional differences.</p>
<h2 id="download-openssh">Download OpenSSH</h2>
<p>As of writing, OpenSSH 8.1p1 (the default on Windows 10), does not support importing resident SSH keys from YubiKeys via <code>ssh-add</code> or <code>ssh-keygen</code>. So first we need to update OpenSSH for Windows:</p>
<ol>
<li>Download the latest .zip from the <a href="https://github.com/PowerShell/Win32-OpenSSH/releases">Win32-OpenSSH GitHub releases page</a>. (OpenSSH-Win64.zip in my case).</li>
<li>Unzip it, and run the <code>install-sshd.ps1</code> script as Administrator.</li>
</ol>
<p>This script will update the appropriate Windows system services (<code>sshd</code> and <code>ssh-agent</code>) to use the executables in the current directory instead of the default ones. The script will not, however install the the executables into your PATH (of which <code>ssh-add</code> and <code>ssh-keygen</code> we'll need later on) so remember where you've unzipped it.</p>
<p>Next restart the <code>ssh-agent</code> Windows service:</p>
<ol>
<li>Open <code>services.msc</code> from Run (<code>Win+R</code>).</li>
<li>Find &quot;OpenSSH Authentication Agent&quot;, right click and select &quot;Restart&quot;.</li>
</ol>
<h2 id="configuring-wsl2">Configuring WSL2</h2>
<ol>
<li><p>Install <code>socat</code> using your package manager (e.g. <code>sudo apt install socat</code>).</p></li>
<li><p>Download the latest npiperelay from the <a href="https://github.com/jstarks/npiperelay/releases">GitHub releases page</a>. Unzip it, and copy npiperelay.exe to somewhere in your WSL's <code>$PATH</code> (<code>~/bin/npiperelay.exe</code> in my case).</p></li>
<li><p>In your <code>~/.profile</code>, or <code>~/.bashrc</code> shell startup script add the following (adapted from the <a href="https://github.com/rupor-github/wsl-ssh-agent"><code>wsl-ssh-agent</code> README</a>):</p>
<pre><code>export NPIPE_CMD=$(command -v npiperelay.exe)
export SSH_AUTH_SOCK=$HOME/.ssh/agent.sock
# setup socat
ss -a | grep -q $SSH_AUTH_SOCK
if [ &quot;$?&quot; -ne &quot;0&quot; ]; then
rm -f $SSH_AUTH_SOCK
( setsid socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:&quot;$NPIPE_CMD -ei -s //./pipe/openssh-ssh-agent&quot;,nofork &amp; ) &gt;/dev/null 2&gt;&amp;1
fi
# add your ssh private key paths here ...
ssh-add ~/.ssh/id_ed25519 ~/.ssh/id_rsa
</code></pre></li>
</ol>
<h2 id="importing-resident-ssh-keys">Importing resident SSH keys</h2>
<p>Importing resident ssh private keys from the YubiKey via <code>ssh-keygen -K</code> isn't supported on the default version of OpenSSH installed on Windows 10. However, we can make use of the updated binaries we downloaded previously, which do support importing resident ssh keys:</p>
<ol>
<li>Open a privileged <strong>Administrator</strong> PowerShell window. It must be a privileged PowerShell window, otherwise it will fail with an &quot;invalid format&quot; error.</li>
<li><code>cd</code> to the directory the previously downloaded OpenSSH binaries reside (the path I told you to keep a not of).</li>
<li>Run <code>.\ssh-keygen -K</code>, and follow the prompts:</li>
</ol>
<p><img src="/static/img/import-resident-key.png" alt="screencap of PowerShell window showing the steps to import a resident ssh key" /></p>
<p>There is another method that requires a second device running anything that isn't Windows (Linux, *BSD, etc). I did this way before realising I could use the updated <code>ssh-keygen</code> binary:</p>
<ol>
<li>Import the resident keys to a file; <code>ssh-keygen -K -f ./id_ed25519_sk</code>, (make sure to set a password).</li>
<li>Either copy the resulting file to a USB drive, or upload it somewhere private (this is, after all, your <em>private</em> key).</li>
<li>Copy/download the file onto your Windows machine, and add it to the authentication agent via <code>ssh-add c:\path\to\file</code>.</li>
</ol>
<h2 id="adding-keys-to-the-agent">Adding keys to the agent</h2>
<p>Adding keys to the agent is fairly simple, whether in WSL or Windows. Just use the <code>ssh-add</code> command.</p>
<p>In WSL2:</p>
<pre><code>$ ssh-add /path/to/file
</code></pre>
<p>In Windows (PowerShell):</p>
<pre><code>PS C:\Windows\system32&gt; ssh-add C:\path\to\file
</code></pre>
<h3 id="automating">Automating</h3>
<p>If like me you have named your SSH keys in a non-standard way, or for some other reason ssh-agent won't load your keys on startup, we can write a simple PowerShell one-liner that runs at login:</p>
<pre><code>C:\Users\zzz\Documents\bin\OpenSSH-Win64\ssh-add.exe &quot;$env:USERPROFILE\.ssh\id_yubikey&quot; &quot;$env:USERPROFILE\.ssh\id_something_nonstandard&quot;
</code></pre>
<p>You will need to replace <code>C:\Users\zzz\Documents\bin\OpenSSH-Win64\</code> with the path to where you unzipped Win32-OpenSSH previously (hope you didn't forget!).</p>
<p>Save this script as <code>ssh-keys.ps1</code> in: <code>C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp</code>, and it should run at startup, prompting you for a password in a PowerShell window if your keys are password protected.</p>
<p>Similarly, for adding the private keys in WSL2 you can add a <code>ssh-add /path/to/key1 /path/to/key1</code> line to your shell rc.</p>
</description>
</item><item>
<link>https://zakaria.org/posts/headscale-setup.html</link>
<title>Installing Headscale on OpenBSD</title>
<pubDate>Tue, 15 Feb 2022 00:00:00 +0000</pubDate>
<description>
<h1 id="installing-headscale-on-openbsd">Installing headscale on OpenBSD</h1>
<p>In this post I'll detail the steps I took to install and configure
<a href="https://github.com/juanfont/headscale">headscale</a>, an open-source self-hostable implementation of the <a href="https://tailscale.com/">Tailscale</a> control server, on OpenBSD.</p>
<p>Code blocks prefixed with <code>#</code> imply that the command should be run as a privileged user/root.</p>
<p>If you get stuck, it'll be probably worthwhile to have a read of headscale's documentation available in their GitHub repo: <a href="https://github.com/juanfont/headscale/tree/main/docs">https://github.com/juanfont/headscale/tree/main/docs</a> .</p>
<h2 id="compilation">Compilation</h2>
<p>Since my server is on OpenBSD-stable, headscale is only available via <code>pkg_add</code> on -current, I need to compile the headscale binary by hand and upload it to the server.
I'm doing this on an OpenBSD machine, so compiling and uploading it to the server
should be a breeze.</p>
<pre><code>$ git clone [email protected]:juanfont/headscale.git
$ cd headscale
$ make generate
$ make build
$ sftp user@server &lt;&lt;EOF
&gt; put ./headscale
&gt; bye
EOF
</code></pre>
<p>Then login, get root access, and copy the binary to <code>/usr/local/bin</code>:</p>
<pre><code>$ ssh user@server
$ doas -s
...
# cp ./headscale /usr/local/bin
# chown root:bin /usr/local/bin/headscale
</code></pre>
<h2 id="configuration">Configuration</h2>
<p>Next we need to setup:</p>
<ol>
<li>a <code>_headscale</code> daemon user that headscale will run as.</li>
<li>directories for headscale to store its sqlite database, private key, and socket (making sure they have the correct permissions/owner).</li>
<li>copy the example config from GitHub, and edit it to our liking</li>
</ol>
<p>First we setup some directories:</p>
<pre><code># mkdir -p /etc/headscale
# touch /etc/headscale/config.yaml
# mkdir -p /var/headscale
</code></pre>
<p>Then we add our <code>_headscale</code> daemon user, and <code>chown</code> all the necessary dirs.<br />
Here we also run <code>doas</code> to get us a shell as <code>_headscale</code> so I can create the db.sqlite file. Note doing it this way requires some <code>doas.conf</code> rules.</p>
<pre><code># useradd -L daemon -s /sbin/nologin -d /var/headscale _headscale
# chown -R _headscale:_headscale /var/headscale
# doas -u _headscale /bin/ksh
$ touch /var/headscale/db.sqlite
</code></pre>
<p>Finally we can edit the headscale config. I highly recommend copying the <a href="https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml">example config</a> and using that as a starting point.</p>
<pre><code># vi /etc/headscale/config.yaml
# # copy the example config
# # change domain, IPs, ports
# # change sock location to /var/headscale/headscale.sock
</code></pre>
<p>I'm currently using <a href="https://man.openbsd.org/relayd">relayd(8)</a> as a TLS proxy, so I don't need to
configure any TLS-related stuff.</p>
<p>Run <code>headscale serve</code> to see if the config works, and all the directories and permissions are correct:</p>
<pre><code># doas -u _headscale headscale serve
...
</code></pre>
<p>If all is well we can move onto setting up an init script for the headscale daemon.</p>
<h2 id="rc-d">rc.d</h2>
<p>I've made an init script to making stopping/starting and running at boot
a lot easier. Drop this simple script into <code>/etc/rc.d/headscale</code>:</p>
<pre><code>#!/bin/ksh
#
# /etc/rc.d/headscale
daemon_user=&quot;_headscale&quot;
daemon=&quot;/usr/local/bin/headscale&quot;
daemon_flags=&quot;serve&quot;
. /etc/rc.d/rc.subr
rc_cmd $1
</code></pre>
<p><code>chmod</code>, enable and start it as usual:</p>
<pre><code># chmod +x /etc/rc.d/headscale
# rcctl enable headscale
# rcctl start headscale
headscale(ok)
</code></pre>
<h2 id="clients">Clients</h2>
<p>So here's where you may run into some problems. Depending on the OS your client is going to be running on, it might be a little difficult to get Tailscale to use your custom control server. On Android, this editing the hardcoded Tailscale control server URL, and compiling the client from source.</p>
<h3 id="openbsd-client">OpenBSD client</h3>
<p>It's pretty easy to get the Tailscale client on OpenBSD to use your control server.</p>
<pre><code># pkg_add tailscale
# rcctl enable tailscaled; rcctl start tailscaled
# tailscale --login-server &quot;https://headscale.example.com:443&quot;
...
</code></pre>
<p>Where <code>https://headscale.example.com:443</code> is your control server URL.</p>
<h3 id="android-client">Android client</h3>
<p>You can find the instructions for patching and building the custom APK <a href="https://github.com/juanfont/headscale/issues/58#issuecomment-950386833">here</a>.</p>
<p>I didn't bother even attempting to install the android sdk, ndk, and all the other required (garbage) on OpenBSD to compile the Android client. So I booted into Windows, then logged into Ubuntu on WSL, then had to install Go manually (since Ubuntu 20.04 packages are old AF and I can't be bothered to move all the junk I've accumulated in my WSL distro's $HOME). Then i installed the sdkmanager manually, then i installed the ndk through that manually, then I ran <code>make tailscale-debug.apk</code>.</p>
<p>Quite frankly this was painful, but it isn't supposed to be. Wrangling with old documentation I found online about installing the android sdk on the outdated WSL Ubuntu setup I have set me back a few hours.</p>
<p>In the end it was quite rewarding to install the APK file, open it up and see it all working!</p>
</description>
</item><item>
<link>https://zakaria.org/posts/stagit-setup.html</link>
<title>My stagit setup</title>
<pubDate>Mon, 14 Feb 2022 00:00:00 +0000</pubDate>
<description>
<h1 id="stagit-setup-on-openbsd">stagit setup on OpenBSD</h1>
<p><a href="https://codemadness.org/stagit.html">stagit</a> is a static page generator for git repos. It's very minimal - which I like.</p>
<p>In this post I'll detail how I set up <a href="https://git.zakaria.org/">git.zakaria.org</a> on my OpenBSD server.</p>
<p>The majority of my setup is based upon a post by <code>poptart</code>. Be sure to check out the original post here: <a href="https://hosakacorp.net/p/stagit-server.html">https://hosakacorp.net/p/stagit-server.html</a>. It's pretty well done, and easy to follow (unlike my post here - which is really more for my sake when I inevitably bork my server again and have to re-do it all). <code>poptart</code> has some other great OpenBSD-related posts on there as well.</p>
<h2 id="setup">Setup</h2>
<p>On the server install <code>git</code>, create the <code>_git</code> user, and make some directories:</p>
<pre><code>server# pkg_add git libgit2
server# groupadd _git
server# mkdir -p -m 744 /var/git/repos /var/git/template
server# useradd -g _git -L daemon -c &quot;git backend user&quot; -d /var/git/repos -s /usr/local/bin/git-shell _git
server# chown _git:_git /var/git/repos
</code></pre>
<p>The directories we created:</p>
<ul>
<li><code>/var/www/repos</code> contains the bare git files (also the <code>$HOME</code> of user <code>_git</code>)</li>
<li><code>/var/git/template</code> will contain the template for new repositories.</li>
</ul>
<p>The next steps are only if like me you've made source changes to stagit and you'd like to use your custom version. If you want to use plain stagit, install it on the server with <code>pkg_add stagit</code>.<br />
On another machine, compile stagit and <code>sftp</code> it over to the server:</p>
<pre><code>laptop$ make
laptop$ md5 stagit stagit-index
laptop$ # remember these checksums for verifying it's been copied over correctly
laptop$ sftp enderman &lt;&lt;EOF
&gt; put stagit
&gt; put stagit-index
&gt; bye
EOF
</code></pre>
<p>Back on the server, verify the binaries were copied over correctly:</p>
<pre><code>server# md5 stagit stagit-index
server# # verify the checksums
</code></pre>
<p>Copy these binaries to <code>/usr/local/bin</code> and set permissions appropriately:</p>
<pre><code>server# cp stagit{,-index} /usr/local/bin
server# chown root:bin /usr/local/bin/stagit{,-index}
server# chmod 755 /usr/local/bin/stagit{,-index}
</code></pre>
<p>The following is a message from the future:</p>
<blockquote>
<p>I wrote this post a while ago. But I recall abandoning the modified stagit binary setup
purely because the libraries on my laptop were newer than my server, and I
could not get static compilation to work...
So I gave up and installed stagit via <code>pkg_add stagit</code> on the server :P</p>
</blockquote>
<h2 id="scripts">Scripts</h2>
<p>Next we'll configure a few scripts:</p>
<ul>
<li><code>/var/git/repos/template/post-receive</code><br />
Will be executed every time we push to a repo.</li>
<li><code>/usr/local/bin/stagit-gen-index</code><br />
A helper script to generate the index page.</li>
<li><code>/usr/local/bin/stagit-newrepo</code><br />
Script to run to create a new repository.</li>
<li><code>/usr/local/bin/stagit-chdesc</code><br />
Script to change the description of an existing repository.</li>
</ul>
<p>Some internal variables will be shared between these scripts, for instance the
git user, htdocs path, etc. Instead of writing these out into each script we
can create a config file that each script will source before running anything.<br />
In <code>/var/git/config.rc</code>:</p>
<pre><code># shared variables
GIT_HOME=&quot;/var/git&quot;
GIT_REPOS=&quot;${GIT_HOME}/repos&quot;
WWW_HOME=&quot;/var/www/htdocs/git.zakaria.org
CLONE_URI=&quot;[email protected]&quot;
DEFAULT_OWNER=&quot;zakaria&quot;
DEFAULT_DESC=&quot;&quot;
GIT_USER=&quot;_git&quot;
</code></pre>
<p>We restrict access to this file to <code>root:wheel</code>:</p>
<pre><code>server# chown root:wheel /var/git/config.rc
</code></pre>
<p>After a <code>git push</code> we want the server to (re)generate the static html pages. To do this, we write a script in <code>/var/git/template/post-receive</code>:</p>
<pre><code>#!/bin/sh
# Author: Cale &quot;poptart&quot; Black
# Modified by: zakaria @ zakaria.org
# License: MIT
set -euf
. /var/git/config.rc
export LC_TYPE=&quot;en_US.UTF-8&quot;
src=&quot;$(pwd)&quot;
name=&quot;$(basename &quot;$src&quot;)&quot;
dest=&quot;${WWW_HOME}/$(basename &quot;$name&quot; '.git')&quot;
mkdir -p &quot;$dest&quot;
cd &quot;$dest&quot; || exit 1
echo &quot;[stagit] building $dest&quot;
/usr/local/bin/stagit &quot;$src&quot;
echo &quot;[stagit] linking $dest&quot;
# if a README.html exists use that as the index.html
# if not use log.html
if [ -f &quot;README.html&quot; ]; then
ln -sf README.html index.html
else
ln -sf log.html index.html
fi
ln -sf ../style.css style.css
ln -sf ../logo.png logo.png
</code></pre>
<p>Next, we need a script to generate the stagit <code>/index.html</code>. In <code>/usr/local/bin/stagit-gen-index</code>:</p>
<pre><code>#!/bin/sh
# Author: Cale &quot;poptart&quot; Black
# License: MIT
set -eu
. /var/git/config.rc
stagit-index &quot;${GIT_REPOS}/*.git&quot; &gt; &quot;${WWW_HOME}/index.html&quot;
</code></pre>
<p>The next script will be used to setup a new repository. In <code>/usr/local/bin/stagit-newrepo</code>:</p>
<pre><code>#!/bin/sh
# Author: Cale &quot;poptart&quot; Black
# Modified by: zakaria @ zakaria.org
# License: MIT
# Usage: stagit-newrepo &lt;name&gt; [desc] [author]
set -eu
. /var/git/config.rc
log() {
printf '%s\n' &quot;$*&quot; &gt;&amp;2
}
die() {
log &quot;error: $*&quot;
printf 'exiting...\n'
exit 1
}
REPO=&quot;$1&quot;
DESC=&quot;${2:-$DEFAULT_DESC}&quot;
OWNER=&quot;${3:-$DEFAULT_OWNER}&quot;
if [ -z &quot;$REPO&quot; ]; then
die &quot;no repo name given&quot;
fi
REPO_PATH=&quot;${GIT_REPOS}/${REPO}.git&quot;
git init --bare &quot;$REPO_PATH&quot;
cp &quot;${GIT_HOME}/template/post-receive&quot; &quot;${REPO_PATH}/hooks/post-receive&quot;
echo &quot;${CLONE_URI}/${REPO}.git&quot; &gt; &quot;${REPO_PATH}/url&quot;
echo &quot;$OWNER&quot; &gt; &quot;${REPO_PATH}/owner&quot;
echo &quot;$DESC&quot; &gt; &quot;${REPO_PATH}/description&quot;
chmod u+x &quot;${REPO_PATH}/hooks/post-receive&quot;
mkdir &quot;${WWW_HOME}/${REPO}&quot;
/usr/local/bin/stagit-gen-index
</code></pre>
<p>At the end of this script we call our <code>stagit-gen-index</code> helper script to regenerate
the <code>index.html</code>.</p>
<p>The last script is a simple wrapper that will allow us to change a repo's description:</p>
<pre><code>#!/bin/sh
# Author: zakaria / zakaria.org
# License: MIT
# Usage: stagit-chdesc &lt;repo&gt; &lt;new_description&gt;
set -eu
. /var/git/config.rc
log() {
printf '%s\n' &quot;$*&quot; &gt;&amp;2
}
die() {
log &quot;error: $*&quot;
printf 'exiting...\n'
exit 1
}
REPO=&quot;$1&quot;
DESC=&quot;$2&quot;
if [ -z &quot;$REPO&quot; ]; then
die &quot;no repo name provided&quot;
fi
if [ -z &quot;$DESC&quot; ]; then
die &quot;no new description provided&quot;
fi
REPO_PATH=&quot;${GIT_REPOS}/${REPO}.git&quot;
echo &quot;$DESC&quot; &gt; ${REPO_PATH}/description
</code></pre>
<p>Then set the appropriate permissions and restrictions for these scripts:</p>
<pre><code>$ chmod +x /usr/local/bin/stagit-{gen-index,newrepo,chdesc}
$ chown -R _git:_git /var/git/template
$ chmod +x $GIT_HOME/template
$ chown -R _git:_git $WWW_HOME
</code></pre>
<p>Replacing <code>GIT_HOME</code> and <code>WWW_HOME</code> with the variables we configured previously in <code>config.rc</code>.</p>
<p>Finally we configure our <a href="https://man.openbsd.org/doas.conf">doas.conf(5)</a> to allow our main user to run these commands:</p>
<pre><code>permit nopass mite as _git cmd /usr/local/bin/stagit-newrepo
permit nopass mite as _git cmd /usr/local/bin/stagit-gen-index
permit nopass mite as _git cmd /usr/local/bin/stagit-chdesc
</code></pre>
<p>I've made a git repository for these scripts (and maybe a few more) available on <a href="https://github.com/e-zk/stagit-scripts">GitHub</a> which is mirrored on <a href="https://git.zakaria.org/stagit-scripts/log.html">git.zakaria.org</a></p>
<h2 id="httpd-config">httpd config</h2>
<p>To actually serve the generated static files we need to configure <a href="https://man.openbsd.org/httpd">httpd(8)</a>. In <a href="https://man.openbsd.org/httpd.conf"><code>/etc/httpd.conf</code></a>:</p>
<pre><code>...snip...
server &quot;git.zakaria.org&quot; {
listen on 127.0.0.1 on 8080
# for letsencrypt
location &quot;/.well-known/acme-challenge/*&quot; {
root &quot;/acme&quot;
request strip 2
}
root &quot;/htdocs/git.zakaria.org&quot;
}
...snip...
</code></pre>
<p>I use <a href="https://man.openbsd.org/relayd">relayd(8)</a> as a TLS proxy, so I don't need to configure TLS certs in httpd.conf.</p>
</description>
</item><item>
<link>https://zakaria.org/posts/temporarybrowsing.html</link>
<title>Temporary web browsing</title>
<pubDate>Mon, 13 Dec 2021 00:00:00 +0000</pubDate>
<description>
<h1 id="temporary-web-browsing">Temporary web browsing</h1>
<p>Here's a few ways of scripting an ephemeral browser environment.</p>
<p>The idea is that the entire state of the browser is ephemeral - on exit
everything should be removed from storage including cookies, history
bookmarks, etc. This is more aggressive than your standard incognito mode
as it directly removes all files associated with the browser instance.</p>
<p>I use this all the time because for some things I don't find it necessary
for history, bookmarks, etc to be saved (a quick web search, logging into
personal, sensitive services like online banking). Some might find it
over-the-top but I think its a useful scripting exercise if anything.</p>
<h2 id="1-temporary-profile">1. Temporary profile</h2>
<p>Browsers typically have a user &quot;profile&quot;, where all bookmarks, history, and
other required state is stored.<br />
Most browsers that matter (Firefox, Chromium) allow the directory this profile
information is stored in to be configured via a CLI flag. For firefox this is
<code>---profile &lt;path&gt;</code>, which defaults to somewhere in <code>~/.local</code> if my memory
serves correct. Similarly, Chromium (and I assume other Chromium-based browsers
by proxy) support this via the <code>--user-data-dir=&lt;path&gt;</code>, which defaults to
<code>~/.config/chromium</code>.</p>
<p>This feature can be used to create a fresh, new profile in a custom directory.
On exit this directory can be aggressively removed by a simple <code>rm -rf &lt;path&gt;</code>
command. Here's a short script to facilitate all this for Firefox:</p>
<pre><code>#!/bin/sh
# make temporary profile directory
PROFILE_DIR=&quot;$(mktemp -d -p /tmp 'firefox.XXXXXX')&quot;
cleanup() {
rm -rvf &quot;$PROFILE_DIR&quot;
}
firefox-esr --new-instance --no-remote --profile &quot;$PROFILE_DIR&quot; &quot;$@&quot;
cleanup
</code></pre>
<p>First the script creates a temporary directory via <code>mktemp</code>, where the profile
will be. Next we define a cleanup function to be run after Firefox, so that on
exit the profile is simply <code>rm -rf</code>'d. Then <code>firefox-esr</code> is run (replace this
with <code>firefox</code> if you don't use the ESR variant), and the profile directory is
specified with <code>--profile</code>.</p>
<p>I also included the <code>--new-instance</code> flag to make sure Firefox didn't just
open a tab in an already running instance, and <code>--no-remote</code> to dissallow
remote commands.</p>
<p>Additional arguments can be passed to the script - these will be directly added to
<code>firefox-esr</code>'s arguments.</p>
<p>I also like to add a <code>trap</code> to the script, so if it ever exits unexpectedly,
the clean-up process will still take place. This is what that looks like:</p>
<pre><code>#!/bin/sh
# trap everything; clean up on exit
trap cleanup 1 2 3 6 15
# make temporary profile directory
PROFILE_DIR=&quot;$(mktemp -d -p /tmp 'firefox.XXXXXX')&quot;
cleanup() {
rm -rvf &quot;$PROFILE_DIR&quot;
}
firefox-esr --new-instance --no-remote --profile &quot;$PROFILE_DIR&quot; &quot;$@&quot;
cleanup
</code></pre>
<p>The equivalent final script for Chromium/Chrome:</p>
<pre><code>#!/bin/sh
# trap everything; clean up on exit
trap cleanup 1 2 3 6 15
# make temporary profile directory
PROFILE_DIR=&quot;$(mktemp -d -p /tmp 'chrome.XXXXXX')&quot;
cleanup() {
rm -rvf &quot;$PROFILE_DIR&quot;
}
chromium --user-data-dir=&quot;$PROFILE_DIR&quot; &quot;$@&quot;
cleanup
</code></pre>
<h2 id="2-temporary-home-directory">2. Temporary home directory</h2>
<p>Instead of specifying a custom profile, then removing it, why not create an
entire ephemeral home directory, then remove <em>that</em>?</p>
<p>This trick is fairly simple and works on <em>any program</em>, not just browsers.
Just re-define the <code>$HOME</code> environment variable to <code>$FAKEHOME</code>, and any program that runs
after that will &quot;think&quot; that's the real home directory:</p>
<pre><code>$ FAKEHOME=/tmp/firefox
$ HOME=$FAKEHOME firefox [...] https://zakaria.org/
</code></pre>
<p>Note this method means removing <code>$FAKEHOME/Downloads</code> and <em>any</em> directory the
program creates in its dedicated home directory on exit. If you've downloaded
something you'd like to keep you'll need to copy it out of <code>$FAKEHOME/Downloads</code>
<em>BEFORE</em> closing the browser.<br />
Since the program runs in a new home directory, if you use X11 it won't have
access to <code>~/.Xauthority</code>, so it won't be able to actually be graphically useful.
To fix this simply symlink <code>~/.Xauthority</code> to <code>$FAKEHOME/.Xauthority</code> before running.</p>
<p>A script that does all this, plus some more:</p>
<pre><code>#!/bin/sh
# trap everything; clean up on exit
trap cleanup 1 2 3 6 15
# setup fake home dir
FAKEHOME=&quot;$(mktemp -d -p /tmp 'firefox.XXXXXX')&quot;
# clean up after exit
cleanup() {
rm -rvf &quot;$FAKEHOME&quot;
}
# link Xauthority
ln -s &quot;${HOME}/.Xauthority&quot; &quot;${FAKEHOME}/.Xauthority&quot;
# unset XDG dirs as these can intefere with the fake $HOME if they're set
# to something non-default
unset XDG_CONFIG_HOME
unset XDG_CACHE_HOME
# set the fake home dir
export HOME=${FAKEHOME}
# run firefox w/ given args
firefox-esr --new-instance --no-remote &quot;$@&quot;
# uncomment for chrome
# chromium &quot;$@&quot;
cleanup
</code></pre>
<h2 id="extra-stuff">Extra stuff</h2>
<p>To add to these scripts, one could also:</p>
<ul>
<li>Force a specific user-agent string</li>
<li>Configure <code>user.js</code> used by Firefox by writing to <code>$PROFILE_DIR/user.js</code>
before actually running the Firefox command</li>
</ul>
<p>My personal scripts do just that, for <a href="https://git.zakaria.org/bin-obsd/file/tmpchrome.html">Chromium</a> and <a href="https://git.zakaria.org/bin-obsd/file/tmpfox.html" title="maybe uses outdated user.js flags">Firefox</a>.</p>
<h2 id="other-methods">Other methods</h2>
<p>Some other methods to look into include:</p>
<ul>
<li>Mounting a new <strong>in-memory filesystem</strong> dedicated to a fake home dir or browser
profile
<ul>
<li>OpenBSD does not support mounting filesystems as non-root
users, so this isn't ideal for my setup.</li>
</ul></li>
<li>Ephemeral <strong>containers</strong>
<ul>
<li>While the security aspects of Linux containers are overrated IMO, using
one as the basis for an ephemeral browser instance doesn't sound like an awful
idea.</li>
</ul></li>
<li>Dedicated <strong>virtual machine</strong>
<ul>
<li>I've actually accomplished this in the past on an Alpine Linux VM running on OpenBSD's
<a href="https://man.openbsd.org/vmd">vmd(8)</a> and X11 forwarding. But I never fleshed out
any scripts for making it truly ephemeral - that might be the focus of a future blog post.
<a href="/static/img/linuxwebvm.png">Here's</a> an old screenshot of that running on OpenBSD 6.7.</li>
</ul></li>
</ul>
</description>
</item><item>
<link>https://zakaria.org/posts/breaking_promises.html</link>
<title>Breaking promises with LD_PRELOAD</title>
<pubDate>Tue, 07 Dec 2021 00:00:00 +0000</pubDate>
<description>
<h1 id="breaking-promises-with-ld-preload">Breaking promises with LD_PRELOAD</h1>
<p>In this post I show how to simply <a href="https://wikipedia.org/wiki/NOP_(code)">noop</a> calls to OpenBSD's <a href="https://man.openbsd.org/pledge">pledge(1)</a> using LD_PRELOAD.</p>
<p>I only focus on pledge(2), only because that's what I first tried this out on.
But this will also work for <a href="https://man.openbsd.org/unveil">unveil(2)</a>, and any syscall you want for that matter.</p>
<p>Before we get into it, this doesn't actually make pledge(2) completely useless, nor is it an oversight by the developers of OpenBSD - LD_PRELOAD can be used to break any and every system call. I just thought it was a neat trick that might be useful for CTFs or something. In regard to the title, I just couldn't pass on the fantastic opportunity for some word play+clickbait.</p>
<h2 id="what-is-pledge">What is pledge?</h2>
<p><a href="https://man.openbsd.org/pledge">pledge(2)</a> is a system call created for OpenBSD for the purpose of application sandboxing.
When called, pledge(2) &quot;promises&quot; that the current process will only make use of certain system calls - if the process violates these promises after pledge(2) has been called the process is killed with a SIGABT signal.
Using this system call appropriately facilitates the <a href="https://wikipedia.org/wiki/Principle_of_least_privilege">security principle of least privilege</a>; only allowing the process access to the parts of the system it needs to operate, and denying it access to functionality it does not need.</p>
<p>Subsequent calls to pledge(2) can reduce the abilities of a program further, but abilities cannot be regained.<br />
This can be useful when writing daemon programs - during initialisation system daemons often need access to a lot of the more &quot;priviledged&quot; system calls, but after initialisation access to these abilities is no longer required. So typically daemon programs on OpenBSD call pledge(2) multiple times. Once to allow access to the initial system calls, then again, once the process is initialised, to deny access to ever use those system calls again.</p>
<h2 id="what-is-the-ld-preload-trick">What is the LD_PRELOAD trick?</h2>
<p>I reccommend reading this article by baeldung to understand what LD_PRELOAD is: <a href="https://www.baeldung.com/linux/ld_preload-trick-what-is">What is the LD_PRELOAD Trick?</a>.</p>
<p>Simply put, LD_PRELOAD influences the &quot;linkage&quot; of shared libraries and resolution of functions at runtime. This can be useful for debugging, reversing, and profiling dynamically linked programs.</p>
<h2 id="crafting-a-shared-object">Crafting a shared object</h2>
<p>LD_PRELOAD works by loading a shared object file that can essentially re-define whatever dynamically linked C function or syscall during the running of the program.</p>
<p>To craft our shared object to use with LD_PRELOAD first we have to look up the function signature of pledge(2) in the <a href="https://man.openbsd.org/pledge">man page</a> and plop it into a .c file:</p>
<pre><code class="language-c">#include &lt;unistd.h&gt;
int
pledge(const char *promises, const char *execpromises)
</code></pre>
<p>Next, we want to make sure when called pledge(2) does absolutely nothing and returns success every time it's called. Alternatively, you could add additional <a href="https://man.openbsd.org/pledge">promises</a>, or remove specific promises, but I think its more jarring to make it do absolutely nothing.</p>
<pre><code class="language-c">// pledge_override.c
#include &lt;unistd.h&gt;
int
pledge(const char *promises, const char *execpromises) {
// noop
return 0;
}
</code></pre>
<p>Compile it into a shared object <code>override.so</code>:</p>
<pre><code class="language-console">$ cc -shared -fPIC -o override.so pledge_override.c
</code></pre>
<p>Now, with this shared object file, you can run anything you want ignoring it's pledge(2) promises:</p>
<pre><code class="language-console">$ LD_PRELOAD=$PWD/override.so &lt;program&gt;
</code></pre>
<h2 id="testing-it-out">Testing it out</h2>
<p>Here we have an example use of pledge(2) (yes, I should be checking return values, but this is just an example).</p>
<pre><code class="language-c">// test.c
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
int
main(void) {
// promise to only use stdio
pledge(&quot;stdio&quot;, NULL);
printf(&quot;Hello! I will abort now :3\n&quot;);
// revoke all promises
pledge(&quot;&quot;, NULL);
// should abort here
printf(&quot;You should not be seeing this :O\n&quot;);
return 0;
}
</code></pre>
<p>When run normally the program should abort at the second call to printf(3) since after the
first one we've revoked the privilege to call <code>stdio</code> functions.</p>
<pre><code class="language-console">$ ./a.out
Hello! I will abort now :3
Abort trap (core dumped)
</code></pre>
<p>Then, when we add our specially crafted <code>override.so</code> to the equation:</p>
<pre><code class="language-console">$ LD_PRELOAD=$PWD/override.so ./a.out
Hello! I will abort now :3
You should not be seeing this :O
</code></pre>
<h2 id="when-this-won-t-work">When this won't work</h2>
<p>Using LD_PRELOAD like this to get around pledge doesn't work when:</p>
<ol>
<li>You statically link your binary
<ul>
<li>If you statically link your binary, LD_PRELOAD does not have any effect.</li>
<li>To test this out, compile the above example program with <code>-static</code>, then try and do the LD_PRELOAD trick again. It'll always abort at the correct <code>printf(3)</code> call.</li>
</ul></li>
<li>SUID / SGID
<ul>
<li>SUID or SGID binaries aren't affected by LD_PRELOAD according to the OpenBSD <a href="https://man.openbsd.org/ld.so#LD_PRELOAD">man page</a>.<br />
The Linux <a href="https://linux.die.net/man/8/ld.so">man page</a> is less clear about this, but I assume its the same.</li>
<li>Simply put, your doas(1) and sudo(1) are safe.</li>
</ul></li>
</ol>
<h2 id="changelog">Changelog</h2>
<ul>
<li>18-07-2022: Added &quot;What is pledge?&quot; and &quot;What is LD_PRELOAD&quot; sections.</li>
</ul>
</description>
</item><item>
<link>https://zakaria.org/posts/winsandbox.html</link>
<title>Windows Sandbox</title>
<pubDate>Sun, 08 Aug 2021 00:00:00 +0000</pubDate>
<description>
<h1 id="windows-sandbox">Windows Sandbox</h1>
<p>If you['re forced to] use Windows this might be useful for compartmentalisation.</p>
<p>Windows Sandbox is a Windows 10 Pro feature that enables the use of temporary Virtual Machines. These can be used as ephemeral sandboxes for applications.</p>
<h2 id="enabling">Enabling</h2>
<p>Windows Sandbox isn't enabled by default. To enable it follow these steps:</p>
<ol>
<li>Open &quot;Control Panel&quot; and click the upside down caret next to the back/forward buttons</li>
<li>Click &quot;All Control Panel Items&quot;</li>
<li>Navigate to &quot;Programs and Features&quot;</li>
<li>On the left click &quot;Turn Windows features on or off&quot;</li>
<li>Scroll down and tick the box next to &quot;Windows Sandbox&quot; if it isn't already ticked</li>
</ol>
<p>From the start menu search for &quot;Windows Sandbox&quot;. Hit enter and a fresh Sandbox window will appear.</p>
<h2 id="configuring">Configuring</h2>
<p>Once the sandbox window is closed all data is erased. Next time you open Windows Sandbox a fresh new VM is created.<br />
This can make it annoying if you wish to sandbox a single program, but have to install it every time you start a new sandbox.</p>
<p>To make it easier for these cases you can pre-configure sandbox instances via <code>.wsb</code> files. With this file you can configure memory, networking, audio/video passthrough, among other things. See the Microsoft official documentation for Windows Sandbox configuration<sup class="footnote-ref" id="fnref:wsb"><a href="#fn:wsb">1</a></sup>. It also supports running a script at startup, and mapping network devices and local shares.</p>
<p>I highly suggest reading through the documentation for the <code>.wsb</code> file format. There are some good examples and interesting features.</p>
<h3 id="example-zoom">Example: Zoom</h3>
<p>Zoom is a very popular group call/video conference software (I won't bore you with the details I'm sure you have <em>some</em> idea what Zoom is by now). However, Zoom has had numerous security and privacy issues. If you're paranoid like me you may feel uneasy just seeing it's icon in the start menu - knowing you have it installed on the same machine along with all your other precious digital data. I digress, here is a configuration file for my Zoom sandbox:</p>
<pre><code class="language-xml">&lt;Configuration&gt;
&lt;MappedFolders&gt;
&lt;MappedFolder&gt;
&lt;HostFolder&gt;C:\Sandbox\Installers&lt;/HostFolder&gt;
&lt;SandboxFolder&gt;C:\Installers&lt;/SandboxFolder&gt;
&lt;ReadOnly&gt;true&lt;/ReadOnly&gt;
&lt;/MappedFolder&gt;
&lt;/MappedFolders&gt;
&lt;AudioInput&gt;Enable&lt;/AudioInput&gt;
&lt;VideoInput&gt;Enable&lt;/VideoInput&gt;
&lt;VGpu&gt;Enable&lt;/VGpu&gt;
&lt;MemoryInMB&gt;12288&lt;/MemoryInMB&gt;
&lt;LogonCommand&gt;
&lt;Command&gt;C:\Installers\ZoomInstaller.exe&lt;/Command&gt;
&lt;/LogonCommand&gt;
&lt;/Configuration&gt;
</code></pre>
<p>It maps the local <code>C:\Sandbox\Installers</code> directory (where I store <code>ZoomInstaller.exe</code>) to <code>C:\Installers</code> in the VM itself. Enables audio (mic) and video (webcam) passthrough. I allocate 12Gb to the VM - should be more than enough on my laptop with 16Gb of RAM. It then runs the Zoom installer on startup.</p>
<p>The verdict? It works fairly well. Recently however webcam passthrough has stopped working, possibly due to a driver issue. (Installing drivers requires the machine to reboot - but you can't reboot a sandbox without deleting everything).</p>
<p>I make use of a couple of these <code>.wsb</code> files for different programs I'd rather install on an ephemeral virtual machine than my main OS.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:wsb"><a href="https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-sandbox/windows-sandbox-configure-using-wsb-file">https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-sandbox/windows-sandbox-configure-using-wsb-file</a></li>
</ol>
</div>
</description>
</item><item>
<link>https://zakaria.org/posts/2021-02-24-feb.html</link>
<title>Feb 2021 Update: Git Server + Hidden Service</title>
<pubDate>Wed, 24 Feb 2021 00:00:00 +0000</pubDate>
<description>
<h1 id="update-feb-2021-git-server-hidden-service">Update Feb 2021: git server + hidden service</h1>
<p>Here's a little site update.</p>
<h2 id="css-changes">CSS changes</h2>
<p>As usual the CSS of this website is ever-changing. I've changed it to be more monochrome, with blue highlights on link hover/focus and text selection. Headings are now all <code>1em</code>.</p>
<h2 id="git-server">Git server</h2>
<p>I've set up a git server on <a href="https://git.zakaria.org/">git.zakaria.org</a> using <a href="https://codemadness.org/stagit.html">stagit</a> to generate the pages. I followed a guide by <code>poptart</code><sup class="footnote-ref" id="fnref:1"><a href="#fn:1">1</a></sup>. It's a very well written guide for setting up stagit on OpenBSD.<br />
I modified some of the scripts provided to suit my workflow.</p>
<p>I plan to move most of my projects that don't require an issue tracker over there. Namely personal configs/programs.</p>
<h2 id="hidden-service">Hidden service</h2>
<p>During January I made this site accessible over Tor via a <a href="https://wikipedia.org/wiki/Tor_(anonymity_network)#Onion_services">hidden service/onion service</a>. I had some (layer 8) issues with the service provider and had to reset the server and so the service URL has changed a few times. The canonical onion service URL can be found on the <a href="/about.html">about page</a> or in the footer.</p>
<p>Why have I setup an onion service? Three reasons:</p>
<ol>
<li>Tor is a technology that interests me</li>
<li>I wanted to see how difficult it would be to setup a hidden service (it was not difficult<sup class="footnote-ref" id="fnref:2"><a href="#fn:2">2</a></sup>)</li>
<li>It's fun</li>
</ol>
<p>Because onion services can't have subdomains, I've setup <code>relayd(8)</code> so the git server can be accessed on the service via a <code>/git/</code> endpoint (this is only available on the hidden service).</p>
<h2 id="other">Other</h2>
<p>Edit 20201-02-25: Apologies for those who now have duplicate RSS entries. I've redone the RSS script, so the new feed might have merged with the old in your readers. Removing and re-adding the feed should fix this.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1"><a href="https://hosakacorp.net/p/stagit-server.html">https://hosakacorp.net/p/stagit-server.html</a></li>
<li id="fn:2">I referred to these two guides: <a href="https://medium.com/@sarala.saraswati/tor-hidden-services-on-openbsd-with-httpd-52852f49358c">https://medium.com/@sarala.saraswati/tor-hidden-services-on-openbsd-with-httpd-52852f49358c</a>, <a href="https://dataswamp.org/~solene/2018-10-11-tor-hidden-service.html">https://dataswamp.org/~solene/2018-10-11-tor-hidden-service.html</a><br />
</li>
</ol>
</div>
</description>
</item><item>
<link>https://zakaria.org/posts/2021-01-11-usbkiller.html</link>
<title>Usbkill the OpenBSD Way</title>
<pubDate>Mon, 11 Jan 2021 00:00:00 +0000</pubDate>
<description>
<h1 id="usbkill-the-openbsd-way"><code>usbkill</code> the OpenBSD way</h1>
<p><a href="https://github.com/hephaest0s/usbkill"><code>usbkill</code></a> is a kill-switch that shuts down your computer on any USB change. It does this by watching the output of <code>lsusb</code> and when there is a change it runs <code>shutdown -h now</code>.</p>
<p>Simple, right? One thing to note is that <code>lsusb</code> is not included in OpenBSD's base packages, but <a href="https://man.openbsd.org/hotplugd"><code>hotplugd(8)</code></a> is.</p>
<h2 id="hotplugd">hotplugd</h2>
<p>So what is <code>hotplugd</code>, and how can it be useful? The <a href="https://man.openbsd.org/hotplugd">man page</a> does a good job of explaining it. Simply put: when any device is attached to or detached from your machine <code>hotplugd</code> will execute a script.</p>
<p>To see how simple it is to write a hotplug script we can start by simply logging device attach events. First, start by enabling and starting <code>hotplugd</code> (as root):</p>
<pre><code># rcctl enable hotplugd
# rcctl start hotplugd
hotplugd(ok)
</code></pre>
<p>Next we will write the script that is executed on a device attach event:</p>
<pre><code># mkdir -p /etc/hotplug
# $EDITOR /etc/hotplug/attach
</code></pre>
<p>In this file we will log the device class and name:</p>
<pre><code>#!/bin/sh
DEVCLASS=$1
DEVNAME=$2
# log attach event
logger -t hotplug &quot;$DEVCLASS:$DEVNAME attached&quot;