Self hosted runner: kiedy warto i jak go zabezpieczyć

0
73
5/5 - (1 vote)

Nawigacja:

Cel korzystania z self hosted runnerów w CI/CD

Self hosted runner daje pełną kontrolę nad środowiskiem wykonywania pipeline’ów, ale równocześnie przenosi odpowiedzialność za bezpieczeństwo i utrzymanie na zespół. Kluczem jest zrozumienie, kiedy to się opłaca i jak zaprojektować runnera, który nie stanie się furtką do całej infrastruktury.

Czym jest self hosted runner i czym różni się od SaaS

Rola runnera w systemach CI/CD

Runner to agent, który wykonuje joby zdefiniowane w pipeline. Odbiera definicję z serwera CI/CD, pobiera kod, uruchamia komendy, buduje artefakty i zwraca status wykonania.

Może działać na fizycznym serwerze, maszynie wirtualnej, w kontenerze lub jako pod w Kubernetes. Logicznie jest pośrednikiem między systemem zarządzania pipeline’ami a infrastrukturą, na której powstaje build.

Wszystko, co może wykonać użytkownik systemu na hoście runnera, może potencjalnie zrobić też job pipeline’u. To sprawia, że runner jest newralgicznym punktem bezpieczeństwa.

Runner zarządzany przez dostawcę (SaaS) vs self hosted

Runner zarządzany przez dostawcę (np. GitHub-hosted, GitLab shared runners, Azure Pipelines hosted) jest utrzymywany, aktualizowany i izolowany przez platformę. Użytkownik dostaje gotowe środowisko z ograniczoną możliwością modyfikacji.

Self hosted runner uruchamiasz samodzielnie: na własnym serwerze, w chmurze publicznej, prywatnej lub w kolokacji. Konfigurujesz system, pakiety, sterowniki, dostęp do sieci, mechanizmy bezpieczeństwa.

Podstawowa różnica: w modelu SaaS bezpieczeństwo i hardening runnera spoczywa głównie na dostawcy; w modelu self hosted – na twoim zespole. Zyskujesz elastyczność i dostęp do swoich zasobów, ale tracisz „bezpłatny” hardening i izolację zapewnianą przez platformę.

Przykłady z popularnych platform CI/CD

GitHub Actions self-hosted – instalujesz binary „actions runner” na hoście i rejestrujesz go w organizacji, repozytorium lub na poziomie enterprise. Możesz decydować, które joby trafiają do self hosted runnerów (np. przez runs-on i etykiety).

GitLab Runner on-premise – uruchamiasz gitlab-runner z wybranym executorem (shell, docker, docker+machine, Kubernetes). Rejestrujesz runnera jako shared, group lub specific. Masz dużą swobodę w tym, jak izolować buildy (np. executor docker z własnymi obrazami).

Azure DevOps self-hosted agent – agent zainstalowany na Windows/Linux/macOS, często w sieci on-premise, aby miał dostęp do wewnętrznych systemów (np. TFS, stare bazy danych, legacy API).

Bitbucket Pipelines self-hosted (runners) – kontener lub VM z agentem, który pozwala wykonywać pipeline’y na własnej infrastrukturze zamiast w managed środowisku Bitbucketa.

Typowe topologie wdrożeń self hosted runnerów

W praktyce pojawia się kilka powtarzalnych wzorców architektonicznych.

  • On-premise – runner na fizycznym serwerze lub VM w lokalnym data center. Ma dostęp do systemów wewnętrznych (ERP, bazy, usługi legacy), często z ograniczonym dostępem do Internetu.
  • Chmura prywatna – runner w środowisku typu OpenStack, VMware, Proxmox. Ułatwia dynamiczne skalowanie, ale dalej wszystko jest pod twoją kontrolą.
  • Chmura publiczna – runner jako VM lub pod w AWS/GCP/Azure, zwykle w dedykowanej VPC/VNet z kontrolowanym ruchem na zewnątrz.
  • Hybryda – część runnerów w chmurze (buildy, testy), część on-premise (dostęp do systemów, które nie mogą wyjść poza lokalną sieć). Pipeline’y są routowane na odpowiednie grupy runnerów przez etykiety i reguły.

Topologia runnerów powinna wynikać z potrzeb dostępu do zasobów, wymogów bezpieczeństwa i możliwości operacyjnych zespołu.

Kiedy self hosted runner ma sens, a kiedy nie

Scenariusze, w których self hosted runner jest dobrym wyborem

Self hosted runner ma największy sens tam, gdzie managed runner nie ma szans spełnić wymagań technicznych lub regulacyjnych.

  • Wymagania compliance i regulacje – organizacja nie może wynosić kodu czy artefaktów poza własne DC lub określony kraj. Self hosted runner uruchamiasz w kontrolowanej lokalizacji, spełniając wymogi RODO, regulacji branżowych (np. finansowych, medycznych).
  • Dostęp do sieci wewnętrznej – build musi łączyć się z wewnętrznymi API, starymi bazami, brokerami MQ, systemami bez ekspozycji na Internet. Runner może być w tej samej sieci lub peered VPC i łączyć się po prywatnych adresach.
  • Specyficzne zależności – customowe toolchainy, kompilatory, sterowniki, biblioteki systemowe, które ciężko zainstalować na ephemeral SaaS runnerze lub są niedostępne ze względów licencyjnych.
  • GPU i specjalistyczny sprzęt – trenowanie modeli ML, testy video, symulacje naukowe, sprzęt HSM. Managed runnery zazwyczaj nie dają dostępu do takiego hardware’u lub robią to w drogich, ograniczonych planach.
  • Buildy mobilne i macOS – Android/iOS, Xcode, wymagane certyfikaty i profile. Self hosted runner na macOS (np. w mac mini farmie lub macOS w chmurze) pozwala kontrolować wersje narzędzi i dystrybucji.
  • Kontrola nad wydajnością – kiedy pipeline’y są ciężkie, długie i ważna jest powtarzalność czasu wykonania. Możesz dobrać dokładnie CPU/RAM/dyski, cache, lokalne rejestry artefaktów.

Kiedy self hosted runner nie jest najlepszym wyborem

Są sytuacje, w których wdrożenie self hosted runnera tworzy więcej problemów niż rozwiązuje.

  • Mały zespół, brak kompetencji operacyjnych – jeżeli zespół nie ma doświadczenia w administracji systemami, sieci, bezpieczeństwie, to dodatkowa warstwa w postaci runnerów szybko stanie się źródłem awarii i ryzyk.
  • Nieregularne pipeline’y – sporadyczne buildy kilka razy w tygodniu nie uzasadniają inwestycji w stałą infrastrukturę. Managed runner „płaci się” tylko czasem wykonania.
  • Brak spójnego modelu bezpieczeństwa – jeśli nie ma polityk haseł, zarządzania sekretami, segmentacji sieci, wprowadzenie runnerów otwiera nową, słabo zabezpieczoną bramę do infrastruktury.
  • Krótki czas do produkcji – gdy priorytetem jest szybkie uruchomienie CI/CD, zaczęcie od managed runnerów jest prostsze. Self hosted można wprowadzić później tam, gdzie rzeczywiście brakuje funkcjonalności lub kontroli.

Self hosted runner bez odpowiedniego zaplecza operacyjnego często kończy jako przestarzała, nieaktualizowana maszyna z uprawnieniami roota i dostępem do całej sieci.

Ukryte koszty: utrzymanie, monitoring, aktualizacje

Self hosted runner nie jest „za darmo”, nawet jeśli stoi na już opłaconej infrastrukturze.

  • Utrzymanie systemu – aktualizacje OS, kernela, runtime’u kontenerów, agenta CI, bibliotek. To stała praca, nie jednorazowy projekt.
  • Monitoring i alerting – trzeba monitorować zasoby (CPU, RAM, dysk), kolejki jobów, błędy runnera, bezpieczeństwo (np. nieudane logowania, podejrzane procesy).
  • Troubleshooting – awarie storage, problemy z siecią, konflikty zależności, wycieki pamięci w buildach. Wszystko spada na zespół.
  • Rewizje bezpieczeństwa – regularne przeglądy konfiguracji, skanowanie podatności, testy penetracyjne runnerów.

Zestawienie należy robić nie tylko na podstawie ceny minut builda u dostawcy, ale też na podstawie czasu ludzi i ryzyka incydentów.

Kryteria podjęcia decyzji: kiedy self hosted runner się opłaca

Przydatna jest prosta macierz decyzyjna: bezpieczeństwo, regulacje, koszt, wydajność, czas wdrożenia.

KryteriumManaged runner (SaaS)Self hosted runner
BezpieczeństwoStandardowy poziom, kontrola u dostawcyPełna kontrola, ale też pełna odpowiedzialność
Regulacje / complianceOgraniczona elastyczność lokalizacjiMożliwość spełnienia specyficznych wymagań
Koszt początkowyNiski, brak setupu infrastrukturyWyższy – projekt, infrastruktura, konfiguracja
Koszt operacyjnyAbonament / minuty wykonaniaCzas zespołu, utrzymanie, monitoring
Wydajność i elastycznośćOgraniczone typy maszyn i konfiguracjeMożliwość pełnego dopasowania do potrzeb
Czas wdrożeniaBardzo szybki startDłuższy, wymagający planowania

Jeżeli kilka kluczowych wymagań (compliance, dostęp do sieci wewnętrznej, GPU) wymusza lokalne środowisko, self hosted runner jest naturalnym wyborem. Gdy jednak to głównie chęć „oszczędzenia na minutach”, lepiej policzyć realny koszt operacyjny.

Kobieta z laptopem idzie między serwerami w nowoczesnej serwerowni
Źródło: Pexels | Autor: Christina Morillo

Modele architektury self hosted runnerów w praktyce

Bare-metal, VM, kontener, Kubernetes – porównanie podejść

Self hosted runner może działać w różnych formach, co wpływa na izolację, zarządzanie i bezpieczeństwo.

  • Bare-metal – runner bezpośrednio na fizycznym serwerze. Plus: maksymalna wydajność, pełna kontrola nad sprzętem (GPU, dyski NVMe). Minus: słabsza izolacja między jobami, trudniejsze skalowanie, większe ryzyko w przypadku kompromitacji.
  • Maszyna wirtualna (VM) – najczęstsza opcja. Każdy runner to osobna VM lub zestaw VM. Możliwa integracja z cloud-init, templatingiem, snapshotami. Izolacja lepsza niż bare-metal, ale trzeba pilnować hypervisora.
  • Kontener – runner jako kontener (np. GitLab Runner z executorem docker). Dobra elastyczność, łatwe aktualizacje, ale wymaga poprawnej konfiguracji Dockera/Podmana (rootless, brak trybu privileged, ograniczenia capabilities).
  • Kubernetes – runner jako pod w klastrze, zwykle w dedykowanym namespace. Joby uruchamiane jako osobne pody. Daje najlepsze wsparcie dla ephemeral runnerów i izolacji per job, kosztem złożoności klastra.

Wybór zależy od tego, czy zespół ma już doświadczenie z daną technologią. Jeżeli firma ma dojrzały Kubernetes, runner jako DaemonSet lub Deployment jest naturalną kontynuacją.

Runnery stałe vs efemeryczne

Runnery stałe (long-lived) działają non stop. Instalujesz agenta raz, konfigurujesz i używasz przez dłuższy czas.

Zalety: mniej ruchomych elementów, prostszy debugging, większa przewidywalność lokalnego cache’u (np. dependency cache). Wady: „brud” po jobach, większe ryzyko lateral movement, wymaganie skutecznego czyszczenia.

Runnery efemeryczne (ephemeral runners) są tworzone dynamicznie: per job, per pipeline lub per gałąź. Po zakończonym zadaniu runner jest niszczony.

Zalety: silna izolacja między pipeline’ami, brak akumulacji stanu, mniejsza powierzchnia ataku. Wady: większa złożoność automatyzacji (provisioning), potencjalnie wyższy czas startu joba.

Dla wrażliwych projektów, publicznych repo z contribution od nieznanych osób i PR-ów z forków warto preferować model efemeryczny, nawet kosztem niewielkiego narzutu czasowego.

Architektura w małym zespole vs w dużej organizacji

Mały zespół zwykle korzysta z kilku runnerów VM w jednej chmurze. Często wystarczy:

  • 1–2 grupy runnerów: „publiczne” do projektów open-source i „prywatne” do wewnętrznych.
  • Podział na typy jobów przez etykiety: build, test, deploy.
  • Podstawowe mechanizmy izolacji (kontenery, osobne użytkowniki systemowe).

Duża organizacja potrzebuje wielopoziomowego podziału:

  • Dedykowane grupy runnerów dla działów / jednostek biznesowych.
  • Oddzielenie runnerów do PR zewnętrznych, wewnętrznych oraz do main/release.
  • Runnery w różnych strefach bezpieczeństwa: np. „build zone”, „test zone”, „deploy zone”.
  • Mechanizmy multi-tenant: różne namespace’y, VPC, security groups, Polityki sieciowe.

Bez wyraźnego podziału runnerów na klasy bezpieczeństwa szybko pojawia się ryzyko, że złośliwy job z mało istotnego projektu dotrze do zasobów krytycznych.

Przykładowa architektura: runners w VPC bez bezpośredniego Internetu

Często pojawiający się wzorzec: runner w prywatnej podsieci VPC, bez publicznego IP, z ruchem wychodzącym tylko przez NAT lub proxy.

Segmentacja sieci i dostęp do zasobów

Runner w prywatnej podsieci zwykle ma tylko tyle dostępu, ile realnie potrzeba do wykonania jobów.

  • Oddzielne subnety/VLAN-y dla runnerów build, test, deploy.
  • Security Group / firewall dopuszczający jedynie ruch do repozytorium kodu, rejestru artefaktów, pakietów, occasional Internet (np. przez proxy).
  • Brak bezpośredniego dostępu z runnerów do baz produkcyjnych, chyba że to osobna klasa runnerów deploy z dodatkowymi kontrolami.

Jeśli runner musi wyjść do Internetu (pobranie zależności, skanery, integracje), sensownym kompromisem jest NAT + egress filtering lub proxy z listą dozwolonych domen.

Kontrola ruchu przychodzącego do runnerów

Agent CI zwykle inicjuje połączenie wychodzące do kontrolera (GitHub/GitLab/jenkins master) i utrzymuje je jako kanał komunikacji.

  • Porty wejściowe runnera nie powinny być otwarte na Internet – ani SSH, ani HTTP.
  • Dostęp administracyjny (SSH, RDP) ograniczony przez VPN lub bastion, z MFA.
  • Brak ekspozycji usług pomocniczych (Docker socket, panelów www narzędzi) na zewnątrz podsieci runnerów.

Każdy dodatkowy port otwarty do świata to potencjalny wektor ataku na hosta runnera, a dalej na sieć wewnętrzną.

Główne wektory ataku na self hosted runner

Złośliwy kod w pipeline’ach

Najbardziej oczywisty scenariusz: ktoś wprowadza do pipeline’a komendę, która wykonuje działania poza zakresem builda.

  • Krzyżowy dostęp do innych projektów przez wspólny runner.
  • Kradzież sekretów ze zmiennych środowiskowych.
  • Instalacja backdoora na hoście runnera lub próba lateral movement w sieci.

Ryzyko rośnie przy publicznych repozytoriach, PR z forków i przy braku code review plików pipeline’a (np. .gitlab-ci.yml, .github/workflows/*.yml).

Ataki na agenta i oprogramowanie pomocnicze

Agent runnera to kolejny element łańcucha z podatnościami.

  • Błędy w implementacji protokołu komunikacji z kontrolerem.
  • Stare wersje Docker/Podman/kubelet pozwalające na escape z kontenera.
  • Niezałatane biblioteki, webhooki, lokalne narzędzia (np. serwisy HTTP do pobierania artefaktów).

Udany exploit często pozwala podnieść uprawnienia w systemie albo przejąć sesję runnera.

Dostęp do sekretów i tokenów

Runner ma dostęp do sekretów używanych w jobach – czy to zmienne środowiskowe, czy mountowane pliki.

  • Skrypty mogą wyświetlić sekrety w logach (świadomie lub przez błąd).
  • Atakujący może wysłać sekret do zewnętrznego endpointu.
  • Przechowywane na dysku klucze (np. SSH, kubeconfig) mogą zostać odczytane z hosta runnera.

W wielu incydentach to właśnie klucz CI stał się punktem wejścia do chmury lub klastra produkcyjnego.

Wykorzystanie runnera do nadużyć zasobów

Runner z dostępem do mocnych maszyn może zostać użyty do kryptokopania, DDoS czy skanowania innych sieci.

  • Pipeline’y z długotrwałymi procesami obliczeniowymi bez limitów czasu.
  • Tworzenie wielu zadań wysyłających ruch na zewnątrz, np. skanery portów.
  • Wykorzystanie GPU/CPU na maksa przez nieautoryzowane joby.

Bez limitów zasobów i throttlingu można generować realne koszty i blokować innych użytkowników runnera.

Projektowanie bezpiecznego środowiska dla runnera

Klasy bezpieczeństwa i podział runnerów

Zanim pojawi się pierwszy host, dobrze jest ustalić „klasy” runnerów i zasady przypisywania do nich projektów.

  • Runnery publiczne – do PR z forków, projektów open-source, bez dostępu do sieci wewnętrznej i sekretów wysokiego poziomu.
  • Runnery wewnętrzne – dla zespołów produktowych, z dostępem do prywatnych rejestrów, ale nadal bez prawa do łączenia się z produkcją.
  • Runnery deploy – ograniczone do zaufanych pipeline’ów release, z minimalnym zestawem uprawnień do klastra/baz.

Przypisanie runnerów do projektów i branchy po etykietach (tags) lub regułach jest równie ważne jak sama konfiguracja hosta.

Najmniejsze możliwe uprawnienia

Runner powinien mieć tylko to, czego naprawdę wymaga proces CI/CD.

  • Osobne konta systemowe dla agenta, bez roota i bez sudo w codziennej pracy.
  • Ograniczone role IAM/STS w chmurze, krótkotrwałe tokeny, brak kluczy statycznych.
  • Dostęp do repozytoriów, rejestrów i API na poziomie „read-only”, jeśli nie potrzebuje zapisu.

Jeżeli pipeline potrzebuje roota (np. budowa obrazów), lepiej zamknąć to w kontrolowanym kontenerze lub oddzielnej klasie runnerów.

Bezpieczny lifecycle runnera

Runner nie powinien żyć „wiecznie” bez przeglądu konfiguracji.

  • Automatyczne provisionowanie z kodu (Terraform, Ansible, Helm), bez ręcznego „dłubania” na serwerze.
  • Regularne wymienianie obrazów bazowych VM/kontenerów na świeże.
  • Rotacja kluczy rejestracji runnera do kontrolera.

Każda zmiana manualna na produkcyjnym runnerze powinna być wyjątkiem, a nie zasadą.

Inżynierka DevOps przy laptopie w nowoczesnej serwerowni
Źródło: Pexels | Autor: Christina Morillo

Izolacja i sandboxowanie jobów

Joby na hoście vs joby w kontenerze

Najprostszy (i najgorszy) wariant to uruchamianie komend joba bezpośrednio na hoście runnera.

  • Brak izolacji plików: każdy job może czytać/usuwać pozostałości po innych.
  • Wspólny zestaw narzędzi i bibliotek – łatwo o konflikt wersji.
  • Wyższe ryzyko trwałej infekcji hosta.

Bardziej bezpieczne podejście to każdy job w osobnym kontenerze z read-only rootfs, mountowanymi tylko potrzebnymi wolumenami i ograniczeniami CPU/RAM.

Rootless containers i ograniczanie capabilities

Domyślny Docker jako root to prosty sposób na eskalację uprawnień.

  • Rootless Podman/Docker – brak bezpośredniego dostępu do hostowego kernela.
  • Usunięcie zbędnych capabilities (np. SYS_ADMIN, NET_ADMIN).
  • Brak montowania /var/run/docker.sock do kontenerów jobów.

Gdy pipeline wymaga uprzywilejowanych operacji (np. budowa obrazów z docker build), warto rozważyć narzędzia typu Kaniko, BuildKit rootless czy buildy w osobnej infrastrukturze.

Izolacja na poziomie kernela: seccomp, AppArmor, SELinux

Dla krytycznych runnerów można włączyć dodatkowe polityki bezpieczeństwa.

  • seccomp – blokada wybranych syscalli, które nie są potrzebne buildom.
  • AppArmor/SELinux – profile ograniczające, do jakich ścieżek i zasobów może sięgać proces.
  • Mandatory Access Control dla daemonów (np. Dockera) i kontenerów jobów.

Konfiguracja wymaga testów, ale znacząco utrudnia typowe ataki na kernel i wyjście z kontenera.

Limity zasobów i QoS dla jobów

Każdy job powinien mieć określone limity, by pojedynczy build nie „zjadł” całej maszyny.

  • Limity CPU/memory/dysku per kontener (np. --memory, --cpus w Dockerze; resources.limits w Kubernetesie).
  • Limity czasu wykonania pipeline’ów i poszczególnych kroków.
  • Priorytety kolejek jobów – inne dla PR, inne dla release.

Oprócz ochrony przed nadużyciami, pomaga to stabilnie planować obciążenie runnerów.

Zarządzanie sekretami i tożsamością w pipeline’ach

Źródła sekretów: system CI vs dedykowane sejfy

Większość platform CI ma wbudowane „protected variables”, ale przy większej skali i wyższym poziomie bezpieczeństwa lepiej użyć zewnętrznych rozwiązań.

  • HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault.
  • System CI trzyma jedynie token do pobrania sekretu na czas joba.
  • Audyt odczytów sekretów po stronie sejfu, nie runnera.

Ogranicza to skutki wycieku konfiguracji CI – bez dostępu do sejfu atakujący ma mniej możliwości.

Zakres sekretów per projekt i środowisko

Wspólne sekrety „globalne” dla całej organizacji to prosty sposób na incydent o dużej skali.

  • Oddzielne zestawy sekretów dla środowisk: dev, test, staging, prod.
  • Oddzielne przestrzenie/sejfy per projekt lub domenę biznesową.
  • Brak współdzielenia production keys z pipeline’ami uruchamianymi z branchy feature/PR.

Jeśli dany job nie deployuje do produkcji, nie powinien mieć do niej credów nawet pośrednio.

Short-lived credentials i federacja tożsamości

Statyczne klucze API lub długowieczne tokeny osobiste to jeden z najczęstszych grzechów CI.

  • STS / IAM roles z krótkim TTL (minuty, nie dni).
  • OIDC/OAuth między systemem CI a chmurą – pipeline dostaje token na czas joba, nie zapisany klucz.
  • Rotacja sekretów zautomatyzowana, bez ręcznych zmian w plikach pipeline’a.

Dzięki temu wyciek logów czy archiwum joba nie daje atakującemu dostępu „na zawsze”.

Bezpieczne logowanie i maskowanie sekretów

Nawet najlepsze sejfy nie pomogą, jeśli sekrety wylądują w logach.

  • Włączone maskowanie wartości zmiennych oznaczonych jako sekrety.
  • Brak debugowych echo $SECRET w pipeline’ach.
  • Logi runnera przechowywane w systemie centralnym (ELK, Loki, cloud logging) z kontrolą dostępu.

Przy incydencie szybki przegląd logów jobów pomaga sprawdzić, czy sekret mógł zostać ujawniony.

Hardening systemu i oprogramowania runnera

Minimalny system operacyjny

Im mniej pakietów na hoście runnera, tym mniejsza powierzchnia ataku.

  • Obrazy „minimal” lub „core” (np. Ubuntu minimal, Flatcar, Bottlerocket, distroless dla kontenerów).
  • Brak zbędnych usług: serwery WWW, DB, SSH jeśli nie jest potrzebny.
  • Pakiety developerskie (kompilatory, debugery) przeniesione do kontenerów jobów, nie na hosta.

Host runnera nie powinien przypominać „zwykłego” serwera aplikacyjnego, raczej specjalizowany appliance.

Regularne aktualizacje i patch management

Patchowanie runnerów ręcznie zwykle kończy się tym, że nikt nie wie, który host jest w jakiej wersji.

  • Nieaktualizowane hosty są cyklicznie terminowane i zastępowane nowymi z aktualnym obrazem.
  • Automatyczne sprawdzanie wersji agenta CI, Dockera, kernel update’ów.
  • Integracja z narzędziami do zarządzania poprawkami (WSUS, Landscape, systemy chmurowe).

Jeśli runner nie może być bezpiecznie zaktualizowany, powinien być zniszczony i odtworzony z poprawionego template’u.

Bezpieczeństwo użytkowników i uprawnień na hoście

Konfiguracja /etc/sudoers, użytkowników i grup na runnerze to często pomijany element.

  • Brak współdzielenia konta między adminami – każdy ma osobne konto, logi z audytem.
  • sudo tylko tam, gdzie to absolutnie konieczne, z wymaganym hasłem lub MFA.
  • Blokada logowania roota przez SSH, jeśli SSH jest w ogóle dostępny.

Przy awarii łatwo pokusić się o dodanie „na chwilę” pełnych uprawnień – te wyjątki powinny mieć jasne ramy i czas życia.

Monitoring bezpieczeństwa i detekcja anomalii

Monitoring runnera nie kończy się na CPU i dysku.

  • Agent EDR/IDS lub co najmniej auditd z wysyłką logów do SIEM.
  • Alerty na nietypowe procesy (np. koparki kryptowalut), długotrwałe joby, ruch do podejrzanych adresów.
  • Skanowanie hostów pod kątem znanych podatności (np. okresowe skany agentowe).

Przykładowo, nagły wzrost outbound traffic z runnera deploy do nieznanego kraju powinien wywołać alarm i automatyczne odcięcie hosta.

Komputer między szafami serwerowymi w nowoczesnym data center
Źródło: Pexels | Autor: Brett Sayles

Ephemeral runners i czyszczenie środowiska po jobach

Dlaczego efemeryczność podnosi bezpieczeństwo

Modele efemeryczne w praktyce

Efemeryczność można osiągnąć na kilka sposobów, zależnie od użytej platformy i skali.

  • Autoskalujące grupy VM w chmurze (AWS ASG, GCE MIG, VMSS) z pre-baked obrazem runnera.
  • Runnery jako pody w Kubernetesie, niszczone po skończeniu joba.
  • Kontenery na pojedynczym hoście, uruchamiane i kasowane przez lokalny orchestrator lub system CI.

Ważne, by teardown był automatyczny: po jobie runner znika razem z lokalnym cache, logami i tymczasowymi plikami.

Automatyczny provisioning i teardown

Efemeryczność bez automatyzacji szybko zamienia się w chaos.

  • Runner rejestrowany w CI w czasie startu (cloud-init, user-data, initContainer).
  • Po zakończeniu joba proces agenta kończy się, a infrastruktura usuwa cały nośnik (VM/pod/kontener).
  • Timeout dla „wiszących” runnerów, które nie dostały joba (np. 10–30 minut bezczynności).

Dzięki temu nie przechowujesz niepotrzebnie maszyn, na których ktoś kiedyś coś uruchomił.

Czyszczenie systemu plików po jobie

Nawet efemeryczny runner może odpalać wiele jobów, jeśli cykl życia jest dłuższy niż pojedynczy pipeline.

  • Oddzielne katalogi robocze per job z jednoznaczną nazwą i właścicielem.
  • Po jobie usunięcie katalogu, tmp, artefaktów lokalnych oraz cache z danymi wrażliwymi.
  • Wyraźne rozdzielenie cache buildowego (np. moduły NPM) od danych runtime (sekrety, klucze).

Cache przyspiesza pipeline, ale nie może zawierać niczego, co pozwoli na dostęp do środowisk zewnętrznych.

Czyszczenie kontenerów i obrazów

Środowisko kontenerowe także zostawia ślady.

  • Usuwanie zakończonych kontenerów i ich wolumenów po zakończeniu joba.
  • Okresowe czyszczenie nieużywanych obrazów (docker image prune / crictl rmi z limitem wieku).
  • Brak trwałych wolumenów współdzielonych między jobami, jeśli nie ma silnego uzasadnienia.

Jeśli runner buildował obrazy z sekretami „wklejonymi” jako build-arg, warto dodać okresowe skanowanie warstw pod kątem przypadkowo zapisanych credów.

Reset środowiska sieciowego

Job może zmieniać reguły firewalli, interfejsy czy routing (świadomie albo w wyniku ataku).

  • Po jobie przywrócenie domyślnej konfiguracji iptables/nftables z szablonu.
  • Reset zmian w /etc/hosts, DNS, tunelach VPN.
  • W środowiskach kontenerowych – własne namespace sieciowe dla każdego joba.

Brak resetu sieci sprzyja „przemycaniu” bocznych kanałów komunikacji między jobami.

Sprzątanie procesów i sesji

Po zakończeniu joba nie powinien zostać żaden proces potomny.

  • Proces runnera jako init (PID 1) w kontenerze i --init lub dedykowany init do reapowania zombie.
  • Killem wszystkich procesów w cgroup joba po zakończeniu kroku.
  • Automatyczne zamykanie połączeń SSH/agentów (np. ssh-agent, gpg-agent), jeśli są używane.

Utrzymujące się procesy to wygodne miejsce na backdoory, które „przeżyją” kolejne pipeline’y.

Weryfikacja i sanity check przed zwolnieniem runnera

Zanim runner trafi z powrotem do puli lub zostanie użyty ponownie, można wykonać szybkie testy.

  • Sprawdzenie, czy nie ma nietypowych procesów lub otwartych portów spoza whitelisty.
  • Test uprawnień filesystemu – brak obcych plików poza standardowym layoutem.
  • W razie wykrycia anomalii – oznaczenie hosta jako „skompromitowany” i automatyczne zniszczenie.

Prosty skrypt health-check + integracja z systemem orkiestracji wystarczy, by nie wpuszczać podejrzanych runnerów z powrotem do gry.

Balans między efemerycznością a wydajnością

Całkowite niszczenie runnera po każdym jobie bywa kosztowne.

  • Efemeryczne pody runnerów w Kubernetesie, ale współdzielone węzły (node’y) z dobrym podziałem cgroups.
  • Stałe hosty z efemerycznymi kontenerami, które czyszczą wszystko poza odseparowanym cache.
  • Strategia „N jobów lub M minut” – runner kasowany po określonej liczbie pipeline’ów lub czasie życia.

Kluczem jest określenie, ile ryzyka akceptujesz i gdzie realnie zyskujesz na trwałości środowiska.

Integracja z systemami skanowania i compliance

Efemeryczne runnery trzeba włączyć w standardowe procesy bezpieczeństwa.

  • Tagowanie instancji/kontenerów tak, by systemy skanujące wiedziały, że to hosty CI.
  • Lekkie, szybkie skany compliance (CIS, benchmarki) wykonywane okresowo na template’ach, nie na każdym pojedynczym runnerze.
  • Przechwytywanie logów z krótkiego życia runnera do centralnego SIEM już od pierwszej sekundy działania.

Audyt musi obejmować również infrastrukturę „na chwilę”, bo incydenty nie wybierają między stałym a tymczasowym hostem.

Bezpieczne przechowywanie artefaktów i cache

Najbardziej wrażliwe dane nie zostają na runnerze, tylko w zewnętrznych magazynach.

  • Artefakty buildów wysyłane do S3, GCS, Artifactory, GitLab Packages, Nexus – z kontrolą dostępu.
  • Cache budowania (np. warstwy Dockera, dependency cache) w dedykowanych bucketach z osobnymi kluczami.
  • Brak ręcznego kopiowania katalogów roboczych między runnerami – użycie mechanizmów artefaktów systemu CI.

Jeśli coś musi przeżyć dłużej niż job, przechowuj to w miejscu z lepszym modelem bezpieczeństwa niż tymczasowy host.

Efemeryczność a testy bezpieczeństwa pipeline’ów

Przy tak zbudowanym środowisku testowanie staje się prostsze.

  • Kontrolowane „atakowanie” efemerycznego runnera (red teaming, chaos security), bez obaw o stałe skutki.
  • Odtwarzanie scenariuszy incydentów – nowy runner z tym samym obrazem, tym samym pipeline’em i tym samym inputem.
  • Gromadzenie wiedzy o typowych wektorach ataku na poziomie template’u, a nie pojedynczej maszyny.

Dzięki temu zmiany w zabezpieczeniach wprowadzasz raz, w obrazie bazowym i kodzie infrastruktury, a nie na dziesiątkach pojedynczych hostów.

Najważniejsze wnioski

  • Self hosted runner daje pełną kontrolę nad środowiskiem CI/CD, ale cała odpowiedzialność za bezpieczeństwo, aktualizacje i dostęp do zasobów przechodzi na zespół.
  • W modelu SaaS dostawca zapewnia izolację, hardening i utrzymanie runnerów, podczas gdy w self hosted trzeba samodzielnie zadbać o system, sieć, pakiety i polityki bezpieczeństwa.
  • Self hosted runner ma sens tam, gdzie są twarde wymagania compliance, konieczny dostęp do sieci wewnętrznej, specyficzne toolchainy, licencjonowane zależności, GPU lub specjalistyczny sprzęt.
  • W projektach mobilnych i na macOS własne runnery pozwalają kontrolować wersje Xcode, SDK i certyfikatów, co jest trudne lub kosztowne na runnerach zarządzanych przez dostawcę.
  • Topologia runnerów (on‑premise, chmura prywatna/publiczna, hybryda) powinna wynikać z potrzeb dostępu do systemów, wymogów regulacyjnych i realnych możliwości operacyjnych zespołu.
  • Dla małych zespołów bez doświadczenia operacyjnego oraz przy rzadko uruchamianych pipeline’ach własne runnery generują nadmiarowy koszt i ryzyko – prościej korzystać z runnerów SaaS.
  • Brak spójnego modelu bezpieczeństwa (segregacja sieci, zarządzanie sekretami, kontrola uprawnień) sprawia, że self hosted runner staje się łatwym wejściem do całej infrastruktury.

Źródła informacji

  • GitHub Actions: Self-hosted runners. GitHub – Dokumentacja self-hosted runnerów, instalacja, bezpieczeństwo, ograniczenia
  • GitLab Runner Documentation. GitLab – Architektura GitLab Runner, executory, izolacja jobów, konfiguracja on‑premise
  • Azure Pipelines agents: Self-hosted agents. Microsoft – Oficjalne wytyczne dot. self-hosted agentów, sieci, bezpieczeństwa i utrzymania
  • Bitbucket Pipelines: Runners. Atlassian – Opis self-hosted runnerów Bitbucket, topologie wdrożeń i wymagania środowiskowe
  • NIST SP 800-190: Application Container Security Guide. National Institute of Standards and Technology (2017) – Zalecenia bezpieczeństwa dla kontenerów używanych m.in. jako runnery CI/CD
  • ISO/IEC 27001:2022 Information security, cybersecurity and privacy protection. International Organization for Standardization (2022) – Norma zarządzania bezpieczeństwem informacji, kontekst compliance dla CI/CD
  • CIS Controls v8. Center for Internet Security (2021) – Kontrole bezpieczeństwa: aktualizacje, zarządzanie dostępem, monitoring hostów runnerów