GitHub Actions bereik nie jou VPN nie: selfgehoste runner of tonnel
'n Gehoste runner is 'n vars VM buite jou netwerk. Sit óf 'n runner binne daardie netwerk, óf bou 'n tonnel vanaf die werk en wag vir 'n werklike roete.
Jou ontplooiingstap hang by 'n interne gasheernaam terwyl dieselfde opdrag dadelik van jou skootrekenaar antwoord, en die kliëntbou is op albei masjiene identies. Die werk loop op 'n GitHub-gehoste runner: 'n virtuele masjien wat vir daardie een werk binne GitHub se wolk geskep is, met geen roete na jou private adresseruimte nie, geen resolver-inskrywing vir die interne sone nie en geen sertifikaat wat die private eindpunt sal aanvaar nie. Net twee ontwerpe pas by daardie beperking, en nie een kan later aangeskakel word sonder om die werkvloei te herbou nie. Kies tussen hulle voor die eerste reël YAML, want migrasie agterna kos meer as die eerste poging.
Die keuse is tussen 'n runner wat geplaas word waar die netwerk reeds eindig, en 'n tonnel wat vir die duur van een werk bestaan. Albei dra al jare produksie-ontplooiings, en hulle faal op verskillende maniere.
Waar die grens werklik lê
Enigiets wat jy op jou skootrekenaar installeer, stop by jou skootrekenaar, en enigiets wat jy tydens 'n werk installeer, verdwyn saam met die runner. Daar is geen blywende netwerknaamruimte waaraan 'n kliënt tussen lopies gekoppel kan bly nie, so elke lopie begin uit dieselfde onbereikbare toestand. Azure dokumenteer hierdie grens vir Microsoft-gehoste agente, waar VPN- en ExpressRoute-verbindings na private netwerke nie ondersteun word nie, en GitHub-gehoste runners dra dieselfde soort beperking eerder as 'n aparte meganisme. Lees dit as 'n produk-eienskap, nie as 'n gebrek om te omseil nie.
Geen bewaarplek-instelling verslap dit nie. Die werk-etiket bepaal watter poel die werk optel, en 'n gehoste etiket beteken 'n masjien buite jou omtrek vir die hele lopie.
Private bereikbaarheid moet dus van iets kom wat jy besit. 'n Runner wat geplaas word waar die netwerk reeds eindig, of 'n tonnel wat vir een werk leef. Die res van hierdie artikel gaan daaroor om doelbewus vir een van dié te betaal eerder as om die rekening in produksie te ontdek.
Besluit op loopfrekwensie, nie op bekendheid nie
Gryp na 'n selfgehoste runner binne die VPC of kantoornetwerk wanneer ontplooiings etlike kere per dag plaasvind, wanneer die werk meer as een private poort raak, of wanneer 'n platformspan reeds nasien en geloofsbriewe-rotasie besit. 'n Werk op daardie masjien resolveer en routeer soos enige ander gasheer agter die vuurmuur. Die netwerkwerk gebeur een keer, by installasie, eerder as by elke lopie.
Bou die tonnel binne die werk wanneer toegang sporadies is en jy nie 'n langdurige agent wil hê wat 'n geloofsbrief in jou netwerk hou nie. Die werk word vir 'n paar minute sy eie netwerkkliënt en laat niks agter nie, wat dikwels die hele punt is.
Skat die tonnel se koste voordat jy dit kies. Elke lopie betaal vir profielmaterialisering, kliëntbegin, gereedheid en afbreek. 'n Tonnel wat opkom maar nooit geverifieer word nie, lewer 'n ontplooiing wat halfpad misluk en private toestand in 'n vorm laat wat niemand beplan het nie. Die herprobeerslag daarna is selde skoon, en die tweede mislukking word gewoonlik aan die kliëntweergawe verwyt.
Skryf die profiel sodat die stap nie kan afbreek nie
Hou profiele en geloofsbriewe uit die bewaarplek en materialiseer hulle uit geheime in die stap wat hulle nodig het. Die volgorde bepaal of die werk oorleef: 'n lêer wat onder sudo geskep word, behoort aan root, en die onbevoorregte stapdop kan dan nie daarin skryf nie.
- name: Bring up OpenVPN
run: |
umask 077
profile="$RUNNER_TEMP/client.ovpn"
printf '%s\n' "$VPN_PROFILE" > "$profile"
: > "$RUNNER_TEMP/openvpn.log"
printf '%s\n' "$VPN_CREDENTIALS" > "$RUNNER_TEMP/credentials.txt"
sudo openvpn --daemon openvpn-job \
--config "$profile" \
--auth-user-pass "$RUNNER_TEMP/credentials.txt" \
--writepid "$RUNNER_TEMP/openvpn.pid" \
--log "$RUNNER_TEMP/openvpn.log"Albei besonderhede in daardie blok bestaan vir latere stappe. Met umask 077 beland die profiel op modus 0600 wat aan die runner-gebruiker behoort, sudo kan dit steeds lees, en die kliënt skryf sy log in 'n lêer wat die runner reeds besit, so die gereedheidslus kan daardie log lees sonder 'n tweede sudo. --writepid teken die PID aan wat die afbreekstap sal teiken.
'n Profiel wat met 'n kliëntsertifikaat verifieer, het sy sleutel op dieselfde manier geskryf nodig, en 'n statiese-uitdaging-profiel het die ooreenstemmende verifikasiemodus nodig. Profiele wat DNS-opsies stoot, het openvpn-systemd-resolved op Ubuntu en Debian nodig, want die stoot alleen verander niks op 'n basisbeeld nie.
Gereedheid is 'n toets, nie 'n pouse nie
'n Vaste slaap hou net totdat die konsentrator stadig is, en dan loop die ontplooiingstap voordat die roete bestaan. Die mislukking kom dan in die ontplooiingsinstrument na vore as 'n tydverstryking teen die private gasheer, wat niks oor die tonnel sê nie.
- name: Await private endpoint
run: |
for attempt in $(seq 1 30); do
if getent hosts private.example.internal > /dev/null && \
timeout 3 bash -c 'exec 3<>/dev/tcp/private.example.internal/8443' 2>/dev/null; then
echo "endpoint answered after $attempt attempts"
exit 0
fi
sleep 2
done
grep -Ei 'error|verify|auth' "$RUNNER_TEMP/openvpn.log" | tail -40
exit 1Die lus kyk na twee waarnemings omdat enigeen die stukkende helfte kan wees. getent hosts bewys die gestootte resolver antwoord vir daardie naam, en die oop socket bewys iets luister op die poort wat die ontplooiing sal gebruik; 'n roete-alleen-profiel het die letterlike adres nodig en die getent-reël moet geskrap word.
Wanneer die lus verstryk, is die log die enigste ding wat drie verskillende foute onderskei: 'n verwerpte sertifikaat, 'n verkeerde verifikasiemodus en 'n roete wat nooit aangekom het nie. Filter voor die stert hou daardie uitset kort genoeg om te lees terwyl die werk steeds misluk. Lees die eerste fout eerder as die laaste reël, want 'n kliënt wat nie kan verifieer nie, herhaal homself tot die lus opgee. ip -4 route show dev tun0 beantwoord die eenvoudiger vraag of 'n roete hoegenaamd gestoot is, en dit kos een reël binne dieselfde lus.
Afbreek, en die vraag oor artefakte
Sluit die werk af met 'n stap wat deur if: always() bewaak word en wat sudo kill "$(sudo cat "$RUNNER_TEMP/openvpn.pid")" uitvoer en niks anders nie. Op 'n gedeelde selfgehoste runner moet jy nooit op prosesnaam doodmaak nie: 'n ander werk kan 'n kliënt met dieselfde binaries en soortgelyke argumente besit. Die mislukking beland dan by watter werk ook al tweede geloop het, en sy log wys na 'n werkvloei wat niks verkeerd gedoen het nie.
Die wag is belangrik omdat 'n mislukte ontplooiing steeds 'n lewendige tonnel laat. Sonder always() word die opruimstap presies oorgeslaan wanneer dit nodig is, en die volgende lopie begin teen 'n kliënt wat reeds gekoppel is.
Een vraag bly oor voordat jy enige van die twee ontwerpe aanvaar: het die boufase hoegenaamd 'n private roete nodig? 'n Ontplooiingsagent wat reeds binne die netwerk sit, kan 'n goedgekeurde artefak trek, wat gewone bouselle weg van die private pad hou. Minder werke wat 'n tonnel hou, beteken minder plekke waar 'n verouderde roete kan wegkruip.