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

182

J'ai une exigence pour sécuriser un point de terminaison de service WCF net.tcp en streaming à l'aide de WIF . Il doit 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.

Cela semble impossible. Et si je ne peux pas contourner le piège, mon Noël sera ruiné et je me boirai à mort dans une 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, le NullReferenceExceptionstyle problemo.

"Bro," ai-je demandé au Framework, "est-ce que vous êtes même nul?" Le cadre était silencieux, j'ai donc démonté et trouvé que

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

était la source de l'exception, et que l' GetPropertyappel revenait null. Alors, WTF? Il s'avère que si j'active la sécurité des messages et que je règle le type d'informations d'identification du client sur, IssuedTokencette 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 blâmé à la naissance (l'aime toujours, tho). En creusant dans les diagnostics WCF (protip: faites faire cela à vos pires ennemis après les avoir écrasés et les avoir conduits avant vous mais juste avant de profiter des lamentations de leurs femmes et enfants), je vois que c'est à cause d'un décalage 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).

En vérifiant les 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-même," 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 du 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 mode de transfert différent ou à choisir le niveau de sécurité du transport.

Ainsi, mon hôte ne peut pas être à la fois 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 ???

BoltClock
la source
3
Ok, question probablement ignorante ici, mais WIF nécessite-t-il vraiment le mode Message? Le mode de transport semble qu'il fonctionnerait mieux avec le streaming, quelque chose comme le évidemment non testé<security mode="Transport" /> <transport clientCredentialType="IssuedToken" /> </security>
Joachim Isaksson
3
TransportWithMessageCredentialle mode peut être une autre option.
Joachim Isaksson
3
TMLK, MessageSecurity peut signer et crypter la charge utile mise en mémoire tampon, mais tâtonne lors du traitement des flux. Avez-vous envisagé d'utiliser authenticationMode = IssuedTokenOverTransport?
OnoSendai
7
Voyons si je peux invoquer des fantômes du passé pour vous aider à sauver vos vacances. Quelques conseils ici: social.msdn.microsoft.com/Forums/vstudio/en-US
...
2
Avez-vous une chance de publier un projet de cas de test que d'autres pourraient expérimenter?
antiduh

Réponses:

41

WCF a des pièges dans quelques domaines avec le streaming (je vous regarde, MTOM 1 ) 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 ultérieures pour ce canal , pas la première demande) Ok, donc ce n'est pas exactement votre problème, mais veuillez suivre car j'arriverai au vôtre à la fin. Normalement, le défi HTTP fonctionne comme ceci:

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

Maintenant, si vous essayez d'activer le streaming MTOM sur un point de terminaison WCF sur le serveur, cela ne se plaindra pas. Mais, lorsque vous le configurez sur le proxy client (comme vous devriez, 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 tente d'empêcher est la suivante:

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

Notez que vous venez d'envoyer 200 Mo au serveur alors que vous ne deviez envoyer que 100 Mo. Eh bien, c'est 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'éloigne du sujet.

Ton problème

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

Il me semble que vous vous promenez maintenant dans une catégorie similaire de problèmes. Si vous activez la sécurité au niveau du message, 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 l'intégralité du flux pour signer le message unique (ce qui n'est pas vraiment un message, mais c'est un flux continu unique), 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 continu.

Donc, 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-Security pour ce faire. 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 pour un exemple comment j'ai résolu mon problème de streaming MTOM pour l'authentification de base, alors peut-être que vous pourriez prendre le courage de cela et mettre en œuvre 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 (elle reste activée sur le serveur,) en dehors 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 car je le fournirai moi-même à l'aide d'un inspecteur de messages et d'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 est destiné à toute personne souffrant du problème MTOM, mais également en tant que squelette pour que vous implémentiez quelque chose de similaire pour authentifier votre jeton généré par le service de jeton sécurisé principal par WIF.

J'espère que cela t'aides.

(1) Données volumineuses et streaming

(2) Sécurité des messages dans WCF (voir «inconvénients»).

x0n
la source
MTOM and Basic Authorization, et MTOM et OAuth2 ?
Kiquenet le