Mikroservis mimarisinin dinamik dünyasında, güçlü bir güvenliğe olan ihtiyaç çok daha önemli hale gelmiştir. Kuruluşlar uygulamalarını daha küçük, bağımsız bir şekilde deploy edilebilir servislere böldükçe, bu servisler arasında alınan ve gönderilen verilerin bütünlüğünü ve gizliliğini sağlamak kritik bir önem haline gelir.

Bu blog yazısında, Ocelot’u kullanan ve güçlü bir açık kaynak kimlik ve erişim yönetimi çözümü olan Keycloak ile mikroservisleri güvence altına almak için kapsamlı bir çözümü keşfedeceğiz. Keycloak’u API Gateway’in arkasına yerleştirerek, bu entegrasyonun kaynakları koruduğunu, diğer hizmetlere yönelik istekleri kimlik doğruladığını ve taleplere erişimi yetkilendirdiğini claim yapılarını kullanarak sunar. Bu, mikroservis ekosisteminiz için sorunsuz ve güvenli bir iletişim çerçevesi sunar. Başlayalım.

Create Keycloak instance

Docker içinde Keycloak’u çalıştırmak çok kolaydır. Bu Docker dosyasını projenizin /Identity dizinine yerleştirin. Tüm kodları yazının sonundaki GitHub reposunda bulabilirsiniz.

FROM quay.io/keycloak/keycloak:latest as builder  
  
# Enable health and metrics support  
ENV KC_HEALTH_ENABLED=true  
ENV KC_METRICS_ENABLED=true  
  
# Configure a database vendor  
ENV KC_DB=postgres  
  
WORKDIR /opt/keycloak  
# for demonstration purposes only, please make sure to use proper certificates in production instead  
RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 -dname "CN=server" -alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -keystore conf/server.keystore  
RUN /opt/keycloak/bin/kc.sh build  
  
FROM quay.io/keycloak/keycloak:latest  
COPY --from=builder /opt/keycloak/ /opt/keycloak/  
  
# change these values to point to a running postgres instance  
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]

And create docker-compose.yml file that access /Identity/Dockerfile and builds image from it. We can use base image rather than custom Dockerfile but with this approach you can customize much more features of Keycloak like frontend theme and secret keys.

/Identity/Dockerfile dosyasına erişen ve ondan bir image oluşturan docker-compose.yml dosyasını oluşturun. Özel Docker dosyası yerine bir temel image kullanabiliriz, ancak bu yaklaşımla Keycloak’un frontend teması ve secret key bilgileri gibi çok daha fazla özelliğini özelleştirebilirsiniz.

version: "3"  
  
services:  
  # PostgreSQL for keycloak  
  secured-identity-db:  
    container_name: secured-identity-db  
    image: postgres:16-alpine  
    ports:  
      - 6063:5432  
    expose:  
      - 6063  
    volumes:  
      - ./data/secured-identity-db:/var/lib/postgresql/data  
    restart: always  
    environment:  
      - POSTGRES_PASSWORD=myStrongPassword123  
      - POSTGRES_USER=keycloak  
      - POSTGRES_DB=keycloak  
    networks:  
      - secured-network  
  
  # Keycloak  
  secured-identity:  
    container_name: secured-identity  
    build: ./Keycloak  
    command: ["start-dev"]  
    ports:  
      - 5053:8080  
    expose:  
      - 5053  
    environment:  
      - KEYCLOAK_ADMIN=admin  
      - KEYCLOAK_ADMIN_PASSWORD=admin  
      - KC_HOSTNAME_URL=http://localhost:5050/identity  
      - KC_DB=postgres  
      - KC_DB_USERNAME=keycloak  
      - KC_DB_PASSWORD=myStrongPassword123  
      - KC_DB_URL=jdbc:postgresql://secured-identity-db:5432/keycloak  
    depends_on:   
      - secured-identity-db  
    networks:  
      - secured-network  
  
networks:  
  secured-network:  
    driver: bridge  
  
volumes:  
  secured-data:  
    driver: local

Ve sadece bu docker-compose.yml dosyasından docker’ı çalıştırmamız yeterlidir.

docker compose build  
docker compose up -d

Put keycloak behind Ocelot API Gateway to protect resources

Microservice architecture with Ocelot and Keycloak

Microservice architecture with Ocelot and Keycloak

Kimlik hizmetinizin (keycloak) güvenliğini sağlamak ve diğer hizmetler gibi istek alabilecek uç noktaları sınırlandırarak saldırı vektörünü azaltmak önemlidir. Keycloak admin paneline harici bir ağdan erişilmemelidir, bu özelliğe yalnızca bu ağdaki kişiler erişebilir. Daha fazla bilgi edinmek için reverse proxy kullanma hakkındaki Keycloak dökümantasyonunu göz atın.

Exposed path recommendations

Exposed path recommendations

Öncelikle Ocelot projesini oluşturalım. Burada .NET 8 kullanıyorum

dotnet new sln -n Secured  
dotnet new webapi -o Secured.ApiGateway  
cd Secured.ApiGateway/  
dotnet add package Ocelot --version 22.0.1

Projeyi başarıyla oluşturduktan sonra Program.cs dosyasını aşağıdaki gibi değiştirip root dizinde ocelot.json dosyasını oluşturun.

var builder = WebApplication.CreateBuilder(args);  
  
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);  
  
builder.Services.AddOcelot(builder.Configuration);  
  
var app = builder.Build();  
  
await app.UseOcelot();  
await app.RunAsync();

Bundan sonra bu ocelot.json dosyasını localhost:5053 üzerinde çalışan keycloak uygulamamıza erişimi sınırlamak için kullanabiliriz.

{  
  "Routes": [  
    {  
      "DownstreamPathTemplate": "/realms/{everything}",  
      "DownstreamScheme": "http",  
      "DownstreamHostAndPorts": [  
        {  
          "Host": "localhost",  
          "Port": 5053  
        }  
      ],  
      "UpstreamPathTemplate": "/identity/realms/{everything}",  
      "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ]  
    },  
    {  
      "DownstreamPathTemplate": "/resources/{everything}",  
      "DownstreamScheme": "http",  
      "DownstreamHostAndPorts": [  
        {  
          "Host": "localhost",  
          "Port": 5053  
        }  
      ],  
      "UpstreamPathTemplate": "/identity/resources/{everything}",  
      "UpstreamHttpMethod": [ "Get" ]  
    },  
    {  
      "DownstreamPathTemplate": "/js/{everything}",  
      "DownstreamScheme": "http",  
      "DownstreamHostAndPorts": [  
        {  
          "Host": "localhost",  
          "Port": 5053  
        }  
      ],  
      "UpstreamPathTemplate": "/identity/js/{everything}",  
      "UpstreamHttpMethod": [ "Get" ]  
    }  
  ],  
  "GlobalConfiguration": {  
    "BaseUrl": "https://localhost:5050"  
  }  
}

Bu yapılandırmalardan sonra, API Gateway’in adresi olan localhost:5050’den keycloak’a erişebilirsiniz. Ancak, bu adres aracılığıyla Keycloak yönetici paneline erişemezsiniz çünkü erişimi sınırladık.

Keycloak yapılandırmalarımızı yapmak için, hala local olarak erişilebilen keycloak adresinde, yani localhost:5053’te yönetici paneline giriş yapabiliriz. Deployment senaryosunda, yalnızca Ocelot’un harici internete erişimi olduğundan emin olacağız.

localhost:5053 adresine admin kullanıcı adı ve admin şifresiyle giriş yapabiliriz. Bu kimlik bilgilerini docker-compose.yml dosyasından değiştirebiliriz.

Keycloak

Giriş yaptıktan sonra, kullanıcıları yönetmek için yeni bir alan (realm) oluşturmak ve uygulamamız için istemci oluşturmak için bu açılır menüden yeni alan (realm) oluştur seçeneğine tıklıyoruz. “Yeni alan (realm) oluştur” düğmesine tıkladıktan sonra, basitçe secured‘ı alan (realm) adı olarak girin ve diğer her şeyi olduğu gibi bırakın.

Keycloak

Clients sekmesinde client oluştur’a tıklayın. Client ID alanını postman olarak adlandırın ve geçerli redirect URL’sini https://oauth.pstmn.io/v1/callback olarak adlandırın. diğer şeyleri olduğu gibi bırakın.

Keycloak

Kullanıcılar sekmesinde Keycloak’ta oturum açacak kullanıcıları ekleyin.

Keycloak

Bu adımlardan sonra, yeni oluşturulan kullanıcıyla Keycloak’a giriş yaparak erişim belirteci alabiliriz. API gateway’e erişmek için Postman kullanıyorum. Postman’de Keycloak ile oturum açabilirsiniz. Yetkilendirme bölümünde OAuth 2.0’ı seçin ve aşağıdaki gibi yapılandırın ve Yeni Erişim Belgesi Al’a tıklayın. Postman, tarayıcı penceresini açacak ve sizi Keycloak giriş sayfasına yönlendirecektir, yeni kullanıcınızın kimlik bilgilerini girin. Admin kimlik bilgilerini girmeyin, bu kimlik bilgileri yalnızca ana (master) alan için geçerlidir.

Grant type: Authorization code  
Auth URL: http://localhost:5050/identity/realms/secured/protocol/openid-connect/auth  
Access Token URL: http://localhost:5050/identity/realms/secured/protocol/openid-connect/token  
Client ID: postman  
Scope: openid profile roles

Her şeyi doğru yaptıysanız, Postman ile API Gateway’in arkasındaki Keycloak’a başarıyla kimlik doğrulaması yaptınız 🎉

Authenticate requests to other services

Ocelot’ta bir isteğin kimliğini doğrulamak istiyorsanız Program.cs ve ocelot.json’da aşağıdaki güncellemeleri yapın. MetadataAddress, JWT doğrulaması için gerekli anahtarları alır. Ancak ekstra network isteklerinden kaçınmak istiyorsanız anahtarları appsettings.json dosyasına yerleştirip kullanabilirsiniz.

Ocelot.json dosyasındaki AuthenticationOptions ile kimlik doğrulama tamamlandı, burada JWT tokenımızı Bearer önekiyle birlikte göndereceğimizi ve JWT token doğrulamasını Program.cs’den yapılandırma ile yapacağımızı söylüyoruz.

var builder = WebApplication.CreateBuilder(args);  
  
builder.Services  
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)  
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =>  
    {  
        o.MetadataAddress = "http://localhost:5050/identity/realms/secured/.well-known/openid-configuration";  
        o.RequireHttpsMetadata = false;  
        o.Authority = "http://localhost:5050/realms/secured";  
        o.Audience = "account";  
    });  
  
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);  
  
  
builder.Services.AddOcelot(builder.Configuration);  
  
var app = builder.Build();  
  
app.UseAuthentication();  
app.UseAuthorization();  
  
await app.UseOcelot();  
await app.RunAsync();
{  
    "Routes": [  
      {  
        "DownstreamPathTemplate": "/get",  
        "DownstreamScheme": "https",  
        "DownstreamHostAndPorts": [  
          {  
            "Host": "httpbin.org",  
            "Port": 443  
          }  
        ],  
        "UpstreamPathTemplate": "/test",  
        "UpstreamHttpMethod": [ "Get" ],  
        "AuthenticationOptions": {  
          "AuthenticationProviderKey": "Bearer"  
        }  
      },  
  
//...

Authorize requests to other services

Yetkilendirme biraz daha zor ve ek çalışma gerektirir çünkü Keycloak, kullanıcı rollerini JWT yükünde nested yani iç içe geçmiş olarak yerleştirir. Aşağıdaki gibi bir şeydir. Görüldüğü gibi, roller alanı realm_access alanı altına yerleştirilir. Bu, Ocelot’ta karışıklığa neden olur, çünkü Ocelot, talepleri string veya object değerleri olarak okur ve nested alanları değerlendirmez. Bu sorunu önlemek için Keycloak token eşlemesini yapılandırırız ve nested biçimi kullanmayız.

{  
  //...  
  "exp": 1706600524,  
  "realm_access": {  
    "roles": [  
      "offline_access",  
      "default-roles-microcommerce",  
      "uma_authorization",  
      "customer"  
    ]  
  },  
  "resource_access": {  
    "account": {  
      "roles": [  
        "manage-account",  
        "manage-account-links",  
        "view-profile"  
      ]  
    }  
  },  
  "scope": "openid email profile",  
  "preferred_username": "berkslv",  
}

Bu özel eşleme için sol menüden Client scopes’a tıklayın ve rolleri seçin.

Keycloak

Rollerin altında, Mappers bölümünü seçin ve ardından realm roles’a tıklayın.

Keycloak

Açılan menüde Token Claim Name seçeneğine realm_access.roles hali hazırda girilidir. Bunu realm_roles olarak güncelliyoruz.

Keycloak

Bu konfigürasyonları yaptıktan sonra Postman üzerinden tekrar giriş yaparak tokenımızı güncelliyoruz. Güncellenen token ile yaptığımız isteklerin Ocelot tarafından talep kontrolüne tabi olması için ocelot.json dosyasında aşağıdaki güncellemeyi yapıyoruz. Kullanıcımızda customer rolü yoksa 403 yanıtı alıyoruz. Keycloak yönetici panelinde özel rollerinizi oluşturabilir ve atayabilirsiniz.

{  
    "Routes": [  
      {  
        "DownstreamPathTemplate": "/get",  
        "DownstreamScheme": "https",  
        "DownstreamHostAndPorts": [  
          {  
            "Host": "httpbin.org",  
            "Port": 443  
          }  
        ],  
        "UpstreamPathTemplate": "/test",  
        "UpstreamHttpMethod": [ "Get" ],  
        "AuthenticationOptions": {  
          "AuthenticationProviderKey": "Bearer"  
        },  
        "RouteClaimsRequirement": {  
          "realm_roles": "customer"  
        }  
      },  
  
//...

Kaynak koduna erişmek isterseniz projenin tamamını GitHub hesabımda bulabilirsiniz:

GitHub - berkslv/lecture-ocelot-and-keycloak


Sonuç

Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için @berkslv adresinden takipte kalabilirsiniz.