web-dev-qa-db-fra.com

Catch-22 empêche le streaming TCP WCF service sécurisable par WIF; ruiner mon Noël, santé mentale

J'ai une exigence pour sécuriser un point de terminaison de service WCF net.tcp en streaming en utilisant WIF . Il devrait authentifier les appels entrants sur notre serveur de jetons. Le service est diffusé car il est conçu pour transférer de grandes quantités de données n trucs.

Cela semble impossible. Et si je ne peux pas contourner le problème, mon Noël sera ruiné et je me boirai à mort dans un Gouttière pendant que de joyeux acheteurs enjambent mon corps qui se refroidit lentement. Totes sérieux, vous les gars.

Pourquoi est-ce impossible? Voici le Catch-22.

Sur le client, je dois créer un canal avec le GenericXmlSecurityToken que j'obtiens de notre serveur de jetons. Pas de problème.

// people around here hate the Framework Design Guidelines.
var token = Authentication.Current._Token;
var service = base.ChannelFactory.CreateChannelWithIssuedToken(token);
return service.Derp();

Ai-je dit "pas de problème"? Problemo. En fait, NullReferenceException style problemo.

"Bro", ai-je demandé au Framework, "est-ce que tu vérifies même null?" Le cadre était silencieux, j'ai donc démonté et constaté que

((IChannel)(object)tChannel).
    GetProperty<ChannelParameterCollection>().
    Add(federatedClientCredentialsParameter);

était la source de l'exception et que l'appel GetProperty renvoyait null. Alors, WTF? Il s'avère que si j'active la sécurité des messages et que je définis le type d'informations d'identification client sur IssuedToken, cette propriété existe maintenant dans le ClientFactory (protip: il n'y a pas d'équivalent "SetProperty" dans IChannel, le bâtard ).

<binding name="OMGWTFLOL22" transferMode="Streamed" >
    <security mode="Message">
        <message clientCredentialType="IssuedToken"/>
    </security>
</binding>

Sucré. Plus de NRE. Cependant, maintenant mon client est fautif à la naissance (toujours l'aimer, tho). En fouillant dans les diagnostics WCF (protip: faites en sorte que vos pires ennemis le fassent après les avoir écrasés et les avoir conduits devant vous, mais juste avant de profiter des lamentations de leurs femmes et de leurs enfants), je vois que c'est à cause d'une inadéquation de sécurité entre le serveur et le client.

La mise à niveau demandée n'est pas prise en charge par "net.tcp: // localhost: 49627/MyService". Cela peut être dû à des liaisons incompatibles (par exemple, la sécurité est activée sur le client et non sur le serveur).

Vérification des diags de l'hôte (encore une fois: écraser, conduire, lire les journaux, profiter des lamentations), je vois que c'est vrai

Le type de protocole application/ssl-tls a été envoyé à un service qui ne prend pas en charge ce type de mise à niveau.

"Eh bien, moi," dis-je, "Je vais simplement activer la sécurité des messages sur l'hôte!" Et je fais. Si vous voulez savoir à quoi il ressemble, c'est une copie exacte de la configuration client. Chercher.

Résultat: Kaboom.

La liaison ('NetTcpBinding', ' http://tempuri.org/ ') prend en charge le streaming qui ne peut pas être configuré avec la sécurité au niveau des messages. Pensez à choisir un autre mode de transfert ou à choisir le niveau de sécurité de transport.

Ainsi, mon hôte ne peut pas être diffusé et sécurisé via des jetons . Catch-22.

tl; dr: Comment puis-je sécuriser un point de terminaison WCF net.tcp en streaming en utilisant WIF ???

180
Will

WCF a obtenu des accrochages dans quelques domaines avec le streaming (je vous regarde, MTOM1) en raison d'un problème fondamental dans la façon dont il ne parvient pas à effectuer la pré-authentification comme la plupart des gens penseraient que cela devrait fonctionner (cela n'affecte que les demandes suivantes pour ce canal, pas la première demande) Ok, donc ce n'est pas exactement votre problème, mais veuillez suivre comme j'arriverai au vôtre à la fin. Normalement, le défi HTTP fonctionne comme ceci:

  1. le client accède au serveur de manière anonyme
  2. serveur dit, désolé, 401, j'ai besoin d'une authentification
  3. le client atteint le serveur avec un jeton d'authentification
  4. serveur accepte.

Maintenant, si vous essayez d'activer le streaming MTOM sur un point de terminaison WCF sur le serveur, il ne se plaindra pas. Mais, lorsque vous le configurez sur le proxy client (comme vous devez, ils doivent correspondre aux liaisons), il explosera dans une mort ardente. La raison en est que la séquence d'événements ci-dessus que WCF essaie d'empêcher est la suivante:

  1. le client transmet le fichier de 100 Mo au serveur de manière anonyme dans un seul POST
  2. serveur dit désolé, 401, j'ai besoin d'une authentification
  3. le client diffuse à nouveau le fichier de 100 Mo sur le serveur avec un en-tête d'authentification
  4. serveur accepte.

Notez que vous venez d'envoyer 200 Mo au serveur alors que vous n'aviez besoin que d'envoyer 100 Mo. Eh bien, c'est ça le problème. La réponse est d'envoyer l'authentification à la première tentative, mais cela n'est pas possible dans WCF sans écrire un comportement personnalisé. Quoi qu'il en soit, je m'égare.

Votre problème

Tout d'abord, laissez-moi vous dire que ce que vous essayez est impossible2. Maintenant, pour que vous arrêtiez de faire tourner vos roues, permettez-moi de vous expliquer pourquoi:

Il me semble que vous vous promenez maintenant dans une classe de problèmes similaire. Si vous activez la sécurité au niveau des messages, le client doit charger l'intégralité du flux de données en mémoire avant de pouvoir fermer le message avec la fonction de hachage habituelle et la signature xml requises par ws-security. S'il doit lire le flux entier pour signer le message unique (qui n'est pas vraiment un message, mais c'est un seul flux continu), vous pouvez voir le problème ici. WCF devra le diffuser une fois "localement" pour calculer la sécurité du message, puis le diffuser à nouveau pour l'envoyer au serveur. C'est clairement une chose stupide, donc WCF n'autorise pas la sécurité au niveau des messages pour les données en streaming.

Ainsi, la réponse simple ici est que vous devez envoyer le jeton soit en tant que paramètre au service Web initial, soit en tant qu'en-tête SOAP et utiliser un comportement personnalisé pour le valider. Vous ne pouvez pas utiliser WS -Sécurité pour faire ça. Franchement, ce n'est pas seulement un problème WCF - je ne vois pas comment cela pourrait pratiquement fonctionner pour d'autres piles.

Résolution du problème MTOM

Ceci est juste un exemple de la façon dont j'ai résolu mon problème de streaming MTOM pour l'authentification de base, donc vous pourriez peut-être en prendre le courage et implémenter quelque chose de similaire pour votre problème. L'essentiel est que pour activer votre inspecteur de message personnalisé, vous devez désactiver toute notion de sécurité sur le proxy client (il reste activé sur le serveur), à l'exception du niveau de transport (SSL):

this._contentService.Endpoint.Behaviors.Add(
    new BasicAuthenticationBehavior(
        username: this.Settings.HttpUser,
        password: this.Settings.HttpPass));
var binding = (BasicHttpBinding)this._contentService.Endpoint.Binding;
binding.Security.Mode = BasicHttpSecurityMode.Transport; // SSL only            
binding.Security.Transport.ClientCredentialType = 
   HttpClientCredentialType.None; // Do not provide

Notez que j'ai désactivé la sécurité du transport ici parce que je fournirai moi-même un inspecteur de messages et un comportement personnalisé:

internal class BasicAuthenticationBehavior : IEndpointBehavior
{
    private readonly string _username;
    private readonly string _password;

    public BasicAuthenticationBehavior(string username, string password)
    {
        this._username = username;
        this._password = password;
    }
    public void AddBindingParameters(ServiceEndpoint endpoint, 
        BindingParameterCollection bindingParameters) { }
    public void ApplyClientBehavior(ServiceEndpoint endpoint,
        ClientRuntime clientRuntime)
    {
        var inspector = new BasicAuthenticationInspector(
            this._username, this._password);
        clientRuntime.MessageInspectors.Add(inspector);
    }
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
        EndpointDispatcher endpointDispatcher) { }
    public void Validate(ServiceEndpoint endpoint) { }
}

internal class BasicAuthenticationInspector : IClientMessageInspector
{
    private readonly string _username;
    private readonly string _password;

    public BasicAuthenticationInspector(string username, string password)
    {
        this._username = username;
        this._password = password;
    }

    public void AfterReceiveReply(ref Message reply,
        object correlationState) { }

    public object BeforeSendRequest(ref Message request,
        IClientChannel channel)
    {
        // we add the headers manually rather than using credentials 
        // due to proxying issues, and with the 101-continue http verb 
        var authInfo = Convert.ToBase64String(
            Encoding.Default.GetBytes(this._username + ":" + this._password));

        var messageProperty = new HttpRequestMessageProperty();
        messageProperty.Headers.Add("Authorization", "Basic " + authInfo);
        request.Properties[HttpRequestMessageProperty.Name] = messageProperty;

        return null;
    }
}

Ainsi, cet exemple s'adresse à toute personne souffrant du problème MTOM, mais également en tant que squelette pour vous permettre d'implémenter quelque chose de similaire pour authentifier votre jeton généré par le service de jeton sécurisé WIF principal.

J'espère que cela t'aides.

(1) Grandes données et streaming

(2) Message Security in WCF (voir "inconvénients".)

41
x0n