Keycloak HA módban való indítása Kubernetes-en

Keycloak HA módban való indítása Kubernetes-en

Röviden összefoglalva: A Keycloak leírása, egy nyílt forráskódú hozzáférés-ellenőrző rendszer, a belső felépítés elemzése, a beállítási részletek.

Bevezetés és alapötletek

A cikkben áttekintjük azokat az alapötleteket, amelyeket figyelembe kell venni a Keycloak klaszter Kubernetes-en való telepítésekor.

Ha szeretne részletesebben megismerkedni a Keycloak-kal, kérjük, tekintse meg a cikk végén található hivatkozásokat. A gyakorlati megismeréshez tanulmányozza a tárolónkat a cikkel, amely megvalósítja a cikk főbb ötleteit (a bevezető ott található, a cikkben áttekintjük az architektúrát és a beállításokat, jegyzet: fordító).

A Keycloak egy komplex rendszer, Java nyelven íródott, és egy alkalmazás-szerverre épül Wildfly. Röviden, ez egy keretrendszer az azonosításhoz, amely lehetővé teszi a felhasználók számára a föderációt és az SSO (single sign-on) lehetőségét.

Szeretettel meghívjuk, hogy olvassa el az hivatalos weboldal vagy Wikipédiát a részletes megértésért.

A Keycloak indítása

A Keycloak működéséhez két folyamatosan tárolt adatforrás szükséges:

  • Egy adatbázis, amelyet a tartós adatok, például a felhasználói adatok tárolására használnak
  • Datagrid cache, amelyet az adatbázisból származó adatok gyorsítótárazására használnak, valamint néhány rövid életű és gyakran módosított metaadat, például felhasználói munkamenetek tárolására. Ezt a Infinispan, amely általában jelentősen gyorsabb, mint az adatbázis. De mindenesetre az Infinispan-ben tárolt adatok efemer természetűek — nem szükséges azokat megőrizni a klaszter újraindításakor.

A Keycloak négy különböző üzemmódban működik:

  • Normál — egy és csak egy folyamat, a konfiguráció fájlja standalone.xml
  • Normál klaszter (magas rendelkezésre állású változat) — minden folyamatnak ugyanazt a konfigurációt kell használnia, amelyet manuálisan kell szinkronizálni. A beállítások a standalone-ha.xmlfájlban találhatóak, emellett meg kell oldani az adatbázis közös használatát és a terheléselosztót.
  • Domain klaszter — A klaszter normál üzemmódban való indítása rutinszerű és unalmas feladattá válik a klaszter növekedésével, mivel minden konfigurációs módosítás esetén az összes módosítást be kell vinni minden egyes klaszternódra. A domain üzemmód ezt a kérdést azzal oldja meg, hogy beállít egy közös tárolót és közzéteszi a konfigurációt. Ezek a beállítások a domain.xml
  • fájlban találhatóak. A rekreáció a különböző adatközpontok között. — abban az esetben, ha szeretné elindítani a Keycloakot több adatközpontból álló klaszterben, amelyek gyakran földrajzilag különböző helyeken találhatók. Ebben a működési módban minden adatközpontnak saját Keycloak szerver klasztere lesz.

Ebben a cikkben részletesen megvizsgáljuk a második lehetőséget, nevezetesen a szokásos klasztert, és érintjük az adatközpontok közötti replikáció témáját is, mivel ezt a két lehetőséget érdemes Kubernetes alatt futtatni. Szerencsére a Kubernetesben nincs probléma a több Pod (Keycloak csomópont) beállításainak szinkronizálásával, így a domain klasztert meglehetősen egyszerűen létrehozhatjuk.

Kérjük, vegye figyelembe, hogy a klaszter szó az egész cikk alatt kizárólag Keycloak csomópontok együttműködéséről szól, nincs szükség a Kubernetes klaszterre való hivatkozásra.

Szokásos Keycloak klaszter

A Keycloak indításához ebben a módban a következőkre van szükség:

  • konfigurálni egy külső megosztott adatbázist
  • telepíteni egy terheléselosztót
  • legyen egy belső hálózat, amely támogatja az IP multicastot

A külső adatbázis beállítását nem tárgyaljuk, mivel ez nem a cikk célja. Feltételezzük, hogy van egy működő adatbázis valahol — és van egy csatlakozási pontunk hozzá. Ezen adatokat egyszerűen hozzáadjuk a környezeti változókhoz.

A Keycloak működésének megértéséhez a hibatűrő (HA) klaszterben fontos tudni, hogy mennyire függ ez Wildfly klaszterezési képességeitől.

A Wildfly több alrendszert használ, ezek egy része terheléselosztóként, míg mások hibatűrés céljára szolgálnak. A terheléselosztó biztosítja az alkalmazás elérhetőségét a klaszter csomópontjának túlterhelése esetén, míg a hibatűrés garantálja az alkalmazás elérhetőségét a klaszter egyes csomópontjainak leállása esetén. Néhány ilyen alrendszer:

  • mod_cluster: az Apache-al együttműködve HTTP terheléselosztóként működik, alapértelmezetten TCP multicastot használ a csomópontok keresésére. Külső terheléselosztóval helyettesíthető.

  • infinispan: elosztott gyorsítótár, amely JGroups csatornákat használ szállítási szintként. Továbbá HotRod protokollt alkalmazhat az Infinispan külső klaszterével való kapcsolatra a gyorsítótár tartalmának szinkronizálása érdekében.

  • jgroups: csoportos kapcsolatkezelést nyújt a JGroups alapú magas rendelkezésre állású szolgáltatásokhoz. A nevesített csatornák lehetővé teszik az alkalmazás példányainak, hogy csoportokban kapcsolódjanak a klaszterben, biztosítva a megbízhatóságot, a rendezett kommunikációt és a hibaérzékenységet.

Terheléselosztó

Ha terheléselosztót állítunk be bejárati vezérlőként a Kubernetes klaszterben, fontos figyelembe venni a következőket:

A Keycloak működése megköveteli, hogy a hitelesítési szerverhez HTTP-n csatlakozó kliens távoli címe a kliens számítógépének valós IP címe legyen. A terheléselosztó és a bejárati vezérlő beállításainak helyesen kell beállítaniuk a HTTP fejlécet X-Forwarded-For és X-Forwarded-Proto, valamint meg kell őrizniük az eredeti fejlécet HOST. Az utolsó verzió ingress-nginx (> 0.22.0) alapértelmezetten ezt letiltja

A proxy-address-forwarding flag aktiválása környezeti változó beállításával PROXY_ADDRESS_FORWARDING be. igaz lehetővé teszi a Keycloak számára, hogy értse, proxy mögött működik.

A bejáratban be kell kapcsolni a sticky sessions -t. A Keycloak az Infinispan elosztott gyorsítótárat alkalmazza a hitelesítési és felhasználói munkamenetekhez kapcsolódó adatok tárolására. A gyorsítótárak alapértelmezetten egyetlen tulajdonossal működnek, vagyis ez a konkrét munkamenet egy klaszter egy csomópontján kerül tárolásra, míg a többi csomópontnak távolról kell lekérdeznie, ha hozzáférésre van szüksége ehhez a munkamenethez.

Konkétan nálunk a dokumentáció ellenére nem működött a munkamenet cookie-nak nevezett csatolása AUTH_SESSION_ID. A Keycloak ciklikus átirányítást hozott létre, ezért ajánljuk, hogy válasszon más nevet a sticky session cookie-jához.

A Keycloak a CSS-név alatt rögzíti az elsőként válaszoló csomópont nevét AUTH_SESSION_ID, és mivel minden csomópont ugyanazt az adatbázist használja a magas rendelkezésre állású variánsban, mindegyiknek különálló és egyedi csomópontazonosítóval kell rendelkeznie a tranzakciók kezeléséhez. Azt javasoljuk, hogy JAVA_OPTS paraméterek a következő legyen jboss.node.name jboss.tx.node.id és minden csomópont esetén egyediek — például a pod nevét is be lehet állítani. Ha a pod nevét állítja be, ne feledje az 23 karakteres korlátra a jboss változók esetében, így érdemesebb StatefulSet-et használni, nem Deployment-et. egyediek minden egyes csomóponthoz - például a pod nevét is beállíthatja. Ha a pod nevét állítja be, ne feledje a jboss változók 23 karakteres korlátozását, így jobb, ha StatefulSet-et használ, nem Deployment-et.

Még egy probléma – ha az alrendszer leáll vagy újraindul, a gyorsítótár elveszik. Ennek figyelembevételével érdemes beállítani a gyorsítótár tulajdonosainak számát mindegyik gyorsítótárra legalább kettőre, így megmarad egy másolat a gyorsítótárból. A megoldás – elindítani a Wildfly-hoz készült szkriptet az alrendszer indításakor, helyezze el azt a könyvtárban /opt/jboss/startup-scripts a konténerben:

A szkript tartalma

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

echo * A CACHE_OWNERS beállítása "${env.CACHE_OWNERS}" minden gyorsítótár-konténerben

/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})

run-batch
stop-embedded-server

majd állítsa be a környezeti változó értékét CACHE_OWNERS a kívántra.

Privát hálózat, amely támogatja az ip multicast-ot

Ha Weavenetet használ CNI-ként, a multicast azonnal működni fog — és a Keycloak csomópontok azonnal látni fogják egymást, amint elindulnak.

Ha a Kubernetes klaszterében nincs ip multicast támogatás, a JGroups-ot úgy lehet beállítani, hogy más protokollokat használjon a csomópontok kereséséhez.

Az első lehetőség a KUBE_DNS, amely a headless service használatával a Keycloak csomópontok keresésére szolgál, egyszerűen átadja a JGroups-nak a szolgáltatás nevét, amelyet a csomópontok keresésére fognak használni.

Egy másik lehetőség a KUBE_PING, amely az API-t használja a csomópontok keresésére (be kell állítani a serviceAccount jogosultságait list és get, majd beállítani a podokat, hogy működjenek ezzel a serviceAccount).

A JGroups csomópontok keresési módját a környezeti változók beállításával konfigurálhatja JGROUPS_DISCOVERY_PROTOCOL és JGROUPS_DISCOVERY_PROPERTIES. A KUBE_PING a podok kiválasztásához meg kell adni namespace és címkéket.

️ Ha multicast-ot használ, és több Keycloak klasztert indít egy Kubernetes klaszterben (például az egyik benne van a namespace production, a másik pedig — staging) – a Keycloak egyik klaszterének csomópontjai csatlakozhatnak a másik klaszterhez. Mindig használjon egyedi multicast címet minden klaszterhez a változók beállításávaljboss.default.multicast.address és jboss.modcluster.multicast.address be. paraméterek a következő legyen.

fájlban találhatóak. A rekreáció a különböző adatközpontok között.

Keycloak HA módban való indítása Kubernetes-en

Kapcsolat

A Keycloak számos különálló Infinispan cache klasztert használ minden adatközpontban, ahol a Keycloak klaszterek a Keycloak csomópontjaiból állnak. De nincs különbség a Keycloak csomópontjai között a különböző adatközpontokban.

A Keycloak csomópontjai külső Java Data Grid-et (Infinispan szerverek) használnak a kapcsolat fenntartására az adatközpontok között. A kapcsolat protokollon keresztül működik Infinispan HotRod.

Az Infinispan cache-eket a következő attribútummal kell konfigurálni remoteStore, hogy az adatok tárolhatók legyenek távoli (más adatközpontban, jegyzet: fordító) cache-ekben. Az JDG szerverek között különálló Infinispan klaszterek találhatók, így az JDG1-en tárolt adatok a site1 JDG2-re lesznek replikálva a site2.

Végül a fogadó JDG szerver értesíti a klaszterének Keycloak szervereit a kliens kapcsolatain keresztül, ami a HotRod protokoll sajátossága. A Keycloak csomópontjai a site2 frissítik az Infinispan cache-eiket, így a konkrét felhasználói munkamenet a Keycloak csomópontjain is elérhetővé válik a site2.

Bizonyos cache-ek esetében lehetséges nem készíteni biztonsági másolatokat és teljesen lemondani az adatok Infinispan szerver általi írásáról. Ehhez el kell távolítani a konfigurációt remote-store az adott Infinispan cache-ról (a fájlban standalone-ha.xml), ezután egy adott replicated-cache szintén feleslegessé válik az Infinispan szerver oldalán.

Cache-ek konfigurálása

Kétféle cache létezik a Keycloakban:

  • Helyi. Ez a fő adatbázis mellett helyezkedik el, csökkenti az adatbázis terhelését, valamint csökkenti a válaszidőt. Ebben a cache-ben a realm, kliensek, szerepek és felhasználói metaadatok tárolódnak. Ez a cache nem replikálódik, még akkor sem, ha ez a cache a Keycloak klaszter része. Ha a cache-ben egy bejegyzés megváltozik, a kluszter többi szervere értesítést kap a változásról, majd a bejegyzés eltávolításra kerül a cache-ből. Lásd a következő work leírást a folyamat részletes magyarázatához.

  • Replikálható. Felhasználói munkameneteket, offline tokeneket kezel, és figyelemmel kíséri a bejelentkezési hibákat a jelszólopási kísérletek és egyéb támadások azonosítására. Az ilyen cache-ekben tárolt adatok ideiglenesek, csupán a memóriában tárolódnak, de replikálhatók a klaszterben.

Infinispan cache-ek

Munkamenetek — fogalom a Keycloakban, külön cache-ek, amelyeket authenticationSessions, adatokat tárolnak konkrét felhasználókról. Ezekből a gyorsítótárakból általában a böngészőnek és a Keycloak szervereknek van szükségük, nem pedig az alkalmazásoknak. Itt mutatkozik meg a sticky session-ök iránti függőség, és ezeket a gyorsítótárakat nem szükséges replikaálni, még Active-Active üzemmódban sem.

Akciótokenek. Egy újabb koncepció, általában különböző forgatókönyvekben alkalmazzák, amikor például a felhasználónak valamit aszinkron módon kell tennie e-mailben. Például a jelszó-visszaállítás során jelszó elfelejtése gyorsítótár actionTokens a metaadatokkal kapcsolatos tokenek nyomon követésére szolgál – például, ha a token már felhasználásra került, és nem aktiválható újra. Ezt a típusú gyorsítótárat általában replikálni kell az adatközpontok között.

Adatok gyorsítótárazása és lejárat azért működik, hogy csökkentse az adatbázisra nehezedő terhelést. Az ilyen típusú gyorsítótárazás javítja a teljesítményt, de nyilvánvaló problémát is okoz. Ha az egyik Keycloak szerver frissíti az adatokat, a többi szervernek értesülnie kell erről, hogy aktualizálni tudja az adatait a saját gyorsítótáraikban. A Keycloak helyi gyorsítótárakat használ realms, Miután megállapítottuk, hogy ki küldte a kérést, meg kell határoznunk, hogy az adott alanynak mit engedélyeztek. Egyes szolgáltatásrácsok lehetővé teszik alapvető irányelvek (arról, hogy ki mit tehet) megadását YAML-fájlok formájában vagy parancssoron, míg mások integrációt kínálnak olyan keretrendszerekkel, mint az és engedélyezés az adatbázisból származó adatok gyorsítótárazására.

Ezen kívül van egy külön gyorsítótár work, amely replikálva van az adatközpontok között. Ez nem tárol adatokat az adatbázisból, hanem arra szolgál, hogy üzeneteket küldjön az adatok lejáratáról a klaszter csomópontjainak az adatközpontok között. Más szavakkal, amint az adatok frissülnek, a Keycloak csomópont üzenetet küld a többi csomópontnak a saját adatközpontjában, valamint más adatközpontok csomópontjainak. A megfelelő üzenet megérkezése után minden csomópont törli a megfelelő adatokat a saját helyi gyorsítótárából.

Felhasználói ülések. A következő nevű gyorsítótárak sessions, clientSessions, offlineSessions és offlineClientSessions, általában az adatközpontok között replikálva vannak, és a felhasználói ülések adatait tárolják, amelyek aktívak a felhasználó böngészési tevékenysége során. Ezek a gyorsítótárak együtt működnek az alkalmazással, amely HTTP kéréseket kezel a végfelhasználóktól, ezért ezek a sticky session-ökkel kapcsolatosak, és replikálva kell lenniük az adatközpontok között.

Brute-force támadások elleni védelem. A gyorsítótár loginFailures az adatok nyomon követésére szolgál, például arra, hogy hányszor írta be a felhasználó helytelenül a jelszót. E cache replikálása az adminisztrátor feladata. De a pontos számolás érdekében érdemes aktiválni a replikálást a adatközpontok között. Másrészt, ha ezeket az adatokat nem replikálják, akkor javítható a teljesítmény, és ha felmerül ez a kérdés — a replikálást akár nem is kell aktiválni.

Az Infinispan klaszter kiterjesztésekor a cache definíciókat hozzá kell adni a beállítási fájlhoz:

Az Infinispan klasztert be kell állítani és el kell indítani a Keycloak klaszter elindítása előtt.

Ezután be kell állítani remoteStore a Keycloak cache-ekhez. Ehhez elegendő egy olyan szkript, amely hasonlóan készül, mint az előző, amely a változó beállításához használható. CACHE_OWNERS, azt egy fájlba kell menteni és elhelyezni a könyvtárban. /opt/jboss/startup-scripts:

A szkript tartalma

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

echo *** Frissítés az infinispan alrendszernél ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)

echo ** Távkapcsolati socket kötés hozzáadása az infinispan szerverhez **
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})

echo ** A replicated-cache munkaelem frissítése **
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=work, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)

/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:write-attribute(name=statistics-enabled,value=true)

echo ** A distributed-cache sessions elem frissítése **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=sessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=statistics-enabled,value=true)

echo ** A distributed-cache offlineSessions elem frissítése **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=offlineSessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true)

echo ** A distributed-cache clientSessions elem frissítése **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=clientSessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=statistics-enabled,value=true)

echo ** A distributed-cache offlineClientSessions elem frissítése **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=offlineClientSessions, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=statistics-enabled,value=true)

echo ** A distributed-cache loginFailures elem frissítése **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    remote-servers=["remote-cache"], 
    cache=loginFailures, 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=statistics-enabled,value=true)

echo ** A distributed-cache actionTokens elem frissítése **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=remote:add( 
    passivation=false, 
    fetch-state=false, 
    purge=false, 
    preload=false, 
    shared=true, 
    cache=actionTokens, 
    remote-servers=["remote-cache"], 
    properties={ 
        rawValues=true, 
        marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, 
        protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion} 
    } 
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=statistics-enabled,value=true)

echo ** A distributed-cache authenticationSessions elem frissítése **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)

echo *** A undertow alrendszer frissítése ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)

run-batch
stop-embedded-server

Ne feledje el beállítani paraméterek a következő legyen a Keycloak csomópontok számára a HotRod működéséhez: remote.cache.host, remote.cache.port és a szolgáltatás neve jboss.site.name.

Hivatkozások és további dokumentáció

A cikket a Habr számára a Slurm oktatóközpont munkatársai fordították és készítették el — intenzív programok, videókurzusok és vállalati képzés gyakorló szakemberektől (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE) TL;DR: lesz.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster