29 Eylül 2007 Cumartesi

WSE 3.0 Security

Web servisinize ulaşan bir mesajın acaba bir saldırımı yoksa gerçekten bir istemci mesajı mı olduğunu nasıl kontrol edersiniz? Peki, mesajın güvenliğini sağladığınızı varsayalım acaba istemci kullanıcının yetkisi dışında bir veriye talepte bulunmadığından emin olabilir misiniz?

WSE 3.0 Policy Framework özelliklerini inceliyoruz.

WSE Policy Framework

Politikalar çalışma zamanı kavramlarıdır. Görevleri istemci veya sunucuyu politikada yer alan bildirgelere göre mesajları zorlamaktır. Bildirgeler mesajları süzen filtrelerdir. Her bildirgede dört adet süzgeç bulunmaktadır. Bu süzgeçler her biri farklı mesaj işleme adımlarında çalıştırılmaktadır.

Geliştirici kendi güvenlik prensiplerine göre bu süzgeçleri oluşturmaktadır. SOAP Filter (süzgeç) nesneleri içinde mesajın bileşenlerine ekleme veya eklenen özellikleri kontrol ederek güvenlik prensiplerimizi gerçekleştirmekteyiz. Bu süzgeçler içerisinde mesajın hepsini şifrelemekte mümkün sertifika bilgilerini değiştirmekte mümkündür. SOAP Filter nesneleri bizim asıl olarak güvenlik kodlarımızı yazacağımız yerlerdir. Hem süzgeç hem bildiri için WSE içinde çok çeşitli kullanmaya hazır üst sınıflar mevcuttur. Biz kendi politikamıza uygun üst sınıfları kullanmalıyız.

Şekilde sistemin tam bir döngüsünü görmekteyiz. Örnek politikamıza iki adet bildirge eklenmiştir. Her bildirgemize ait dört tane SOAP Filter görülmektedir.

İlk olarak istemci bir web servis isteğinde bulunur. Daha sonra bu istek politikamıza ait bildirgelerin SOAP Filter Client Output nesnelerinden geçirilecektir. Bu kısımda mesaja doğrulama (authentication) için ihtiyacımız olacak token ve sunucu tarafında ki süzgeçte kullanılacak diğer güvenlik (security) nesneleri eklenmektedir. Daha sonra mesajımız network ile sunucuya ulaşmaktadır.

WSE sunucuya gelen SOAP mesajlarını doğrulamak (authentication) için Win32’ye ait LogonUser fonksiyonunu çağırmaktadır. LogonUser fonksiyonu varsayılan olarak Windows hesabına ait bir doğrulama (Windows Authentication) denemekte ve mesaja eklemektedir. Eğer doğrulama başarılı olursa oluşturulan Principal nesnesi UsernameToken’nın Principal alanına atanacaktır. Eğer WSE’nin UsernameToken nesnesini Windows ile doğrulamasını istemiyorsanız UsernameTokenManager sınıfın türetmeli ve AuthenticateToken metodunu tekrar yazmalısınız.

Kullanıcı doğrulama adımı geçildikten sonra mesaj ilk olarak politika bildirgelerimizin SOAP Filter Service Input nesnelerine gelmektedir. Eğer mesaj bu adımda ki süzgeçten geçebilirse web servis kodlarımız çalışacaktır. Daha sonra servis geri dönüşü mesajını oluşturacaktır. Oluşturulan geri dönüş (response) mesajı SOAP Filter Servis Output nesnelerinden geçecektir.

En son olarak tekrar istemci tarafa ulaşan geri dönüş mesajı SOAP Filter Client Input süzgeçlerinden geçecek ve web servis çağrımız yerde geri dönüşte bulunarak çağrımız son bulacaktır.

Bir örmek ile devam edelim. Biz mesajımıza Client Output süzgeci içerisinde bir UsernameToken ve aynı token ile mesajı imzasını ekleyeceğiz ve Service Input süzgecinden gelen mesajın önce token ile belirtilen kullanıcı adı ve şifresinin doğru olduğunu daha sonrada mesajın imzasının doğru olduğunu denetleyip mesajın doğruluğundan emin olacağız. Bu güvenlik prensiplerimizi web servisimize ve istemcimize ekleyeceğimiz politika ile uygulayacağız.

Öncelikle istemci tarafında SOAP Client Output süzgeci ile sunucu tarafta doğrulayacağımız token ve mesaj imzasını ekleyelim. Süzgeç için SOAP mesajlarında güvenlik, mesaj imzası ve şifrelemeyi destekleyen SendSecurityFilter sınıfı türetmeliyiz. Süzgecimizi kullanacak bildirge nesnemizi aynı şekilde güvenlik özelliklerine sahip SecurityPolicyAssertion sınıfından türetmeliyiz..

[cs]

Internal class UsernameClientAssertion : SecurityPolicyAssertion {
    private string username;
    private string password;
 
    public UsernameClientAssertion(string username, string password) {
        this.username = username;
        this.password = password;
    }
 
    public override SoapFilter CreateClientOutputFilter(FilterCreationContext context) {
        return new ClientOutputFilter(this, context);
    }
……
class ClientOutputFilter : SendSecurityFilter {
    UsernameClientAssertion parentAssertion;
    FilterCreationContext filterContext;
 
    public ClientOutputFilter(UsernameClientAssertion parentAssertion, FilterCreationContext filterContext)
        : base(parentAssertion.ServiceActor, false, parentAssertion.ClientActor) {
        this.parentAssertion = parentAssertion;
        this.filterContext = filterContext;
    }
 
    public override void SecureMessage(SoapEnvelope envelope, Security security) {
        UsernameToken userToken = new UsernameToken(
            parentAssertion.username,
            parentAssertion.password,
            PasswordOption.SendHashed);
 
        security.Tokens.Add(userToken);
 
        MessageSignature sig = new MessageSignature(userToken);
        security.Elements.Add(sig);
    }
}

Mesajımıza süzgeç içerisinde token ve mesaj imzası eklemiş bulunuyoruz. Artık istemci tarafı güvenlik politikamızı web servis Proxy nesnemize uygulayabiliriz.

[cs]

UsernameClientAssertion assert=new UsernameClientAssertion(userName,password);
Policy policy = new Policy();
policy.Assertions.Add(assert);
serviceProxy.SetPolicy(policy);

İstemci tarafta işimiz bitti şimdi sunucu tarafında istemci tarafta eklenen verileri doğrulayacak ve bu doğrulamaya göre mesajılar süzecek bir politika oluşturalım. Politikalar oluşmadan önce LoginUser metodu çalışacak ve kullanıcı doğrulaması yapılacaktır. Kendi politikamız için kendi kullanıcı doğrulama sınıfımızı yazalım. Senaryoda anlattığımız gibi UsernameTokenManager sınıfını türetmeli ve AuthenticateToken metodunu yeniden yazmalıyız.

[cs]

public class ServiceUsernameTokenManager : UsernameTokenManager {
protected override string AuthenticateToken(UsernameToken token) {
//TODO şifrenin olması gereken halini bul ve return et
    AuthenticationService proxy = new AuthenticationService();
    AuthenticationModel.PERSONELDataTable personel=proxy.AttempSelectUser(id);
     if (personel == null)
        throw new SecurityFault (id ,"Geçersiz kullanıcı ile service erşimi :"+token.Username);
     return personel[0].CH_SIFRE;
}
}

Sunucu politikamızı oluşturalım. İstemci kısmı gibi SOAP Security eklentilerini destekleyen sınıfları türeterek kendi mesaj süzgecimizi ve bildirgemizi oluşturalım.

Şimdi kendi kurallarımızı UsernameServiceAssertion içinde yazabiliriz. Bu politika sunucu tarafında çalışacağı için sadece CreateServiceInputFilter uygulamamız yeterlidir.

[cs]

public class UsernameServiceAssertion : SecurityPolicyAssertion {
    public override SoapFilter CreateServiceInputFilter(FilterCreationContext context) {
        return new ServiceInputFilter(this, context);
    }
public override void ReadXml(XmlReader reader, IDictionary<string, Type> extensions) {
    // TODO: bildirgenin kendisine ait node’u bul oku.
    // mesaj işlemeye devam etsin
    reader.ReadStartElement(tagName);
}

Süzgeç içinde gelen mesajın doğrulamasını yapacağız:

[cs]

public class ServiceInputFilter : ReceiveSecurityFilter {
public override void ValidateMessageSecurity(SoapEnvelope envelope, Security security) {
    bool IsSigned = false;
    if (security != null) {
        foreach (ISecurityElement element in security.Elements) {
            if (element is MessageSignature) {                            
                MessageSignature sign = element as MessageSignature;
                //Beklenen ve gelen mesaj özelliklerini karşılaştır
                if (CheckSignature(envelope, security, sign)) {                            
                    if (sign.SigningToken is UsernameToken) {                            
                        IsSigned = true;
                        // mesaj için gecerli token nesnesi olarak imzanın token nesnesini ata
                        envelope.Context.IdentityToken = sign.SigningToken;
                    }
                }
            }
        }
    }
    if (!IsSigned)
        throw new SecurityFault ("Mesaj güvenlik sorgulaması başarısız oldu. Gecersiz mesaj imzası.");
}………………

Sunucu politika nesnemizi kodlama ile veya config üzerine xml ile oluşturabiliriz.

[cs]

public class ServerPolicy : Policy{
    public ServerPolicy() {
        this.Assertions.Add(new UsernameServiceAssertion());
    }
}

[xml]

<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy">
    <extensions>
        <extension name="usernameAssertion" 
type="UsernameAssertionLibrary.UsernameServiceAssertion, 
 UsernameAssertionLibrary"
  />
    </extensions>
    <policy name="ServerPolicy">
        <usernameAssertion />
    </policy>
</policies>

  Her iki şeklinde etkisi aynı olacaktır. İçerisinde sadece UsernameServiceAssertion bildirgesi olan bir politika oluşturacaktır. Daha sonra bu politikayı kendi web servisimize uygulamamız gerekmektedir.

[cs]

[Policy("ServerPolicy")]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService {

Artık Service web servisimiz ServerPolicy ile belirlediğimiz kurallara uyan mesajları alacaktır. Önce istemci tarafından kullanıcı adı ve şifre ile bir token oluşturduk bu token ile mesajı imzaladık. Daha sonra sunucu tarafında bu token ile kullanıcı (şifre) doğrulaması yaptık. Daha sonra mesaj imzasını ve mesaj özelliklerini test ettik. Tüm bu adımları gecen mesajın politikamıza uyan güvenli bir mesajdır.

Mesajın güvenliğinden emin olduğumuza göre son bir işlemimiz kaldı. İstekte bulunan kullanıcıya ait diğer bilgileri bir SOAP başlığına ekleyecek ve kullanıcının yetkisi dışında bir istekte bulunup bulunmadığını kontrol edeceğiz.

[cs]

public CustomSoapHeader header;
 
[WebMethod,SoapHeader("header")]
public byte[] GetAllRaporRow() {
    if(header.BolgeKod != 1000 )
         throw new SecurityFault ("Yetkiniz olmayan bir veriye 
 erişmeye çalışıyorsunuz"
 );
    //TODO birşeyler yap
    return new byte[]{};
}
………
public class CustomSoapHeader : SoapHeader {
    private int _bolgeKod;
 
    public int BolgeKod {
        get {
            return _bolgeKod;
        }
        set {
            _bolgeKod = value;
        }
    }

İstemci tarafında başlık verisini oluşturalım ve mesaja ekleyelim.

[cs]

serviceProxy.CustomSoapHeaderValue = new CustomSoapHeader();
serviceProxy.CustomSoapHeaderValue.BolgeKod = 1000;