Istio Cross Cluster Microservice Security - Authorization & mTLS

Emre Savcı
Trendyol Tech
Published in
6 min readJan 12, 2021

--

Merhabalar, bu yazıda Trendyol Platform ekibi olarak geliştirmesini yaptığımız cross cluster service authorization projesinde Istio’nun sağladığı özellikleri kullanarak oluşturduğumuz yapının geliştirme sürecini anlatmaya çalışacağım.

Yazı içerisinde bahsedilecek konu başlıkları:

Service Identification

Servisler birbirleriyle haberleşirken, kendilerine gelen istekleri authorize edebilmeleri için, isteğin kimliğini (kimin tarafından gönderildiğini) bilmesi gerekiyor.

Bu noktada servisler için identification ihtiyacı ortaya çıkıyor.

Spiffe ID

Spiffe (Secure Production Identity Framework for Everyone) kendisini “dinamik ortamlarda yazılım sistemlerini güvenli bir şekilde tanımlamak için belirlenmiş bir takım open source standartlar” olarak tanıtıyor.

Bu noktada Istio da Spiffe standartlarını implement edip bizler için Spiffe ID atamasını yapıyor.

https://istio.io/latest/docs/concepts/security/id-prov.svg

Meshinizde herhangi bir servis için spiffe id değerini görmek isterseniz aşağıdaki adımları takip ederek test edebilirsiniz:

Response içerisinde istek atarken gönderilen headerları görüyoruz. Burada gönderilenspiffe://cluster.local/ns/default/sa/default bizim spiffe id değerimiz.

Konunun detayları için Spiffe hakkında detayları merak edenler için:

EnvoyFilter

Buraya kadar her şey güzel. Fakat biz yukarıda belirtilen spiffe id değerine tek başına erişmek istiyoruz ve bu değeri ayrı bir headerda servislere geçmek istiyoruz.

Bu işlemi yapmak için spiffe id nin yapısına bir bakalım:

spiffe:// _mesh_id_ /ns/ _namespace_ /sa/ _serviceAccount_

Yapının bu şekilde olduğunu biliyoruz. Şimdi yapmamız gereken şey bütün servislerin dışarıya giden isteklerine değer spiffe id olan bir header eklemek.

Bu işlemi yapabilmemiz için Istio bizlere EnvoyFilter ile envoy proxyi ayarlayabilmemizi sağlıyor.

Aşağıda belirttiğim EnvoyFilter custom resource’u ile outbound requestlerin tamamına bir header değeri ekliyoruz.

Yapılan işlemi açıklamak gerekirse:

  • Line 7:

Burada EnvoyFilter’ı Istio namespace’inde oluşturacağımızı belirtiyoruz. Böylelikle mesh içerisindeki tüm servisler için uygulanmış oluyor.

  • Line 12:

context: SIDECAR_OUTBOUND

Burada EnvoyFilter’ın sidecarın outbound isteklerinde çalışması gerektiğini belirtiyoruz.

  • Line 27:

EnvoyFilter içerisinde lua scripti yazarak giden requestlere spiffe id ekliyoruz.

Spiffe id istio proxy containerı içerisindeki environment değikenleri ile şu şekilde üretiliyor:

spiffe://..os.getenv("ISTIO_META_MESH_ID")../ns/..os.getenv("POD_NAMESPACE")../sa/..os.getenv("SERVICE_ACCOUNT")

(.. syntaxı lua’da string concat işlemi yapıyor).

headers():replace() methodu ile çağırmamızın sebebi, bu header uygulama tarafından gönderilmeye çalışılırsa(bir servis başka bir servisi taklit etmeye çalışırsa), en son aşamada headerı doğru değeri ile değiştiriyoruz.

Secure Communication With mTLS

https://istio.io/v1.7/docs/concepts/security/arch-sec.svg

Servisler arası güvenli haberleşmeyi sağlamak için Istio bizlere mutual TLS ile haberleşebilme özelliği sunuyor. Böylelikle mesh içerisindeki servislerimiz TLS özelinde bir işlem yapma ihtiyacı duymadan bu sorumluluğu Istio proxy sidecarına yüklüyor.

Cross Cluster mTLS & Root CA

Birbirinden farklı clusterlarda bulunan meshlerimiz arasında mTLS haberleşme sağlayabilmek için, iki meshin Root CA’lerinin aynı olması gerekiyor. Öncelikle iki clusterın sertifikalarını ortaklaştıralım.

Istio Rotating Root CA

Elimizde cluster1 ve cluster2 adında farklı contextler olduğunu varsayalım. Cluster1 içerisinde istio-system namespace’i altında bulunan istio-ca-secret (veya cacert) isimli secret değerini kopyalayalım.

kubectl get secrets -n istio-system istio-ca-secret -o yaml | pbcopy

Daha sonra cluster2 içerisinde bulunan istio-ca-secret’i silelim.

kubectl delete secret -n istio-system istio-ca-secret

Şimdi ilk clusterdan aldığımız secret değerini ikinci clustera apply edelim:

pbpaste | kubectl apply -f -

Bu işlemleri yaptıktan sonra istio-system namespace’indeki tüm deploymentları restart edelim:

kubectl rollout restart deployment -n istio-system

Artık iki mesh birbiriyle mTLS haberleşmek için hazır.

Enabling mTLS

Gerçekleştireceğimiz senaryoyu özetlemek gerekirse, iki clusterımıza da httpbin podları deploy ettik ve cluster2'den cluster1'e istek göndereceğiz. İki farklı meshin mTLS ile haberleştiğini gözlemleyeceğiz.

cluster1 docker ip: 172.17.0.2

cluster1 istio-ingressgateway 443 NodePort: 31321

Öncelikle iki cluster içerisinde de mTLS strict mode ile açık hale getirelim.

kubectl apply -f mtls.yaml :

PeerAuthentication mtls mode parametresi için 4 farklı değer alabiliyor.

Strict = sadece mtls haberleşmeyi kabul eder

Permissive = plain text & mtls haberleşmeyi kabul eder

Daha sonra requestlerin secure port üzerinden kabul edilebilmesi için bir gateway tanımı yapıyoruz.

kubectl apply -f mtls-gw.yaml :

Şimdi VirtualService tanımını yapalım.

kubectl apply -f virtual-service.yaml

Burada response headerlarına custom bir değer ile cevabın cluster1 den döndüğü bilgisini ekledik.

Son olarak yapılması gereken bir DestinationRule oluşturmak ve requestleri proxylere mtls ile iletmek.

kubectl apply -f mtls-dest-rule.yaml :

Şimdi cluster2 ye geçelim.

Öncelikle mesh içerisinde bulunmayan bir servisi meshe tanıtmak ve mesh featurelarını (VirtualService, DestRule, AuthPolicy etc..) kullanabilmek için harici servisleri meshimize ServiceEntry resource’u ile ekliyoruz. (Mevcut örneğimiz ServiceEntry olmadan da çalışabilir.)

Diğer clusterdaki servise, mesh içerisinden service discovery ile erişebilmek için Service ve Endpoint tanımı yapıyoruz.

kubectl apply -f svc-ep.yaml :

Artık request atmaya hazırız. Cluster2'de bulunan httpbin podu içerisinden curl isteği atalım:

curl external-httpbin-test-service.external/headers :

Burada haberleşmenin mTLS olduğunun kanıtı olan X-Forwarded-Client-Cert headerını görüyoruz. Bunun yanında EnvoyFilter ile eklediğimiz Ty-Spiffe-Id headerını da görebildik. Requestin cluster1 tarafından karşılandığını da response-comes-from: cluster1 headerı ile gördük.

Konuyla ilgili daha fazla bilgiye ulaşmak için:

Thus, all traffic between workloads with proxies uses mutual TLS, without you doing anything. For example, take the response from a request to httpbin/header. When using mutual TLS, the proxy injects the X-Forwarded-Client-Cert header to the upstream request to the backend. That header’s presence is evidence that mutual TLS is used.

Authorization Policy

AuthorizationPolicy Istio tarafından bizlere sunulan servisler arası haberleşmeyi policyler ile kısıtlayabileceğimiz bir custom resource tipidir.

AuthorizationPolicy sayesinde, mesh içerisinde:

  • Namespace
  • Method
  • Header
  • Workload
  • Path

bazlı kısıtlamaları yapabiliriz.

https://istio.io/v1.7/docs/concepts/security/authn.svg

Biz policylerimizde uygulama, path, method ve header değeri bazında bir kısıtlama getirdik.

AuthorizationPolicy yazarken kuralları Deny ve Allow olarak belirtiyorsunuz. Deny kuralları önce işletiliyor. Eğer bir path için allow kuralı yazıldıysa, geri kalan tüm istekler deny ediliyor ve sadece allow olarak yazılan istek kabul ediliyor.

Örneğin default namespace’inde çalışan httpbin uygulamasının sadece /headers endpointine GET requestlerinin gelmesine izin verelim. Aşağıdaki policyi uyguladığımızda httpbin servisinin /headers dışında başka bir endpointine istek atmaya çalıştığınızda “403 Forbidden - RBAC: access denied” şeklinde bir cevap alacaksınız.

AuthorizationPolicy hakkında detaylı bilgi edinmek ve diğer örneklere göz atmak isteyenler için aşağıya ilgili Istio dökümanını bırakıyorum.

Trendyol Platform Operator

Buraya kadar anlattıklarım authorization kısmını nasıl sağlayabileceğimiz üzerineydi. Farklı clusterlarda çalışan servisler arası authorization yapabilmek için tüm ihtiyaçlarımızı karşılayacak çözümleri bulduk. Yapılması gerek son şey bu sürecin otomatize edilmesi.

Bu sebeple clusterlarda bulunan servislerin spiffe idlerini Vault üzerinde saklamak ve oluşturulacak policyler için abstraction sağlamak amacıyla bir Operator geliştirmesi yaptık.

Geliştirdiğimiz operator sayesinde domain ekipleri doğrudan Istio bağımlı bir policy yazmak yerine, oluşturduğumuz custom resource definition ile özel bir resource üzerinden policy tanımı yapacaklar. Böylelikle operator ile bu custom resource işlemlerini kontrol edebileceğiz.

Yazının başında belirttiğim spiffe id bilgisini ekipler policy oluştururken bilmek zorunda kalmayacak veya ilerleyen zamanlarda authorizationı spiffe id dışında farklı bir yöntem ile yapmak istediğimizde ekiplerin herhangi bir değişiklik yapması gerekmeyecek. Bütün bu süreçler Operator üzerinden yönetilebilecek.

Operator ile kontrolü sağlanan resource tanımlamasına örnek vermek gerekirse:

Burada authorization aktif etmek istediğimiz servis ismini serviceName alanında belirtiyoruz. Sonrasında ise endpointler için izin verilen servislerin tanımlamasını yapıyoruz.

Endpoint + method bazında kırılım yapılıyor. Örneğin ilk tanımlamada editor ve gateway servisleri /v1/example endpointine GET isteği atabilirken, aynı endpointe sadece editor uygulaması POST isteği atabilir.

Operator geliştirmesini operator-sdk kullanarak yaptık. Oldukça kolay bir şekilde boiler plate kodları bizim için oluşturup kolayca geliştirmemizi yapmamızı sağladı. Operator sdk kullanımını ve sdk özelinde yapılan işlemleri ayrı bir yazıda ele alacağım.

Burada operator özelinde çok fazla kod paylaşmadan yeni geliştirdiğimiz ServiceAuthorizationRule resource’undan AuthorizationPolicy resource’una çevirme işleminin nasıl yapıldığını örnek bir kod bloğu ile göstermek istiyorum.

12. satırda ilgili servis ismine ait spiffe id değerini vault üzerinden alıp arkaplanda oluşturduğumuz Istio AuthorizationPolicy içerisinde kullanmak üzere hazırlıyoruz. Bu sayede ekipler spiffe idlerden habersiz bir şekilde sadece servis isimlerini kullanarak policylerini yazabiliyorlar.

Bu işlem sonucunda oluşturulan AuthorizationPolicy:

Konuyu toparlayıp özetlemek gerekirse, taklit edilemeyen (vault üzerinde unique kontrolü olan), manuel olarak id ataması yapılamayan (request gönderirken header manuel olarak geçilse bile EnvoyFilter son aşamada gerçek değeri ile ezecek), dinamik policyler ile ayarlanabilen, mTLS ile haberleşebilen service authorization alt yapısını bu şekilde geliştirmiş olduk.

Okuduğunuz için teşekkürler, bir sonraki yazıda görüşmek üzere.

#cometotrendyol #followus

https://twitter.com/trendyoltech

https://github.com/Trendyol/

--

--

Emre Savcı
Trendyol Tech

Tech. Lead @Trendyol & Couchbase Ambassador | Go Türkiye, Kubernetes, Istio, CNCF, Scalability. Open Source Contributor.