WSGIPassAuthorization

Een RESTful API die niet helemaal publiek mag zijn is meestal beveiligd met een variant van HTTP Basic Authentication. Daar is natuurlijk niets mis mee, want het is veruit de eenvoudigste manier voor een API-consument om zich te identificeren.

Maar onlangs stootte ik toch op een caveat emptor: wanneer je een API in Python (bv. Flask) via mod_wsgi beschikbaar stelt, dan werkt het plots niet meer om met Basic Authentication aan te melden. Mysterie, mysterie, zeker omdat het in de ontwikkelomgeving wel werkte.

Na lang (~ 1/2e dag) zoeken bleek mod_wsgi de schuldige (en mijn gebrekkige lectuur van de handleiding, want het is gedocumenteerd). In een standaardopstelling worden de authorization headers immers onderschept door Apache, die zelf de authenticatie wil uitvoeren. Uiteraard wist ik dat niet, en had Apache ook niet zo geconfigureerd. En dus stond ik voor het mysterie van de missende header.

Maar via wat google-fu en een blogpost kwam ik dan toch op de oplossing. In de vhost (of elders, al naargelang) waar je de wsgi-applicatie hebt geconfigureerd moet je de instelling WSGIPassAuthorization op On zetten (en dan de webserver herstarten, dat spreekt).

WSGIPassAuthorization On

En hoera! De authenticatie werkt! Op naar het volgende probleem!

Wetteren – Gent-Sint-Pieters: in het zadel!

Ik ben vandaag met de fiets van Wetteren naar het Sint-Pietersstation gereden. Op zich geen wereldschokkend nieuws (al is het een beetje vreemd voor iemand die altijd de trein neemt), maar ik ga het er toch even over hebben. We moeten immers allemaal groen zijn (fiets! openbaar vervoer!), maar wanneer iemand het dan ook echt eens wil doen blijkt dit toch ook weer niet zo eenvoudig …

Er is geen “fietsostrade” van Wetteren naar Gent (wel een autostrade, de E40, een steenweg, de N9, en diverse spoorwegen), dus er is geen mooi recht fietspad. Ik heb dus zelf een beetje een route gefabriceerd, met hopelijk een mooie balans tussen snelheid (de droom om per fiets naar Gent en dan per trein naar Brussel is nog niet dood) en veiligheid (één accident is meer dan genoeg).

“Wetteren – Gent-Sint-Pieters: in het zadel!” verder lezen

Git: terugdraaien naar een specifieke commit

Iets wat heel eenvoudig is. Als je weet hoe.

git reset --soft <commit_hash>

Dit zet alle wijzigingen in jouw lokale kopie terug naar de status van die specifieke commit. Alle latere commits worden hierdoor gewist. Afhankelijk van de instellingen van je remote (bv. Github) kan je dit ook pushen, wat ook daar alle latere commits doet verdwijnen (ze zijn ook niet meer terug te halen!).

SSH SOCKS-tunnel en Firefox

Ik heb een VPN. Maar dat is eigenlijk een management-VPN die bedoeld is om servers te beheren, en niet echt om internetverkeer te verwerken om spionerende netwerkbeheerders te slim af te zijn.

Dus, een andere oplossing. Een simpele oplossing.

Met SSH, en de jump host uit dit bericht is het zelfs heel simpel. SSH kan immers zonder al te veel moeite een SOCKS-tunnel opzetten, waarmee je al het verkeer dat naar de getunnelde poort gestuurd wordt kan doorsturen naar de remote host.

Het SSH-commando is simpel, de uitleg iets minder.

ssh -D 9999 -C -q -N pieter@vpn.helptux.be

Dit commando doet het volgende:

  • -D 9999: forward poort 9999 op jouw lokaal systeem naar vpn.helptux.be
  • -C: comprimeer het verkeer
  • -q: geen output
  • -N: er volgt geen commando (houdt de tunnel open)

Je maakt hiermee automatisch een SOCKS-tunnel aan die al het verkeer dat je naar poort 9999 stuurt zal forwarden naar vpn.helptux.be, waar het verkeer dan verder via het juiste protocol (bv. HTTP) wordt verwerkt.

Je kan dan in jouw browser als SOCKS-proxy (niet als HTTP(S)-proxy, dit zal niet werken) 127.0.0.1:9999 ingeven. Stuur al het verkeer (ook DNS) via de proxy, maar laat de instellingen voor de HTTP, HTTPS en FTP(S)-proxies leeg. SOCKS zal dit automatisch oplossen.

SSH Jump host

Als je meerdere servers hebt, dan is het aangeraden om een intern, management-only, netwerk te voorzien, om beheertaken (DNS, Puppet, Zabbix enz.) uit te voeren die je niet via het publieke netwerk wil sturen. Uiteraard heeft dit netwerk geen toegang tot het internet. Gezien mijn hosts niet allemaal in hetzelfde datacenter zitten, en ik geen geld heb voor een dedicated link, gebruik ik OpenVPN voor het achterliggende netwerk.

Nu wil ik wel kunnen inloggen op alle hosts via dit netwerk, voornamelijk omdat ik dan de interne domeinnamen kan gebruiken. Maar daarvoor heb je natuurlijk een toegang nodig, de jump host. Voor mij is dat de VPN-server (bereikbaar via SSH vanop het publieke netwerk).

Uiteraard ondersteunt SSH dit, en het is zelfs niet zo moeilijk.

Zeg dat alle hosts zich in het interne *.dc.helptux.be-domein bevinden, en dat pieter de gebruiker is om aan te loggen. vpn.helptux.be is de domeinnaam van de VPN-server (dit werkt ook met IP-adressen).

Voeg dan het volgende toe in $home/.ssh/config:

Host *.dc.helptux.be
 ProxyJump vpn.helptux.be

Omdat SSH eerst inlogt op de jump host kan je de hostnamen van het interne netwerk en de interne DNS-server gebruiken, zolang de jump host de domeinnamen kan resolven natuurlijk.

Als jouw lokale gebruiker niet gelijk is aan de gebruiker op de jump host, voeg dan je gebruikersnaam toe aan de jump host:

ProxyJump pieter@vpn.helptux.be

Uiteraard kan je ook User en IdentityFile toevoegen, maar let wel dat IdentityFile de locatie is op jouw lokaal systeem (bv. laptop) en niet op de jump host. User is de gebruiker waarmee je inlogt op de uiteindelijke host, niet de jump host.

Voor gebruikers van een OpenSSH-versie onder 7.3 werkt het bovenstaande, mooie commando niet. Maar, niet getreurd, ook voor hen is er hulp! In plaats van ProxyJump user@jumphost moet je het volgende in $home/.ssh/config plaatsen:

ProxyCommand ssh pieter@vpn.helptux.be -W %h:%p

Nu werkt het ook op Ubuntu …

SELinux en poorten

Om een proces (bv. httpd) toegang te geven tot een bepaalde poort moet je in SELinux de context van de poort veranderen.

Dat is niet zo moeilijk:

semanage port -a -t http_port_t -p tcp 8443

Maar, soms is de poort al toegewezen aan een context en krijg je een toffe foutmelding:

Port tcp/8443 already defined

Maar, je kan gelukkig ook, in plaats van een combinatie toe te voegen (-a), ook de bestaande combinatie wijzigen (-m):

semanage port -m -t http_port_t -p tcp 8443

(Het staat ook in de man-page.)

En zo is SELinux gelukkig, en is iedereen gelukkig!

hiera-eyaml – caveat emptor

Ik gebruik Puppet voor mijn persoonlijk serverpark, en ben daar best tevreden over. Maar af en toe …

Zoals nu dus. In het kader van een herstructurering wordt de Puppetmaster gemigreerd van een VPS naar een fysieke server. Wanneer ik zeg gemigreerd, bedoel ik eigenlijk opnieuw opgezet, met een aantal verbeteringen, zoals daar zijn verschillende environments en r10k voor de synchronisatie van repositories en modules.

Omdat alles in git zit (uiteraard) en ik een beetje paranoïde ben, versleutel ik mijn wachtwoorden en SSH-sleutels (opgeslagen in Hiera) met hiera-eyaml.

De gem (Ruby …) was mooi geïnstalleerd, en mijn hiera.yaml (geconfigureerd per environment) leek in orde.

:hierarchy:
 - "%{::osfamily}"
 - webservers
 - databases
 - roles
 - common
:backends:
 - eyaml
 - yaml
:yaml:
 :datadir: "/etc/puppetlabs/code/environments/%{::environment}/hieradata"
:eyaml:
 :datadir: "/etc/puppetlabs/code/environments/%{::environment}/hieradata"
 :pkcs7_private_key: "/etc/puppetlabs/puppet/keys/private_key.pkcs7.pem"
 :pkcs7_public_key: "/etc/puppetlabs/puppet/keys/public_key.pkcs7.pem"

Maar … Het werkte toch niet (anders was er nu geen blogpost). Na een (vruchteloze) speurtocht doorheen /etc om alle mogelijke hiera.yaml-bestanden die de Puppetserver (foutief) zou kunnen inladen te verwijderen, bleef dezelfde foutmelding terugkomen.

Sep 1 21:14:40 stock puppet-agent[6330]: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Function lookup() did not find a value for the name 'account_data' on node s1.admin.dc.nidavellir.be

Maar dan, in de krochten van de README (…), bleek dat het niet voldoende was om de gem te installeren via gem install hiera-eyaml. Neen. Dat moest gebeuren via de Puppetserver:

/opt/puppetlabs/bin/puppetserver gem install hiera-eyaml

En jawel …

Sep 2 12:32:53 stock puppet-agent[23914]: Applied catalog in 152.87 seconds

Hoera!

Kerberos: een tragikomedie

Prelude: Vandaag een update uitgevoerd van Fedora 25 naar Fedora 26, en alles ging goed. Alles? Neen, niet alles. Kerberos bleef weerstand bieden aan de overweldigers. Alles is geconfigureerd om via Kerberos en SSH passwordless te kunnen inloggen op het serverpark, maar SSH weigerde koppig dienst.

Confrontatie: De eerste foutmelding leek te wijzen naar het ontbreken van /etc/krb5.keytab (Server not found in Kerberos database). En inderdaad, het vermaledijde bestand was niet aanwezig. Helaas is het niet zo eenvoudig (“onmogelijk”) om te vinden hoe je dat bestand moet aanmaken. Wel wat er moet instaan, maar niet hoe je het moet maken. Ik bespaar u de details, als het /etc/krb5.conf-bestand hetzelfde is als toen het nog werkte, dan moet het zo (ktutil: moet je niet overtypen):

ktutil
ktutil: addent -password -p pieter@dc.helptux.be -k 1 -e RC4-HMAC
ktutil: wkt /etc/krb5.keytab
ktutil: q

kinit pieter@dc.helptux.be

Catharsis: En toch bleef het probleem bestaan. De ontbrekende keytab was dus niet het probleem. Na het raadplegen van een orakel bleek het probleem, zoals altijd, DNS. Ik gebruik korte hostnames om in te loggen (i.e. web1 ipv. web1.dc.helptux.be), maar Kerberos verwacht lange hostnames. Je kan dat oplossen door altijd ssh web1.dc.helptux.be te gebruiken, maar dat was vroeger niet nodig, en dus gaan we dat ook nu niet zo doen. Gelukkig is er in /etc/krb5.conf een instelling die het probleem oplost: dns_canonicalize_hostname. Dit staat standaard op true, maar om onbekende redenen stond het op false. Een kleine aanpassing later werkte alles terug. Zeus zij geprezen!

dns_canonicalize_hostname = true

Puppet: invalid byte sequence in US-ASCII

Puppet probeert een bestand te parsen, denkt dat het ASCII is, maar dat is het niet en faalt dus. Een é gebruiken doe je niet ongestraft.

Dit is geen probleem met Puppet, maar met de locale die ingesteld staat op het systeem. Op zich eenvoudig op te lossen, ware het niet dat het “systeem” in kwestie een docker container is, gebaseerd op Red Hat Enterprise Linux 7.3.

Om een lang verhaal kort te maken, je moet in de Dockerfile de locale-environment variables toevoegen (het uitvoeren van localegen is op RHEL 7.3 niet nodig).

ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8
ENV LANGUAGE en_US:en

En zo staat de locale ingesteld op UTF-8, waardoor de puppet parser dit overneemt en de foutmelding magisch verdwijnt.

Hoera!