ABC e Sigurisë në Kubernetes: Autentifikimi, Autorizimi, Auditimi

ABC e Sigurisë në Kubernetes: Autentifikimi, Autorizimi, Auditimi

Herët a vonë, në funksionimin e çdo sistemi, lind çështja e sigurisë: sigurimi i vërtetimit, ndarja e të drejtave, auditimi dhe detyra të tjera. Krijuar tashmë për Kubernetes shumë zgjidhje, të cilat ju lejojnë të arrini përputhjen me standardet edhe në mjedise shumë kërkuese... I njëjti material i kushtohet aspekteve bazë të sigurisë të zbatuara brenda mekanizmave të integruar të K8-ve. Para së gjithash, do të jetë e dobishme për ata që kanë filluar të njihen me Kubernetes - si një pikënisje për studimin e çështjeve të lidhura me sigurinë.

vërtetim

Ekzistojnë dy lloje të përdoruesve në Kubernetes:

  • Llogaritë e Shërbimit — llogaritë e menaxhuara nga Kubernetes API;
  • Përdorues — Përdoruesit “normalë” të menaxhuar nga shërbime të jashtme dhe të pavarura.

Dallimi kryesor midis këtyre llojeve është se për Llogaritë e Shërbimit ka objekte të veçanta në API Kubernetes (ata quhen kështu - ServiceAccounts), të cilat janë të lidhura me një hapësirë ​​emri dhe një grup të dhënash autorizimi të ruajtura në grup në objekte të tipit Secrets. Përdorues të tillë (Llogaritë e Shërbimit) kanë për qëllim kryesisht të menaxhojnë të drejtat e aksesit në API të Kubernetes të proceseve që ekzekutohen në grupin Kubernetes.

Përdoruesit e zakonshëm nuk kanë hyrje në Kubernetes API: ata duhet të menaxhohen nga mekanizma të jashtëm. Ato janë të destinuara për njerëzit ose proceset që jetojnë jashtë grupit.

Çdo kërkesë API shoqërohet ose me një llogari shërbimi, një përdorues ose konsiderohet anonime.

Të dhënat e vërtetimit të përdoruesit përfshijnë:

  • Emri i përdoruesit — emri i përdoruesit (i ndjeshëm ndaj shkronjave të vogla!);
  • UID - një varg identifikimi i përdoruesit të lexueshëm nga makineritë që është "më i qëndrueshëm dhe unik se emri i përdoruesit";
  • Grupet — lista e grupeve të cilave përdoruesi i përket;
  • shtesë — fusha shtesë që mund të përdoren nga mekanizmi i autorizimit.

Kubernetes mund të përdorë një numër të madh mekanizmash vërtetimi: certifikatat X509, shenjat e bartësit, përfaqësuesin e vërtetimit, Vërtetimi bazë HTTP. Duke përdorur këto mekanizma, ju mund të zbatoni një numër të madh skemash autorizimi: nga një skedar statik me fjalëkalime në OpenID OAuth2.

Për më tepër, është e mundur të përdoren disa skema autorizimi njëkohësisht. Si parazgjedhje, grupi përdor:

  • shenjat e llogarisë së shërbimit - për Llogaritë e Shërbimit;
  • X509 - për përdoruesit.

Pyetja në lidhje me menaxhimin e llogarive të shërbimit është përtej qëllimit të këtij artikulli, por për ata që duan të njihen me këtë çështje në mënyrë më të detajuar, unë rekomandoj të fillojnë me faqet e dokumentacionit zyrtar. Ne do të hedhim një vështrim më të afërt në çështjen se si funksionojnë certifikatat X509.

Certifikata për përdoruesit (X.509)

Mënyra klasike e punës me certifikatat përfshin:

  • gjenerimi kryesor:
    mkdir -p ~/mynewuser/.certs/
    openssl genrsa -out ~/.certs/mynewuser.key 2048
  • gjenerimi i një kërkese për certifikatë:
    openssl req -new -key ~/.certs/mynewuser.key -out ~/.certs/mynewuser.csr -subj "/CN=mynewuser/O=company"
  • përpunimi i një kërkese për certifikatë duke përdorur çelësat CA të grupit Kubernetes, marrja e një certifikate përdoruesi (për të marrë një certifikatë, duhet të përdorni një llogari që ka akses në çelësin CA të grupit Kubernetes, i cili si parazgjedhje ndodhet në /etc/kubernetes/pki/ca.key):
    openssl x509 -req -in ~/.certs/mynewuser.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out ~/.certs/mynewuser.crt -days 500
  • krijimi i një skedari konfigurimi:
    • përshkrimi i grupit (specifikoni adresën dhe vendndodhjen e skedarit të certifikatës CA për një instalim të grupit specifik):
      kubectl config set-cluster kubernetes --certificate-authority=/etc/kubernetes/pki/ca.crt --server=https://192.168.100.200:6443
    • ose si joopsioni i rekomanduar - nuk duhet të specifikoni certifikatën rrënjë (atëherë kubectl nuk do të kontrollojë korrektësinë e serverit api të grupit):
      kubectl config set-cluster kubernetes  --insecure-skip-tls-verify=true --server=https://192.168.100.200:6443
    • shtimi i një përdoruesi në skedarin e konfigurimit:
      kubectl config set-credentials mynewuser --client-certificate=.certs/mynewuser.crt  --client-key=.certs/mynewuser.key
    • duke shtuar kontekstin:
      kubectl config set-context mynewuser-context --cluster=kubernetes --namespace=target-namespace --user=mynewuser
    • caktimi i parazgjedhur i kontekstit:
      kubectl config use-context mynewuser-context

Pas manipulimeve të mësipërme, në dosje .kube/config do të krijohet një konfigurim si ky:

apiVersion: v1
clusters:
- cluster:
    certificate-authority: /etc/kubernetes/pki/ca.crt
    server: https://192.168.100.200:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    namespace: target-namespace
    user: mynewuser
  name: mynewuser-context
current-context: mynewuser-context
kind: Config
preferences: {}
users:
- name: mynewuser
  user:
    client-certificate: /home/mynewuser/.certs/mynewuser.crt
    client-key: /home/mynewuser/.certs/mynewuser.key

Për ta bërë më të lehtë transferimin e konfigurimit midis llogarive dhe serverëve, është e dobishme të modifikoni vlerat e çelësave të mëposhtëm:

  • certificate-authority
  • client-certificate
  • client-key

Për ta bërë këtë, mund të kodoni skedarët e specifikuar në to duke përdorur base64 dhe t'i regjistroni ato në konfigurim, duke shtuar prapashtesën në emrin e çelësave -data, d.m.th. duke marrë certificate-authority-data etj

Certifikata me kubeadm

Me lirimin Kubernetes 1.15 Puna me certifikata është bërë shumë më e lehtë falë versionit alfa të mbështetjes së tij në mjeti kubeadm. Për shembull, kjo është se si mund të duket tani krijimi i një skedari konfigurimi me çelësat e përdoruesit:

kubeadm alpha kubeconfig user --client-name=mynewuser --apiserver-advertise-address 192.168.100.200

NB: Kërkohet reklamoni adresën mund të gjendet në konfigurimin e serverit api, i cili si parazgjedhje ndodhet në /etc/kubernetes/manifests/kube-apiserver.yaml.

Konfigurimi që rezulton do të dalë në stdout. Duhet të ruhet në ~/.kube/config llogaria e përdoruesit ose në një skedar të specifikuar në një variabël mjedisi KUBECONFIG.

Gërmoni më thellë

Për ata që duan të kuptojnë çështjet e përshkruara më në detaje:

Autorizim

Llogaria e autorizuar e parazgjedhur nuk ka të drejta për të funksionuar në grup. Për të dhënë leje, Kubernetes zbaton një mekanizëm autorizimi.

Përpara versionit 1.6, Kubernetes përdorte një lloj autorizimi të quajtur ABAC (Kontrolli i aksesit i bazuar në atribute). Detaje rreth tij mund të gjenden në dokumentacion zyrtar. Kjo qasje konsiderohet aktualisht e trashëguar, por ju mund ta përdorni përsëri së bashku me llojet e tjera të vërtetimit.

Mënyra aktuale (dhe më fleksibël) e ndarjes së të drejtave të aksesit në një grup quhet RBAC (Kontroll aksesi i bazuar në role). Ai është deklaruar i qëndrueshëm që nga versioni Kubernetes 1.8. RBAC zbaton një model të drejtash në të cilin çdo gjë që nuk lejohet në mënyrë eksplicite është e ndaluar.
Për të aktivizuar RBAC, duhet të nisni serverin api Kubernetes me parametrin --authorization-mode=RBAC. Parametrat vendosen në manifest me konfigurimin e serverit api, i cili si parazgjedhje ndodhet përgjatë shtegut /etc/kubernetes/manifests/kube-apiserver.yaml, në seksion command. Sidoqoftë, RBAC tashmë është aktivizuar si parazgjedhje, kështu që ka shumë të ngjarë që nuk duhet të shqetësoheni për këtë: mund ta verifikoni këtë me vlerën authorization-mode (në të përmendurit tashmë kube-apiserver.yaml). Nga rruga, midis kuptimeve të tij mund të ketë lloje të tjera autorizimi (node, webhook, always allow), por shqyrtimin e tyre do ta lëmë jashtë objektit të materialit.

Nga rruga, ne kemi publikuar tashmë një artikull me një përshkrim mjaft të detajuar të parimeve dhe veçorive të punës me RBAC, kështu që më tej do të kufizohem në një listë të shkurtër të bazave dhe shembujve.

Subjektet e mëposhtme API përdoren për të kontrolluar aksesin në Kubernetes nëpërmjet RBAC:

  • Role и ClusterRole — rolet që shërbejnë për të përshkruar të drejtat e aksesit:
  • Role ju lejon të përshkruani të drejtat brenda një emri;
  • ClusterRole - brenda grupit, duke përfshirë objekte specifike të grupit, si nyjet, url-të pa burime (d.m.th. që nuk lidhen me burimet Kubernetes - për shembull, /version, /logs, /api*);
  • RoleBinding и ClusterRoleBinding - përdoret për lidhje Role и ClusterRole te një përdorues, grup përdoruesi ose llogari shërbimi.

Subjektet Role dhe RoleBinding janë të kufizuara nga hapësira e emrave, d.m.th. duhet të jetë brenda të njëjtës hapësirë ​​emri. Sidoqoftë, një RoleBinding mund t'i referohet një ClusterRole, i cili ju lejon të krijoni një grup lejesh gjenerike dhe të kontrolloni aksesin duke përdorur ato.

Rolet përshkruajnë të drejtat duke përdorur grupe rregullash që përmbajnë:

  • Grupet API - shih dokumentacion zyrtar nga apiGroups dhe output kubectl api-resources;
  • burimet (burime: pod, namespace, deployment dhe kështu me radhë.);
  • Foljet (foljet: set, update dhe kështu me radhë.).
  • emrat e burimeve (resourceNames) - për rastin kur duhet të siguroni akses në një burim specifik, dhe jo në të gjitha burimet e këtij lloji.

Një analizë më e detajuar e autorizimit në Kubernetes mund të gjendet në faqe dokumentacion zyrtar. Në vend të kësaj (ose më mirë, përveç kësaj), unë do të jap shembuj që ilustrojnë punën e saj.

Shembuj të entiteteve RBAC

E thjeshtë Role, i cili ju lejon të merrni një listë dhe status të pods dhe t'i monitoroni ato në hapësirën e emrave target-namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: target-namespace
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

Shembull ClusterRole, i cili ju lejon të merrni një listë dhe status të pods dhe t'i monitoroni ato në të gjithë grupin:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  # секции "namespace" нет, так как ClusterRole задействует весь кластер
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]

Shembull RoleBinding, e cila i lejon përdoruesit mynewuser "lexoni" pods në hapësirën e emrave my-namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: target-namespace
subjects:
- kind: User
  name: mynewuser # имя пользователя зависимо от регистра!
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role # здесь должно быть “Role” или “ClusterRole”
  name: pod-reader # имя Role, что находится в том же namespace,
                   # или имя ClusterRole, использование которой
                   # хотим разрешить пользователю
  apiGroup: rbac.authorization.k8s.io

Auditimi i ngjarjeve

Skematikisht, arkitektura Kubernetes mund të përfaqësohet si më poshtë:

ABC e Sigurisë në Kubernetes: Autentifikimi, Autorizimi, Auditimi

Komponenti kryesor Kubernetes përgjegjës për përpunimin e kërkesave është api-server. Të gjitha operacionet në grup kalojnë përmes tij. Ju mund të lexoni më shumë rreth këtyre mekanizmave të brendshëm në artikullin "Çfarë ndodh në Kubernetes kur drejtoni kubectl run?'.

Auditimi i sistemit është një veçori interesante në Kubernetes, e cila është e çaktivizuar si parazgjedhje. Kjo ju lejon të regjistroni të gjitha thirrjet në Kubernetes API. Siç mund ta merrni me mend, të gjitha veprimet që lidhen me monitorimin dhe ndryshimin e gjendjes së grupit kryhen përmes këtij API. Një përshkrim i mirë i aftësive të tij (si zakonisht) mund të gjendet në dokumentacion zyrtar K8s. Më pas, do të përpiqem ta paraqes temën në një gjuhë më të thjeshtë.

Pra, për të mundësuar auditimin, duhet të kalojmë tre parametra të kërkuar në kontejnerin në serverin api, të cilat përshkruhen më në detaje më poshtë:

  • --audit-policy-file=/etc/kubernetes/policies/audit-policy.yaml
  • --audit-log-path=/var/log/kube-audit/audit.log
  • --audit-log-format=json

Përveç këtyre tre parametrave të nevojshëm, ka shumë cilësime shtesë që lidhen me auditimin: nga rrotullimi i regjistrave deri te përshkrimet e uebhook. Shembull i parametrave të rrotullimit të regjistrit:

  • --audit-log-maxbackup=10
  • --audit-log-maxsize=100
  • --audit-log-maxage=7

Por ne nuk do të ndalemi në to më në detaje - mund t'i gjeni të gjitha detajet në dokumentacioni kube-apiserver.

Siç u përmend tashmë, të gjithë parametrat vendosen në manifest me konfigurimin e serverit api (si parazgjedhje /etc/kubernetes/manifests/kube-apiserver.yaml), në seksion command. Le të kthehemi te 3 parametrat e kërkuar dhe t'i analizojmë ato:

  1. audit-policy-file — rruga për në dosjen YAML që përshkruan politikën e auditimit. Ne do të kthehemi në përmbajtjen e tij më vonë, por tani për tani do të vërej se skedari duhet të jetë i lexueshëm nga procesi i serverit api. Prandaj, është e nevojshme ta montoni atë brenda kontejnerit, për të cilin mund të shtoni kodin e mëposhtëm në seksionet e duhura të konfigurimit:
      volumeMounts:
        - mountPath: /etc/kubernetes/policies
          name: policies
          readOnly: true
      volumes:
      - hostPath:
          path: /etc/kubernetes/policies
          type: DirectoryOrCreate
        name: policies
  2. audit-log-path - rruga për në skedarin e regjistrit. Shtegu duhet gjithashtu të jetë i aksesueshëm për procesin e serverit api, kështu që ne përshkruajmë montimin e tij në të njëjtën mënyrë:
      volumeMounts:
        - mountPath: /var/log/kube-audit
          name: logs
          readOnly: false
      volumes:
      - hostPath:
          path: /var/log/kube-audit
          type: DirectoryOrCreate
        name: logs
  3. audit-log-format — Formati i regjistrit të auditimit. Parazgjedhja është json, por formati i tekstit të trashëguar është gjithashtu i disponueshëm (legacy).

Politika e auditimit

Tani në lidhje me skedarin e përmendur që përshkruan politikën e regjistrimit. Koncepti i parë i politikës së auditimit është level, niveli i prerjeve. Ato janë si më poshtë:

  • None - mos regjistroni;
  • Metadata — të dhënat meta të kërkesave të regjistrit: përdoruesi, koha e kërkesës, burimi i synuar (pod, hapësira e emrave, etj.), lloji i veprimit (folja), etj.;
  • Request — regjistri i meta të dhënave dhe trupi i kërkesës;
  • RequestResponse — të dhënat meta të regjistrit, trupi i kërkesës dhe trupi i përgjigjes.

Dy nivelet e fundit (Request и RequestResponse) mos regjistroni kërkesat që nuk kanë akses në burime (qasje në të ashtuquajturat url-a jo-burimesh).

Gjithashtu të gjitha kërkesat kalojnë disa faza:

  • RequestReceived — faza kur kërkesa është marrë nga përpunuesi dhe ende nuk është transmetuar më tej përgjatë zinxhirit të përpunuesve;
  • ResponseStarted — titujt e përgjigjeve dërgohen, por përpara se të dërgohet trupi i përgjigjes. Krijuar për pyetje të gjata (për shembull, watch);
  • ResponseComplete — organi i përgjigjes është dërguar, nuk do të dërgohet më informacion;
  • Panic — Ngjarjet krijohen kur zbulohet një situatë jonormale.

Për të kapërcyer çdo hap që mund të përdorni omitStages.

Në një skedar politikash, ne mund të përshkruajmë disa seksione me nivele të ndryshme regjistrimi. Rregulli i parë i përputhjes që gjendet në përshkrimin e politikës do të zbatohet.

Daemon kubelet monitoron ndryshimet në manifest me konfigurimin e serverit api dhe, nëse zbulohet ndonjë, rinis kontejnerin me serverin api. Por ka një detaj të rëndësishëm: ndryshimet në dosjen e politikave do të injorohen prej tij. Pasi të bëni ndryshime në skedarin e politikave, do t'ju duhet të rinisni manualisht serverin api. Meqenëse serveri api është nisur si pod statike, ekip kubectl delete nuk do të shkaktojë rinisjen e tij. Ju do të duhet ta bëni atë me dorë docker stop në kube-masters, ku politika e auditimit është ndryshuar:

docker stop $(docker ps | grep k8s_kube-apiserver | awk '{print $1}')

Kur mundësoni auditimin, është e rëndësishme të mbani mend këtë ngarkesa në kube-apiserver rritet. Në veçanti, konsumi i memories për ruajtjen e kontekstit të kërkesës rritet. Regjistrimi fillon vetëm pasi të dërgohet titulli i përgjigjes. Ngarkesa varet gjithashtu nga konfigurimi i politikës së auditimit.

Shembuj të politikave

Le të shohim strukturën e skedarëve të politikave duke përdorur shembuj.

Këtu është një skedar i thjeshtë policypër të regjistruar gjithçka në nivel Metadata:

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata

Në politikë mund të specifikoni një listë të përdoruesve (Users и ServiceAccounts) dhe grupet e përdoruesve. Për shembull, kjo është mënyra se si ne do të injorojmë përdoruesit e sistemit, por do të regjistrojmë gjithçka tjetër në nivel Request:

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: None
    userGroups:
      - "system:serviceaccounts"
      - "system:nodes"
    users:
      - "system:anonymous"
      - "system:apiserver"
      - "system:kube-controller-manager"
      - "system:kube-scheduler"
  - level: Request

Është gjithashtu e mundur të përshkruhen objektivat:

  • hapësirat e emrave (namespaces);
  • Foljet (foljet: get, update, delete dhe të tjerët);
  • burimet (burime, Si vijon: pod, configmaps etj.) dhe grupet e burimeve (apiGroups).

Kushtoj vëmendje! Burimet dhe grupet e burimeve (grupet API, d.m.th. apiGroups), si dhe versionet e tyre të instaluara në grup, mund të merren duke përdorur komandat:

kubectl api-resources
kubectl api-versions

Politika e mëposhtme e auditimit ofrohet si një demonstrim i praktikave më të mira në Dokumentacioni i Alibaba Cloud:

apiVersion: audit.k8s.io/v1beta1
kind: Policy
# Не логировать стадию RequestReceived
omitStages:
  - "RequestReceived"
rules:
  # Не логировать события, считающиеся малозначительными и не опасными:
  - level: None
    users: ["system:kube-proxy"]
    verbs: ["watch"]
    resources:
      - group: "" # это api group с пустым именем, к которому относятся
                  # базовые ресурсы Kubernetes, называемые “core”
        resources: ["endpoints", "services"]
  - level: None
    users: ["system:unsecured"]
    namespaces: ["kube-system"]
    verbs: ["get"]
    resources:
      - group: "" # core
        resources: ["configmaps"]
  - level: None
    users: ["kubelet"]
    verbs: ["get"]
    resources:
      - group: "" # core
        resources: ["nodes"]
  - level: None
    userGroups: ["system:nodes"]
    verbs: ["get"]
    resources:
      - group: "" # core
        resources: ["nodes"]
  - level: None
    users:
      - system:kube-controller-manager
      - system:kube-scheduler
      - system:serviceaccount:kube-system:endpoint-controller
    verbs: ["get", "update"]
    namespaces: ["kube-system"]
    resources:
      - group: "" # core
        resources: ["endpoints"]
  - level: None
    users: ["system:apiserver"]
    verbs: ["get"]
    resources:
      - group: "" # core
        resources: ["namespaces"]
  # Не логировать обращения к read-only URLs:
  - level: None
    nonResourceURLs:
      - /healthz*
      - /version
      - /swagger*
  # Не логировать сообщения, относящиеся к типу ресурсов “события”:
  - level: None
    resources:
      - group: "" # core
        resources: ["events"]
  # Ресурсы типа Secret, ConfigMap и TokenReview могут содержать  секретные данные,
  # поэтому логируем только метаданные связанных с ними запросов
  - level: Metadata
    resources:
      - group: "" # core
        resources: ["secrets", "configmaps"]
      - group: authentication.k8s.io
        resources: ["tokenreviews"]
  # Действия типа get, list и watch могут быть ресурсоёмкими; не логируем их
  - level: Request
    verbs: ["get", "list", "watch"]
    resources:
      - group: "" # core
      - group: "admissionregistration.k8s.io"
      - group: "apps"
      - group: "authentication.k8s.io"
      - group: "authorization.k8s.io"
      - group: "autoscaling"
      - group: "batch"
      - group: "certificates.k8s.io"
      - group: "extensions"
      - group: "networking.k8s.io"
      - group: "policy"
      - group: "rbac.authorization.k8s.io"
      - group: "settings.k8s.io"
      - group: "storage.k8s.io"
  # Уровень логирования по умолчанию для стандартных ресурсов API
  - level: RequestResponse
    resources:
      - group: "" # core
      - group: "admissionregistration.k8s.io"
      - group: "apps"
      - group: "authentication.k8s.io"
      - group: "authorization.k8s.io"
      - group: "autoscaling"
      - group: "batch"
      - group: "certificates.k8s.io"
      - group: "extensions"
      - group: "networking.k8s.io"
      - group: "policy"
      - group: "rbac.authorization.k8s.io"
      - group: "settings.k8s.io"
      - group: "storage.k8s.io"
  # Уровень логирования по умолчанию для всех остальных запросов
  - level: Metadata

Një shembull tjetër i mirë i politikës së auditimit është profili i përdorur në GCE.

Për t'iu përgjigjur shpejt ngjarjeve të auditimit, është e mundur përshkruaj webhook. Kjo çështje është e mbuluar në dokumentacion zyrtar, do ta lë jashtë qëllimit të këtij artikulli.

Rezultatet e

Artikulli ofron një përmbledhje të mekanizmave bazë të sigurisë në grupimet Kubernetes, të cilat ju lejojnë të krijoni llogari të personalizuara përdoruesish, të ndani të drejtat e tyre dhe të regjistroni veprimet e tyre. Shpresoj se do të jetë e dobishme për ata që përballen me çështje të tilla në teori ose në praktikë. Unë gjithashtu rekomandoj që të lexoni listën e materialeve të tjera mbi temën e sigurisë në Kubernetes, e cila është dhënë në "PS" - ndoshta midis tyre do të gjeni detajet e nevojshme për problemet që janë të rëndësishme për ju.

PS

Lexoni edhe në blogun tonë:

Burimi: www.habr.com

Shto një koment