<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Tarık Anafarta</title>
    <description>The latest articles on Forem by Tarık Anafarta (@tarikanafarta).</description>
    <link>https://forem.com/tarikanafarta</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3794394%2Faa68f549-a9ff-4768-beb9-ec304b3e86d3.png</url>
      <title>Forem: Tarık Anafarta</title>
      <link>https://forem.com/tarikanafarta</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tarikanafarta"/>
    <language>en</language>
    <item>
      <title>GNS3 Üzerinde OpenLDAP, FreeRADIUS, TACACS+, Keycloak ve Elastic ile Merkezi Cisco Switch AAA Kurulumu</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Tue, 05 May 2026 08:24:32 +0000</pubDate>
      <link>https://forem.com/aciklab/gns3-ortaminda-openldap-freeradius-tacacs-ile-cisco-switch-aaa-kurulumu-8a0</link>
      <guid>https://forem.com/aciklab/gns3-ortaminda-openldap-freeradius-tacacs-ile-cisco-switch-aaa-kurulumu-8a0</guid>
      <description>&lt;h1&gt;
  
  
  GNS3 Üzerinde OpenLDAP, FreeRADIUS, TACACS+, Keycloak ve Elastic ile Merkezi AAA Lab Kurulumu
&lt;/h1&gt;

&lt;p&gt;Bu doküman, GNS3 üzerinde Cisco switch'lerin merkezi kullanıcı veritabanı üzerinden kimlik doğrulaması yapmasını, grup bazlı yetki almasını, komut bazlı TACACS+ authorization uygulanmasını ve TACACS accounting loglarının Elastic/Kibana üzerinde görüntülenmesini özetler.&lt;/p&gt;

&lt;p&gt;Kurulumda Active Directory yerine açık kaynak bir LDAP sunucusu olan OpenLDAP kullanıldı. Keycloak, OpenLDAP ile user federation yapacak şekilde eklendi. SW1 üzerinde RADIUS + OpenLDAP login doğrulandı. SW2 üzerinde final durumda TACACS+ + OpenLDAP kullanıldı; admin, operator ve readonly roller için komut bazlı yetkilendirme yapıldı.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not: IP adresleri, parolalar ve bazı isimler dokümantasyon amacıyla maskelenmiştir. Kendi ortamınıza göre değiştirmeniz gerekir.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Terimler
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Active Directory (AD):&lt;/strong&gt; Microsoft'un merkezi kullanıcı ve grup dizinidir. Şirketteki kullanıcı hesapları, gruplar ve parolalar burada tutulur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LDAP:&lt;/strong&gt; AD veya OpenLDAP gibi dizin servisleriyle konuşmak için kullanılan protokol/dildir. Uygulamalar LDAP ile kullanıcı var mı, şifresi doğru mu, hangi grupta gibi sorular sorabilir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenLDAP:&lt;/strong&gt; LDAP protokolünü kullanan açık kaynak dizin servisidir. Bu labda AD yerine kullanıldı.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;phpLDAPadmin:&lt;/strong&gt; OpenLDAP yönetimi için web arayüzüdür. Kullanıcı, OU ve grup oluşturmak için kullanıldı.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keycloak:&lt;/strong&gt; Kimlik yönetimi ve SSO platformudur. Bu labda OpenLDAP ile user federation yapacak şekilde eklendi. Keycloak, LDAP kullanıcılarını/gruplarını görebilir ve writable modda LDAP'a kullanıcı/grup değişikliği yazabilir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RADIUS:&lt;/strong&gt; Ağ erişimi için yaygın kullanılan AAA protokolüdür. Wi-Fi, VPN, 802.1X ve temel cihaz login senaryolarında kullanılır.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TACACS+:&lt;/strong&gt; Özellikle network cihazlarının yönetimi için kullanılan AAA protokolüdür. Switch/router/firewall CLI login, privilege seviyesi, komut bazlı authorization ve accounting için uygundur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MAVIS:&lt;/strong&gt; tac_plus-ng'nin LDAP gibi dış kaynaklarla konuşmak için kullandığı backend/adapter mekanizmasıdır. Bu labda tac_plus-ng, MAVIS LDAP script'i ile OpenLDAP'a bağlanır.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logstash / Elasticsearch / Kibana:&lt;/strong&gt; TACACS accounting loglarını parse etmek, indekslemek ve arayüzden filtrelemek için kullanıldı.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Amaç
&lt;/h2&gt;

&lt;p&gt;Amaç, Cisco switch'lere lokal kullanıcılarla değil, merkezi dizindeki hesaplarla giriş yapılmasını sağlamaktır.&lt;/p&gt;

&lt;p&gt;Hedef akış:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SW1 kullanıcı login -&amp;gt; FreeRADIUS -&amp;gt; OpenLDAP -&amp;gt; Grup kontrolü -&amp;gt; Yetki seviyesi
SW2 kullanıcı login -&amp;gt; TACACS+    -&amp;gt; OpenLDAP -&amp;gt; Grup kontrolü -&amp;gt; Yetki seviyesi + komut yetkilendirme + accounting log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final yetkilendirme modeli:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;network-admins     -&amp;gt; admin profile    -&amp;gt; privilege 15, tüm shell komutları izinli
network-operators  -&amp;gt; operator profile -&amp;gt; privilege 15, sınırlı operasyon komutları izinli
network-readonly   -&amp;gt; readonly profile -&amp;gt; privilege 1, sadece show/exit/logout izinli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu yapı sayesinde 81 il veya çok sayıda kullanıcı için TACACS config dosyasına tek tek kullanıcı yazılmaz. Kullanıcı hangi LDAP grubundaysa o profile uygulanır.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Kullanılan bileşenler
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bileşen&lt;/th&gt;
&lt;th&gt;Rolü&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;Docker servislerinin ve local GNS3 server'ın çalıştığı makine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GNS3&lt;/td&gt;
&lt;td&gt;Ağ topolojisinin oluşturulduğu lab ortamı&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenLDAP&lt;/td&gt;
&lt;td&gt;Kullanıcı ve grup veritabanı&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;phpLDAPadmin&lt;/td&gt;
&lt;td&gt;OpenLDAP için web yönetim arayüzü&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keycloak&lt;/td&gt;
&lt;td&gt;OpenLDAP user federation ve IAM/SSO katmanı&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FreeRADIUS&lt;/td&gt;
&lt;td&gt;SW1 için RADIUS AAA sunucusu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tac_plus-ng&lt;/td&gt;
&lt;td&gt;SW2 için TACACS+ sunucusu&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rsyslog&lt;/td&gt;
&lt;td&gt;Cisco switch syslog mesajlarını Ubuntu üzerinde UDP 514 ile alır ve dosyaya yazar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logstash&lt;/td&gt;
&lt;td&gt;TACACS accounting ve Cisco syslog loglarını parse eder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elasticsearch&lt;/td&gt;
&lt;td&gt;Parse edilen logları indeksler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kibana&lt;/td&gt;
&lt;td&gt;Logları arama, filtreleme ve dashboard için kullanılır&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cisco vIOS-L2&lt;/td&gt;
&lt;td&gt;Lab switch imajı&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GNS3 Cloud + TAP&lt;/td&gt;
&lt;td&gt;Switch'leri Ubuntu üzerindeki AAA servislerine bağlayan sanal ağ&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  3. Lab IP planı
&lt;/h2&gt;

&lt;p&gt;Gerçek IP adresleri dokümantasyon amacıyla maskelenmiştir.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ubuntu AAA Server       : &amp;lt;AAA_SERVER_IP&amp;gt;
GNS3 TAP Interface      : &amp;lt;TAP_GATEWAY_IP&amp;gt;/24
SW1 Management IP       : &amp;lt;SW1_MGMT_IP&amp;gt;/24
SW2 Management IP       : &amp;lt;SW2_MGMT_IP&amp;gt;/24
OpenLDAP Docker IP      : &amp;lt;LDAP_CONTAINER_IP&amp;gt;
FreeRADIUS Docker IP    : &amp;lt;RADIUS_CONTAINER_IP&amp;gt;
TACACS+ Docker IP       : &amp;lt;TACACS_CONTAINER_IP&amp;gt;
Keycloak Docker IP      : &amp;lt;KEYCLOAK_CONTAINER_IP&amp;gt;
Elasticsearch Docker IP : &amp;lt;ELASTICSEARCH_CONTAINER_IP&amp;gt;
Kibana Docker IP        : &amp;lt;KIBANA_CONTAINER_IP&amp;gt;
Logstash Docker IP      : &amp;lt;LOGSTASH_CONTAINER_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lab içinde kullanılan örnek TAP ağı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TAP Gateway             : &amp;lt;TAP_GATEWAY_IP&amp;gt;
SW1                     : &amp;lt;SW1_MGMT_IP&amp;gt;
SW2                     : &amp;lt;SW2_MGMT_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Klasör yapısı
&lt;/h2&gt;

&lt;p&gt;Ana dizin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/aaa-stack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Klasör yapısı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/opt/aaa-stack
├── docker-compose.yml
├── freeradius
│   ├── Dockerfile
│   ├── clients.conf
│   ├── mods-enabled
│   │   └── ldap
│   └── sites-enabled
│       └── default
├── tacacs
│   ├── tac_plus-ng.cfg
│   └── logs
│       └── accounting.log
├── logstash
│   ├── pipeline
│   │   ├── tacacs-accounting.conf
│   │   └── cisco-syslog.conf
│   └── data
├── elasticsearch
│   └── data
└── keycloak
    └── data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Docker Compose servisleri
&lt;/h2&gt;

&lt;p&gt;Aşağıdaki compose örneği labda kullanılan servislerin genel yapısını gösterir. Parolalar, IP adresleri ve subnet değerleri maskelenmiştir.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;openldap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;osixia/openldap:1.5.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openldap&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openldap&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;LDAP_ORGANISATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lab"&lt;/span&gt;
      &lt;span class="na"&gt;LDAP_DOMAIN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lab.local"&lt;/span&gt;
      &lt;span class="na"&gt;LDAP_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;LDAP_ADMIN_PASSWORD&amp;gt;"&lt;/span&gt;
      &lt;span class="na"&gt;LDAP_CONFIG_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;LDAP_CONFIG_PASSWORD&amp;gt;"&lt;/span&gt;
      &lt;span class="na"&gt;LDAP_TLS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;openldap_data:/var/lib/ldap&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;openldap_config:/etc/ldap/slapd.d&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;389:389"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aaa_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;LDAP_CONTAINER_IP&amp;gt;&lt;/span&gt;

  &lt;span class="na"&gt;phpldapadmin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;osixia/phpldapadmin:0.9.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;phpldapadmin&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PHPLDAPADMIN_LDAP_HOSTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openldap&lt;/span&gt;
      &lt;span class="na"&gt;PHPLDAPADMIN_HTTPS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:80"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;openldap&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aaa_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;PHPLDAPADMIN_CONTAINER_IP&amp;gt;&lt;/span&gt;

  &lt;span class="na"&gt;freeradius&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./freeradius&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;freeradius&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;openldap&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1812:1812/udp"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1813:1813/udp"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./freeradius/clients.conf:/etc/freeradius/3.0/clients.conf:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./freeradius/mods-enabled/ldap:/etc/freeradius/3.0/mods-enabled/ldap:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./freeradius/sites-enabled/default:/etc/freeradius/3.0/sites-enabled/default:ro&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;freeradius -X&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aaa_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;RADIUS_CONTAINER_IP&amp;gt;&lt;/span&gt;

  &lt;span class="na"&gt;tacacs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;christianbecker/tac_plus-ng:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tacacs&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;openldap&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;49:49/tcp"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./tacacs/tac_plus-ng.cfg:/etc/tac_plus-ng.cfg:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./tacacs/logs:/var/log/tac_plus-ng&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tac_plus-ng /etc/tac_plus-ng.cfg&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aaa_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;TACACS_CONTAINER_IP&amp;gt;&lt;/span&gt;

  &lt;span class="na"&gt;keycloak&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quay.io/keycloak/keycloak:26.6.1&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keycloak&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;start-dev&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;KC_BOOTSTRAP_ADMIN_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;KEYCLOAK_ADMIN_USER&amp;gt;&lt;/span&gt;
      &lt;span class="na"&gt;KC_BOOTSTRAP_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;KEYCLOAK_ADMIN_PASSWORD&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8081:8080"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./keycloak/data:/opt/keycloak/data&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aaa_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;KEYCLOAK_CONTAINER_IP&amp;gt;&lt;/span&gt;

  &lt;span class="na"&gt;elasticsearch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.elastic.co/elasticsearch/elasticsearch:8.15.3&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elasticsearch&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;discovery.type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;single-node&lt;/span&gt;
      &lt;span class="na"&gt;xpack.security.enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
      &lt;span class="na"&gt;ES_JAVA_OPTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-Xms1g&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-Xmx1g"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9200:9200"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./elasticsearch/data:/usr/share/elasticsearch/data&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aaa_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;ELASTICSEARCH_CONTAINER_IP&amp;gt;&lt;/span&gt;

  &lt;span class="na"&gt;kibana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.elastic.co/kibana/kibana:8.15.3&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kibana&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;elasticsearch&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ELASTICSEARCH_HOSTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://elasticsearch:9200"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5601:5601"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aaa_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;KIBANA_CONTAINER_IP&amp;gt;&lt;/span&gt;

  &lt;span class="na"&gt;logstash&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.elastic.co/logstash/logstash:8.15.3&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;logstash-tacacs&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;elasticsearch&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;tacacs&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./logstash/pipeline:/usr/share/logstash/pipeline:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./logstash/data:/usr/share/logstash/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./tacacs/logs:/var/log/tacacs:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/log/cisco-gns3.log:/var/log/cisco/cisco-gns3.log:ro&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aaa_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ipv4_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;LOGSTASH_CONTAINER_IP&amp;gt;&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;aaa_net&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
    &lt;span class="na"&gt;ipam&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;subnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;DOCKER_AAA_SUBNET&amp;gt;&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;openldap_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;openldap_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Not: Keycloak bu labda &lt;code&gt;start-dev&lt;/code&gt; ile çalıştırıldı. Production ortamda dev mode yerine kalıcı veritabanı, TLS, reverse proxy/hostname ve HA planı yapılmalıdır.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. OpenLDAP kullanıcı ve grup verisi
&lt;/h2&gt;

&lt;p&gt;Bu labda ilk kullanıcı ve grup kayıtları phpLDAPadmin web arayüzü üzerinden oluşturuldu.&lt;/p&gt;

&lt;p&gt;phpLDAPadmin arayüzüne tarayıcıdan erişildi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;AAA_SERVER_IP&amp;gt;:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Login bilgileri:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Login DN : cn=admin,dc=lab,dc=local
Password : &amp;lt;LDAP_ADMIN_PASSWORD&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenLDAP içinde temel dizin yapısı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dc=lab,dc=local
├── ou=people
└── ou=groups
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Başlangıç kullanıcıları:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uid=netadmin,ou=people,dc=lab,dc=local
uid=readonly,ou=people,dc=lab,dc=local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sonradan kullanıcılar eklendi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uid=100001,ou=people,dc=lab,dc=local
uid=100002,ou=people,dc=lab,dc=local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu kullanıcı adları switch login username olarak kullanılır:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Username: 100001
Username: 100002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gruplar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cn=network-admins,ou=groups,dc=lab,dc=local
cn=network-operators,ou=groups,dc=lab,dc=local
cn=network-readonly,ou=groups,dc=lab,dc=local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grup üyelikleri &lt;code&gt;member&lt;/code&gt; attribute'u ile tanımlandı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;network-admins:
member: uid=netadmin,ou=people,dc=lab,dc=local

network-operators:
member: uid=100001,ou=people,dc=lab,dc=local

network-readonly:
member: uid=readonly,ou=people,dc=lab,dc=local
member: uid=100002,ou=people,dc=lab,dc=local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Özet yetkilendirme:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;netadmin -&amp;gt; network-admins    -&amp;gt; admin profile
100001   -&amp;gt; network-operators -&amp;gt; operator profile
readonly -&amp;gt; network-readonly  -&amp;gt; readonly profile
100002   -&amp;gt; network-readonly  -&amp;gt; readonly profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Not: &lt;code&gt;groupOfNames&lt;/code&gt; objectClass'i en az bir &lt;code&gt;member&lt;/code&gt; attribute'u ister. Bu yüzden gruptaki son kullanıcı silinmeye çalışılırsa LDAP object class violation hatası alınabilir.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. Keycloak ve OpenLDAP federation
&lt;/h2&gt;

&lt;p&gt;Keycloak bu labda TACACS+ server'ın yerine geçmedi. Switch tarafında cihazlar TACACS+ konuştuğu için enforcement noktası tac_plus-ng olarak kaldı.&lt;/p&gt;

&lt;p&gt;Keycloak'un bu labdaki rolü:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OpenLDAP kullanıcı ve gruplarını görmek
OpenLDAP ile user federation yapmak
Writable modda Keycloak UI üzerinden OpenLDAP'a kullanıcı/grup/membership yazabilmek
İleride SSO/OIDC/SAML/MFA gibi IAM ihtiyaçları için zemin sağlamak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gerçek AAA flow şu şekildedir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SW2 -&amp;gt; TACACS+ -&amp;gt; tac_plus-ng -&amp;gt; OpenLDAP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keycloak doğrudan switch login isteğini karşılamaz. Cisco switch Keycloak token okumaz; switch TACACS+ konuşur.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 Keycloak erişimi
&lt;/h3&gt;

&lt;p&gt;Keycloak host üzerinde şu porttan yayınlandı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;AAA_SERVER_IP&amp;gt;:8081
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin login:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Username: &amp;lt;KEYCLOAK_ADMIN_USER&amp;gt;
Password: &amp;lt;KEYCLOAK_ADMIN_PASSWORD&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Realm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;network-aaa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.2 LDAP User Federation ayarı
&lt;/h3&gt;

&lt;p&gt;Keycloak içinde:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User federation -&amp;gt; Add provider -&amp;gt; ldap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Örnek ayarlar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UI display name       : openldap
Vendor                : Other
Connection URL        : ldap://openldap:389
Enable StartTLS       : Off
Bind type             : simple
Bind DN               : cn=admin,dc=lab,dc=local
Bind credentials      : &amp;lt;LDAP_ADMIN_PASSWORD&amp;gt;
Users DN              : ou=people,dc=lab,dc=local
Username LDAP attr    : uid
RDN LDAP attr         : uid
UUID LDAP attr        : entryUUID
Search scope          : Subtree
Import users          : On
Edit mode             : WRITABLE
Sync registrations    : On
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kullanıcı object class ayarı writable kullanım için sade tutuldu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inetOrgPerson, organizationalPerson, person
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu nedenle TACACS LDAP filter da &lt;code&gt;inetOrgPerson&lt;/code&gt; ile uyumlu olacak şekilde güncellendi.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.3 LDAP group mapper
&lt;/h3&gt;

&lt;p&gt;Keycloak LDAP provider altında group mapper eklendi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mapper type                   : group-ldap-mapper
LDAP Groups DN                : ou=groups,dc=lab,dc=local
Group Name LDAP Attribute     : cn
Group Object Classes          : groupOfNames
Membership LDAP Attribute     : member
Membership Attribute Type     : DN
Membership User LDAP Attribute: uid
User Groups Retrieve Strategy : LOAD_GROUPS_BY_MEMBER_ATTRIBUTE
Mode                          : LDAP_ONLY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu ayar ile Keycloak'ta kullanıcıyı bir gruba eklemek OpenLDAP'taki &lt;code&gt;groupOfNames/member&lt;/code&gt; attribute'unu günceller.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.4 Keycloak'tan oluşturulan kullanıcıların OpenLDAP'a yazılması
&lt;/h3&gt;

&lt;p&gt;Keycloak üzerinden kullanıcı oluşturuldu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Username: 100002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenLDAP üzerinde kontrol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; openldap ldapsearch &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; ldap://localhost:389 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="s2"&gt;"cn=admin,dc=lab,dc=local"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;LDAP_ADMIN_PASSWORD&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="s2"&gt;"ou=people,dc=lab,dc=local"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"(uid=100002)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Örnek beklenen çıktı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dn: uid=100002,ou=people,dc=lab,dc=local
uid: 100002
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
cn: 100002
sn: 100002
userPassword: &amp;lt;PASSWORD_HASH_OR_VALUE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keycloak üzerinden &lt;code&gt;100002&lt;/code&gt; kullanıcısı &lt;code&gt;network-readonly&lt;/code&gt; grubuna eklendi. LDAP grup üyeliği şu komutla doğrulandı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; openldap ldapsearch &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; ldap://localhost:389 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="s2"&gt;"cn=admin,dc=lab,dc=local"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;LDAP_ADMIN_PASSWORD&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="s2"&gt;"ou=groups,dc=lab,dc=local"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"(cn=network-readonly)"&lt;/span&gt; member
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;member: uid=100002,ou=people,dc=lab,dc=local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu test ile şu doğrulandı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keycloak UI üzerinden kullanıcı oluşturuldu.
Kullanıcı OpenLDAP'a yazıldı.
Keycloak üzerinden grup üyeliği verildi.
Bu üyelik OpenLDAP groupOfNames/member attribute'una yazıldı.
TACACS+ aynı OpenLDAP grup üyeliğini okuyarak yetki verdi.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.5 Keycloak token konusu
&lt;/h3&gt;

&lt;p&gt;Keycloak üzerinden kullanıcı token'ı alınabilir ve token içinde roller görülebilir. Ancak Cisco IOS switch bu token'ı kullanmaz.&lt;/p&gt;

&lt;p&gt;Önemli ayrım:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keycloak token -&amp;gt; OIDC/SAML/web uygulamaları için anlamlıdır.
Cisco switch   -&amp;gt; TACACS+ konuşur, OIDC token okumaz.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu nedenle bu labda Keycloak token'ı üzerinden doğrudan command authorization yapılmadı. Token/role bazlı doğrudan TACACS karar modeli istenirse ek bir MAVIS adapter, policy service veya bunu native destekleyen kurumsal AAA ürünü gerekir.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. FreeRADIUS Dockerfile
&lt;/h2&gt;

&lt;p&gt;Dosya:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/aaa-stack/freeradius/Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:24.04&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; DEBIAN_FRONTEND=noninteractive&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; freeradius freeradius-ldap ldap-utils &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 1812/udp 1813/udp&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["freeradius", "-X"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. FreeRADIUS client tanımları
&lt;/h2&gt;

&lt;p&gt;Dosya:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/aaa-stack/freeradius/clients.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="n"&gt;localhost&lt;/span&gt; {
    &lt;span class="n"&gt;ipaddr&lt;/span&gt; = &lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt; = &amp;lt;&lt;span class="n"&gt;RADIUS_SHARED_SECRET&lt;/span&gt;&amp;gt;
    &lt;span class="n"&gt;require_message_authenticator&lt;/span&gt; = &lt;span class="n"&gt;no&lt;/span&gt;
    &lt;span class="n"&gt;nas_type&lt;/span&gt; = &lt;span class="n"&gt;other&lt;/span&gt;
}

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="n"&gt;docker_bridge&lt;/span&gt; {
    &lt;span class="n"&gt;ipaddr&lt;/span&gt; = &amp;lt;&lt;span class="n"&gt;DOCKER_AAA_SUBNET&lt;/span&gt;&amp;gt;
    &lt;span class="n"&gt;secret&lt;/span&gt; = &amp;lt;&lt;span class="n"&gt;RADIUS_SHARED_SECRET&lt;/span&gt;&amp;gt;
    &lt;span class="n"&gt;require_message_authenticator&lt;/span&gt; = &lt;span class="n"&gt;no&lt;/span&gt;
    &lt;span class="n"&gt;nas_type&lt;/span&gt; = &lt;span class="n"&gt;other&lt;/span&gt;
}

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="n"&gt;gns3_tap_lab&lt;/span&gt; {
    &lt;span class="n"&gt;ipaddr&lt;/span&gt; = &amp;lt;&lt;span class="n"&gt;GNS3_TAP_SUBNET&lt;/span&gt;&amp;gt;
    &lt;span class="n"&gt;secret&lt;/span&gt; = &amp;lt;&lt;span class="n"&gt;RADIUS_SHARED_SECRET&lt;/span&gt;&amp;gt;
    &lt;span class="n"&gt;require_message_authenticator&lt;/span&gt; = &lt;span class="n"&gt;no&lt;/span&gt;
    &lt;span class="n"&gt;nas_type&lt;/span&gt; = &lt;span class="n"&gt;cisco&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. FreeRADIUS LDAP modülü
&lt;/h2&gt;

&lt;p&gt;Dosya:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/aaa-stack/freeradius/mods-enabled/ldap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;ldap&lt;/span&gt; {
    &lt;span class="n"&gt;server&lt;/span&gt; = &lt;span class="s2"&gt;"openldap"&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt; = &lt;span class="m"&gt;389&lt;/span&gt;

    &lt;span class="n"&gt;identity&lt;/span&gt; = &lt;span class="s2"&gt;"cn=admin,dc=lab,dc=local"&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; = &lt;span class="s2"&gt;"&amp;lt;LDAP_ADMIN_PASSWORD&amp;gt;"&lt;/span&gt;

    &lt;span class="n"&gt;base_dn&lt;/span&gt; = &lt;span class="s2"&gt;"dc=lab,dc=local"&lt;/span&gt;

    &lt;span class="n"&gt;user&lt;/span&gt; {
        &lt;span class="n"&gt;base_dn&lt;/span&gt; = &lt;span class="s2"&gt;"ou=people,dc=lab,dc=local"&lt;/span&gt;
        &lt;span class="n"&gt;filter&lt;/span&gt; = &lt;span class="s2"&gt;"(uid=%{%{Stripped-User-Name}:-%{User-Name}})"&lt;/span&gt;
    }

    &lt;span class="n"&gt;group&lt;/span&gt; {
        &lt;span class="n"&gt;base_dn&lt;/span&gt; = &lt;span class="s2"&gt;"ou=groups,dc=lab,dc=local"&lt;/span&gt;
        &lt;span class="n"&gt;filter&lt;/span&gt; = &lt;span class="s2"&gt;"(objectClass=groupOfNames)"&lt;/span&gt;
        &lt;span class="n"&gt;membership_filter&lt;/span&gt; = &lt;span class="s2"&gt;"(&amp;amp;(objectClass=groupOfNames)(member=%{control:Ldap-UserDn}))"&lt;/span&gt;
        &lt;span class="n"&gt;name_attribute&lt;/span&gt; = &lt;span class="n"&gt;cn&lt;/span&gt;
    }

    &lt;span class="n"&gt;options&lt;/span&gt; {
        &lt;span class="n"&gt;chase_referrals&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
        &lt;span class="n"&gt;rebind&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
        &lt;span class="n"&gt;res_timeout&lt;/span&gt; = &lt;span class="m"&gt;10&lt;/span&gt;
        &lt;span class="n"&gt;srv_timelimit&lt;/span&gt; = &lt;span class="m"&gt;3&lt;/span&gt;
        &lt;span class="n"&gt;net_timeout&lt;/span&gt; = &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;idle&lt;/span&gt; = &lt;span class="m"&gt;60&lt;/span&gt;
        &lt;span class="n"&gt;probes&lt;/span&gt; = &lt;span class="m"&gt;3&lt;/span&gt;
        &lt;span class="n"&gt;interval&lt;/span&gt; = &lt;span class="m"&gt;3&lt;/span&gt;
    }

    &lt;span class="n"&gt;tls&lt;/span&gt; {
        &lt;span class="n"&gt;start_tls&lt;/span&gt; = &lt;span class="n"&gt;no&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  11. FreeRADIUS default site
&lt;/h2&gt;

&lt;p&gt;Dosya:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/aaa-stack/freeradius/sites-enabled/default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; {
    &lt;span class="n"&gt;listen&lt;/span&gt; {
        &lt;span class="n"&gt;type&lt;/span&gt; = &lt;span class="n"&gt;auth&lt;/span&gt;
        &lt;span class="n"&gt;ipaddr&lt;/span&gt; = *
        &lt;span class="n"&gt;port&lt;/span&gt; = &lt;span class="m"&gt;1812&lt;/span&gt;
    }

    &lt;span class="n"&gt;listen&lt;/span&gt; {
        &lt;span class="n"&gt;type&lt;/span&gt; = &lt;span class="n"&gt;acct&lt;/span&gt;
        &lt;span class="n"&gt;ipaddr&lt;/span&gt; = *
        &lt;span class="n"&gt;port&lt;/span&gt; = &lt;span class="m"&gt;1813&lt;/span&gt;
    }

    &lt;span class="n"&gt;authorize&lt;/span&gt; {
        &lt;span class="n"&gt;ldap&lt;/span&gt;

        &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;ok&lt;/span&gt;) {
            &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt; {
                &lt;span class="n"&gt;Auth&lt;/span&gt;-&lt;span class="n"&gt;Type&lt;/span&gt; := &lt;span class="n"&gt;LDAP&lt;/span&gt;
            }
        }
    }

    &lt;span class="n"&gt;authenticate&lt;/span&gt; {
        &lt;span class="n"&gt;Auth&lt;/span&gt;-&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;LDAP&lt;/span&gt; {
            &lt;span class="n"&gt;ldap&lt;/span&gt;
        }
    }

    &lt;span class="n"&gt;post&lt;/span&gt;-&lt;span class="n"&gt;auth&lt;/span&gt; {
        &lt;span class="n"&gt;if&lt;/span&gt; (&amp;amp;&lt;span class="n"&gt;LDAP&lt;/span&gt;-&lt;span class="n"&gt;Group&lt;/span&gt; == &lt;span class="s2"&gt;"network-admins"&lt;/span&gt;) {
            &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt; {
                &lt;span class="n"&gt;Service&lt;/span&gt;-&lt;span class="n"&gt;Type&lt;/span&gt; := &lt;span class="n"&gt;Administrative&lt;/span&gt;-&lt;span class="n"&gt;User&lt;/span&gt;
                &lt;span class="n"&gt;Cisco&lt;/span&gt;-&lt;span class="n"&gt;AVPair&lt;/span&gt; := &lt;span class="s2"&gt;"shell:priv-lvl=15"&lt;/span&gt;
                &lt;span class="n"&gt;Reply&lt;/span&gt;-&lt;span class="n"&gt;Message&lt;/span&gt; := &lt;span class="s2"&gt;"LDAP RADIUS Admin Login OK"&lt;/span&gt;
            }
        }
        &lt;span class="n"&gt;elsif&lt;/span&gt; (&amp;amp;&lt;span class="n"&gt;LDAP&lt;/span&gt;-&lt;span class="n"&gt;Group&lt;/span&gt; == &lt;span class="s2"&gt;"network-readonly"&lt;/span&gt;) {
            &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt; {
                &lt;span class="n"&gt;Service&lt;/span&gt;-&lt;span class="n"&gt;Type&lt;/span&gt; := &lt;span class="n"&gt;NAS&lt;/span&gt;-&lt;span class="n"&gt;Prompt&lt;/span&gt;-&lt;span class="n"&gt;User&lt;/span&gt;
                &lt;span class="n"&gt;Cisco&lt;/span&gt;-&lt;span class="n"&gt;AVPair&lt;/span&gt; := &lt;span class="s2"&gt;"shell:priv-lvl=1"&lt;/span&gt;
                &lt;span class="n"&gt;Reply&lt;/span&gt;-&lt;span class="n"&gt;Message&lt;/span&gt; := &lt;span class="s2"&gt;"LDAP RADIUS Readonly Login OK"&lt;/span&gt;
            }
        }
        &lt;span class="n"&gt;else&lt;/span&gt; {
            &lt;span class="n"&gt;reject&lt;/span&gt;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  12. TACACS+ config
&lt;/h2&gt;

&lt;p&gt;TACACS+ tarafında &lt;code&gt;tac_plus-ng&lt;/code&gt; kullanıldı. Bu servis, MAVIS LDAP backend üzerinden OpenLDAP'a bağlanır.&lt;/p&gt;

&lt;p&gt;Final model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;network-admins     -&amp;gt; admin_profile
network-operators  -&amp;gt; operator_profile
network-readonly   -&amp;gt; readonly_profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dosya:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/aaa-stack/tacacs/tac_plus-ng.cfg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;id&lt;/span&gt; = &lt;span class="n"&gt;spawnd&lt;/span&gt; {
    &lt;span class="n"&gt;listen&lt;/span&gt; { &lt;span class="n"&gt;port&lt;/span&gt; = &lt;span class="m"&gt;49&lt;/span&gt; }
    &lt;span class="n"&gt;spawn&lt;/span&gt; {
        &lt;span class="n"&gt;instances&lt;/span&gt; &lt;span class="n"&gt;min&lt;/span&gt; = &lt;span class="m"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;instances&lt;/span&gt; &lt;span class="n"&gt;max&lt;/span&gt; = &lt;span class="m"&gt;16&lt;/span&gt;
    }
}

&lt;span class="n"&gt;id&lt;/span&gt; = &lt;span class="n"&gt;tac_plus&lt;/span&gt;-&lt;span class="n"&gt;ng&lt;/span&gt; {
    &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="n"&gt;acctlog&lt;/span&gt; {
        &lt;span class="n"&gt;destination&lt;/span&gt; = /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;tac_plus&lt;/span&gt;-&lt;span class="n"&gt;ng&lt;/span&gt;/&lt;span class="n"&gt;accounting&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt;
    }

    &lt;span class="n"&gt;accounting&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; = &lt;span class="n"&gt;acctlog&lt;/span&gt;

    &lt;span class="n"&gt;mavis&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; = &lt;span class="n"&gt;external&lt;/span&gt; {
        &lt;span class="n"&gt;setenv&lt;/span&gt; &lt;span class="n"&gt;LDAP_HOSTS&lt;/span&gt; = &lt;span class="s2"&gt;"ldap://openldap:389"&lt;/span&gt;
        &lt;span class="n"&gt;setenv&lt;/span&gt; &lt;span class="n"&gt;LDAP_BASE&lt;/span&gt; = &lt;span class="s2"&gt;"ou=people,dc=lab,dc=local"&lt;/span&gt;
        &lt;span class="n"&gt;setenv&lt;/span&gt; &lt;span class="n"&gt;LDAP_BASE_GROUP&lt;/span&gt; = &lt;span class="s2"&gt;"ou=groups,dc=lab,dc=local"&lt;/span&gt;
        &lt;span class="n"&gt;setenv&lt;/span&gt; &lt;span class="n"&gt;LDAP_SCOPE&lt;/span&gt; = &lt;span class="n"&gt;sub&lt;/span&gt;
        &lt;span class="n"&gt;setenv&lt;/span&gt; &lt;span class="n"&gt;LDAP_SCOPE_GROUP&lt;/span&gt; = &lt;span class="n"&gt;sub&lt;/span&gt;
        &lt;span class="n"&gt;setenv&lt;/span&gt; &lt;span class="n"&gt;LDAP_FILTER&lt;/span&gt; = &lt;span class="s2"&gt;"(&amp;amp;(objectClass=inetOrgPerson)(uid=%s))"&lt;/span&gt;
        &lt;span class="n"&gt;setenv&lt;/span&gt; &lt;span class="n"&gt;LDAP_FILTER_GROUP&lt;/span&gt; = &lt;span class="s2"&gt;"(&amp;amp;(objectClass=groupOfNames)(member=%s))"&lt;/span&gt;
        &lt;span class="n"&gt;setenv&lt;/span&gt; &lt;span class="n"&gt;LDAP_USER&lt;/span&gt; = &lt;span class="s2"&gt;"cn=admin,dc=lab,dc=local"&lt;/span&gt;
        &lt;span class="n"&gt;setenv&lt;/span&gt; &lt;span class="n"&gt;LDAP_PASSWD&lt;/span&gt; = &lt;span class="s2"&gt;"&amp;lt;LDAP_ADMIN_PASSWORD&amp;gt;"&lt;/span&gt;
        &lt;span class="n"&gt;exec&lt;/span&gt; = /&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;local&lt;/span&gt;/&lt;span class="n"&gt;lib&lt;/span&gt;/&lt;span class="n"&gt;mavis&lt;/span&gt;/&lt;span class="n"&gt;mavis_tacplus&lt;/span&gt;-&lt;span class="n"&gt;ng_ldap&lt;/span&gt;.&lt;span class="n"&gt;pl&lt;/span&gt;
    }

    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt; = &lt;span class="n"&gt;mavis&lt;/span&gt;
    &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt; = &lt;span class="n"&gt;mavis&lt;/span&gt;
    &lt;span class="n"&gt;pap&lt;/span&gt; &lt;span class="n"&gt;backend&lt;/span&gt; = &lt;span class="n"&gt;mavis&lt;/span&gt;

    &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="n"&gt;gns3_tap_switches&lt;/span&gt; {
        &lt;span class="n"&gt;address&lt;/span&gt; = &amp;lt;&lt;span class="n"&gt;GNS3_TAP_SUBNET&lt;/span&gt;&amp;gt;
        &lt;span class="n"&gt;key&lt;/span&gt; = &amp;lt;&lt;span class="n"&gt;TACACS_SHARED_SECRET&lt;/span&gt;&amp;gt;
    }

    &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="n"&gt;admin_profile&lt;/span&gt; {
        &lt;span class="n"&gt;script&lt;/span&gt; {
            &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;service&lt;/span&gt; == &lt;span class="n"&gt;shell&lt;/span&gt;) {
                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; == &lt;span class="s2"&gt;""&lt;/span&gt;) {
                    &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;priv&lt;/span&gt;-&lt;span class="n"&gt;lvl&lt;/span&gt; = &lt;span class="m"&gt;15&lt;/span&gt;
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }
                &lt;span class="n"&gt;permit&lt;/span&gt;
            }
        }
    }

    &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="n"&gt;operator_profile&lt;/span&gt; {
        &lt;span class="n"&gt;script&lt;/span&gt; {
            &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;service&lt;/span&gt; == &lt;span class="n"&gt;shell&lt;/span&gt;) {
                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; == &lt;span class="s2"&gt;""&lt;/span&gt;) {
                    &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;priv&lt;/span&gt;-&lt;span class="n"&gt;lvl&lt;/span&gt; = &lt;span class="m"&gt;15&lt;/span&gt;
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;show&lt;/span&gt;(\&lt;span class="n"&gt;s&lt;/span&gt;|$)/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;configure&lt;/span&gt;\&lt;span class="n"&gt;s&lt;/span&gt;+&lt;span class="n"&gt;terminal&lt;/span&gt;(\&lt;span class="n"&gt;s&lt;/span&gt;|$)/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;interface&lt;/span&gt;\&lt;span class="n"&gt;s&lt;/span&gt;+/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;ip&lt;/span&gt;\&lt;span class="n"&gt;s&lt;/span&gt;+&lt;span class="n"&gt;address&lt;/span&gt;\&lt;span class="n"&gt;s&lt;/span&gt;+/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;no&lt;/span&gt;\&lt;span class="n"&gt;s&lt;/span&gt;+&lt;span class="n"&gt;shutdown&lt;/span&gt;(\&lt;span class="n"&gt;s&lt;/span&gt;|$)/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;exit&lt;/span&gt;(\&lt;span class="n"&gt;s&lt;/span&gt;|$)/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;end&lt;/span&gt;(\&lt;span class="n"&gt;s&lt;/span&gt;|$)/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;logout&lt;/span&gt;(\&lt;span class="n"&gt;s&lt;/span&gt;|$)/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;deny&lt;/span&gt;
            }
        }
    }

    &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="n"&gt;readonly_profile&lt;/span&gt; {
        &lt;span class="n"&gt;script&lt;/span&gt; {
            &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;service&lt;/span&gt; == &lt;span class="n"&gt;shell&lt;/span&gt;) {
                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; == &lt;span class="s2"&gt;""&lt;/span&gt;) {
                    &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;priv&lt;/span&gt;-&lt;span class="n"&gt;lvl&lt;/span&gt; = &lt;span class="m"&gt;1&lt;/span&gt;
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;show&lt;/span&gt;(\&lt;span class="n"&gt;s&lt;/span&gt;|$)/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;exit&lt;/span&gt;(\&lt;span class="n"&gt;s&lt;/span&gt;|$)/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;cmd&lt;/span&gt; =~ /^&lt;span class="n"&gt;logout&lt;/span&gt;(\&lt;span class="n"&gt;s&lt;/span&gt;|$)/) {
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }

                &lt;span class="n"&gt;deny&lt;/span&gt;
            }
        }
    }

    &lt;span class="n"&gt;ruleset&lt;/span&gt; {
        &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="n"&gt;admin_rule&lt;/span&gt; {
            &lt;span class="n"&gt;enabled&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
            &lt;span class="n"&gt;script&lt;/span&gt; {
                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;memberof&lt;/span&gt; =~ /^&lt;span class="n"&gt;cn&lt;/span&gt;=&lt;span class="n"&gt;network&lt;/span&gt;-&lt;span class="n"&gt;admins&lt;/span&gt;,&lt;span class="n"&gt;ou&lt;/span&gt;=&lt;span class="n"&gt;groups&lt;/span&gt;,&lt;span class="n"&gt;dc&lt;/span&gt;=&lt;span class="n"&gt;lab&lt;/span&gt;,&lt;span class="n"&gt;dc&lt;/span&gt;=&lt;span class="n"&gt;local&lt;/span&gt;$/) {
                    &lt;span class="n"&gt;profile&lt;/span&gt; = &lt;span class="n"&gt;admin_profile&lt;/span&gt;
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }
            }
        }

        &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="n"&gt;operator_rule&lt;/span&gt; {
            &lt;span class="n"&gt;enabled&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
            &lt;span class="n"&gt;script&lt;/span&gt; {
                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;memberof&lt;/span&gt; =~ /^&lt;span class="n"&gt;cn&lt;/span&gt;=&lt;span class="n"&gt;network&lt;/span&gt;-&lt;span class="n"&gt;operators&lt;/span&gt;,&lt;span class="n"&gt;ou&lt;/span&gt;=&lt;span class="n"&gt;groups&lt;/span&gt;,&lt;span class="n"&gt;dc&lt;/span&gt;=&lt;span class="n"&gt;lab&lt;/span&gt;,&lt;span class="n"&gt;dc&lt;/span&gt;=&lt;span class="n"&gt;local&lt;/span&gt;$/) {
                    &lt;span class="n"&gt;profile&lt;/span&gt; = &lt;span class="n"&gt;operator_profile&lt;/span&gt;
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }
            }
        }

        &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="n"&gt;readonly_rule&lt;/span&gt; {
            &lt;span class="n"&gt;enabled&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
            &lt;span class="n"&gt;script&lt;/span&gt; {
                &lt;span class="n"&gt;if&lt;/span&gt; (&lt;span class="n"&gt;memberof&lt;/span&gt; =~ /^&lt;span class="n"&gt;cn&lt;/span&gt;=&lt;span class="n"&gt;network&lt;/span&gt;-&lt;span class="n"&gt;readonly&lt;/span&gt;,&lt;span class="n"&gt;ou&lt;/span&gt;=&lt;span class="n"&gt;groups&lt;/span&gt;,&lt;span class="n"&gt;dc&lt;/span&gt;=&lt;span class="n"&gt;lab&lt;/span&gt;,&lt;span class="n"&gt;dc&lt;/span&gt;=&lt;span class="n"&gt;local&lt;/span&gt;$/) {
                    &lt;span class="n"&gt;profile&lt;/span&gt; = &lt;span class="n"&gt;readonly_profile&lt;/span&gt;
                    &lt;span class="n"&gt;permit&lt;/span&gt;
                }
            }
        }

        &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="n"&gt;deny_all&lt;/span&gt; {
            &lt;span class="n"&gt;enabled&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
            &lt;span class="n"&gt;script&lt;/span&gt; {
                &lt;span class="n"&gt;deny&lt;/span&gt;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Syntax test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; aaa-stack_aaa_net &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /opt/aaa-stack/tacacs/tac_plus-ng.cfg:/etc/tac_plus-ng.cfg:ro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; /opt/aaa-stack/tacacs/logs:/var/log/tac_plus-ng &lt;span class="se"&gt;\&lt;/span&gt;
  christianbecker/tac_plus-ng:latest &lt;span class="se"&gt;\&lt;/span&gt;
  tac_plus-ng /etc/tac_plus-ng.cfg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/aaa-stack
docker compose restart tacacs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  13. Servisleri başlatma
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/aaa-stack

docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kontrol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen container'lar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openldap
phpldapadmin
freeradius
tacacs
keycloak
elasticsearch
kibana
logstash-tacacs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port kontrolü:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ss &lt;span class="nt"&gt;-lntup&lt;/span&gt; | egrep &lt;span class="s1"&gt;':389|:8080|:8081|:1812|:1813|:49|:9200|:5601'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen portlar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;389/tcp    OpenLDAP
8080/tcp   phpLDAPadmin
8081/tcp   Keycloak
1812/udp   RADIUS authentication
1813/udp   RADIUS accounting
49/tcp     TACACS+
9200/tcp   Elasticsearch
5601/tcp   Kibana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  14. GNS3 tarafında yapılanlar
&lt;/h2&gt;

&lt;p&gt;Local Ubuntu üzerindeki GNS3 server kullanıldı.&lt;/p&gt;

&lt;p&gt;TAP interface oluşturuldu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ip tuntap add dev tap-gns3 mode tap user &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip addr add &amp;lt;TAP_GATEWAY_IP&amp;gt;/24 dev tap-gns3
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip &lt;span class="nb"&gt;link set &lt;/span&gt;tap-gns3 up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GNS3 Cloud node içinde &lt;code&gt;tap-gns3&lt;/code&gt; seçildi.&lt;/p&gt;

&lt;p&gt;Neden ESW1 kullanıldı?&lt;/p&gt;

&lt;p&gt;Cloud üzerindeki TAP port tek bağlantı kabul ettiği için, birden fazla switch'i aynı TAP ağına bağlamak amacıyla araya GNS3 built-in Ethernet switch eklendi.&lt;/p&gt;

&lt;h3&gt;
  
  
  14.1 TAP interface'i kalıcı hale getirme
&lt;/h3&gt;

&lt;p&gt;TAP interface reboot sonrasında kaybolmasın diye systemd service oluşturuldu.&lt;/p&gt;

&lt;p&gt;Script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /usr/local/sbin/gns3-tap-up.sh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/usr/bin/env bash
set -e

TAP_NAME="tap-gns3"
TAP_USER="&amp;lt;LINUX_USER&amp;gt;"
TAP_IP="&amp;lt;TAP_GATEWAY_IP&amp;gt;/24"

if ! ip link show "&lt;/span&gt;&lt;span class="nv"&gt;$TAP_NAME&lt;/span&gt;&lt;span class="sh"&gt;" &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then
    ip tuntap add dev "&lt;/span&gt;&lt;span class="nv"&gt;$TAP_NAME&lt;/span&gt;&lt;span class="sh"&gt;" mode tap user "&lt;/span&gt;&lt;span class="nv"&gt;$TAP_USER&lt;/span&gt;&lt;span class="sh"&gt;"
fi

ip addr flush dev "&lt;/span&gt;&lt;span class="nv"&gt;$TAP_NAME&lt;/span&gt;&lt;span class="sh"&gt;" || true
ip addr add "&lt;/span&gt;&lt;span class="nv"&gt;$TAP_IP&lt;/span&gt;&lt;span class="sh"&gt;" dev "&lt;/span&gt;&lt;span class="nv"&gt;$TAP_NAME&lt;/span&gt;&lt;span class="sh"&gt;"
ip link set "&lt;/span&gt;&lt;span class="nv"&gt;$TAP_NAME&lt;/span&gt;&lt;span class="sh"&gt;" up
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/sbin/gns3-tap-up.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stop script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /usr/local/sbin/gns3-tap-down.sh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/usr/bin/env bash
set -e

TAP_NAME="tap-gns3"

if ip link show "&lt;/span&gt;&lt;span class="nv"&gt;$TAP_NAME&lt;/span&gt;&lt;span class="sh"&gt;" &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then
    ip link set "&lt;/span&gt;&lt;span class="nv"&gt;$TAP_NAME&lt;/span&gt;&lt;span class="sh"&gt;" down || true
    ip tuntap del dev "&lt;/span&gt;&lt;span class="nv"&gt;$TAP_NAME&lt;/span&gt;&lt;span class="sh"&gt;" mode tap || true
fi
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/sbin/gns3-tap-down.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Systemd service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/systemd/system/gns3-tap.service &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
[Unit]
Description=Create persistent TAP interface for GNS3 lab
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/gns3-tap-up.sh
ExecStop=/usr/local/sbin/gns3-tap-down.sh

[Install]
WantedBy=multi-user.target
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;gns3-tap.service
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start gns3-tap.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kontrol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status gns3-tap.service &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
ip &lt;span class="nt"&gt;-br&lt;/span&gt; addr show tap-gns3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tap-gns3 UP &amp;lt;TAP_GATEWAY_IP&amp;gt;/24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  15. SW1 temel IP config
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enable
conf t
hostname SW1

interface vlan 1
 ip address &amp;lt;SW1_MGMT_IP&amp;gt; 255.255.255.0
 no shutdown
exit

ip default-gateway &amp;lt;TAP_GATEWAY_IP&amp;gt;

end
write memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show ip interface brief
ping &amp;lt;TAP_GATEWAY_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  16. SW2 temel IP config
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enable
conf t
hostname SW2

interface vlan 1
 ip address &amp;lt;SW2_MGMT_IP&amp;gt; 255.255.255.0
 no shutdown
exit

ip default-gateway &amp;lt;TAP_GATEWAY_IP&amp;gt;

end
write memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show ip interface brief
ping &amp;lt;TAP_GATEWAY_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  17. SW1 için Cisco RADIUS AAA config
&lt;/h2&gt;

&lt;p&gt;SW1 final durumda RADIUS örneği olarak bırakıldı.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conf t

aaa new-model

radius server AAA-RADIUS
 address ipv4 &amp;lt;TAP_GATEWAY_IP&amp;gt; auth-port 1812 acct-port 1813
 key &amp;lt;RADIUS_SHARED_SECRET&amp;gt;

aaa group server radius RADIUS-GRP
 server name AAA-RADIUS

aaa authentication login default group RADIUS-GRP local
aaa authorization exec default group RADIUS-GRP local

username localadmin privilege 15 secret &amp;lt;LOCAL_FALLBACK_PASSWORD&amp;gt;

line vty 0 15
 login authentication default
 authorization exec default
 transport input telnet ssh

end
write memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kontrol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show aaa servers
show running-config | section radius
show running-config | section aaa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SW1 login testi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;telnet &amp;lt;SW1_MGMT_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin kullanıcı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Username: netadmin
Password: &amp;lt;NETADMIN_PASSWORD&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SW1#
Current privilege level is 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Readonly kullanıcı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Username: readonly
Password: &amp;lt;READONLY_PASSWORD&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SW1&amp;gt;
Current privilege level is 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  18. SW2 için Cisco TACACS+ AAA config
&lt;/h2&gt;

&lt;p&gt;SW2 final durumda TACACS+ kullanacak şekilde bırakıldı.&lt;/p&gt;

&lt;p&gt;Lab imajı eski IOS syntax'ını kabul ettiği için TACACS server şu formatla tanımlandı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conf t

aaa new-model

tacacs-server host &amp;lt;TAP_GATEWAY_IP&amp;gt; key &amp;lt;TACACS_SHARED_SECRET&amp;gt;

aaa group server tacacs+ TACACS-GRP
 server &amp;lt;TAP_GATEWAY_IP&amp;gt;

username localadmin privilege 15 secret &amp;lt;LOCAL_FALLBACK_PASSWORD&amp;gt;

aaa authentication login default group TACACS-GRP local
aaa authorization exec default group TACACS-GRP local

aaa authorization commands 1 default group TACACS-GRP local
aaa authorization commands 15 default group TACACS-GRP local
aaa authorization config-commands

aaa accounting exec default start-stop group TACACS-GRP
aaa accounting commands 1 default start-stop group TACACS-GRP
aaa accounting commands 15 default start-stop group TACACS-GRP

line vty 0 15
 login authentication default
 authorization exec default
 transport input ssh

end
write memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Komutların anlamı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aaa authentication login default group TACACS-GRP local
  Login authentication için önce TACACS+, sonra local fallback.

aaa authorization exec default group TACACS-GRP local
  Kullanıcı login sonrası shell/exec açabilir mi ve privilege seviyesi ne olacak?

aaa authorization commands 1/15 default group TACACS-GRP local
  Privilege 1 ve 15 komutları için command authorization yap.

aaa authorization config-commands
  Config mode içindeki komutları da authorization kapsamına al.

aaa accounting exec default start-stop group TACACS-GRP
  Oturum başlangıç/bitiş accounting kaydı üret.

aaa accounting commands 1/15 default start-stop group TACACS-GRP
  Privilege 1 ve 15 komutlarını accounting'e gönder.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Not: &lt;code&gt;local&lt;/code&gt; fallback labda kilitlenmemek için kullanıldı. Production ortamda fallback davranışı güvenlik politikasına göre ayrıca değerlendirilmelidir.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TACACS+ test komutları:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test aaa group TACACS-GRP netadmin &amp;lt;NETADMIN_PASSWORD&amp;gt; legacy
test aaa group TACACS-GRP netadmin &amp;lt;WRONG_PASSWORD&amp;gt; legacy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Doğru şifre  -&amp;gt; User was successfully authenticated
Yanlış şifre -&amp;gt; User authentication request was rejected by server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  19. SW2 SSH config
&lt;/h2&gt;

&lt;p&gt;Telnet yerine SSH kullanılması için SW2 üzerinde RSA key ve SSH v2 açıldı.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conf t

ip domain-name lab.local
crypto key generate rsa modulus 2048

ip ssh version 2
ip ssh time-out 60
ip ssh authentication-retries 3

line vty 0 15
 login authentication default
 authorization exec default
 transport input ssh

end
write memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kontrol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show ip ssh
show running-config | section line vty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SSH Enabled - version 2.0
transport input ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Labdaki vIOS-L2 imajı eski SSH algoritmaları sunduğu için Ubuntu'dan bağlantıda legacy OpenSSH seçenekleri kullanıldı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-oKexAlgorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+diffie-hellman-group14-sha1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-oHostKeyAlgorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+ssh-rsa &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-oPubkeyAcceptedAlgorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+ssh-rsa &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt;USERNAME&amp;gt;@&amp;lt;SW2_MGMT_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Örnek:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-oKexAlgorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+diffie-hellman-group14-sha1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-oHostKeyAlgorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+ssh-rsa &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-oPubkeyAcceptedAlgorithms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+ssh-rsa &lt;span class="se"&gt;\&lt;/span&gt;
  100001@&amp;lt;SW2_MGMT_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production ortamda güncel IOS-XE veya güncel SSH algoritmaları destekleyen cihazlarda normal SSH komutu yeterli olabilir.&lt;/p&gt;




&lt;h2&gt;
  
  
  20. SW2 TACACS+ komut bazlı yetkilendirme testleri
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Admin test
&lt;/h3&gt;

&lt;p&gt;Kullanıcı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;netadmin -&amp;gt; network-admins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SW2#show privilege
Current privilege level is 15

SW2#write memory
# çalışır
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Operator test
&lt;/h3&gt;

&lt;p&gt;Kullanıcı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100001 -&amp;gt; network-operators
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SW2#show privilege
Current privilege level is 15

SW2#conf t
SW2(config)#interface vlan 1
SW2(config-if)#no shutdown
# çalışır

SW2#write memory
Command authorization failed.

SW2#reload
Command authorization failed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Operator kullanıcının privilege seviyesi 15 olsa bile tüm komutları çalıştıramaz. Çünkü command authorization açık olduğu için switch her yetkilendirilen komutta TACACS+ server'a sorar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Readonly test
&lt;/h3&gt;

&lt;p&gt;Kullanıcı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100002 -&amp;gt; network-readonly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SW2&amp;gt;show privilege
Current privilege level is 1

SW2&amp;gt;show ip interface brief
# çalışır

SW2&amp;gt;conf t
Command authorization failed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  21. TACACS accounting loglama
&lt;/h2&gt;

&lt;p&gt;SW2 üzerinde accounting açıldığı için login/logout ve komut kayıtları TACACS+ server tarafında tutulur. Bu loglar switch diskine yazılmaz.&lt;/p&gt;

&lt;p&gt;TACACS config içinde accounting log dosyası tanımı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="n"&gt;acctlog&lt;/span&gt; {
    &lt;span class="n"&gt;destination&lt;/span&gt; = /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;tac_plus&lt;/span&gt;-&lt;span class="n"&gt;ng&lt;/span&gt;/&lt;span class="n"&gt;accounting&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt;
}

&lt;span class="n"&gt;accounting&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; = &lt;span class="n"&gt;acctlog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker volume sayesinde container içindeki log dosyası host üzerinde şu path'e yazılır:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/opt/aaa-stack/tacacs/logs/accounting.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Canlı izleme:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /opt/aaa-stack/tacacs/logs/accounting.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Örnek log satırları:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;TIMESTAMP&amp;gt; &amp;lt;SWITCH_IP&amp;gt;    100001    tty2    &amp;lt;SOURCE_IP&amp;gt;    start    shell
&amp;lt;TIMESTAMP&amp;gt; &amp;lt;SWITCH_IP&amp;gt;    100001    tty2    &amp;lt;SOURCE_IP&amp;gt;    stop     shell    show privilege &amp;lt;cr&amp;gt;
&amp;lt;TIMESTAMP&amp;gt; &amp;lt;SWITCH_IP&amp;gt;    100001    tty2    &amp;lt;SOURCE_IP&amp;gt;    stop     shell    write memory &amp;lt;cr&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alanların anlamı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;timestamp       -&amp;gt; olay zamanı
switch IP       -&amp;gt; observer.ip, işlem yapılan switch
username        -&amp;gt; user.name
tty             -&amp;gt; terminal/session hattı
source IP       -&amp;gt; switch'e bağlanan kaynak IP
start/stop      -&amp;gt; accounting action
shell           -&amp;gt; servis tipi
command         -&amp;gt; çalıştırılan komut
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;source.ip&lt;/code&gt; labda genelde TAP gateway IP olarak görünür. Çünkü SSH bağlantıları Ubuntu host/TAP üzerinden yapılmaktadır. Gerçek ortamda bu alan admin bilgisayarı veya jump server IP'si olabilir.&lt;/p&gt;




&lt;h2&gt;
  
  
  22. Logstash pipeline
&lt;/h2&gt;

&lt;p&gt;Logstash, TACACS accounting log dosyasını ve Cisco syslog dosyasını okuyup alanlara ayırır ve Elasticsearch'e gönderir. TACACS accounting ayrı indexe, Cisco syslog ayrı indexe yazılır.&lt;/p&gt;

&lt;p&gt;Bu bölümde önce TACACS accounting pipeline gösterilir. Cisco syslog pipeline ise 24. bölümde anlatılmıştır.&lt;/p&gt;

&lt;p&gt;Dosya:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/aaa-stack/logstash/pipeline/tacacs-accounting.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;input&lt;/span&gt; {
  &lt;span class="n"&gt;file&lt;/span&gt; {
    &lt;span class="n"&gt;path&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"/var/log/tacacs/accounting.log"&lt;/span&gt;
    &lt;span class="n"&gt;start_position&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"beginning"&lt;/span&gt;
    &lt;span class="n"&gt;sincedb_path&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"/usr/share/logstash/data/tacacs-accounting.sincedb"&lt;/span&gt;
    &lt;span class="n"&gt;mode&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"tail"&lt;/span&gt;
    &lt;span class="n"&gt;codec&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"plain"&lt;/span&gt;
  }
}

&lt;span class="n"&gt;filter&lt;/span&gt; {
  &lt;span class="n"&gt;if&lt;/span&gt; [&lt;span class="n"&gt;message&lt;/span&gt;] =~ /^\&lt;span class="n"&gt;s&lt;/span&gt;*$/ {
    &lt;span class="n"&gt;drop&lt;/span&gt; { }
  }

  &lt;span class="n"&gt;mutate&lt;/span&gt; {
    &lt;span class="n"&gt;replace&lt;/span&gt; =&amp;gt; {
      &lt;span class="s2"&gt;"[event][original]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"%{message}"&lt;/span&gt;
    }
    &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
      &lt;span class="s2"&gt;"[event][dataset]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"tacacs.accounting"&lt;/span&gt;
      &lt;span class="s2"&gt;"[event][module]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"tacacs"&lt;/span&gt;
    }
  }

  &lt;span class="n"&gt;grok&lt;/span&gt; {
    &lt;span class="n"&gt;match&lt;/span&gt; =&amp;gt; {
      &lt;span class="s2"&gt;"message"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"^%{YEAR:[tacacs][year]}-%{MONTHNUM:[tacacs][month]}-%{MONTHDAY:[tacacs][day]} %{TIME:[tacacs][time]} %{ISO8601_TIMEZONE:[tacacs][timezone]} %{IP:[observer][ip]}\t%{DATA:[user][name]}\t%{DATA:[tacacs][tty]}\t%{IP:[source][ip]}\t%{WORD:[event][action]}\t%{WORD:[service][name]}(?:\t%{GREEDYDATA:[tacacs][command]})?$"&lt;/span&gt;
    }
    &lt;span class="n"&gt;tag_on_failure&lt;/span&gt; =&amp;gt; [ &lt;span class="s2"&gt;"tacacs_parse_failed"&lt;/span&gt; ]
  }

  &lt;span class="n"&gt;mutate&lt;/span&gt; {
    &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
      &lt;span class="s2"&gt;"[tacacs][timestamp_raw]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"%{[tacacs][year]}-%{[tacacs][month]}-%{[tacacs][day]} %{[tacacs][time]} %{[tacacs][timezone]}"&lt;/span&gt;
    }
    &lt;span class="n"&gt;strip&lt;/span&gt; =&amp;gt; [
      &lt;span class="s2"&gt;"[tacacs][command]"&lt;/span&gt;,
      &lt;span class="s2"&gt;"[user][name]"&lt;/span&gt;,
      &lt;span class="s2"&gt;"[observer][ip]"&lt;/span&gt;,
      &lt;span class="s2"&gt;"[source][ip]"&lt;/span&gt;,
      &lt;span class="s2"&gt;"[event][action]"&lt;/span&gt;,
      &lt;span class="s2"&gt;"[service][name]"&lt;/span&gt;
    ]
  }

  &lt;span class="n"&gt;date&lt;/span&gt; {
    &lt;span class="n"&gt;match&lt;/span&gt; =&amp;gt; [ &lt;span class="s2"&gt;"[tacacs][timestamp_raw]"&lt;/span&gt;, &lt;span class="s2"&gt;"yyyy-MM-dd HH:mm:ss Z"&lt;/span&gt; ]
    &lt;span class="n"&gt;target&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"@timestamp"&lt;/span&gt;
    &lt;span class="n"&gt;tag_on_failure&lt;/span&gt; =&amp;gt; [ &lt;span class="s2"&gt;"tacacs_dateparse_failed"&lt;/span&gt; ]
  }

  &lt;span class="n"&gt;if&lt;/span&gt; ![&lt;span class="n"&gt;tacacs&lt;/span&gt;][&lt;span class="n"&gt;command&lt;/span&gt;] &lt;span class="n"&gt;or&lt;/span&gt; [&lt;span class="n"&gt;tacacs&lt;/span&gt;][&lt;span class="n"&gt;command&lt;/span&gt;] == &lt;span class="s2"&gt;""&lt;/span&gt; {
    &lt;span class="n"&gt;mutate&lt;/span&gt; {
      &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
        &lt;span class="s2"&gt;"[tacacs][command_type]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"session"&lt;/span&gt;
      }
    }
  } &lt;span class="n"&gt;else&lt;/span&gt; {
    &lt;span class="n"&gt;mutate&lt;/span&gt; {
      &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
        &lt;span class="s2"&gt;"[tacacs][command_type]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"command"&lt;/span&gt;
      }
    }

    &lt;span class="n"&gt;ruby&lt;/span&gt; {
      &lt;span class="n"&gt;code&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt; = &lt;span class="n"&gt;event&lt;/span&gt;.&lt;span class="n"&gt;get&lt;/span&gt;(&lt;span class="s2"&gt;"[tacacs][command]"&lt;/span&gt;)
        &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &amp;amp;&amp;amp; &lt;span class="n"&gt;cmd&lt;/span&gt;.&lt;span class="n"&gt;strip&lt;/span&gt; != &lt;span class="s2"&gt;""&lt;/span&gt;
          &lt;span class="n"&gt;event&lt;/span&gt;.&lt;span class="n"&gt;set&lt;/span&gt;(&lt;span class="s2"&gt;"[tacacs][command_prefix]"&lt;/span&gt;, &lt;span class="n"&gt;cmd&lt;/span&gt;.&lt;span class="n"&gt;strip&lt;/span&gt;.&lt;span class="n"&gt;split&lt;/span&gt;(/\&lt;span class="n"&gt;s&lt;/span&gt;+/)[&lt;span class="m"&gt;0&lt;/span&gt;])
        &lt;span class="n"&gt;end&lt;/span&gt;
      &lt;span class="err"&gt;'&lt;/span&gt;
    }
  }

  &lt;span class="n"&gt;if&lt;/span&gt; [&lt;span class="n"&gt;tacacs&lt;/span&gt;][&lt;span class="n"&gt;command_prefix&lt;/span&gt;] == &lt;span class="s2"&gt;"no"&lt;/span&gt; {
    &lt;span class="n"&gt;mutate&lt;/span&gt; {
      &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
        &lt;span class="s2"&gt;"[event][risk_score]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"50"&lt;/span&gt;
        &lt;span class="s2"&gt;"[tacacs][command_category]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"negative_config"&lt;/span&gt;
      }
    }
  } &lt;span class="n"&gt;else&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; [&lt;span class="n"&gt;tacacs&lt;/span&gt;][&lt;span class="n"&gt;command_prefix&lt;/span&gt;] == &lt;span class="s2"&gt;"configure"&lt;/span&gt; {
    &lt;span class="n"&gt;mutate&lt;/span&gt; {
      &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
        &lt;span class="s2"&gt;"[event][risk_score]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"40"&lt;/span&gt;
        &lt;span class="s2"&gt;"[tacacs][command_category]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"configuration_mode"&lt;/span&gt;
      }
    }
  } &lt;span class="n"&gt;else&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; [&lt;span class="n"&gt;tacacs&lt;/span&gt;][&lt;span class="n"&gt;command_prefix&lt;/span&gt;] == &lt;span class="s2"&gt;"write"&lt;/span&gt; {
    &lt;span class="n"&gt;mutate&lt;/span&gt; {
      &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
        &lt;span class="s2"&gt;"[event][risk_score]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"30"&lt;/span&gt;
        &lt;span class="s2"&gt;"[tacacs][command_category]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"save_config"&lt;/span&gt;
      }
    }
  } &lt;span class="n"&gt;else&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; [&lt;span class="n"&gt;tacacs&lt;/span&gt;][&lt;span class="n"&gt;command_prefix&lt;/span&gt;] == &lt;span class="s2"&gt;"show"&lt;/span&gt; {
    &lt;span class="n"&gt;mutate&lt;/span&gt; {
      &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
        &lt;span class="s2"&gt;"[event][risk_score]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"10"&lt;/span&gt;
        &lt;span class="s2"&gt;"[tacacs][command_category]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"read_only"&lt;/span&gt;
      }
    }
  }

  &lt;span class="n"&gt;mutate&lt;/span&gt; {
    &lt;span class="n"&gt;remove_field&lt;/span&gt; =&amp;gt; [
      &lt;span class="s2"&gt;"[tacacs][year]"&lt;/span&gt;,
      &lt;span class="s2"&gt;"[tacacs][month]"&lt;/span&gt;,
      &lt;span class="s2"&gt;"[tacacs][day]"&lt;/span&gt;,
      &lt;span class="s2"&gt;"[tacacs][time]"&lt;/span&gt;,
      &lt;span class="s2"&gt;"[tacacs][timezone]"&lt;/span&gt;
    ]
  }
}

&lt;span class="n"&gt;output&lt;/span&gt; {
  &lt;span class="n"&gt;elasticsearch&lt;/span&gt; {
    &lt;span class="n"&gt;hosts&lt;/span&gt; =&amp;gt; [&lt;span class="s2"&gt;"http://elasticsearch:9200"&lt;/span&gt;]
    &lt;span class="n"&gt;index&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"tacacs-accounting-%{+YYYY.MM.dd}"&lt;/span&gt;
  }

  &lt;span class="n"&gt;stdout&lt;/span&gt; {
    &lt;span class="n"&gt;codec&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;rubydebug&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logstash restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/aaa-stack
docker compose restart logstash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Index kontrolü:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"http://localhost:9200/_cat/indices/tacacs-accounting-*?v"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  23. Kibana kullanımı
&lt;/h2&gt;

&lt;p&gt;Kibana erişimi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;AAA_SERVER_IP&amp;gt;:5601
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Data view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name          : tacacs-accounting
Index pattern : tacacs-accounting-*
Timestamp     : @timestamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Discover ekranında önerilen kolonlar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@timestamp
observer.ip
source.ip
user.name
event.action
tacacs.command_type
tacacs.command_prefix
tacacs.command_category
tacacs.command
tacacs.tty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Örnek filtreler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user.name : "100001"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;observer.ip : "&amp;lt;SW2_MGMT_IP&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tacacs.command_type : "command"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tacacs.command_prefix : "no"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tacacs.command : "write memory &amp;lt;cr&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user.name : "100001" and tacacs.command_prefix : "configure"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user.name : "100002" and tacacs.command_type : "command"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu şekilde şu sorular yanıtlanabilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kim switch'e login oldu?
Kim hangi switch üzerinde işlem yaptı?
Kim hangi komutu çalıştırdı?
Kim configure terminal yaptı?
Kim no ile başlayan komut çalıştırdı?
Kim write memory denedi?
Readonly kullanıcı hangi komutları çalıştırdı?
Operator kullanıcı hangi komutlarda deny aldı?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Not: TACACS accounting bazı IOS davranışlarında deny edilen her komutu loglamayabilir. Çalıştırılan/işlenen komutlar accounting tarafında daha net görünür. Deny eventlerinin ayrıca saklanması gerekiyorsa switch syslog veya TACACS authorization debug/log seçenekleri ayrıca değerlendirilmelidir.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  24. Cisco syslog ve archive config logging
&lt;/h2&gt;

&lt;p&gt;TACACS accounting'e ek olarak, SW2 üzerinde cihazın kendi syslog mesajları da toplandı. Bu kısım TACACS veya RADIUS üzerinden çalışmaz. Switch, syslog mesajlarını doğrudan Ubuntu üzerindeki rsyslog servisine UDP 514 ile gönderir.&lt;/p&gt;

&lt;p&gt;Akış:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cisco SW2
   |
   | UDP/514 syslog
   v
Ubuntu rsyslog
   |
   | /var/log/cisco-gns3.log
   v
Logstash
   |
   v
Elasticsearch -&amp;gt; Kibana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu nedenle bu bölümdeki log akışı TACACS+ authentication veya TACACS accounting'e bağlı değildir. TACACS accounting kullanıcı-komut audit kaynağı olarak kalır. Syslog ise cihaz eventleri, SSH olayları, interface/link durumları ve archive config logging ile üretilen config değişiklik mesajları için kullanılır.&lt;/p&gt;

&lt;h3&gt;
  
  
  24.1 SW2 üzerinde syslog ayarları
&lt;/h3&gt;

&lt;p&gt;SW2 üzerinde syslog mesajlarının Ubuntu TAP gateway IP adresine gönderilmesi için aşağıdaki ayarlar yapıldı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conf t

service timestamps log datetime msec localtime show-timezone
logging source-interface Vlan1
logging host &amp;lt;TAP_GATEWAY_IP&amp;gt;
logging trap informational

end
write memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Komutların anlamı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service timestamps log datetime msec localtime show-timezone
  Syslog mesajlarına tarih/saat, milisaniye ve timezone bilgisini ekler.

logging source-interface Vlan1
  Syslog paketlerinin kaynak interface'ini Vlan1 yapar.
  Böylece syslog mesajları switch management IP'sinden gelmiş görünür.

logging host &amp;lt;TAP_GATEWAY_IP&amp;gt;
  Syslog mesajlarının gönderileceği remote syslog sunucusudur.
  Bu labda Ubuntu üzerindeki TAP IP adresidir.

logging trap informational
  Informational ve daha kritik seviyedeki logların remote syslog'a gönderilmesini sağlar.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kontrol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show logging
show running-config | include logging|service timestamps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen örnek çıktı mantığı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Syslog logging: enabled
Trap logging: level informational
Logging to &amp;lt;TAP_GATEWAY_IP&amp;gt; udp port 514
Logging Source-Interface: Vlan1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  24.2 Archive config logging
&lt;/h3&gt;

&lt;p&gt;Normal syslog ayarı yalnızca genel config eventini gösterir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%SYS-5-CONFIG_I: Configured from console by netadmin on vty0 (&amp;lt;SOURCE_IP&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Config mode içinde girilen komutların daha detaylı syslog'a düşmesi için archive config logging açıldı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conf t
archive
 log config
  logging enable
  notify syslog
  hidekeys
end
write memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Komutların anlamı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;archive log config
  Cisco IOS üzerinde config değişikliklerinin loglanacağı bölümü açar.

logging enable
  Config komutlarının loglanmasını aktif eder.

notify syslog
  Loglanan config komutlarını syslog'a da gönderir.

hidekeys
  Şifre, key ve secret gibi hassas değerlerin loglarda açık görünmesini engellemeye yardımcı olur.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu ayar sonrasında örnek bir test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conf t
interface vlan 1
description SYSLOG_TEST_DESCRIPTION
no description
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ubuntu tarafında görülen örnek loglar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%SYS-5-CONFIG_I: Configured from console by netadmin on vty0 (&amp;lt;SOURCE_IP&amp;gt;)
%PARSER-5-CFGLOG_LOGGEDCMD: User:netadmin logged command:interface Vlan1
%PARSER-5-CFGLOG_LOGGEDCMD: User:netadmin logged command:description SYSLOG_TEST_DESCRIPTION
%PARSER-5-CFGLOG_LOGGEDCMD: User:netadmin logged command:no description
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada önemli nokta şudur:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bu log akışında TACACS kullanılmaz.
Switch syslog mesajını doğrudan Ubuntu rsyslog'a gönderir.
Kalıcı merkezi saklama switch üzerinde değil Ubuntu/Elastic tarafında yapılır.
Archive config logging, config komutlarını syslog'a daha detaylı basmak için açılmıştır.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switch üzerinde ayrıca persistent logging veya local disk dosyasına yazma yapılandırılmadı. Bu nedenle bu labda syslog saklama hedefi switch diski değil, Ubuntu ve Elasticsearch tarafıdır. Yine de archive config logging cihaz üzerinde ek log üretimi oluşturduğu için production ortamda log hacmi, hassas veri maskeleme ve retention politikası ayrıca değerlendirilmelidir.&lt;/p&gt;

&lt;h3&gt;
  
  
  24.3 Ubuntu rsyslog alıcı ayarı
&lt;/h3&gt;

&lt;p&gt;Ubuntu üzerinde rsyslog UDP 514 dinleyecek ve Cisco loglarını ayrı bir dosyaya yazacak şekilde ayarlandı.&lt;/p&gt;

&lt;p&gt;Dosya:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/etc/rsyslog.d/10-cisco-gns3.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;İçerik:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;module&lt;/span&gt;(&lt;span class="n"&gt;load&lt;/span&gt;=&lt;span class="s2"&gt;"imudp"&lt;/span&gt;)
&lt;span class="n"&gt;input&lt;/span&gt;(&lt;span class="n"&gt;type&lt;/span&gt;=&lt;span class="s2"&gt;"imudp"&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;=&lt;span class="s2"&gt;"514"&lt;/span&gt;)

&lt;span class="n"&gt;if&lt;/span&gt; ($&lt;span class="n"&gt;fromhost&lt;/span&gt;-&lt;span class="n"&gt;ip&lt;/span&gt; == &lt;span class="s2"&gt;"&amp;lt;SW2_MGMT_IP&amp;gt;"&lt;/span&gt;) &lt;span class="n"&gt;then&lt;/span&gt; {
    &lt;span class="n"&gt;action&lt;/span&gt;(&lt;span class="n"&gt;type&lt;/span&gt;=&lt;span class="s2"&gt;"omfile"&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;=&lt;span class="s2"&gt;"/var/log/cisco-gns3.log"&lt;/span&gt;)
    &lt;span class="n"&gt;stop&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Servis restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart rsyslog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UDP 514 dinleme kontrolü:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ss &lt;span class="nt"&gt;-lunp&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;':514'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Canlı log izleme:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/cisco-gns3.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switch'ten paket gelip gelmediğini doğrulamak için:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;tcpdump &lt;span class="nt"&gt;-ni&lt;/span&gt; tap-gns3 udp port 514
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Örnek beklenen trafik:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;SW2_MGMT_IP&amp;gt;.&amp;lt;random_port&amp;gt; &amp;gt; &amp;lt;TAP_GATEWAY_IP&amp;gt;.514: SYSLOG local7.notice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logstash container'ın dosyayı okuyabilmesi için host üzerinde dosya izni düzenlendi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;644 /var/log/cisco-gns3.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  24.4 Logstash'e Cisco syslog dosyasını mount etme
&lt;/h3&gt;

&lt;p&gt;Logstash container'ın Ubuntu üzerindeki syslog dosyasını okuyabilmesi için &lt;code&gt;docker-compose.yml&lt;/code&gt; içindeki &lt;code&gt;logstash&lt;/code&gt; servisine aşağıdaki volume eklendi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/log/cisco-gns3.log:/var/log/cisco/cisco-gns3.log:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mount değişikliği sonrası sadece restart yeterli değildir. Container yeniden oluşturuldu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/aaa-stack
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--force-recreate&lt;/span&gt; logstash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Container içinden dosya kontrolü:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; logstash-tacacs &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lah&lt;/span&gt; /var/log/cisco/
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; logstash-tacacs &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 5 /var/log/cisco/cisco-gns3.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  24.5 Cisco syslog Logstash pipeline
&lt;/h3&gt;

&lt;p&gt;Cisco syslog logları için ayrı pipeline oluşturuldu.&lt;/p&gt;

&lt;p&gt;Dosya:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/aaa-stack/logstash/pipeline/cisco-syslog.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;İçerik:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;input&lt;/span&gt; {
  &lt;span class="n"&gt;file&lt;/span&gt; {
    &lt;span class="n"&gt;path&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"/var/log/cisco/cisco-gns3.log"&lt;/span&gt;
    &lt;span class="n"&gt;start_position&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"beginning"&lt;/span&gt;
    &lt;span class="n"&gt;sincedb_path&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"/usr/share/logstash/data/cisco-syslog.sincedb"&lt;/span&gt;
    &lt;span class="n"&gt;mode&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"tail"&lt;/span&gt;
    &lt;span class="n"&gt;codec&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"plain"&lt;/span&gt;
  }
}

&lt;span class="n"&gt;filter&lt;/span&gt; {
  &lt;span class="n"&gt;if&lt;/span&gt; [&lt;span class="n"&gt;message&lt;/span&gt;] =~ /^\&lt;span class="n"&gt;s&lt;/span&gt;*$/ {
    &lt;span class="n"&gt;drop&lt;/span&gt; { }
  }

  &lt;span class="n"&gt;mutate&lt;/span&gt; {
    &lt;span class="n"&gt;replace&lt;/span&gt; =&amp;gt; { &lt;span class="s2"&gt;"[event][original]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"%{message}"&lt;/span&gt; }
    &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
      &lt;span class="s2"&gt;"[event][module]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"cisco"&lt;/span&gt;
      &lt;span class="s2"&gt;"[event][dataset]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"cisco.syslog"&lt;/span&gt;
    }
  }

  &lt;span class="n"&gt;grok&lt;/span&gt; {
    &lt;span class="n"&gt;match&lt;/span&gt; =&amp;gt; {
      &lt;span class="s2"&gt;"message"&lt;/span&gt; =&amp;gt; [
        &lt;span class="s2"&gt;"^%{TIMESTAMP_ISO8601:[syslog][received_at]} %{IP:[observer][ip]} %{NUMBER:[cisco][sequence]}: \*%{SYSLOGTIMESTAMP:[cisco][device_timestamp]} %{WORD:[cisco][timezone]}: %%%{DATA:[cisco][facility]}-%{INT:[event][severity]}-%{DATA:[cisco][mnemonic]}: %{GREEDYDATA:[cisco][message]}$"&lt;/span&gt;,
        &lt;span class="s2"&gt;"^%{TIMESTAMP_ISO8601:[syslog][received_at]} %{IP:[observer][ip]} %{GREEDYDATA:[cisco][message]}$"&lt;/span&gt;
      ]
    }
    &lt;span class="n"&gt;tag_on_failure&lt;/span&gt; =&amp;gt; [ &lt;span class="s2"&gt;"cisco_syslog_parse_failed"&lt;/span&gt; ]
  }

  &lt;span class="n"&gt;if&lt;/span&gt; [&lt;span class="n"&gt;cisco&lt;/span&gt;][&lt;span class="n"&gt;message&lt;/span&gt;] =~ /&lt;span class="n"&gt;logged&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;:/ {
    &lt;span class="n"&gt;grok&lt;/span&gt; {
      &lt;span class="n"&gt;match&lt;/span&gt; =&amp;gt; {
        &lt;span class="s2"&gt;"[cisco][message]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"^User:%{DATA:[user][name]}\s+logged command:%{GREEDYDATA:[cisco][command]}$"&lt;/span&gt;
      }
      &lt;span class="n"&gt;tag_on_failure&lt;/span&gt; =&amp;gt; [ &lt;span class="s2"&gt;"cisco_command_parse_failed"&lt;/span&gt; ]
    }

    &lt;span class="n"&gt;mutate&lt;/span&gt; {
      &lt;span class="n"&gt;strip&lt;/span&gt; =&amp;gt; [ &lt;span class="s2"&gt;"[user][name]"&lt;/span&gt;, &lt;span class="s2"&gt;"[cisco][command]"&lt;/span&gt; ]
      &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
        &lt;span class="s2"&gt;"[cisco][event_type]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"config_command"&lt;/span&gt;
        &lt;span class="s2"&gt;"[event][action]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"config_command"&lt;/span&gt;
      }
    }

    &lt;span class="n"&gt;ruby&lt;/span&gt; {
      &lt;span class="n"&gt;code&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt; = &lt;span class="n"&gt;event&lt;/span&gt;.&lt;span class="n"&gt;get&lt;/span&gt;(&lt;span class="s2"&gt;"[cisco][command]"&lt;/span&gt;)
        &lt;span class="n"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &amp;amp;&amp;amp; &lt;span class="n"&gt;cmd&lt;/span&gt;.&lt;span class="n"&gt;strip&lt;/span&gt; != &lt;span class="s2"&gt;""&lt;/span&gt;
          &lt;span class="n"&gt;event&lt;/span&gt;.&lt;span class="n"&gt;set&lt;/span&gt;(&lt;span class="s2"&gt;"[cisco][command_prefix]"&lt;/span&gt;, &lt;span class="n"&gt;cmd&lt;/span&gt;.&lt;span class="n"&gt;strip&lt;/span&gt;.&lt;span class="n"&gt;split&lt;/span&gt;(/\&lt;span class="n"&gt;s&lt;/span&gt;+/)[&lt;span class="m"&gt;0&lt;/span&gt;])
        &lt;span class="n"&gt;end&lt;/span&gt;
      &lt;span class="err"&gt;'&lt;/span&gt;
    }
  } &lt;span class="n"&gt;else&lt;/span&gt; &lt;span class="n"&gt;if&lt;/span&gt; [&lt;span class="n"&gt;cisco&lt;/span&gt;][&lt;span class="n"&gt;mnemonic&lt;/span&gt;] == &lt;span class="s2"&gt;"CONFIG_I"&lt;/span&gt; {
    &lt;span class="n"&gt;mutate&lt;/span&gt; {
      &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
        &lt;span class="s2"&gt;"[cisco][event_type]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"config_change"&lt;/span&gt;
        &lt;span class="s2"&gt;"[event][action]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"config_change"&lt;/span&gt;
      }
    }
  } &lt;span class="n"&gt;else&lt;/span&gt; {
    &lt;span class="n"&gt;mutate&lt;/span&gt; {
      &lt;span class="n"&gt;add_field&lt;/span&gt; =&amp;gt; {
        &lt;span class="s2"&gt;"[cisco][event_type]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"system_event"&lt;/span&gt;
        &lt;span class="s2"&gt;"[event][action]"&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"system_event"&lt;/span&gt;
      }
    }
  }

  &lt;span class="n"&gt;date&lt;/span&gt; {
    &lt;span class="n"&gt;match&lt;/span&gt; =&amp;gt; [ &lt;span class="s2"&gt;"[syslog][received_at]"&lt;/span&gt;, &lt;span class="s2"&gt;"ISO8601"&lt;/span&gt; ]
    &lt;span class="n"&gt;target&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"@timestamp"&lt;/span&gt;
    &lt;span class="n"&gt;tag_on_failure&lt;/span&gt; =&amp;gt; [ &lt;span class="s2"&gt;"cisco_syslog_dateparse_failed"&lt;/span&gt; ]
  }
}

&lt;span class="n"&gt;output&lt;/span&gt; {
  &lt;span class="n"&gt;elasticsearch&lt;/span&gt; {
    &lt;span class="n"&gt;hosts&lt;/span&gt; =&amp;gt; [&lt;span class="s2"&gt;"http://elasticsearch:9200"&lt;/span&gt;]
    &lt;span class="n"&gt;index&lt;/span&gt; =&amp;gt; &lt;span class="s2"&gt;"cisco-syslog-%{+YYYY.MM.dd}"&lt;/span&gt;
  }

  &lt;span class="n"&gt;stdout&lt;/span&gt; {
    &lt;span class="n"&gt;codec&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;rubydebug&lt;/span&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logstash yeniden oluşturuldu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/aaa-stack
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--force-recreate&lt;/span&gt; logstash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Index kontrolü:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"http://localhost:9200/_cat/indices/cisco-syslog-*?v"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beklenen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cisco-syslog-YYYY.MM.DD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  24.6 Kibana data view ve önerilen kolonlar
&lt;/h3&gt;

&lt;p&gt;Kibana üzerinde ayrı bir data view oluşturuldu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name          : cisco-syslog
Index pattern : cisco-syslog-*
Timestamp     : @timestamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Discover ekranında önerilen kolonlar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@timestamp
observer.ip
user.name
event.action
cisco.event_type
cisco.facility
cisco.mnemonic
cisco.command
cisco.command_prefix
cisco.message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Örnek KQL filtreleri:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event.dataset : "cisco.syslog"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event.action : "config_command"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user.name : "netadmin"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cisco.command_prefix : "description"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;observer.ip : "&amp;lt;SW2_MGMT_IP&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  24.7 TACACS accounting ve syslog farkı
&lt;/h3&gt;

&lt;p&gt;Bu labda iki farklı log kaynağı birlikte kullanıldı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TACACS accounting:
  Kullanıcı login/logout ve CLI komut accounting kaynağıdır.
  TACACS+ server üzerinden üretilir.
  Kim hangi komutu çalıştırdı sorusu için ana kaynaktır.

Cisco syslog:
  Cihaz eventleri ve archive config logging mesajları için kullanılır.
  TACACS veya RADIUS üzerinden geçmez.
  Switch doğrudan remote syslog server'a mesaj gönderir.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Özet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TACACS accounting -&amp;gt; AAA komut audit için daha doğru kaynak.
Cisco syslog      -&amp;gt; cihaz olayları, config değişiklik bildirimleri ve sistem eventleri için ek kaynak.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Syslog ile toplanabilecek örnek olaylar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface up/down
line protocol up/down
STP/VLAN eventleri
SSH login/logout veya login failure mesajları
reload/reboot mesajları
%SYS-5-CONFIG_I genel config eventleri
%PARSER-5-CFGLOG_LOGGEDCMD archive config komut logları
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  25. Log retention ve disk konusu
&lt;/h2&gt;

&lt;p&gt;TACACS accounting logları switch diskine yazılmaz. Loglar AAA sunucusu tarafında tutulur:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/opt/aaa-stack/tacacs/logs/accounting.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu yüzden switch diskini şişirmez. Disk büyümesi Ubuntu/Elastic tarafında olur.&lt;/p&gt;

&lt;p&gt;Raw accounting log için logrotate örneği:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/logrotate.d/tacacs-accounting &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
/opt/aaa-stack/tacacs/logs/accounting.log {
    daily
    rotate 30
    compress
    missingok
    notifempty
    copytruncate
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;logrotate &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/logrotate.d/tacacs-accounting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elasticsearch tarafında production için ILM/retention policy planlanmalıdır.&lt;/p&gt;




&lt;h2&gt;
  
  
  26. Final Akış
&lt;/h2&gt;

&lt;p&gt;Final durumda lab akışı şöyledir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keycloak UI
   |
   | LDAP federation / writable user &amp;amp; group management
   v
OpenLDAP
   |
   | LDAP user + group membership
   v
TACACS+ Server - tac_plus-ng
   |
   | permit / deny / priv-lvl
   v
Cisco SW2
   |\
   | \ TACACS accounting
   |  \-&amp;gt; accounting.log -&amp;gt; Logstash -&amp;gt; Elasticsearch -&amp;gt; Kibana
   |
   | syslog UDP/514
   v
Ubuntu rsyslog -&amp;gt; /var/log/cisco-gns3.log -&amp;gt; Logstash -&amp;gt; Elasticsearch -&amp;gt; Kibana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switch login/authorization akışı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Kullanıcı SSH ile SW2'ye bağlanır.
2. SW2, username/password bilgisini TACACS+ server'a gönderir.
3. tac_plus-ng, MAVIS LDAP backend ile OpenLDAP'a sorar.
4. OpenLDAP kullanıcıyı doğrular.
5. tac_plus-ng kullanıcının LDAP grup üyeliğini okur.
6. Grup üyeliğine göre admin/operator/readonly profile seçilir.
7. Kullanıcı komut çalıştırdığında SW2 yine TACACS+ server'a command authorization sorar.
8. tac_plus-ng ilgili profile göre permit/deny döner.
9. Accounting logları Logstash üzerinden Elasticsearch'e gider.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keycloak'un rolü:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keycloak doğrudan switch authentication server'ı değildir.
Switch Keycloak token okumaz.
Switch TACACS+ konuşur.
Keycloak bu labda OpenLDAP kullanıcı/grup yönetimi ve IAM/federation katmanıdır.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu nedenle doğru teknik anlatım:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Keycloak, OpenLDAP/AD ile entegre edildi. Kullanıcı ve grup yönetimi Keycloak üzerinden yapılabiliyor ve LDAP'a yazılıyor. TACACS+ ise switch CLI enforcement noktası olarak kalıyor. tac_plus-ng, LDAP grup üyeliklerine göre admin/operator/readonly profillerini uyguluyor. Tüm login ve komut kayıtları TACACS accounting ile Elastic/Kibana'ya aktarılıyor.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  27. Sonuç
&lt;/h2&gt;

&lt;p&gt;Bu lab ile Cisco switch login ve yönetim işlemleri merkezi hale getirildi.&lt;/p&gt;

&lt;p&gt;Final mimari:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SW1 -&amp;gt; FreeRADIUS -&amp;gt; OpenLDAP -&amp;gt; LDAP Group -&amp;gt; Cisco privilege level
SW2 -&amp;gt; TACACS+    -&amp;gt; OpenLDAP -&amp;gt; LDAP Group -&amp;gt; Cisco privilege level + command authorization + accounting
Keycloak -&amp;gt; OpenLDAP federation/writable user-group management
TACACS accounting -&amp;gt; Logstash -&amp;gt; Elasticsearch -&amp;gt; Kibana
Cisco syslog/archive config logging -&amp;gt; rsyslog -&amp;gt; Logstash -&amp;gt; Elasticsearch -&amp;gt; Kibana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RADIUS tarafı merkezi login ve privilege atamasını göstermek için kullanıldı.&lt;/p&gt;

&lt;p&gt;TACACS+ tarafı network cihaz yönetiminde daha ileri seviye kontrol sağlamak için kullanıldı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kullanıcı doğrulama
Exec authorization
Komut bazlı authorization
Komut accounting
Merkezi loglama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keycloak bu yapıda kullanıcı/grup yönetimini ve IAM katmanını sağlar. Switch tarafındaki gerçek CLI enforcement ise TACACS+ ile yapılır.&lt;/p&gt;

&lt;p&gt;Bu yapı gerçek ortamda Active Directory ile benzer mantıkta uygulanabilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cisco Switch -&amp;gt; TACACS+/RADIUS -&amp;gt; AD/OpenLDAP -&amp;gt; Grup üyeliği -&amp;gt; Yetki profili
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>networking</category>
      <category>opensource</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>TimescaleDB'deki verileri okuyup alarm yapısına dönüştürmek</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Mon, 16 Mar 2026 10:23:02 +0000</pubDate>
      <link>https://forem.com/aciklab/timescaledbdeki-verileri-okuyup-alarm-yapisina-donusturmek-jfa</link>
      <guid>https://forem.com/aciklab/timescaledbdeki-verileri-okuyup-alarm-yapisina-donusturmek-jfa</guid>
      <description>&lt;p&gt;Alarm üretim mantığı genelde manuel yazılıyor. Bu ya PostgreSQL tarafında bir trigger + NOTIFY akışıyla başlatılıyor, ya da uygulama tarafında çalışan bir servis ile okuyup dönüştürülüyor.&lt;/p&gt;

&lt;p&gt;Elimizde TimescaleDB üzerinde bir hypertable olduğunu düşünelim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sensör verisi geliyor&lt;/li&gt;
&lt;li&gt;bazı kayıtlar alarm üretmeli&lt;/li&gt;
&lt;li&gt;bazı alarmlar tek satırdan çıkıyor, bazıları ise pencere bazlı hesap gerektiriyor, yani son 5 dakikanın ortalaması %80'i geçti mi? gibi&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TimescaleDB bu tip akışlar için ne kadar uygun?
&lt;/h2&gt;

&lt;p&gt;TimescaleDB, PostgreSQL'in üzerinde çalıştığı için PostgreSQL'in trigger mekanizmasını destekliyor. Resmi dokümana göre hypertable üzerinde oluşturulan trigger'lar alttaki chunk'lara da yayılıyor.&lt;/p&gt;

&lt;p&gt;Ayrıca TimescaleDB'de job mekanizması var ve continuous aggregate desteği de var. Yani alarmı doğrudan her satır insert edildiğinde üretmek zorunda değiliz. Özellikle pencere bazlı kurallarda önce aggregate üretmek, sonra alarm çıkarmak daha mantıklı.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yaklaşım 1: PostgreSQL LISTEN/NOTIFY ile alarm akışı
&lt;/h2&gt;

&lt;p&gt;Bu modelin mantığı basit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;TimescaleDB hypertable'ına veri gelir.&lt;/li&gt;
&lt;li&gt;'AFTER INSERT' trigger çalışır.&lt;/li&gt;
&lt;li&gt;Trigger, uygun gördüğü kayıt için &lt;code&gt;pg_notify(...)&lt;/code&gt; çağırır.&lt;/li&gt;
&lt;li&gt;Uygulamadaki bir listener servis ilgili kanalı dinler.&lt;/li&gt;
&lt;li&gt;Listener, payload'daki anahtar bilgiyle gerçek kaydı okur.&lt;/li&gt;
&lt;li&gt;Alarm nesnesini oluşturur ve &lt;code&gt;alarms&lt;/code&gt; tablosuna yazar.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Artıları nelerdir?
&lt;/h3&gt;

&lt;p&gt;Öncelikle gecikme düşük. NOTIFY, commit sonrası dinleyicilere sinyal gönderir. Yani veri başarılı şekilde commit olduktan hemen sonra uygulaman uyanabilir. Ayrı bir queue ürünü kurmadan, veritabanının içinden olay sinyali alabiliyorsun.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dikkat edilmesi gereken noktalar:
&lt;/h3&gt;

&lt;p&gt;PostgreSQL dokümanına göre:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bildirimler transaction commit edilmeden teslim edilmiyor&lt;/li&gt;
&lt;li&gt;dinleyici transaction içindeyse mesaj ona da transaction bitince teslim ediliyor&lt;/li&gt;
&lt;li&gt;aynı transaction içinde aynı kanal ve aynı payload tekrar tekrar gönderilirse tek bildirime indirgenebiliyor&lt;/li&gt;
&lt;li&gt;payload varsayılan yapılandırmada 8000 bayttan kısa olmak zorunda&lt;/li&gt;
&lt;li&gt;queue dolarsa NOTIFY çağıran transaction commit anında hata alabiliyor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yani kısaca NOTIFY veri taşımıyor. Sadece olay sinyali veriyor.&lt;/p&gt;

&lt;h3&gt;
  
  
  NOTIFY için basit örnek
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;sensor_events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;event_id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;device_id&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;metric&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="nb"&gt;double&lt;/span&gt; &lt;span class="nb"&gt;precision&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;create_hypertable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sensor_events'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ts'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;notify_alarm_candidate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;
&lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'temperature'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
    &lt;span class="n"&gt;PERFORM&lt;/span&gt; &lt;span class="n"&gt;pg_notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'alarm_candidates'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;trg_notify_alarm_candidate&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sensor_events&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;notify_alarm_candidate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu örnekte trigger sadece bu kayıt alarm adayı olabilir diyor. Alarmın gerçekten açılıp açılmayacağına uygulama karar veriyor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yaklaşım 2: Yazılımla okumak ve alarma dönüştürmek
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Uygulama tarafında bir worker veya servis çalışır.&lt;/li&gt;
&lt;li&gt;Bu servis TimescaleDB'den yeni verileri belli bir mantıkla okur.&lt;/li&gt;
&lt;li&gt;Okuduğu veriyi alarm modeline dönüştürür.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Bu modelde raw hypertable ayrı kalır, alarm işleme için ayrı bir &lt;code&gt;alarm_candidates&lt;/code&gt; tablosu tutulur.&lt;/p&gt;

&lt;p&gt;Uygulama worker'ları bu tablodan kayıt çekip işler. Çoklu worker varsa PostgreSQL'in &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; özelliği ile çakışmadan paralel tüketim yapılabilir. PostgreSQL dokümanı da &lt;code&gt;SKIP LOCKED&lt;/code&gt; kullanımının queue benzeri yapılarda lock çakışmasını azaltmak için uygun olduğunu söylüyor.&lt;/p&gt;

&lt;p&gt;Örnek:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;picked&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;alarm_outbox&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'new'&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt; &lt;span class="n"&gt;LOCKED&lt;/span&gt;
  &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;alarm_outbox&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'processing'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;picked_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;picked&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;picked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sonuç
&lt;/h2&gt;

&lt;p&gt;NOTIFY, iyi bir tetikleme sinyali, uygulama worker'ı ise daha iyi bir işleme motoru.&lt;/p&gt;

</description>
      <category>database</category>
      <category>dataengineering</category>
      <category>monitoring</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Elasticsearch vs RDBMS, Logstash vs Fluentd, Elasticsearch vs Opensearch</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Mon, 09 Mar 2026 11:29:07 +0000</pubDate>
      <link>https://forem.com/aciklab/elasticsearch-vs-rdbms-logstash-vs-fluentd-elasticsearch-vs-opensearch-2263</link>
      <guid>https://forem.com/aciklab/elasticsearch-vs-rdbms-logstash-vs-fluentd-elasticsearch-vs-opensearch-2263</guid>
      <description>&lt;h2&gt;
  
  
  Elasticsearch vs RDBMS
&lt;/h2&gt;

&lt;p&gt;Elasticsearch, büyük veri içinde özellikle metin tabanlı arama (full-text search) yapmak için kullanılan, açık kaynaklı ve dağıtık mimariye sahip bir arama motorudur. Temel amacı çok büyük veri kümeleri içinde belirli kelime veya bilgileri çok hızlı bulabilmektir. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2exxft8nzhgtxnuvcr7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2exxft8nzhgtxnuvcr7p.png" alt="full-text search" width="720" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Klasik bir veritabanında veri doğrudan satırlar üzerinden taranırken, Elasticsearch verileri index üzerinden arar. Bu yaklaşım sayesinde milyarlarca kayıt içinde arama işlemleri neredeyse gerçek zamanlı olarak gerçekleştirilebilir.&lt;/p&gt;

&lt;p&gt;Elasticsearch indeksleri, ilişkisel veritabanlarında (RDBMS) bulunan indekslerle aynı değildir. Bir Elasticsearch cluster'ı, içinde birçok indeks barındırabilen bir veritabanı gibidir. Her indeks bir tabloya benzer, ve her indeksin içinde birçok doküman bulunur.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RDBMS =&amp;gt; Databases =&amp;gt; Tables =&amp;gt; Columns/Rows
Elasticsearch =&amp;gt; Clusters =&amp;gt; Indices =&amp;gt; Shards =&amp;gt; Documents (key-value pairs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elasticsearch temelde Apache Lucene üzerine kuruludur. Lucene, metinlerin indekslenmesi ve aranması için kullanılan güçlü bir kütüphanedir. Elasticsearch ise bu altyapıyı kullanarak hem structured hem de unstructured verilerin indekslenmesini ve aranmasını sağlar. Elasticsearch'te tutulan her veri JSON formatında bir document olarak saklanır hepsinin bir id'si vardır.&lt;/p&gt;

&lt;p&gt;Bir veri Elasticsearch'e eklendiğinde sistem bu veriyi doğrudan taranabilir halde saklamaz. Bunun yerine belirlenen alanlar indekslenir. İndeksleme sırasında her kelimenin hangi dokümanlarda geçtiği bir liste halinde tutulur. Buna inverted index mantığı denir. Bu yapı Elasticsearch’ün çok hızlı çalışmasının temel sebebidir.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lx87kkdiw5efpqyvaxc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lx87kkdiw5efpqyvaxc.png" alt="Inverted index" width="638" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Elasticsearch'ün dağıtık çalışmasını sağlayan önemli yapılardan biri shard ve replica kavramlarıdır. Büyük veri kümelerini tek bir sunucuda tutmak zor olabileceği için bir index birden fazla shard'a bölünür. Shard'lar farklı node'lara dağıtılarak paralel işlem yapılması sağlanır. Böylece hem performans artar hem de sistem yatay olarak ölçeklenebilir.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslqb56miwr4j3ze7jglv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fslqb56miwr4j3ze7jglv.png" alt="Scaling" width="714" height="456"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Logstash vs Fluentd
&lt;/h2&gt;

&lt;p&gt;Logstash'ın çalışabilmesi için JVM gereklidir. Bu bağımlılık yüksek bellek tüketiminin temel nedeni haline gelmiştir. Logstash daha ağırdır ama güçlü transform yapar, Fluentd ise daha hafif ve yüksek ölçekli log toplama için tercih edilir.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfxx5qxprwliz3rlxgfn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfxx5qxprwliz3rlxgfn.png" alt="ban-java" width="594" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ayrıca Fluent Bit yalnızca log değil, aynı zamanda metrik verilerini de toplayabilir.&lt;/p&gt;

&lt;p&gt;Fluentd + Elasticsearch genelde DaemonSet olarak çalışan bir log collector modeliyle kullanılır; logları pod dosyalarından okuyup Kubernetes metadata ekleyerek direkt Elasticsearch'e yollar. Logstash + Elasticsearch tarafında ise Logstash da Kubernetes'te çalıştırılabilir ama JVM tabanlı olduğu için kaynak ayarı ve operasyonu daha ağırdır.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Sonuçları
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fierm9xyfijclm5w49fpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fierm9xyfijclm5w49fpf.png" alt="workflow" width="720" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Düşük hacimli veri akışlarında Logstash ve Fluentd sistem üzerinde benzer bir yük oluşturur. Ancak bu durum 16 thread / iş yükü düğümü (workload nodes) noktasına gelindiğinde değişir ve Logstash'ın aynı miktarda olayı işleyebilmek için daha fazla CPU kullandığı açıkça görülür. Ortalama olarak CPU kullanımı %25 daha yüksektir.&lt;/p&gt;

&lt;p&gt;Performans elbette kullanılan senaryoya bağlı olsa da Logstash'ın Fluentd'ye kıyasla daha fazla bellek tükettiği bilinmektedir. Fluentd verimli bir log toplayıcıdır ve ölçeklenebilirliği oldukça iyidir. &lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Desteği
&lt;/h3&gt;

&lt;p&gt;Docker'ın Fluentd için yerleşik bir logging driver'ı vardır, ancak Logstash için böyle bir driver bulunmaz. Fluentd kullanıldığında container üzerinde logları Fluentd'ye göndermek için ekstra bir agent çalıştırmaya gerek yoktur. Loglar ek bir log dosyasına ihtiyaç duymadan doğrudan STDOUT üzerinden Fluentd servisine gönderilir. Logstash'ta ise uygulama loglarının okunabilmesi ve Logstash'a gönderilebilmesi için bir plugin'e (filebeat) ihtiyaç vardır.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sonuç
&lt;/h3&gt;

&lt;p&gt;Kubernetes ortamlarında da Fluentd, yerleşik Docker logging driver'ı ve parser'ı sayesinde ideal bir aday gibi görünmektedir. Bu yapı sayesinde container üzerinde logları Fluentd'ye göndermek için ek bir agent çalıştırmaya gerek kalmaz. Logstash ile karşılaştırıldığında bu durum mimarinin daha az karmaşık olmasını sağlar ve loglama hataları oluşma riskini de azaltır.&lt;/p&gt;




&lt;h2&gt;
  
  
  Elasticsearch vs OpenSearch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Benchmark Sonuçları
&lt;/h3&gt;

&lt;p&gt;"Çeşitli testlerin sonuçları dikkate alındığında, Elasticsearch'ün OpenSearch'e kıyasla sürekli olarak daha iyi performans gösterdiği açıkça görülmektedir. Basit sorguların çalıştırılması, verilerin sıralanması, histogram oluşturulması, terim veya aralık sorgularının işlenmesi ya da kaynakların daha verimli kullanılması gibi durumların hepsinde Elasticsearch öne çıkmaktadır." (Kobar &amp;amp; Sangiorgi, 2023)&lt;/p&gt;

&lt;p&gt;Elasticsearch'ün arama özellikleri Opensearch'e göre daha gelişmiştir ve AI/ML alanında da geliştirmeler yapmaya başlamıştır.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>distributedsystems</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Kubernetes Objeleri</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Fri, 06 Mar 2026 11:05:58 +0000</pubDate>
      <link>https://forem.com/aciklab/kubernetes-objeleri-37ei</link>
      <guid>https://forem.com/aciklab/kubernetes-objeleri-37ei</guid>
      <description>&lt;h2&gt;
  
  
  Pod
&lt;/h2&gt;

&lt;p&gt;Çalışan en küçük birim. İçerisinde bir veya birden fazla konteyner bulunur. Buradaki konteynerlar kendi içerisinde localhost üzerinden iletişim kurabilirler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;İstenen replica sayısını izler, eksikse yeni pod oluşturur fazlaysa siler.&lt;/p&gt;

&lt;h2&gt;
  
  
  ReplicaSet
&lt;/h2&gt;

&lt;p&gt;İstenilen pod sayısının çalışır olmasını garantileyen objedir. Deployment tarafından arka planda yönetilir.&lt;/p&gt;

&lt;h2&gt;
  
  
  StatefulSet
&lt;/h2&gt;

&lt;p&gt;Podlara pod-postgres-56945869d5-2kgp8, pod-postgres-75ff6f9cd-4w5xd gibi kimlik verir ve pod yeniden başlasa bile bu değişmez. Her birinin kendi kalıcı volume'ü vardır.&lt;/p&gt;

&lt;h2&gt;
  
  
  DaemonSet
&lt;/h2&gt;

&lt;p&gt;Cluster'daki her Node üzerinde tam olarak bir pod çalıştırılmasını sağlar. Cluster'a yeni bir node eklendiğinde ilgili pod otomatik olarak o node'a da yerleştirilir. Prometheus Node Exporter gibi altyapı servislerinde de kullanılır.&lt;/p&gt;

&lt;h2&gt;
  
  
  Job
&lt;/h2&gt;

&lt;p&gt;Tek seferlik ve sonlanması beklenen görevleri çalıştırmak için kullanılır. Pod görevini tamamlarsa durur, hata alırsa başarılı olana kadar yeniden çalıştırılır.&lt;/p&gt;

&lt;h2&gt;
  
  
  CronJob
&lt;/h2&gt;

&lt;p&gt;İstenilen zamanda job objesi ve buna bağlı pod oluşturulur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service
&lt;/h2&gt;

&lt;p&gt;Podların ip ve dns üzerinden erişilebilir olmasını sağlar. NodePort, ClusterIP ve LoadBalancer gibi tipleri vardır.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ingress
&lt;/h2&gt;

&lt;p&gt;Cluster dışından gelen HTTP ve HTTPS trafiğini service'lere yönlendirir.&lt;/p&gt;

&lt;h2&gt;
  
  
  NetworkPolicy
&lt;/h2&gt;

&lt;p&gt;Podlar arasındaki ağ trafiğini kontrol eder.&lt;/p&gt;

&lt;h2&gt;
  
  
  PV
&lt;/h2&gt;

&lt;p&gt;Kalıcı depolama kaynağıdır. Podlar silinse bile veriler durur.&lt;/p&gt;

&lt;h2&gt;
  
  
  PVC
&lt;/h2&gt;

&lt;p&gt;Pod'ların ihtiyacı olan depolamayı talep etmek için kullandığı objedir.&lt;/p&gt;

&lt;h2&gt;
  
  
  StorageClass
&lt;/h2&gt;

&lt;p&gt;PVC oluşturulduğunda otomatik olarak PV sağlar.&lt;/p&gt;

&lt;h2&gt;
  
  
  ConfigMap
&lt;/h2&gt;

&lt;p&gt;Konfigürasyon verilerini saklar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secret
&lt;/h2&gt;

&lt;p&gt;Şifre ve token gibi hassas verileri encoded şekilde saklar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Namespace
&lt;/h2&gt;

&lt;p&gt;Farklı ortamlar için alan oluşturur. Namespace üzerinde RBAC uygulanabilir.&lt;/p&gt;

&lt;h2&gt;
  
  
  ServiceAccount
&lt;/h2&gt;

&lt;p&gt;Pod'ların Kubernetes API ile kimlik doğrulaması yapmasını sağlayan hesaptır.&lt;/p&gt;

&lt;h2&gt;
  
  
  Role/ClusterRole
&lt;/h2&gt;

&lt;p&gt;Kaynaklar üzerinde neler yapılabileceğini tanımlayan yetki kurallarıdır. Role yalnızca tanımlandığı namespace içinde geçerliyken cluster role tüm cluster genelinde geçerlidir.&lt;/p&gt;

&lt;h2&gt;
  
  
  RoleBinding/ClusterRoleBinding
&lt;/h2&gt;

&lt;p&gt;Tanımlanmış bir role'ü kullanıcı, grup veya service account'a bağlayan objedir. Kimin neye erişebileceği açıkça tanımlar. &lt;/p&gt;

&lt;h2&gt;
  
  
  ResourceQuota
&lt;/h2&gt;

&lt;p&gt;Bir namespace içinde kullanılabilecek kaynakların sınırını belirler.&lt;/p&gt;

&lt;h2&gt;
  
  
  LimitRange
&lt;/h2&gt;

&lt;p&gt;Namespace içindeki podlar için varsayılan ve maksimum kaynak değerlerini otomatik olarak uygular.&lt;/p&gt;

&lt;h2&gt;
  
  
  InitContainer
&lt;/h2&gt;

&lt;p&gt;Ana konteyner başlamadan önce çalışan, hazırlık görevi yapan konteynerdır.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Kubernetes - Keycloak OIDC Entegrasyonu</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Tue, 03 Mar 2026 13:11:44 +0000</pubDate>
      <link>https://forem.com/tarikanafarta/kubernetes-keycloak-oidc-entegrasyonu-829</link>
      <guid>https://forem.com/tarikanafarta/kubernetes-keycloak-oidc-entegrasyonu-829</guid>
      <description>&lt;p&gt;&lt;strong&gt;Amaç:&lt;/strong&gt; kube-apiserver'ı Keycloak'u OIDC Identity Provider olarak kullanacak şekilde yapılandırmak ve kubelogin tabanlı kubeconfig ile kubectl erişimini sağlamak.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ortam / Topoloji
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bileşen&lt;/th&gt;
&lt;th&gt;Değer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes VM (control-plane)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;ip_adresi&amp;gt;&lt;/code&gt; (RKE2, single-node, static pod)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keycloak&lt;/td&gt;
&lt;td&gt;Helm ile &lt;code&gt;keycloak&lt;/code&gt; namespace'inde&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rancher&lt;/td&gt;
&lt;td&gt;Helm ile &lt;code&gt;cattle-system&lt;/code&gt; namespace'inde&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingress Controller&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kube-system&lt;/code&gt; içindeki &lt;code&gt;rke2-ingress-nginx&lt;/code&gt; (hostPort 80/443)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keycloak host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;keycloak.example.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rancher host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rancher.example.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes API&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;ip_adresi&amp;gt;:6443&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;keycloak.example.com&lt;/code&gt; ve &lt;code&gt;rancher.example.com&lt;/code&gt; gerçek DNS'te yoksa istemci makinede &lt;code&gt;/etc/hosts&lt;/code&gt;'a &lt;code&gt;&amp;lt;ip_adresi&amp;gt; keycloak.example.com&lt;/code&gt; ve &lt;code&gt;&amp;lt;ip_adresi&amp;gt; rancher.example.com&lt;/code&gt; olarak eklenmelidir.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Keycloak TLS Sertifikası
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Adım 1: CA oluştur&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;kc-ca.key ve kc-ca.crt üretildi. &lt;code&gt;basicConstraints: CA:TRUE&lt;/code&gt; ve &lt;code&gt;keyUsage: keyCertSign&lt;/code&gt; ile CA olarak işaretlendi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adım 2: SAN config&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;san.cnf oluşturuldu, &lt;code&gt;subjectAltName&lt;/code&gt; olarak &lt;code&gt;DNS.1 = keycloak.example.com&lt;/code&gt; eklendi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adım 3: Server sertifikası&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;keycloak.key ve keycloak.csr üretildi. CSR, kc-ca.crt ile imzalanarak keycloak.crt elde edildi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adım 4: Keycloak proxy/hostname ayarları&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RKE2 ingress hostPort 443 üzerinden yayın yaptığı için issuer'ın port içermemesi gerekiyor. Bu yüzden &lt;code&gt;KC_HOSTNAME&lt;/code&gt; şu şekilde set edildi:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;KC_PROXY_HEADERS=xforwarded&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KC_HOSTNAME=https://keycloak.example.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;/.well-known/openid-configuration&lt;/code&gt; ile issuer URL doğrulandı.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Keycloak Realm / Client / User / Group
&lt;/h2&gt;

&lt;p&gt;Tüm işlemler Keycloak pod'u içinde kcadm.sh ile yapılır. Container FS read-only olabildiği için &lt;code&gt;HOME=/tmp&lt;/code&gt; set edilmeli.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Realm:&lt;/strong&gt; kubernetes adıyla oluşturuldu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client:&lt;/strong&gt; kubernetes client ID'siyle oluşturuldu. Browser'sız kubeconfig için &lt;code&gt;directAccessGrantsEnabled&lt;/code&gt; açık olmalı (password grant).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Groups claim:&lt;/strong&gt; Token içinde &lt;code&gt;groups&lt;/code&gt; claim'i gelmesi için &lt;code&gt;oidc-group-membership-mapper&lt;/code&gt; eklendi (claim name: &lt;code&gt;groups&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Group, User, Membership:&lt;/strong&gt; k8s-admins grubu oluşturuldu. Kullanıcı oluşturuldu, şifresi set edildi (Temporary: Off) ve gruba eklendi.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. kube-apiserver OIDC Yapılandırması
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Yeni Kubernetes sürümlerinde &lt;code&gt;--oidc-ca-file&lt;/code&gt; yerine AuthenticationConfiguration daha stabil bir yol. Biz de AuthenticationConfiguration kullandık.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Adım 1: Auth config dosyası&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/kubernetes/oidc-auth-config.yaml&lt;/code&gt; oluşturuldu. &lt;code&gt;apiVersion: apiserver.config.k8s.io/v1beta1&lt;/code&gt;, &lt;code&gt;kind: AuthenticationConfiguration&lt;/code&gt; formatında; issuer URL, audiences, CA sertifikası ve claim mapping'leri (&lt;code&gt;preferred_username&lt;/code&gt;, &lt;code&gt;groups&lt;/code&gt;) içeriyor.&lt;/p&gt;

&lt;p&gt;Önemli nokta: issuer URL port içermemeli ve Keycloak'ın &lt;code&gt;.well-known&lt;/code&gt; içindeki issuer ile birebir aynı olmalı.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adım 2: RKE2'de apiserver'a bağlama&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RKE2 kube-apiserver manifest'ini direkt editlemek yerine &lt;code&gt;/etc/rancher/rke2/config.yaml&lt;/code&gt; kullanılır. Biz şu argümanı ekledik:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;authentication-config=/etc/kubernetes/oidc-auth-config.yaml&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RKE2 bunu static pod manifest'ine yansıtır.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adım 3: Doğrulama&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;kube-apiserver args içinde &lt;code&gt;--authentication-config=/etc/kubernetes/oidc-auth-config.yaml&lt;/code&gt; göründüğünde ve loglarda OIDC/TLS hatası kalmadığında entegrasyon başarılı.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. İstemci Tarafı: kubelogin + kubeconfig
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;kubelogin kurulumu:&lt;/strong&gt; GitHub releases'ten Linux amd64 zip indirildi, &lt;code&gt;/usr/local/bin/kubelogin&lt;/code&gt; olarak kuruldu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CA kopyalama:&lt;/strong&gt; kc-ca.crt (Keycloak CA) istemci makinede &lt;code&gt;~/.kube/keycloak-ca.crt&lt;/code&gt; olarak tutulur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;kubeconfig:&lt;/strong&gt; Her kullanıcı için &lt;code&gt;~/.kube/&amp;lt;kullaniciadi&amp;gt;-kubeconfig&lt;/code&gt; oluşturulur. exec plugin olarak &lt;code&gt;kubelogin get-token&lt;/code&gt; tanımlanır.&lt;/p&gt;

&lt;p&gt;Kubernetes cluster CA'sı için istemciye &lt;code&gt;server-ca.crt&lt;/code&gt; kopyalanabilir ve kubeconfig'te &lt;code&gt;certificate-authority:&lt;/code&gt; ile kullanılabilir (base64 gömmek şart değil).&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Kullanıcı Bazlı kubeconfig Dağıtımı
&lt;/h2&gt;

&lt;p&gt;Her kullanıcı için Keycloak'ta hesap açılır, o kullanıcının bilgilerini içeren bir kubeconfig üretilir ve dosya kullanıcıya verilir. Kullanıcı sadece bu dosyayla kubectl kullanır, tarayıcı açılmaz, başka bir şey bilmesine de gerek yoktur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kullanıcı oluşturma:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keycloak UI'dan kubernetes realm'ı altında Users -&amp;gt; Add user ile oluşturulur. Credentials sekmesinden şifre set edilir (Temporary: Off). Required Actions boş olmalıdır.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;kubeconfig üretme:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.kube/&amp;lt;kullaniciadi&amp;gt;-kubeconfig&lt;/code&gt; dosyası oluşturulur. exec plugin olarak &lt;code&gt;kubelogin get-token&lt;/code&gt; tanımlandı; &lt;code&gt;--grant-type=password&lt;/code&gt;, &lt;code&gt;--username&lt;/code&gt;, &lt;code&gt;--password&lt;/code&gt;, &lt;code&gt;--oidc-client-secret&lt;/code&gt; ve &lt;code&gt;--certificate-authority&lt;/code&gt; argümanları verilir. &lt;code&gt;interactiveMode: Never&lt;/code&gt; ile tarayıcı açılması engellenir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RBAC:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Her kullanıcı Kubernetes loglarında kendi adıyla görünür. Gruba göre ClusterRoleBinding veya namespace bazlı RoleBinding ile yetkilendirilir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;KUBECONFIG=~/.kube/&amp;lt;kullaniciadi&amp;gt;-kubeconfig kubectl auth whoami&lt;/code&gt; ile kullanıcının doğru kimlikle bağlandığı doğrulanır.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Kubernetes'te TimescaleDB Retention Testleri</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Mon, 02 Mar 2026 12:43:34 +0000</pubDate>
      <link>https://forem.com/aciklab/kuberneteste-timescaledb-retention-testleri-3i8m</link>
      <guid>https://forem.com/aciklab/kuberneteste-timescaledb-retention-testleri-3i8m</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/tarikanafarta/kubernetes-uzerinde-kucuk-veri-iceren-veritabani-icin-postgresql-ve-timescaledb-storage-kiyaslamasi-566p"&gt;Önceki yazımızda&lt;/a&gt; Kubernetes üzerinde iki ayrı kurulum (vanilla PostgreSQL ve TimescaleDB) yapıp az veriyle tablo + index boyutlarını kıyaslamıştık. Bu yazıda aynı environment üzerinde TimescaleDB'nin data retention (eski veriyi silme) yaklaşımını pratikte test ediyoruz.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ortam
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Namespace: database&lt;/li&gt;
&lt;li&gt;TimescaleDB pod: timescaledb-single chart (master pod)&lt;/li&gt;
&lt;li&gt;StorageClass: local-path (tek node)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Komutlarda iki değişken kullanacağız: NS ve TSPOD.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NS=database
TSPOD="$(kubectl get pod -n $NS -l release=timescaledb,role=master -o jsonpath='{.items[0].metadata.name}')"
echo "TSPOD=$TSPOD"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu blok sonraki komutlarda kullanacağımız değişkenleri hazırlar.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deney 1 - Manuel chunk droplama (drop_chunks)
&lt;/h2&gt;

&lt;p&gt;Bu deneyde 48 saatlik veri bastık (5 dakikada bir), sonra 24 saatten eski chunk'ları manuel olarak dropladık.&lt;/p&gt;

&lt;h3&gt;
  
  
  1A) Kurulum ve veri basma
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
CREATE EXTENSION IF NOT EXISTS timescaledb;
CREATE SCHEMA IF NOT EXISTS lab;

DROP TABLE IF EXISTS lab.sensor_ret_manual;
CREATE TABLE lab.sensor_ret_manual (
  time        TIMESTAMPTZ NOT NULL,
  device_id   INT,
  temperature DOUBLE PRECISION
);

SELECT create_hypertable(
  'lab.sensor_ret_manual',
  'time',
  chunk_time_interval =&amp;gt; INTERVAL '1 hour',
  create_default_indexes =&amp;gt; FALSE
);

CREATE INDEX sensor_ret_manual_time_idx ON lab.sensor_ret_manual(time);

INSERT INTO lab.sensor_ret_manual
SELECT
  generate_series(NOW() - INTERVAL '48 hours', NOW(), INTERVAL '5 minutes'),
  (random() * 10)::int,
  random() * 100;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada lab schema'sını ve 1 saatlik chunk'larla hypertable'ı oluşturup sonra 48 saatlik örnek veri basıyoruz.&lt;/p&gt;

&lt;h3&gt;
  
  
  1B) Silmeden önce ölçüm
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT count(*) AS rows_before
FROM lab.sensor_ret_manual;

SELECT show_chunks('lab.sensor_ret_manual') AS chunk_before;

SELECT
  pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_before
FROM show_chunks('lab.sensor_ret_manual') AS chunk;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada ise silmeden önce satır sayısı, hangi chunk'ların oluştuğu ve chunk'ların toplam disk boyutunu görüyoruz.&lt;/p&gt;

&lt;p&gt;Benim makinemdeki çıktılar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rows_before: 577&lt;/li&gt;
&lt;li&gt;chunk_before: 49 chunk&lt;/li&gt;
&lt;li&gt;total_before: 1176 kB&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1C) 24 saatten eski chunk'ları dropla (manuel)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT drop_chunks('lab.sensor_ret_manual', older_than =&amp;gt; INTERVAL '24 hours');
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;İşte şimdi 'now() - 24 hours' çizgisinin tamamen solunda kalan chunk tablolarını topluca DROP ettik. Satır satır silme yok. Chunk bazında hızlıca droplar.&lt;/p&gt;

&lt;h3&gt;
  
  
  1D) Silmeden sonra ölçüm
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT count(*) AS rows_after
FROM lab.sensor_ret_manual;

SELECT show_chunks('lab.sensor_ret_manual') AS chunk_after;

SELECT
  pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_after
FROM show_chunks('lab.sensor_ret_manual') AS chunk;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada drop_chunks sonrasında satır/chunk/boyutun gerçekten azaldığını doğruladık.&lt;/p&gt;

&lt;p&gt;Benim makinemdeki çıktılar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rows_after: 299&lt;/li&gt;
&lt;li&gt;chunk_after: 25 chunk&lt;/li&gt;
&lt;li&gt;total_after: 600 kB&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Önemli not: 'older_than =&amp;gt; 24 hours' her zaman 'tam 24 saatin dışındaki tüm satırlar gider' demek değildir. Cutoff çizgisini kesen bir chunk içinde hem eski hem yeni veri varsa, o chunk komple droplanmaz. Bu nedenle az miktarda daha eski satır kalabilir.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Deney 2 - Otomatik veri saklama politikası (add_retention_policy + run_job)
&lt;/h2&gt;

&lt;p&gt;Bu deneyde ise TimescaleDB'nin dahili job mekanizması ile otomatik retention kurduk ve job'ı manuel tetikledik.&lt;/p&gt;

&lt;h3&gt;
  
  
  2A) Hypertable oluştur ve veri ekle
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
DROP TABLE IF EXISTS lab.sensor_ret_policy;
CREATE TABLE lab.sensor_ret_policy (
  time        TIMESTAMPTZ NOT NULL,
  device_id   INT,
  temperature DOUBLE PRECISION
);

SELECT create_hypertable(
  'lab.sensor_ret_policy',
  'time',
  chunk_time_interval =&amp;gt; INTERVAL '1 hour',
  create_default_indexes =&amp;gt; FALSE
);

CREATE INDEX sensor_ret_policy_time_idx ON lab.sensor_ret_policy(time);

INSERT INTO lab.sensor_ret_policy
SELECT
  generate_series(NOW() - INTERVAL '48 hours', NOW(), INTERVAL '5 minutes'),
  (random() * 10)::int,
  random() * 100;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu blok ile otomatik retention deneyi için ikinci bir hypertable oluşturduk ve aynı şekilde 48 saatlik örnek veriyi doldurduk.&lt;/p&gt;

&lt;h3&gt;
  
  
  2B) Retention policy ekle (24 saat tut)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT add_retention_policy('lab.sensor_ret_policy', INTERVAL '24 hours');
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada ise TimescaleDB içinde arka planda çalışacak bir retention job'u oluşturduk. Dönüş değeri job_id olur.&lt;/p&gt;

&lt;h3&gt;
  
  
  2C) Job'ı bul ve çalıştır
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT job_id, proc_name, schedule_interval, config
FROM timescaledb_information.jobs
WHERE proc_name = 'policy_retention'
ORDER BY job_id DESC;

CALL run_job(&amp;lt;yukaridaki_job_id&amp;gt;);
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burası retention job'unun konfigürasyonunu gösterir ve beklemeden hemen çalıştırır. Normalde job schedule_interval'a göre otomatik çalışır.&lt;/p&gt;

&lt;h3&gt;
  
  
  2D) Sonucu doğrula
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT count(*) AS rows_after
FROM lab.sensor_ret_policy;

SELECT
  pg_size_pretty(sum(pg_total_relation_size(chunk::regclass))) AS total_after
FROM show_chunks('lab.sensor_ret_policy') AS chunk;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu blok policy çalıştıktan sonra kalan satır sayısını ve chunk toplam boyutunu ölçer. Yani job'un gerçekten eski chunk'ları dropladığını kanıtlar.&lt;/p&gt;

&lt;p&gt;Benim makinemdeki çıktılar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rows_after: 299&lt;/li&gt;
&lt;li&gt;total_after: 600 kB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bu, retention policy'nin temelde drop_chunks ile aynı chunk droplama mekanizmasını otomatik olarak çalıştırdığını gösteriyor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Temizlik
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl exec -n $NS -i $TSPOD -- psql -U postgres -v ON_ERROR_STOP=1 &amp;lt;&amp;lt;'SQL'
SELECT remove_retention_policy('lab.sensor_ret_policy');
DROP SCHEMA IF EXISTS lab CASCADE;
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burası retention policy'yi kaldırır ve lab schema'sını CASCADE ile silerek tüm test tablolarını ve internal chunk tablolarını temizler.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQL DELETE vs TimescaleDB retention
&lt;/h2&gt;

&lt;p&gt;TimescaleDB'nin sunduğu yerleşik, chunk tabanlı silme yöntemlerini kullanmak çoğu zaman en verimli yoldur. Yine de hangi yöntemin doğru olduğuna senaryo karar verir.&lt;/p&gt;

&lt;h3&gt;
  
  
  SQL DELETE (satır bazlı)
&lt;/h3&gt;

&lt;p&gt;Artıları:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;İnce ayar: Sadece belirli cihazın verisi, belirli aralık, GDPR gibi seçici silmeler için uygundur.&lt;/li&gt;
&lt;li&gt;Chunk sınırına bağlı kalmadan hedeflediğin satırları silersin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eksileri:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL MVCC nedeniyle satırlar diskten hemen silinmez; dead tuple olarak kalır.&lt;/li&gt;
&lt;li&gt;Büyük silmeler table bloat üretir; disk alanını gerçekten geri almak için VACUUM (bazen agresif vacuum) ve kimi zaman REINDEX gerekebilir.&lt;/li&gt;
&lt;li&gt;Çok miktarda veride satır satır silmek yavaş ve maliyetlidir (IO/CPU artışı).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  TimescaleDB retention (chunk bazlı)
&lt;/h3&gt;

&lt;p&gt;Artıları:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chunk'lar komple droplandığı için genellikle çok hızlıdır (DROP TABLE benzeri).&lt;/li&gt;
&lt;li&gt;Relation size çoğu senaryoda hemen düşer (örneğin benim makinemde 1176 kB -&amp;gt; 600 kB).&lt;/li&gt;
&lt;li&gt;add_retention_policy ile cron/script yazmadan, DB içindeki job mekanizmasıyla otomatik yönetim sağlanır.&lt;/li&gt;
&lt;li&gt;Zaman serisi kullanımında en doğal model: 'ham veriyi X süre tut, eskisini at'.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eksileri / dikkat edilmesi gerekenler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Granularity: Retention satır bazlı değil chunk bazlıdır. Cutoff'u kesen chunk kalabilir; bu yüzden az miktarda daha eski satır tutulabilir.&lt;/li&gt;
&lt;li&gt;Seçici silme için uygun değildir (ör. sadece device_id=3 verisini silmek). Bu tip işlerde DELETE gerekir.&lt;/li&gt;
&lt;li&gt;Lock ihtiyacı: Chunk drop, ilgili chunk'lar üzerinde lock alır; uzun transaction varsa drop gecikebilir veya timeout görebiliriz.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sonuç
&lt;/h3&gt;

&lt;p&gt;Hedefiniz 'X süreden eski zaman serisi ham verisini otomatik ve verimli şekilde kaldırmak' ise TimescaleDB'nin retention policy (add_retention_policy) veya manuel drop_chunks yaklaşımı genellikle en doğru ve yönetimi en kolay çözümdür. Buna rağmen seçici ve ince ayarlı silme ihtiyaçlarında SQL DELETE hala gerekli bir araçtır.&lt;/p&gt;

</description>
      <category>database</category>
      <category>kubernetes</category>
      <category>postgres</category>
      <category>testing</category>
    </item>
    <item>
      <title>Kubernetes Üzerinde Az Veri İçeren Veritabanı İçin PostgreSQL ve TimescaleDB Storage Kıyaslaması</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Mon, 02 Mar 2026 10:09:22 +0000</pubDate>
      <link>https://forem.com/aciklab/kubernetes-uzerinde-kucuk-veri-iceren-veritabani-icin-postgresql-ve-timescaledb-storage-kiyaslamasi-566p</link>
      <guid>https://forem.com/aciklab/kubernetes-uzerinde-kucuk-veri-iceren-veritabani-icin-postgresql-ve-timescaledb-storage-kiyaslamasi-566p</guid>
      <description>&lt;h2&gt;
  
  
  Amaç
&lt;/h2&gt;

&lt;p&gt;Kubernetes üzerinde &lt;strong&gt;iki ayrı kurulum&lt;/strong&gt; yapıp (vanilla PostgreSQL + TimescaleDB) küçük bir veri setiyle storage kullanımını kıyaslamak.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not: TimescaleDB zaten PostgreSQL üzerinde çalışır. Burada "TimescaleDB kurulumu" dediğimiz şey, içinde PostgreSQL + Timescale extension bulunan ayrı bir kurulumdur. "vanilla PostgreSQL" de kuruyoruz ki sonuçlar daha net olsun.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Ön koşullar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Çalışan bir Kubernetes cluster'ı ve &lt;code&gt;kubectl&lt;/code&gt; erişimi&lt;/li&gt;
&lt;li&gt;Helm&lt;/li&gt;
&lt;li&gt;Kurulum tek node'lu bir cluster'da yapılmıştır ve StorageClass local-path'tir.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Aşama: Kurulum
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Namespace
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create namespace database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1.2 Helm repo'ları
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add timescaledb 'https://charts.timescale.com'
helm repo update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Aşama: Vanilla PostgreSQL (bitnami) kurulumu
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;postgres-values.yaml&lt;/code&gt; oluşturalım:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; postgres-values.yaml &amp;lt;&amp;lt; 'EOF'
auth:
  postgresPassword: "postgres123"

primary:
  persistence:
    storageClass: "local-path"
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kurulum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install postgres bitnami/postgresql   -n database   -f postgres-values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pod'un ayağa kalkmasını bekleyelim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get pods -n database -w
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Aşama: TimescaleDB kurulumu (Timescale chart)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;timescaledb-values.yaml&lt;/code&gt; oluşturalım:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; timescaledb-values.yaml &amp;lt;&amp;lt; 'EOF'
replicaCount: 1

image:
  repository: timescale/timescaledb-ha
  tag: pg14.6-ts2.9.1-p1

secrets:
  credentials:
    PATRONI_SUPERUSER_PASSWORD: "postgres123"
    PATRONI_REPLICATION_PASSWORD: "replication123"
    PATRONI_admin_PASSWORD: "admin123"

backup:
  enabled: false
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kurulum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install timescaledb timescaledb/timescaledb-single   -n database   -f timescaledb-values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pod'un ayağa kalkmasını bekleyelim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get pods -n database -w
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Aşama: Bağlantı (2 ayrı terminal açabilirsiniz)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 PostgreSQL’e bağlanma (Şifre: postgres123)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PGPOD="$(kubectl get pod -n database -l app.kubernetes.io/instance=postgres,app.kubernetes.io/name=postgresql -o name | head -n 1)"
kubectl exec -it -n database ${PGPOD} -- psql -U postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.2 TimescaleDB’ye bağlanma (Şifre: postgres123)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MASTERPOD="$(kubectl get pod -o name --namespace database -l release=timescaledb,role=master)"
kubectl exec -it --namespace database ${MASTERPOD} -- psql -U postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Not: İki ayrı pod'a bağlanıyoruz. Aynı işlemleri iki farklı DB kurulumunda da çalıştırıyoruz.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. Aşama: Küçük veri setiyle test
&lt;/h2&gt;

&lt;p&gt;Aşağıdaki SQL'i &lt;strong&gt;iki tarafta da&lt;/strong&gt; uygulayacağız:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL'de &lt;code&gt;sensor_pg&lt;/code&gt; normal tablo olacak&lt;/li&gt;
&lt;li&gt;TimescaleDB'de &lt;code&gt;sensor_ts&lt;/code&gt; hypertable olacak&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Veri az olsun diye 30 gün yerine &lt;strong&gt;1 gün&lt;/strong&gt; üretiyoruz.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  5.1 PostgreSQL:
&lt;/h3&gt;

&lt;p&gt;PostgreSQL pod'unda &lt;code&gt;psql&lt;/code&gt; içindeyken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE sensor_pg (
  time        TIMESTAMPTZ NOT NULL,
  device_id   INT,
  temperature FLOAT
);

CREATE INDEX sensor_pg_time_idx ON sensor_pg(time);

INSERT INTO sensor_pg
SELECT
  generate_series(NOW() - INTERVAL '1 day', NOW(), INTERVAL '1 minute'),
  (random() * 10)::int,
  random() * 100;

SELECT count(*) FROM sensor_pg;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5.2 TimescaleDB:
&lt;/h3&gt;

&lt;p&gt;TimescaleDB pod'unda &lt;code&gt;psql&lt;/code&gt; içindeyken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE EXTENSION IF NOT EXISTS timescaledb;

CREATE TABLE sensor_ts (
  time        TIMESTAMPTZ NOT NULL,
  device_id   INT,
  temperature FLOAT
);

SELECT create_hypertable('sensor_ts', 'time');

CREATE INDEX sensor_ts_time_idx ON sensor_ts(time);

INSERT INTO sensor_ts
SELECT
  generate_series(NOW() - INTERVAL '1 day', NOW(), INTERVAL '1 minute'),
  (random() * 10)::int,
  random() * 100;

SELECT count(*) FROM sensor_ts;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Not: TimescaleDB'de hypertable tek tablo gibi görünür ama arkada chunk'lara ayrılır. Az veri testinde bile en azından bir chunk oluşur.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. Aşama: Storage ölçümü
&lt;/h2&gt;

&lt;p&gt;Bu işi iki seviyede ölçmek daha somut sonuçlar sağlar:&lt;/p&gt;

&lt;p&gt;1) &lt;strong&gt;Relation size (SQL ile)&lt;/strong&gt;: tablo+index boyutları için&lt;br&gt;
2) &lt;strong&gt;Data directory (filesystem ile)&lt;/strong&gt;: gerçek disk kullanımı için&lt;/p&gt;


&lt;h3&gt;
  
  
  6.1 PostgreSQL: SQL ile tablo boyutu
&lt;/h3&gt;

&lt;p&gt;PostgreSQL pod'unda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  pg_size_pretty(pg_table_size('sensor_pg'))        AS table_only,
  pg_size_pretty(pg_indexes_size('sensor_pg'))      AS indexes,
  pg_size_pretty(pg_total_relation_size('sensor_pg')) AS total;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  6.2 TimescaleDB: SQL ile hypertable boyutu (chunk bazında)
&lt;/h3&gt;

&lt;p&gt;TimescaleDB pod'unda:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
  pg_size_pretty(sum(pg_table_size(chunk.id::regclass))) AS table_only,
  pg_size_pretty(sum(pg_indexes_size(chunk.id::regclass))) AS indexes,
  pg_size_pretty(sum(pg_total_relation_size(chunk.id::regclass))) AS total
FROM show_chunks('sensor_ts') AS chunk(id);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Chunk bazında bakmamızın nedeni hypertable fiziksel olarak chunk tablolardan oluşur. Yani gerçek data+index boyutu chunk'ların toplamıdır.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. Benim ortamımdaki çıktı
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres=# SELECT count(*) FROM sensor_pg;
 count 
-------
  1441
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;TimescaleDB&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres=# SELECT count(*) FROM sensor_ts;
 count 
-------
  1441
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.1 SQL (relation) boyutları
&lt;/h3&gt;

&lt;p&gt;Tabloların ve indexlerin PostgreSQL içinde kapladığı alanlar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL (&lt;code&gt;sensor_pg&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; table_only | indexes | total  
------------+---------+--------
 112 kB     | 48 kB   | 160 kB
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;TimescaleDB (&lt;code&gt;sensor_ts&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; table_only | indexes | total  
------------+---------+--------
 112 kB     | 72 kB   | 184 kB
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;table_only iki tarafta da aynı iken indexes kısmı Timescale tarafında daha fazla.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Neden Timescale küçük veride daha büyük çıkabiliyor?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TimescaleDB tarafında hypertable fiziksel olarak &lt;strong&gt;chunk tablolardan&lt;/strong&gt; oluşur. Her chunk kendi tablo/index yapısını taşır.&lt;/li&gt;
&lt;li&gt;Küçük veri setlerinde bu yapı "overhead" olarak daha görünür olur.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sonuç olarak üzerinde çok veri olmayan bir tablo senaryosunda, Timescale hypertable'ın chunk/index overhead'i yüzünden PostgreSQL biraz daha küçük göründü.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>database</category>
    </item>
    <item>
      <title>Kubernetes'te StorageClass Nedir?</title>
      <dc:creator>Tarık Anafarta</dc:creator>
      <pubDate>Thu, 26 Feb 2026 11:49:45 +0000</pubDate>
      <link>https://forem.com/aciklab/kuberneteste-storageclass-nedir-dha</link>
      <guid>https://forem.com/aciklab/kuberneteste-storageclass-nedir-dha</guid>
      <description>&lt;h2&gt;
  
  
  PV, PVC ve Veri Gerçekten Nerede Saklanıyor?
&lt;/h2&gt;

&lt;p&gt;Bu yazıda şu konuları inceleyeceğim:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;PV (Persistent Volume) nedir?&lt;/li&gt;
&lt;li&gt;PVC (Persistent Volume Claim) nedir?&lt;/li&gt;
&lt;li&gt;StorageClass ne işe yarar?&lt;/li&gt;
&lt;li&gt;Static ve Dynamic provisioning farkı nedir?&lt;/li&gt;
&lt;li&gt;Disk gerçekte nerede? (Local PV, NFS, Cloud Storage)&lt;/li&gt;
&lt;li&gt;Object storage (S3) nedir? MinIO ne işe yarar?&lt;/li&gt;
&lt;li&gt;Distributed storage örnekleri: Ceph ve Longhorn&lt;/li&gt;
&lt;li&gt;MariaDB / PostgreSQL gibi uygulamalarda disk nerede?&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Storage Akışının Mantığı
&lt;/h2&gt;

&lt;p&gt;Öncelikle Kubernetes'te storage zinciri şu şekildedir:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pod -&amp;gt; PVC -&amp;gt; StorageClass -&amp;gt; PV -&amp;gt; Physical Storage&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Persistent Volume (PV)
&lt;/h2&gt;

&lt;p&gt;PV, cluster içindeki gerçek disk kaynağını temsil eder.&lt;/p&gt;

&lt;p&gt;Bu kaynak şunlardan birisidir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node üzerindeki local disk&lt;/li&gt;
&lt;li&gt;NFS share&lt;/li&gt;
&lt;li&gt;Cloud block storage (AWS EBS, GCE PD, Azure Disk)&lt;/li&gt;
&lt;li&gt;Distributed storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yani aslında PV, storage'ın kendisidir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Persistent Volume Claim (PVC)
&lt;/h2&gt;

&lt;p&gt;PVC ise uygulamanın disk talebidir.&lt;/p&gt;

&lt;p&gt;Örneğin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10Gi storage&lt;/li&gt;
&lt;li&gt;Access mode: ReadWriteOnce&lt;/li&gt;
&lt;li&gt;storageClass: fast-storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PVC diski oluşturmaz, bir disk talep eder.&lt;/p&gt;




&lt;h2&gt;
  
  
  StorageClass Nedir?
&lt;/h2&gt;

&lt;p&gt;StorageClass, PVC oluşturulduğunda disk'in nasıl sağlanacağını belirler.&lt;/p&gt;

&lt;p&gt;Yani, disk local mi olacak? NFS mi olacak? Cloud block storage mı olacak? Otomatik mi üretilecek? Hangi performans sınıfı kullanılacak? Gibi sorulara cevap veren bir policy katmanıdır.&lt;/p&gt;




&lt;h2&gt;
  
  
  Static ve Dynamic Provisioning
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Static Provisioning
&lt;/h3&gt;

&lt;p&gt;Static provisioning modelinde PV manuel olarak oluşturulur. Yani önce storage kaynağı tanımlanır, ardından PVC bu PV'ye bağlanır.&lt;/p&gt;

&lt;p&gt;Bu yöntem daha fazla kontrol sağlar ancak her yeni disk ihtiyacında manuel işlem gerektirir. Genellikle test ortamlarında veya local/NFS kurulumlarında tercih edilir.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Provisioning
&lt;/h3&gt;

&lt;p&gt;Dynamic provisioning modelinde ise sadece PVC oluşturulur.&lt;br&gt;
Bağlı olduğu StorageClass, arka planda otomatik olarak uygun bir PV üretir ve PVC'ye bağlar.&lt;/p&gt;

&lt;p&gt;Bu süreç CSI (Container Storage Interface) driver'ları sayesinde çalışır.&lt;br&gt;
Production ortamlarında genellikle dynamic provisioning tercih edilir çünkü daha otomatik ve ölçeklenebilirdir.&lt;/p&gt;


&lt;h2&gt;
  
  
  Disk Gerçekte Nerede?
&lt;/h2&gt;

&lt;p&gt;Bu sorunun cevabı PV tanımındadır.&lt;/p&gt;
&lt;h3&gt;
  
  
  Local PV
&lt;/h3&gt;

&lt;p&gt;PV şu path'i gösteriyorsa: &lt;code&gt;/mnt/k8s/data&lt;/code&gt;, disk node üzerindedir. Node arızalanırsa veri kaybedilebilir. Pod başka node'a taşınırsa diske erişilemez.&lt;/p&gt;
&lt;h3&gt;
  
  
  NFS
&lt;/h3&gt;

&lt;p&gt;PV şu şekilde tanımlanmışsa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nfs:
  server: 192.168.1.10
  path: /srv/nfs/share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Disk NFS server üzerindedir. Pod hangi node'da olursa olsun aynı veriye erişebilir.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Storage
&lt;/h3&gt;

&lt;p&gt;PV bir cloud volume'a bağlıysa, disk cloud provider tarafındadır.&lt;/p&gt;




&lt;h2&gt;
  
  
  Object Storage (S3) ve MinIO
&lt;/h2&gt;

&lt;p&gt;Şimdiye kadar bahsettiğim storage türleri çoğunlukla disk mantığında (block/file) çalışıyordu. Ancak pratikte sıkça kullanılan bir model daha var: Object storage. S3/MinIO genellikle database'in primary storage'ı olarak kullanılmaz; daha çok backup, log ve dosya arşivi gibi senaryolarda tercih edilir.&lt;/p&gt;

&lt;h3&gt;
  
  
  S3 (Simple Storage Service) Nedir?
&lt;/h3&gt;

&lt;p&gt;S3 bir object storage yaklaşımıdır. Dosyalar filesystem gibi "klasör/dosya" mantığından ziyade "object" olarak saklanır. Genellikle şu senaryolarda kullanılır:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dosya yükleme (upload)&lt;/li&gt;
&lt;li&gt;Log arşivleme&lt;/li&gt;
&lt;li&gt;Backup (özellikle database backup)&lt;/li&gt;
&lt;li&gt;Media saklama (image/video)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;S3 çoğunlukla PV/PVC gibi mount edilerek kullanılmaz. Uygulama S3 API üzerinden erişir.&lt;/p&gt;

&lt;h3&gt;
  
  
  MinIO Nedir?
&lt;/h3&gt;

&lt;p&gt;MinIO, S3 uyumlu bir object storage çözümüdür. Özellikle cloud'dan bağımsız (on-premise) ortamlarda veya Kubernetes içinde S3 benzeri bir servis ihtiyacında kullanılır.&lt;/p&gt;

&lt;p&gt;MinIO genellikle PV üzerinde çalışır. Yani MinIO'nun kendi datası için altında yine PV/PVC bulunabilir. Uygulamalar ise MinIO’ya S3 API ile erişir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Distributed Storage: Ceph ve Longhorn
&lt;/h2&gt;

&lt;p&gt;Distributed storage, veriyi tek bir node'a bağımlı bırakmak yerine birden fazla node'a yayarak (replication) daha dayanıklı bir yapı sunmayı hedefler. Ceph/Longhorn gibi çözümler Kubernetes'e genellikle CSI driver ile entegre olur ve StorageClass üzerinden dynamic provisioning sağlar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ceph
&lt;/h3&gt;

&lt;p&gt;Ceph yaygın bir distributed storage sistemidir. Kubernetes ortamında çoğunlukla Rook-Ceph gibi operatörlerle yönetilir. Ceph ile farklı storage tipleri sağlanabilir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block storage&lt;/li&gt;
&lt;li&gt;File storage&lt;/li&gt;
&lt;li&gt;Object storage (S3-compatible)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ceph'in temel avantajı, verinin birden fazla node'a replike edilebilmesi ve node kayıplarında veri kaybı riskinin azalmasıdır.&lt;/p&gt;

&lt;h3&gt;
  
  
  Longhorn
&lt;/h3&gt;

&lt;p&gt;Longhorn, Kubernetes-native bir distributed block storage çözümüdür. Longhorn tarafında öne çıkan özellikler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Volume replication&lt;/li&gt;
&lt;li&gt;Snapshot / backup mekanizmaları&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Longhorn genellikle küçük veya orta ölçekli cluster'larda veya daha hızlı kurulup yönetilmesi gereken ortamlarda tercih edilir.&lt;/p&gt;




&lt;h2&gt;
  
  
  Access Modes
&lt;/h2&gt;

&lt;p&gt;RWO (ReadWriteOnce) -&amp;gt; Sadece tek node yazabilir&lt;br&gt;
RWX (ReadWriteMany) -&amp;gt; Birden fazla node yazabilir&lt;br&gt;
ROX (ReadOnlyMany)  -&amp;gt; Birden fazla node sadece okuyabilir&lt;/p&gt;


&lt;h2&gt;
  
  
  Reclaim Policy
&lt;/h2&gt;

&lt;p&gt;PVC silindiğinde disk'in nasıl davranacağını reclaim policy belirler. Kubernetes'te 3 farklı reclaim policy vardır (1 tanesi eski). &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retain -&amp;gt; PVC silinse bile PV ve içindeki veri korunur.&lt;/li&gt;
&lt;li&gt;Delete -&amp;gt; PVC silindiğinde PV ve arkasındaki storage kaynağı silinir.&lt;/li&gt;
&lt;li&gt;Recycle (Deprecated) -&amp;gt; PV içindeki veriyi basitçe temizleyip tekrar kullanılabilir hale getirirdi.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Örneğin: MariaDB ve PostgreSQL Verisi Nerede?
&lt;/h2&gt;

&lt;p&gt;Bir database container'ı çalıştırdığınızda veri her zaman belirli bir data directory içine yazılır. Önemli olan, bu klasör container içinde mi kalıyor, yoksa bir PVC üzerinden gerçek diske mi bağlanıyor?&lt;/p&gt;
&lt;h3&gt;
  
  
  MariaDB
&lt;/h3&gt;

&lt;p&gt;MariaDB'nin varsayılan veri dizini: &lt;code&gt;/var/lib/mysql&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Eğer persistence yapılandırılmadıysa bu dizin container filesystem'i üzerindedir. Pod silindiğinde veri kaybolur.&lt;/p&gt;

&lt;p&gt;Eğer persistence enabled ise bu path bir volumeMount ile bir PVC'ye bağlanır.&lt;/p&gt;

&lt;p&gt;PVC -&amp;gt; PV -&amp;gt; Physical storage&lt;/p&gt;

&lt;p&gt;Pod silinse bile veri korunur.&lt;/p&gt;

&lt;p&gt;Pod içinden mount durumunu görmek için:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl describe pvc -n &amp;lt;namespace&amp;gt; &amp;lt;pvc-ismi&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mount edilen path'leri burada görebilirsiniz.&lt;/p&gt;

&lt;h3&gt;
  
  
  PostgreSQL
&lt;/h3&gt;

&lt;p&gt;PostgreSQL’in varsayılan veri dizini: &lt;code&gt;/var/lib/postgresql/data&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Mantık MariaDB ile aynıdır. Persistence yoksa veri geçicidir. Pod restart edildiğinde veri kaybolabilir.&lt;/p&gt;

&lt;p&gt;Persistence varsa bu dizin bir PVC'ye mount edilir. Gerçek disk PV tarafındadır.&lt;/p&gt;




&lt;h2&gt;
  
  
  Gerçek Disk Yeri Nasıl Bulunur?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get pvc -n &amp;lt;namespace&amp;gt; # PVC'yi bul
kubectl describe pvc &amp;lt;pvc-ismi&amp;gt; # Bağlı olduğu PV'yi bul
kubectl describe pv &amp;lt;pv-ismi&amp;gt; # PV detayını incele
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada şunu görürüz: Local path mi? NFS server mı? Cloud volume mu? Distributed storage mı?&lt;/p&gt;

&lt;p&gt;Kısaca, gerçek disk yeri PV tanımında yazar.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
