Webbservrar och reverse proxy
Den här sidan förklarar vad som står mellan webbläsaren och din Express-applikation när den är driftsatt. Den innehåller:
- vad en webbserver och en reverse proxy är, och varför du behöver en
- vad en reverse proxy lägger till i förfrågan, och vad det betyder för din applikation
- hur förfrågningar styrs till rätt applikation
- hur HTTPS, certifikat och certifikatutfärdare hänger ihop
- grunderna i Caddy, som är kursens reverse proxy
- övningar att göra på din egen dator
Allt du gör här gör du sedan på riktigt på din server, i guiden Din server på Cumulus.
Tidsåtgång: Ungefär 20–30 minuter att läsa och en timme för övningarna.
Förberedelser
Section titled “Förberedelser”Du behöver Docker Desktop och en Bash-terminal, som i Docker-kommandon. Den sista övningen bygger på din lösning av övningen Containerisera en webbapplikation.
Webbservrar
Section titled “Webbservrar”En webbserver tar emot HTTP-förfrågningar och skickar svar. Den kan skicka filer direkt från disken, som HTML, CSS och bilder, men den kan också skicka förfrågan vidare till ett program som skapar svaret. De vanligaste är nginx och Apache. I kursen använder du Caddy, som skaffar och förnyar certifikat för HTTPS helt av sig själv och har en kort konfiguration.
Din Express-applikation är också en webbserver, men en du inte vill släppa direkt mot internet. Den ska göra en sak, applikationens logik, och låta en beprövad webbserver sköta resten.
Reverse proxy
Section titled “Reverse proxy”En reverse proxy står framför en eller flera applikationer och tar emot alla förfrågningar i deras ställe. Klienten pratar bara med proxyn och vet inte vilken applikation som till sist svarar.
webbläsare ──HTTPS──▶ reverse proxy ──HTTP──┬──▶ applikation A (port 443) └──▶ applikation BEn vanlig proxy, en forward proxy, gör tvärtom: den står framför klienterna, till exempel på en arbetsplats, och gör förfrågningar åt dem ut mot internet.
Det här får du av en reverse proxy:
- En enda ingång. Bara proxyn lyssnar på port 80 och 443. Många applikationer kan dela samma server och samma DNS-namn.
- HTTPS på ett ställe. Proxyn har certifikatet och sköter krypteringen. Applikationerna bakom talar vanlig HTTP och behöver inte veta något om certifikat.
- Applikationerna syns inte utåt. De publicerar inga portar och kan bara nås via proxyn.
- Mer på köpet. Omdirigering från HTTP till HTTPS, komprimering, loggning och, om det behövs, lastbalansering mellan flera instanser av samma applikation.
Det proxyn lägger till
Section titled “Det proxyn lägger till”Applikationen får förfrågan från proxyn, inte från webbläsaren. För att applikationen ändå ska veta vem som frågade och hur, lägger proxyn till headers:
| Header | Innehåll |
|---|---|
X-Forwarded-For |
Klientens IP-adress |
X-Forwarded-Proto |
https eller http, alltså hur klienten anslöt till proxyn |
X-Forwarded-Host |
Värdnamnet klienten använde |
Express litar inte på de här headers från början, eftersom vem som helst kan skicka dem. Står applikationen bakom en proxy talar du om det:
app.set('trust proxy', 1)Då ger req.ip klientens adress i stället för proxyns, och req.protocol blir https. Det
behövs bland annat för att cookies med flaggan secure ska skickas.
Styra förfrågningar till rätt applikation
Section titled “Styra förfrågningar till rätt applikation”En reverse proxy kan välja applikation på två sätt:
| Sätt | Exempel | Kräver |
|---|---|---|
| Värdnamn | api.example.com, app.example.com |
Ett DNS-namn per applikation |
| Sökväg | example.com/api/, example.com/app/ |
Bara ett DNS-namn |
Din server på Cumulus har ett enda DNS-namn, så där styr du med sökväg.
Med sökväg måste du bestämma om prefixet ska tas bort innan förfrågan skickas vidare. I Caddy gör
handle_path /app/* det, så applikationen får / när klienten frågade efter /app/. handle
låter sökvägen vara, så applikationen får hela /app/.
Tas prefixet bort vet applikationen inte att den ligger under /app/. Länkar som börjar med /,
som <a href="/login"> eller <link href="/css/style.css">, pekar då förbi prefixet och ger 404.
Använd relativa länkar, eller låt applikationen få veta sin bas-URL, till exempel i en
miljövariabel.
HTTPS och certifikat
Section titled “HTTPS och certifikat”HTTPS är HTTP i en krypterad TLS-anslutning. För att webbläsaren ska veta att den pratar med rätt server visar servern ett certifikat: ett intyg om att en viss nyckel hör till ett visst DNS-namn, signerat av en certifikatutfärdare (certificate authority, CA). Webbläsaren och operativsystemet har en lista med CA:er de litar på. Är certifikatet signerat av en av dem syns ett hänglås, annars en varning.
Certifikat hämtas och förnyas automatiskt med protokollet ACME. Servern bevisar för CA:n att den kontrollerar DNS-namnet och får ett certifikat tillbaka.
| CA | Används | Litar webbläsaren på den? |
|---|---|---|
| Let’s Encrypt | Publika webbplatser | Ja, från början |
| LNU FTK Campus CA | Din server på Cumulus | Nej, du importerar rotcertifikatet en gång |
| Caddys lokala CA | Utveckling på din egen dator | Nej, men du kan importera det |
Certifikat har kort livslängd, några dagar till några månader, och förnyas därför automatiskt. Det är just det Caddy gör åt dig.
Caddy konfigureras i filen Caddyfile. Varje block börjar med en adress som anger vilka
förfrågningar blocket gäller, och innehåller direktiv som anger vad som ska hända:
example.com { handle_path /app/* { reverse_proxy app:3000 } respond "Ingen app här" 404}Indragen ska vara tabbar.
| Adress | Betydelse |
|---|---|
example.com |
HTTPS för example.com, med certifikat som Caddy hämtar själv. HTTP omdirigeras till HTTPS. |
localhost |
HTTPS med ett certifikat från Caddys lokala CA |
:80 |
Vanlig HTTP på port 80, för alla värdnamn |
| Direktiv | Vad det gör |
|---|---|
reverse_proxy app:3000 |
Skickar förfrågan vidare till app på port 3000 |
handle_path /app/* { … } |
Förfrågningar under /app/, med prefixet borttaget |
handle /app/* { … } |
Förfrågningar under /app/, med sökvägen orörd |
redir /app /app/ |
Omdirigerar till en annan adress |
respond "text" 404 |
Svarar direkt med en text och en statuskod |
file_server |
Skickar filer från disken |
import sites/*.caddy |
Läser in fler filer, till exempel en per applikation |
Ändrar du Caddyfile läser Caddy in den på nytt med caddy reload, utan att avbryta pågående
förfrågningar. Kör Caddy i en container gör du det med
docker exec -w /etc/caddy <container> caddy reload.
Övningar
Section titled “Övningar”Tidsåtgång: Ungefär en timme för alla sju övningarna.
Övningarna görs på din egen dator. Gör dem i ordning och i samma katalog. Försök själv innan du
tittar på lösningsförslaget. Kör Git Bash på Windows? Se till att du har lagt till funktionen
docker() från Docker-kommandon.
Som applikation används traefik/whoami:v1.11, en liten webbserver som svarar med den förfrågan
den tog emot. Då ser du exakt vad proxyn skickar vidare.
1. Caddy med en egen Caddyfile
Section titled “1. Caddy med en egen Caddyfile”Tidsåtgång: Ungefär 5 minuter.
Du kör Caddy i en container och styr den med en konfigurationsfil som du skriver på din egen dator. Filen lämnar du över till containern när du startar den.
- Skapa katalogen
caddyi din övningskatalog, och i den en fil med namnetCaddyfile. Filen ska få Caddy att svaraHej från Caddypå alla förfrågningar på port 80. - Starta en container från imagen
caddy:2och ge den namnetproxy. När du startar den:- montera katalogen
caddypå/etc/caddyi containern, som en bind mount. Caddy läser sin konfiguration från/etc/caddy/Caddyfile, så din fil ersätter den som följer med imagen. - publicera containerns port 80 på port 8080 på
127.0.0.1.
- montera katalogen
- Öppna localhost:8080.
Lösningsförslag
Section titled “Lösningsförslag”caddy/Caddyfile:
:80 { respond "Hej från Caddy"}docker run -d --rm --name proxy -p 127.0.0.1:8080:80 -v "./caddy:/etc/caddy:ro" caddy:22. En reverse proxy
Section titled “2. En reverse proxy”Tidsåtgång: Ungefär 10 minuter.
Nu lägger du en applikation bakom Caddy. Applikationen publicerar ingen egen port, utan nås bara via Caddy över ett gemensamt nätverk.
- Skapa nätverket
rp. - Starta en container från imagen
traefik/whoami:v1.11med namnetapppå nätverketrp. Publicera ingen port. - Ändra din
Caddyfileså att Caddy skickar alla förfrågningar vidare tillapppå port 80. - Stoppa containern
proxyoch starta den igen, som i övning 1 men på nätverketrp. - Öppna localhost:8080/hej. Vilka headers har Caddy lagt till, och
vilken IP-adress står i
X-Forwarded-For?
Lösningsförslag
Section titled “Lösningsförslag”caddy/Caddyfile:
:80 { reverse_proxy app:80}docker network create rpdocker run -d --rm --name app --network rp traefik/whoami:v1.11docker rm -f proxydocker run -d --rm --name proxy --network rp -p 127.0.0.1:8080:80 -v "./caddy:/etc/caddy:ro" caddy:2Caddy har lagt till X-Forwarded-For, X-Forwarded-Host och X-Forwarded-Proto, och Host är
det värdnamn du skrev i webbläsaren. I X-Forwarded-For står adressen som din förfrågan kom från
sett inifrån Docker, inte 127.0.0.1. RemoteAddr är Caddys adress på nätverket rp, eftersom det
är Caddy som ansluter till app.
app kan inte nås direkt från din dator. Caddy hittar den på namnet, eftersom de är på samma
nätverk.
3. Två applikationer på olika sökvägar
Section titled “3. Två applikationer på olika sökvägar”Tidsåtgång: Ungefär 10 minuter.
Nu lägger du två applikationer bakom samma Caddy och låter sökvägen avgöra vilken som får
förfrågan. Den här gången läser Caddy in den ändrade Caddyfile utan att containern startas om.
- Starta en container till från imagen
traefik/whoami:v1.11med namnetapp2på nätverketrp. Publicera ingen port. - Ändra din
Caddyfileså att:- förfrågningar under
/a/går tillappmedhandle_path - förfrågningar under
/b/går tillapp2medhandle - allt annat får svaret
Ingen app härmed status 404.
- förfrågningar under
- Läs in den ändrade
Caddyfilei den körande containernproxy, utan att starta om den. - Öppna localhost:8080/a/x och localhost:8080/b/x. Vilken sökväg får applikationerna?
- Öppna localhost:8080/a, utan snedstreck sist. Vad händer?
Lösningsförslag
Section titled “Lösningsförslag”caddy/Caddyfile:
:80 { handle_path /a/* { reverse_proxy app:80 } handle /b/* { reverse_proxy app2:80 } respond "Ingen app här" 404}docker run -d --rm --name app2 --network rp traefik/whoami:v1.11docker exec -w /etc/caddy proxy caddy reloadapp får GET /x, eftersom handle_path tar bort /a. app2 får GET /b/x. /a utan
snedstreck matchar inte /a/* och ger 404. Lägg till redir /a /a/ för att skicka den rätt.
4. 502 Bad Gateway
Section titled “4. 502 Bad Gateway”Tidsåtgång: Ungefär 5 minuter.
Stoppa app2 och öppna /b/x igen. Vilken statuskod får du, och vad står i Caddys logg?
Lösningsförslag
Section titled “Lösningsförslag”docker stop app2docker logs proxy502 Bad Gateway: Caddy fick förfrågan men kunde inte nå applikationen bakom. Loggen säger att
namnet app2 inte gick att slå upp. Får du 502 på din server är det första du kontrollerar alltså
att applikationen är igång och att namnet och porten i reverse_proxy stämmer.
5. HTTPS på din egen dator
Section titled “5. HTTPS på din egen dator”Tidsåtgång: Ungefär 10 minuter.
Ändra adressen i Caddyfile till localhost och låt den skicka allt till app. Starta om Caddy
och koppla även port 8443 på 127.0.0.1 till port 443. Öppna
https://localhost:8443. Varför varnar webbläsaren? Vem har utfärdat
certifikatet, och hur länge gäller det? Vad står i X-Forwarded-Proto?
Lösningsförslag
Section titled “Lösningsförslag”caddy/Caddyfile:
localhost { reverse_proxy app:80}docker rm -f proxydocker run -d --rm --name proxy --network rp -p 127.0.0.1:8080:80 -p 127.0.0.1:8443:443 -v "./caddy:/etc/caddy:ro" caddy:2Certifikatet är utfärdat av Caddy Local Authority, en CA som Caddy skapade när den startade.
Din webbläsare litar inte på den, därav varningen. Certifikatet gäller bara i tolv timmar. Klicka
på hänglåset eller varningen i webbläsaren för att se detaljerna. X-Forwarded-Proto är nu
https.
Du kan lita på CA:n genom att hämta dess rotcertifikat, precis som du gör med Cumulus CA på din server:
docker cp proxy:/data/caddy/pki/authorities/local/root.crt caddy-root.crtcurl --ssl-no-revoke --cacert caddy-root.crt https://localhost:8443--ssl-no-revoke behövs i Git Bash på Windows, där curl annars försöker kontrollera en spärrlista
som den här CA:n inte har.
6. Produktion på din egen dator
Section titled “6. Produktion på din egen dator”Tidsåtgång: Ungefär 15–20 minuter.
Nu ska du köra din lösning på övningen Containerisera en webbapplikation på samma sätt som på
servern: Caddy i ett eget compose-projekt i katalogen proxy, med konfigurationen i
proxy/caddy och en fil per applikation i proxy/caddy/sites, och applikationen med compose.production.yaml på nätverket proxy. Städa först
bort containrarna och nätverket från övningarna ovan. Gör så att
localhost:8080/hello-world/ visar applikationen.
Lösningsförslag
Section titled “Lösningsförslag”docker rm -f proxy appdocker network rm rpdocker network create proxymkdir -p proxy/caddy/sitesproxy/compose.yaml:
services: caddy: image: caddy:2 restart: unless-stopped ports: - 127.0.0.1:8080:80 volumes: - ./caddy:/etc/caddy:ro networks: - proxy
networks: proxy: external: trueproxy/caddy/Caddyfile:
:80 { import sites/*.caddy respond "Caddy fungerar"}proxy/caddy/sites/hello-world.caddy:
redir /hello-world /hello-world/handle_path /hello-world/* { reverse_proxy hello-world:3000}cd proxydocker compose up -dcd <katalogen med din lösning>docker compose -f compose.production.yaml up -d --buildApplikationen ansluter till nätverket proxy med namnet hello-world och publicerar ingen port.
Det enda som skiljer mot servern är adressen i Caddyfile, där servern har sitt DNS-namn och får
ett certifikat från Cumulus CA.
7. Städa
Section titled “7. Städa”Tidsåtgång: Ungefär 2 minuter.
Stoppa och ta bort applikationen, Caddy och nätverket proxy.
Lösningsförslag
Section titled “Lösningsförslag”docker compose -f compose.production.yaml down # i katalogen med din lösningdocker compose down # i katalogen proxydocker network rm proxy