LinxSMS

En fristående tjänst för att snabbt kunna skapa nya Linux-konton via SMS.

Beskrivning

Under min tid på 46elks höll vi ofta i workshops om att använda vårt API på olika sätt. I flera av dessa workshops behövde deltagarna tillgång till en webbservern för att kunna testköra kod. Deltagarna hade sällan tillgång till en egen server så vi valde att sätta upp en server enbart för våra workshops.

Vi insåg snabbt att det skulle bli ohållbart i längden att manuellt behöva skapa konton åt alla deltagare. Jag byggde därför ett script som tillät deltagarna att själva skapa ett konto genom att SMS:a sitt namn till ett telefonnummer. Som svar tillbaka fick de sina inloggningsuppgifter till servern.

En telefon och en terminal på datorn som illustrerar att du kan skapa ett Linux-konto via SMS.

Utmaningar och lösningar

Sätta en unik port

Ett av behoven vi hade var att varje deltagare skulle få en egen port där de kunde köra applikationen som byggdes under workshopen. Utmaningen låg i att hålla koll på hur många som hade skapat ett konto för att veta vilket portnummer varje nytt konto skulle få.

Jag valde att skapa en enkel textfil som innehöll siffran 0 och vid varje ny användare ökade denna siffra. Med start från port 5000 adderade jag siffran från filen för att på så sätt få fram vilken port användaren skulle få. Det funkade fint.

“Varför port 5000?” – Ja, någon port måste man ju börja på. Jag valde att avsätta port 5000 till 7000 till denna lösning. Det kändes som ett säkert kort för att undvika att det blev krockar med andra processer på servern.

Den dagen vi skulle komma upp till port 7000 kunde vi enkelt nollställa filen och börja om på port 5000. “Men kommer det inte bli krock då med befintliga konton?” – Jo, men vid det laget skulle de tidigare kontona vara inaktuella och troligtvis redan borttagna, så jag såg det som ett icke-problem.

Skapa en Linux-användare programmatiskt

Att skapa en användare i Linux med adduser visste jag sedan tidigare. Problemet med adduser är att du får upp en interaktiv prompt där du ska fylla i olika saker. I detta fall kunde jag inte använda adduser eftersom jag behövde skapa användaren automatiskt via ett script. Istället fann jag ett annat sätt att skapa användaren, nämligen:

sudo useradd -s /bin/bash -p <hashed_password> -m <username>

Det som är mest intressant i detta kommando är flaggan -p som tar ett hashat lösenord för användarkontot. Det skapade nämligen ett nytt problem för mig; hur saltar och hashar jag lösenordet korrekt så att Linux förstår?

Skapa en Linux-kompatibel lösenordshash

Efter lite research fann jag att Linux har använt olika hash-algoritmer genom åren. Vilken algoritm systemet använder som standard specificeras i variabeln ENCRYPT_METHOD i filen /etc/login.defs.

Det var egentligen skitsamma vilken standardalgoritm systemet använde eftersom jag skulle generera hashen själv. Det enda viktiga var att använda en säker algoritm och att systemet hade stöd för den algoritmen.

I mitt systemet var SHA512 standardalgoritmen, så jag valde att använda den. Det skulle dock inte vara så enkelt som att bara salta och hasha lösenordet och sedan ge Linux hashen. Linux behöver nämligen veta vilken hash-algoritm som har använts. Hur talar man om det då?

Hashens struktur

Jag visste sedan tidigare att alla användarkonton är listade i filen /etc/passwd. Däremot kände jag inte till att alla kontons hashade lösenord med tillhörande salt är lagrat i filen /etc/shadow/.

Om vi tittar i filen /etc/shadow/ kan vi se lite smått kryptiska strängar som ser ut ungefär så här:

$6$9sZxT9FQ$VZvhR.kW/1nPHkgPdcx...
$5$K1vQJq3P$4QnJL7hO.kqA9TULKmk...
$1$BZcG8MkP$8rckcqC08vNlEF8l5MJ...
$2y$10$HjvW6dPgH5XhhPQu9PbK5ezq...
$6$V1dMlKmX$iwxLnn1nU2I2WqOH8Vp...

Strängarna följer ett mönster som Linux kan avläsa. Strängarna börjar med ett id för hash-algoritmen, följt av ett salt och sedan det hashade lösenordet. De tre delarna separeras med ett dollartecken ($), alltså $ hash id $ salt $ hashat lösenord.

Bra att veta är att det verkar vara vanligare att man pratar om “hash prefix” snarare än “hash id”. Ett prefix är hashens id omgivet av två dollartecken, t.ex. $6$. I tabellen nedan kan du se vilket prefix/id respektive hash-algoritm har.

Algoritm Prefix
MD5 $1$
Blowfish (bcrypt) $2y$
SHA-256 $5$
SHA-512 $6$

Generera hashen

När jag visste hur strängen skulle se ut tänkte jag att jag kunde generera den manuellt så här:

$salt = bin2hex(random_bytes(8));
$pwd_hash = hash('sha512', $password . $salt);
$linux_hash = '$6$' . $salt . '$' . $pwd_hash; 

Men nej, så enkelt skulle inte det heller vara. Varför? – För att hashen behöver hashas minst 5000 gånger (tydligen standard för SHA512 i Linux). Med min metod skulle jag alltså behöva skriva en funktion som gjorde denna iteration på hashen. Tråkigt, tänkte jag.

Som tur är finns det inbyggda funktioner som fixar en Linux-kompatibel sträng åt oss. I PHP heter funktionen crypt() och tar ett lösenord och ett salt med ett prefix. Det kan se ut så här:

$salt = '$6$' . bin2hex(random_bytes(8));
$linux_hash = crypt($password, $salt);

När jag hade landat i denna lösning och verifierat att den fungerade så skrattade jag för mig själv och tänkte “mycket research för två rader kod”.

Säkerhetsaspekter

Jag var lite orolig över säkerheten på servern. Ville ju inte att deltagarna skulle kunna göra en massa hyss.

En kollega till mig (som är kunnig inom Linux) sa att Linux är relativt säkert som standard och att det troligtvis inte skulle vara så stort problem i vårt fall. Det kändes bra att veta. I vårt fall var det dessutom en separat server som vi lätt kunde stänga ner och sätta upp på nytt om det skulle behövas.

Av ren princip så ville jag dock förhindra att deltagarna kunde gå in i varandras hemmappar och kolla på varandras kod. Jag satte därför behörighet på hemmapparna till 750. Hade jag istället satt behörigheterna till 755 (vilket är standard för mappar) så hade deltagarna kunnat gå in i varandras hemmappar och se innehållet.

Projektdatum: oktober 2021