Implementing a simple RMI Application over the Internet
←
→
Transcription du contenu de la page
Si votre navigateur ne rend pas la page correctement, lisez s'il vous plaît le contenu de la page ci-dessous
Implementing a simple RMI Application over the Internet (using and comparing HTTP tunneling, RMI Proxy) Abstract Un problème se pose lorsque l’on veut effectuer des appels RMI à travers un firewall, car souvent il empêche l’accès à certains ports pour cause de sécurité. Une technique peut être le HTTP tunneling où les appels se font sous forme de requête HTTP à l’aide d’une servlet. Une deuxième technique expliquée dans ce document est le RMIProxy, dans laquel on place un proxy RMI pour chaque firewall, celui-ci effectue un contrôle pour ne laisser passer que les requête RMI, il permet aussi d’ajouter d’autres contraintes. Ce document permettra de se faire une idée sur ces deux concepts permettant aux requêtes RMI de traverser des firewall. Master Seminar Advanced Software Engineering Topics Prof. Jacques Pasquier-Rocha University of Fribourg, Switzerland Departement of Informatics Software Engineering Group Author : Fabien Pochon fabien.pochon@unifr.ch Supervisor : Patrik Fuhrer patrik.fuhrer@unifr.ch Jun 12, 2003
Master Seminar RMI Application over the Internet Table des matières 1. Introduction....................................................................................... 3 2. Problématique ................................................................................... 3 3. HTTP Tunneling ............................................................................... 4 3.1 Comment RMI « tunnelle » des messages.................................. 5 3.2 Naming services and « the server machine ».............................. 6 3.3 L’implémentation d’un servlet pour le HTTP tunneling ............ 7 3.3.1 Le code du servlet ................................................................ 7 3.4 Modifications à apporter afin d’utiliser le Tunneling ............... 12 3.5 Désavantages du HTTP tunneling ............................................ 12 3.6 Configuration du fichier policy................................................. 12 3.7 Exemple d’appel RMI utilisant HTTP tunneling...................... 13 4. RMIProxy ....................................................................................... 15 4.1 Où trouve-t-on RMIProxy ........................................................ 15 4.2 Les objectifs .............................................................................. 15 4.3 Les caractéristiques................................................................... 15 4.4 Le contrôle d’accès ................................................................... 16 4.5 L’architecture............................................................................ 17 4.6 Comment RMIProxy fonctionne............................................... 17 4.7 L’API coté client....................................................................... 18 4.8 L’API côté serveur.................................................................... 19 4.9 Proxies multiple ........................................................................ 20 4.10 Limitations du RMI Proxy ...................................................... 21 4.10.1 Activation......................................................................... 21 4.10.2 Stubs cachés ..................................................................... 21 4.10.3 Socket Factories ............................................................... 21 4.10.4 GetClientHost .................................................................. 21 4.10.5 RMI / IIOP ....................................................................... 21 4.10.6 Stubs distants indirectes................................................... 21 4.11 Les modifications à apporter au client et au serveur............... 22 5. Les différences entre RMI Proxy et HTTP tunneling..................... 23 6. Conclusion ...................................................................................... 23 Le RMI Proxy est certainement une méthode bien meilleur que le HTTP tunneling, car elle ne comporte que des avantages sur cette dernière. Malheureusement je n’ai pas testée. .................................... 23 7. Annexes : Configurations ............................................................... 24 7.1 Installation du seveur Apache................................................... 24 7.2 Installation de Tomcat............................................................... 24 7.3 Configuration du serveur Apache ............................................. 24 7.4 Redirection du script CGI ......................................................... 26 8. Bibliographie............................................................................... 27 2
Master Seminar RMI Application over the Internet 1. Introduction RMI (Remote Method Invocation) est un système distribué, ce qui signifie que une ou plusieurs applications peuvent s’exécuter sur une ou plusieurs machines. Une caractéristique d’un système distribué est le besoin de collaboration entre les différents processus qui composent le système. Ce qui donne l’illusion à l’utilisateur qu’il utilise une application locale, alors que des invocations de méthodes se font dans un autre processus qui peut être sur une machine différente. Il y a donc un échange d’informations qui s’effectue entre plusieurs machines à l’aide du protocole RMI. Ceci peut s’effectuer sans aucun problème lorsque il n’y a aucun firewall entre les deux parties communiquant via RMI. Lorsque un firewall sépare le client d’un serveur il y aura certainement un problème. C’est ce problème et la solution pour le contourner qui va donc être traité plus en détail dans ce document. 2. Problématique Le problème principal qui intervient lorsqu’un client veut effectuer un appel RMI sur une machine distante est effectivement le firewall qui peut y avoir entre les deux. En effet les firewalls interdisent souvent l’accès à certains ports spécialisés comme ceux qu’on désire utiliser lors d’un appel RMI. Les firewalls sont capables de filtrer des paquets selon : • Leur adresse source • Leur adresse de destination • Le port de destination • Le protocole de niveau supérieur (exemple : TCP, UDP) utilisé Cependant les firewalls sont généralement incapables de prendre des décisions en fonction des données contenues dans ces paquets. C’est pourquoi les techniques de tunneling, RMI Proxy ou encore d’autres peuvent être utilisées. 3
Master Seminar RMI Application over the Internet 3. HTTP Tunneling Le principe de base du http tunneling est d’utiliser comme protocole de communication des appelles http à travers le Web pour des application qui ne se font pas par le Web. Le requête originale émise n’est pas envoyé par un Web browser mais par une instance d’une URLClassLoader, et la réponse n’est pas une page html mais le code binaire d’une classe. HTTP tunneling est utilisé pour deux raisons essentielles, qui sont les deux très pragmatiques. Premièrement il permet au développeur d’applications d’utiliser les composants et l’infrastructure qui existe déjà pour les applications web, il est alors facile de définir un protocole utilisant ces composants. De plus il rend aussi facilement possible aux développeurs d’applications d’incorporer de multiples langages. Depuis que presque chaque langage moderne a sa librairie permettant de traiter et de générer du XML, utiliser HTTP et XML pour définir un protocole permet au client d’être écrit dans un langage et le serveur dans un autre. La seconde raison d’utiliser HTTP tunneling est qu’il est possible pour des applications distribuées d’éviter les firewalls. Pour comprendre pourquoi il suffit de regarger la figure 1. Figure 1 : HTTP tunneling en action (tirée de O’Reilly, Java RMI) Le point clé dans cette architecture est que le client utilise une couche supplémentaire (marshalling layer) qui encode la requête du client en une requête HTTP valide. Le serveur de l’autre coté possède aussi une couche supplémentaire (layer of demarshalling code), laquelle transforme une requête HTTP en une requête correspondant à celle attendue par le serveur. Le HTTP tunneling est divisé en trois parties : 4
Master Seminar RMI Application over the Internet • Le client : Envoie une requête au serveur web • Le servlet : Transmet la requête à la socket du serveur RMI en préservant la structure HTTP qui a été envoyée par le client • Le serveur : Transforme automatiquement l’envoi HTTP en une commande JRMP (Java Remote Method Invocation) 3.1 Comment RMI « tunnelle » des messages Maintenant le but du mécanisme de RMI HTTP tunneling devrait être clair : il encode un appel de méthode à distance à la façon d’une requête HTTP POST, et ensuite décode la « page web » retournée en une réponse du type approprié. RMI accomplit ceci de la même manière en utilisant un type élaboré de socket, dans laquelle une sérialisation est faite pour générer aussi bien les requêtes que les réponses HTTP. Bien qu’un différent mode de socket est utilisé, RMI va utiliser par défaut son propre mode de socket quand des connections seront créées. Les sockets par défauts retournées par RMI tentent d’utiliser HTTP tunneling si auparavant elles ont reçu une erreur du serveur. Les sockets par défaut emploient la stratégie suivante quand elles tentent une invocation de méthode sur un serveur : 1. Tentent d’établir une connexion JRMP directe vers le serveur. 2. Si la connexion échoue, elles tentent d’établir une connexion HTTP directe avec le serveur. Ainsi elles créent une connexion par socket vers le port sur lequel le serveur est en train d’écouter et ensuite communique en encapsulant les méthodes demandées dans des requêtes HTTP. La première chose que le « RMI demarshalling » effectue est de déterminer si la requête a été encodée en utilisant JRMP ou HTTP. 3. Si la requête échoue encore, elles tentent d’utiliser le firewall comme un serveur proxy (demandant au firewall de transmettre la requête au port approprié du serveur). Le firewall transmettra la requête comme une requête HTTP (le firewall ne va pas traduire la requête en appel RMI). 4. Si il y a encore un échec, elles tentent de se connecter sur le port 80 de la machine serveur et lui envoie la requête selon un URL commençant avec /cgi-bin/java-rmi.cgi. Cet URL signifie que la requête doit être transmise vers un programme qui interprète les requêtes HTTP et qui la transmet, comme une requête HTTP, vers le port approprié du serveur. Cette façon de communiquer peut paraître plutôt complexe. Ces points sont classés dans l’ordre décroissant de leur privilège. De plus ces points sont indépendants les uns des autres, il est possible d’imaginer un scénario pour lequel le quatrième point soit l’unique possibilité d’établir une communication. Par exemple supposons qu’un firewall ait été installé pour ne permettre qu’uniquement les connexions HTTP vers un serveur et qu’un serveur RMI soit derrière le firewall. Un client qui essaye de communiquer avec le serveur aura besoin 5
Master Seminar RMI Application over the Internet d’utiliser une stratégie différente selon si il se trouve derrière le firewall (il se trouve dans un intranet) ou en dehors du firewall (il se situe n’importe où ailleurs sur l’Internet). Si il se trouve à l’intérieur du firewall, la première stratégie de connexion (direct JRMP) sera disponible et aura donc un sens. Si le client est à l’extérieur du firewall, il y aura deux options qui dépendront du « firewall policy » . Si le firewall permet les connexions HTTP sur n’importe quel port et n’insiste pas sur un trafic sur le port 80, la seconde option d’envoyer une information HTTP directement sur le port sur lequel le serveur écoute est plus efficace. Cependant si le firewall insiste sur le fait que tout trafic HTTP doit être envoyé sur le port 80, alors la quatrième option sera la seule façon d’établir une connexion. 3.2 Naming services and « the server machine » Un point important, qui n’a pas encore été traité est que « la machine serveur » dans la quatrième option est une abstraction, elle n’est pas nécessairement sur la même machine que l’objet serveur actuel. C’est simplement un nom de machine stocké dans le stub. Ce niveau supplémentaire d’indirection permet au HTTP tunneling d’être implémenté de façon flexible. Prenons par exemples un firewall qui réduit toutes les connexions entrantes à être des requêtes HTTP sur le port 80. Si nous mettons le « naming service » à l’extérieur du firewall et la valeur du java.rmi.server.hostname prend le nom du server web, alors la quatrième option devra être utilisée. De cette façon, RMI tentera d’envoyer des messages à notre serveur en appelant un URL commançant par /cgi-bin/java-rmi.cgi. Ceci ne force pas le serveur RMI à fonctionner sur la même machine que le serveur web. Cela signifie simplement que nous avons besoin d’écrire une classe servlet qui effectue l’étape finale dans la redirection de la requête RMI et qui ensuite configure le serveur web afin qu’il envoye toutes les requêtes avec l’URL approprié au servlet. Figure 2 : Le cheminement des messages (tirée de O’Reilly, Java RMI) 6
Master Seminar RMI Application over the Internet L’architecture résultante ressemble au diagramme de la figure 2. Les nombres montrent le cheminement des messages. 3.3 L’implémentation d’un servlet pour le HTTP tunneling Pour le déroulement de la quatrième stratégie, il y a une manière de transmettre l’invocation de méthode au serveur RMI. La première implémentation de cette méthode que Sun Microsystems, Inc. développa était un script CGI. Les points suivants sont requis : • Chaque invocation distante de méthodes est envoyée comme une requête HTTP POST • L’URL complet utilisé est de la forme /cgi-bin/java-rmi.cgi ?forward=[port number] • Le corps du POST contient toutes les données de la requête distante comme un objet sérialisé qui est ensuite converti en une chaîne de caractères ASCII. Après que la spécification des servlets fut défini, Sun créa un servlet qui fournit les mêmes fonctionnalités que le script CGI. 3.3.1 Le code du servlet Le code du servlet développé consiste en deux classes principales et en une liste de classes d’exceptions. Les deux classes principales sont SimplifiedServletHandler et ServletForwardCommand. Elles ont les rôles suivants : SimplifiedServletHandler : Cette classe étend HTTPServlet. Elles reçoit les requêtes et effectue un validation préliminaire. ServletForwardCommand : C’est une liste de méthodes statiques qui sait comment interpréter un HTTP POST et renvoyer ceci au serveur RMI qui tourne sur la même machine. Le code de SimplifiedServletHandler : public class SimplifiedServletHandler extends HttpServlet { public void init(ServletConfig config) throws ServletException { super.init(config); System.out.println("Simplified RMI Servlet Handler loaded sucessfully."); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { String queryString = req.getQueryString(); String command, param; 7
Master Seminar RMI Application over the Internet int delim = queryString.indexOf("="); if (delim == -1) { command = queryString; param = ""; } else { command = queryString.substring(0, delim); param = queryString.substring(delim + 1); } if (command.equalsIgnoreCase("forward")) { try { LoggingServletForwardCommand.execute(req, res, param); } catch (ServletClientException e) { returnClientError(res, "client error: " + e.getMessage()); e.printStackTrace(); } catch (ServletServerException e) { returnServerError(res, "internal server error: " + e.getMessage()); e.printStackTrace(); } } else { returnClientError(res, "invalid command: " + command); } } catch (Exception e) { returnServerError(res, "internal error: " + e.getMessage()); e.printStackTrace(); } } public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { returnClientError(res, "GET Operation not supported: " + "Can only forward POST requests."); } public void doPut(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { returnClientError(res, "PUT Operation not supported: " + "Can only forward POST requests."); } public String getServletInfo() { return "RMI Call Forwarding Servlet Servlet.\n"; } private static void returnClientError(HttpServletResponse res, String message) throws IOException { res.sendError(HttpServletResponse.SC_BAD_REQUEST, "" + "Java RMI Client Error" + "" + "" + "Java RMI Client Error" + message + ""); System.err.println(HttpServletResponse.SC_BAD_REQUEST + "Java RMI Client Error" + message); } private static void returnServerError(HttpServletResponse res, String message) 8
Master Seminar RMI Application over the Internet throws IOException { res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "" + "Java RMI Server Error" + "" + "" + "Java RMI Server Error" + message + ""); System.err.println(HttpServletResponse.SC_INTERNAL_SERVER_ERROR + "Java RMI Server Error: " + message); } } Presque tout du processus logique de SimplifiedServletHandler est contenu à l’intérieur de la méthode doPost(). La méthode doPost() parcourt l’URL pour déterminer le port sur lequel le serveur écoute. Si l’URL est un URL valide et contient le port, la requête est ensuite transmise à la méthode statique execute() du ServletForwardCommand . Le ServletForwardCommand comprends une série de méthodes statiques présentées ci-dessous : public class ServletForwardCommand { public static void execute(HttpServletRequest request, HttpServletResponse response, String stringifiedPort) throws ServletClientException, ServletServerException, IOException { int port = convertStringToPort(stringifiedPort); Socket connectionToLocalServer = null; try { connectionToLocalServer = connectToLocalServer(port); forwardRequest(request, connectionToLocalServer); forwardResponse(response, connectionToLocalServer); } finally { if (null != connectionToLocalServer) { connectionToLocalServer.close(); } } } private static int convertStringToPort(String stringfiedPort) throws ServletClientException { int returnValue; try { returnValue = Integer.parseInt(stringfiedPort); } catch (NumberFormatException e) { throw new ServletClientException("invalid port number: " + stringfiedPort); } if (returnValue 0xFFFF) { throw new ServletClientException("invalid port: " + returnValue); } if (returnValue < 1024) { throw new ServletClientException("permission denied for port: " + returnValue); 9
Master Seminar RMI Application over the Internet } return returnValue; } private static Socket connectToLocalServer(int port) throws ServletServerException { Socket returnValue; try { returnValue = new Socket(InetAddress.getLocalHost(), port); } catch (IOException e) { throw new ServletServerException("could not connect to " + "local port"); } return returnValue; } private static void forwardRequest(HttpServletRequest request, Socket connectionToLocalServer) throws IOException, ServletClientException, ServletServerException { byte buffer[]; DataInputStream clientIn = new DataInputStream(request.getInputStream()); buffer = new byte[request.getContentLength()]; try { clientIn.readFully(buffer); } catch (EOFException e) { throw new ServletClientException("unexpected EOF " + "reading request body"); } catch (IOException e) { throw new ServletClientException("error reading request" + " body"); } DataOutputStream socketOut = null; // send to local server in HTTP try { socketOut = new DataOutputStream(connectionToLocalServer.getOutputStream()); socketOut.writeBytes("POST / HTTP/1.0\r\n"); socketOut.writeBytes("Content-length: " + request.getContentLength() + "\r\n\r\n"); socketOut.write(buffer); socketOut.flush(); } catch (IOException e) { throw new ServletServerException("error writing to server"); } } private static void forwardResponse(HttpServletResponse response, Socket connectionToLocalServer) throws IOException, ServletClientException, ServletServerException { byte[] buffer; DataInputStream socketIn; try { socketIn = new DataInputStream(connectionToLocalServer.getInputStream()); } catch (IOException e) { throw new ServletServerException("error reading from " + "server"); } String key = "Content-length:".toLowerCase(); boolean contentLengthFound = false; String line; int responseContentLength = -1; 10
Master Seminar RMI Application over the Internet do { try { line = socketIn.readLine(); } catch (IOException e) { throw new ServletServerException("error reading from server"); } if (line == null) { throw new ServletServerException("unexpected EOF reading server response"); } if (line.toLowerCase().startsWith(key)) { responseContentLength = Integer.parseInt(line.substring(key.length()).trim()); contentLengthFound = true; } } while ((line.length() != 0) && (line.charAt(0) != '\r') && (line.charAt(0) != '\n')); if (!contentLengthFound || responseContentLength < 0) throw new ServletServerException("missing or invalid content length in server response"); buffer = new byte[responseContentLength]; try { socketIn.readFully(buffer); } catch (EOFException e) { throw new ServletServerException("unexpected EOF reading server response"); } catch (IOException e) { throw new ServletServerException("error reading from server"); } response.setStatus(HttpServletResponse.SC_OK); response.setContentType("application/octet-stream"); response.setContentLength(buffer.length); try { OutputStream out = response.getOutputStream(); out.write(buffer); out.flush(); } catch (IOException e) { throw new ServletServerException("error writing response"); } } } Il y a trois points importants à relever dans ServletForwardCommand. Premièrement, on utilise des sockets pour transmettre le corps du message HTTP au serveur RMI et ensuite des simples « pipes » que le serveur RMI retourne dans des sorties HttpServletResponse. Le second point important est que ServletForwarder transmet le message sans décoder les données ou comprendre le message. Même si le corps de la requête HTTP contient des instances de classes sérialisées, ServletForwarder n’a pas besoin d’avoir ces classes dans son classpath ou de traiter les instances sérialisées, il s’occupe simplement de copier des bytes d’un endroit vers un autre. 11
Master Seminar RMI Application over the Internet 3.4 Modifications à apporter afin d’utiliser le Tunneling La socket RMISocketFactory tentera par défaut automatiquement d’utiliser le HTTP tunneling quand les autres connexions auront échouées. Il n’y a pas besoin de changer ni le code du serveur ni le code du client, il suffit simplement d’ajouter un ligne dans le code du client afin qu’il puisse utiliser le HTTP tunneling. Cette ligne est la suivante : RMISocketFactory.setSocketFactory( new sun.rmi.transport.proxy.RMIHttpTOCGISocketFactory()) ; 3.5 Désavantages du HTTP tunneling HTTP est souvent considéré comme une mauvaise idée à cause des quatres raisons suivantes : • Sécurité Le HTTP tunneling évite délibérément un mécanisme de sécurité qui a été mis en place par une autre personne qui le jugeait nécessaire. • L’inefficacité de la bande passante RMI est déjà à la base un protocole gourmand, il encode beaucoup d’informations dans chaque message de requête. Donc prendre le message RMI et l’insérer dans un envoi HTTP utilisera une plus grande largeur de bande. • L’inefficacité de la connexion HTTP tunneling ne peut pas créer une connexion puis la laisser ouverte et la réutiliser, il devra créer une nouvelle socket pour chaque requête qu’il effectuera. • La fragilité de l’application L’application entière est vulnérable à des changements dans la configuration du firewall ou du la topologie du réseau. 3.6 Configuration du fichier policy Chaque servlet est obligatoirement exécutée sous le contrôle d’un gestionnaire de sécurité (Security Manager). Celui-ci s’interpose entre la servlet et les ressources du système afin d’empêcher toutes tentatives d’accès illégales aux ressources du client. Les permissions d’accès sont définies par la politique de sécurité (Security Policy) installée sur le poste de travail. Lorsque aucune politique de sécurité n’est définie explicitement sur le client, le gestionnaire utilise alors la politique par défaut qui s’appelle le bac à sable (Sandbox). Le bac à sable interdit aux servlets tout accès (lecture et écriture) aux ressources de la machine locale. Quand une servlet tente d’accéder à une ressource du système, le gestionnaire de sécurité vérifie si le code exécuté a la permission d’accéder à cette ressource en 12
Master Seminar RMI Application over the Internet consultant la politique de sécurité. Si la permission est accordée, tout se passe normalement. Dans le cas contraire, une exception est levée. Les fichiers « .policy » servent à paramétrer la politique de sécurité du système local. Un fichier « .policy » contient un ensemble de répertoires auxquels sont attribués individuellement un ensemble de permissions. Avant de lancer le serveur web il est important de noter que dans Java 2 (jdk1.2 et versions ultérieures) les politiques de sécurité appliquées par la JVM ne permettent pas du téléchargement du code sans permission explicite. Pour cela nous avons besoin, du côté client comme du côté serveur d'un fichier de politique de sécurité qu'on nommera java.policy et contenant les lignes suivantes : permission java.net.SocketPermission "*:1024-65535","connect,accept"; permission java.net.SocketPermission "*:80", "connect"; Le fichier « .policy » utilisé pour pouvoir effectuer un HTTP tunneling doit contenir une ligne de plus chez le client afin de lui permettre l’utilisation de la « RMISocketFactory ». Cette ligne est la suivante : permission java.lang.RuntimePermission "setFactory"; 3.7 Exemple d’appel RMI utilisant HTTP tunneling Le serveur Apache, Tomcat et le serveur RMI tourne sur la même machine (ip : 134.201.21.73) à l’intérieur du firewall. Afin de pouvoir effectuer un appel RMI à travers le firewall nous devons nous connecter au serveur Apache depuis une machine extérieur au firewall. Si l’on a à disposition uniquement une machine à l’intérieur du réseau local, on peut utiliser comme illustré dans la figure 3 un programme s’appelant VNC qui permet de prendre le contrôle d’une machine distante (dans ce cas nous prendrons le contrôle d’une machine à l’extérieur du firewall). Déroulement des étapes : • Le client fait un appel RMI au serveur sous forme de requête HTTP grâce au socket factory utilisé. Un message de la forme /cgi-bin/java- rmi.cgi?forward=[port number] sera envoyé au serveur Apache (dans le cas présent le port 80 sera utilisé car c’est celui qui est à disposition par le firewall). • Dans un premier temps une servlet (fonctionnant sur le serveur grâce à Tomcat) contrôlera la syntaxe de ce message. Puis l’enverra sous forme HTTP POST au serveur RMI. • Le serveur RMI la traitera et enverra au serveur Apache une réponse de type HttpServletResponse. • Le servlet transmettra cette réponse au client sous la forme d’une réponse HTTP à l’aide de la socket factory utilisée. 13
Master Seminar RMI Application over the Internet Figure 3 : Déroulement d’un appel RMI utilisant HTTP tunneling 14
Master Seminar RMI Application over the Internet 4. RMIProxy Une seconde technique permettant le passage des appels RMI à travers un firewall est la méthode RMIProxy. Dans cette technique le client doit être configuré afin de pouvoir utiliser des sockets qui traversent le firewall. 4.1 Où trouve-t-on RMIProxy La version d’évaluation de RMIProxy peut être téléchargé sur le site www.rmiproxy.com. 4.2 Les objectifs Les objectifs de RMI Proxy sont les suivants : 1. Eliminer les problèmes de sécurités de RMI à travers HTTP pour l’administrateur réseau en fournissant des contrôles des exportations et des importations des services RMI, via une application firewall qui ne supporte uniquement le protocole RMI/JRMP. Aucune autre solution que RMIProxy existe. 2. Fournir une grande réduction des overhead dans le passage à travers un firewall comparé au HTTP tunneling. 3. Permettre un accès contrôlé vers le serveur RMI derrière le firewall. Aucune autre solution que RMIProxy existe. 4. Permettre un accès contrôlé des appels du côté client derrière un firewall. Aucune autre solution que RMIProxy existe. 5. Permettre au client RMI se trouvant derrière un firewall de contrôler l’accès vers un serveur RMI de l’autre côté du firewall. Les seules solutions existantes à ce problème sont les SOCKS et le HTTP tunneling. 6. Préserver toutes les propriétés de RMI. 7. Requérant un minimum de changement de code chez le client et le serveur RMI. 8. Reprendre le maximum d’avantages existants dans les classes java, les packages, et la configuration et les dispositifs de sécurité. 4.3 Les caractéristiques RMI Proxy est une application Java et une API qui permet le contrôle de la pénétration dans un firewall en approuvant les clients et les serveurs RMI. 15
Master Seminar RMI Application over the Internet RMI Proxy fournit un protocole RMI intermédiaire qui remplace RMI/HTTP tunneling, avec un haut degré de contrôle d’accès pour l’administrateur réseau. RMI Proxy est capable de : • bloquer l’accès aux protocoles non-JRMP • contrôler les écritures vers la RMI Proxy registry selon l’identification de l’hôte client. • permettre ou refuser l’accès et l’exécution de méthodes distantes au client RMI, selon l’identification de l’hôte client. • Permettre ou refuser le transfert de code par le service du codebase RMI, dans les deux directions. RMI Proxy fournit donc une grande réduction des overhead par rapport au HTTP tunneling. La durée de la connexion TCP est identique à une connexion RMI directe. L’overhead découlant de tout RMI Proxy dans la chaîne de proxy est moindre qu’une indirection RMI supplémentaire par proxy. RMI Proxy supporte entièrement : • Les firewalls coté client • Les firewalls coté serveur • Les appels du client derrière le firewall client RMI Proxy préserve toutes les propriétés de RMI excepté l’activation (les stubs activables sont séparés comme des stubs unicast), RMI/IIOP, et l’exactitude des valeurs retournées par le RemoteServer.getClientHost. L’utilisation de RMI Proxy nécessite uniquement un changement d’utilisation de la classe Naming en ProxyNaming, et modifier les propriétés du rmi.proxyHost chez le client, le serveur et les proxy intermédiaires. RMI Proxy utilise le mécanisme Java de security-policy existant pour contrôler la configuration, et utilise la java.rmi.AccessException existante pour le rapport des erreurs. RMI Proxy est une solution 100% Java. Les composants firewall RMI Proxy implémentent une RMI registry standard. 4.4 Le contrôle d’accès Le contrôle d’accès est fournit par les fichiers Java policy, lesquels contrôlent la communication à travers les firewalls. Le contrôle d’accès s’effectuent selon l’adresse IP ou le nom d’un client voulant passé le firewall et selon l’action que celui-ci veut effectuer, à l’aide d’une classe spéciale FirewallPermission. Le contrôle d’accès est capable d’être contre les actions suivantes : • L’accès à certains objets reçus ou envoyés implémentant l’interface distant • Bind, rebind ou unbind certains noms • L’exécution de certaines méthodes distantes • Chercher certains noms dans la proxy registry 16
Master Seminar RMI Application over the Internet 4.5 L’architecture RMI Proxy comprend les composants importants suivants : 1. le programme RMI Proxy, lequel s’exécute dans les proxy machines désignées 2. la classe ProxyNaming, une classe Naming modifiée, appelée par les clients et serveurs RMI. RMI Proxy comprend les sous-composants suivants : 1. Le Proxy Registry : une RMI Registry normale qui est soumise aux règles de contrôles d’accès fournis par l’implémentation du serveur RMIProxy. 2. Un protocole RMI qui négocie entre les flux descendants du serveur RMI et les flux montants du client RMI, effectuant la validation du protocole et le contrôle d’accès dans le processus. Le système compte sur deux faits élémentaires de RMI : une référence distante peut seulement être obtenue par un paramètre ou le résultat d’un appel de méthode distante, et la référence distante initiale est amorcée par un naming service (la RMI Registry). 4.6 Comment RMIProxy fonctionne Un serveur RMI derrière un firewall utilise le plus proche RMI Proxy afin d’avoir son RMI Registry. Le coté extérieur du serveur RMI Proxy rend disponible un objet distant aux clients extérieurs. Pour le client RMI, une opération « lookup » sur un objet distant doit être dirigée vers le serveur proxy. Si le client est aussi lui-même derrière un firewall, le « lookup » est délégué à son RMI Proxy. La partie extérieure du proxy du client est capable de communiquer par RMI avec la partie extérieure du proxy serveur. Les proxies RMI servent d’intermédiaires entre les entrées vers le serveur et les sorties du client. Ils effectuent la validation du protocole RMI et transmet les appels RMI de l’un vers l’autre. 17
Master Seminar RMI Application over the Internet Figure 4 : Exemple de configuration réseau RMI (tirée du guide de RMIProxy) Le RMI Proxy comprend trois composants : • RMI proxy Registry • un serveur RMI Proxying • une API utilisée par les client et les serveurs Le RMI proxy Registry et les composants serveur sont installés en association avec chaque firewall qui existe entre le client RMI et le serveur RMI. Il peut y avoir un nombre quelconque de firewall. Chacun doit ouvrir son port au trafique RMI, lequel est ensuite manipulé d’un manière sécurisée par le RMI proxy associé. La propriété système rmi.proxyHost, placée sur chaque hôte impliqué, dicte s’il faut déléguer la requête du coté client (comme par exemple : lookup), ou s’il faut la propager à la registry du coté serveur (par exemple : bind ou rebind), vers le prochain RMI Proxy dans la chaîne. Chaque RMI proxy a le droit de restreindre l’accès à l’aide d’un fichier policy. Des Restrictions peuvent être imposées comme nous l’avons vu plus haut par l’adresse IP ou le nom de l’hôte, par le nom du service distant, ou par l’interface distant et les noms de méthodes. 4.7 L’API coté client Quand un client RMI obtient une référence, il utilise normalement la méthode naming.lookup, avec un URL RMI de la forme : rmi://host/name où le host est le nom de l’hôte sur lequel la RMI Registry tourne, et le name est le nom de l’objet distant se trouvant dans la registry. 18
Master Seminar RMI Application over the Internet Dans l’API de RMIProxy, le client utilise ProxyNaming à la place de Naming, Si un proxy est présent du coté client, les propriétés du système rmi.proxyHost spécifient son emplacement comme une URL RMI. Si cette propriété est présente, le client fonctionne dans un environnement RMI Proxy, donc le lookup est délégué au RMI Proxy, lequel est moins restrictif vers l’extérieur que le client original. Si ce Proxy fonctionne avec un autre Proxy le lookup est redélégué et ainsi de suite jusqu’au Proxy final atteint à l’extérieur du firewall coté client, à ce point le Naming.lookup standard est exécuté, comme si aucun Proxy n’avait été entre deux. Ce lookup, soit il réussi soit il échoue. Si il échoue, une exception RMI est propagé en retour vers le client original, et rien de remarquable se produit (l’objet distant n’a pas été trouvé). Si le lookup réussit, le stub obtenu (le flux descendant du stub) vérifie l’accès au RMI Proxy. Cela peut encore échouer avec une java.rmi.AccessException ; autrement un stub se référant à l’hôte RMI Proxy est retourné au client (le flux montant du stub). Le RMI Proxy maintient un lien entre le flux montant et descendant du stub, et transmet les appels RMI de l’un à l’autre, sujet du contrôle d’accès. Du point de vue du client, l’existence de RMI Proxy est invisible. Tout ce que le client sait, c’est qu’il a demandé un naming service (en utilisant ProxaNaming) pour une référence distante et qu’il la obtenu ; le référence distante est du type expected, et le travail de la référence est de communiquer avec l’objet distant. 4.8 L’API côté serveur Un serveur RMI accessible par internet est lié avec la méthode ProxyNaming.bind à la place de Naming.bind ou Registry.bind, avec une URL RMI de la forme : rmi://host/name où host est le nom de l’hôte sur lequel tourne la RMI Registry, et name est le nom de l’objet distant dans cette registry. Avec les règles normales de RMI, un serveur RMI peut seulement être lié à un nom dans la RMI Registry par un processus tournant sur la même machine que la Registry. RMI Proxy modifie ces contraintes (le sujet du contrôle d’accès pour le proxy). Les propriétés système rmi.ProxHost nomment un hôte accessible où un RMI Proxy tourne. Si cette propriété est présente, la JVM tourne dans un environnement RMI Proxy, donc l’opération ProxyNaming.bind est exécutée en même temps dans la registry locale et dans le RMI Proxy. Si il y a un autre proxy pour ce proxy, l’opération est redéléguée et ainsi de suite jusqu’à ce que le proxy final soit atteint. Dans l’hôte original et dans chaque proxy incluant le dernier, le standard Naming.bind est exécuté par le ProxyNaming.bind. Ceci force le binding à se produire dans toutes les registry exécutées dans la chaîne des proxy. Le serveur est donc lié à la registry locale et aux registries de la chaîne des proxy. Une opération lookup de chaque proxy registries retourne un stub de flux montant se référant à un RMI Proxy, lequel transmet vers le stub de flux descendant après avoir effectué un contrôle d’accès, exactement comme dans le cas de l’API coté client, éventuellement en atteignant le serveur RMI original. 19
Master Seminar RMI Application over the Internet A l’instar de l’appel Naming.unbind ou Registry.unbind, un proxied RMI serveur est délié par ProxyNaming.unbind. Cette action d’abord appelée Naming.unbind et ensuite délégué à la chaîne de proxy, entraîne unbinding de toutes les registries de la chaîne de proxy. De cette manière, chaque RMI serveur se lie vers une registry se trouvant dernière un ou plusieurs firewalls, et chaque serveur RMI accessible du serveur initial, sont rendus accessibles depuis internet, en étant rendus visibles par le plus proche RMI Proxy, et ainsi de suite récursivement. Un client RMI peut maintenant rechercher un serveur RMI à l’extérieur du proxy côté serveur et obtenir un stub de flux descendant lequel finalement il se réfère, en passant par une chaîne de RMI Proxies, vers un serveur à l’intérieur du firewall. Ce serveur n’est donc pas directement accessible par le client. 4.9 Proxies multiple Si le client est derrière un ou plusieurs firewalls, ses propres RMI Proxies côté client feront leurs propre délégations, ainsi le client reçoit un stub qu’il peut utiliser. Le dernier stub de flux montant pointe à l’intérieur du proxy côté client, lequel pointe lui-même vers le suivant, lequel pointe éventuellement à l’extérieur du proxy côté serveur, lequel pointe à l’intérieur vers le suivant, lequel finalement point vers le serveur de flux descendant, comme illustré dans la figure 4. Figure 5 : multiples proxies côté client et serveur (tirée des « white paper » de RMIProxy) 20
Master Seminar RMI Application over the Internet 4.10 Limitations du RMI Proxy 4.10.1 Activation RMI Proxy supporte l’activation RMI de façon limitée. L’implémentation actuelle réécrit les stubs activables comme des stubs unicasts normaux (UnicastRemoteObject). L’effet de ceci est que « l’activabilité » de l’objet est perdu après le passage dans le premier proxy de la chaîne sortant du serveur ; tous les clients de flux montant, incluant les autres chaînes de proxies du coté serveur et client et éventuellement le vrai client, sont retournés comme des stubs non- activables. 4.10.2 Stubs cachés L’implémentation actuelle n’effectue pas d’arrangement proxying pour les stubs cachés (les stubs à l’intérieur de MarshalledObjects), c’est le but de la classe MarshalledObject pour rendre ses contenus invisibles aux programmes Java intermédiaires. 4.10.3 Socket Factories Les socket factories ne sont actuellement pas supportées, principalement pour des raisons liées à la standardisation des numéros de port. Chaque nouvelle socket factory introduite dans une nouvelle RMI JVM requiert un nouveau numéro de port. Nous créons un plan pour supporter RMI/SSL par un numéro de port standard, ce qui résout de nombreux problèmes liés. Quand ce sera disponible, RMI Proxy pourra être intégré avec l’extension de sécurité RMI proposée par JDK 1.4. 4.10.4 GetClientHost Le résultat retourné par RemoteServer.getClientHost est l’InetAddress du RMI Proxy le plus proche dans la chaîne de proxies, jamais le vrai client RMI. Pour cette raison l’hôte client validé par la firewallPermission peut être le RMI Proxy suivant plutôt que le vrai client. 4.10.5 RMI / IIOP L’implémentation actuelle ne supporte pas les stubs RMI/IIOP. Avec une petite modification dans l’implémentation des classes stub javax.rmi.* ce problème peut être résolu. 4.10.6 Stubs distants indirectes 21
Master Seminar RMI Application over the Internet L’implémentation actuelle traite correctement les stubs distants lesquels sont accessibles par des paramètres ou des valeurs retournées. Ceci oblige de changer le RMIProxy avec une petite modification dans la classe sun.rmi.MarshalOutputStream. Ceci est requit uniquement chez le RMI Proxy mais pas dans l’API du serveur ou du client. 4.11 Les modifications à apporter au client et au serveur Les points suivants sont nécessaires pour établir un RMI Proxying : • Modifier le code du client et du serveur pour qu’ils utilisent la classe com.rmiproxy.ProxyNaming au lieu de java.rmi.Naming. Exemple de code : RemoteEcho echoObject = (RemoteEcho)ProxyNaming.lookup(« rmi://rmi.server.com/ »+ RemoteEcho.class.getName()) ; • Définir correctement les propriétés du système rmi.proxyHost chez le client, le serveur et chaque serveurs proxy afin que celle-ci contienne l’URL du RMI Proxy le plus proche. Exemple : Rmi.proxyHost=rmi://proxy.clientlan0.client.com • Configurer correctement le fichier policy chez le client et le serveur. • Installer, configurer et exécuter le programme RMI Proxy sur les serveurs proxy. • Du coté serveur la classe com.rmiproxy.ProxyNaming doit être utilisée à la place de la classe java.rmi.Naming. Exemple de code : ProxyNaming.bind(« rmi://localhost/ »+RemoteEcho.class.getName()); 22
Master Seminar RMI Application over the Internet 5. Les différences entre RMI Proxy et HTTP tunneling HTTP tunneling RMI Proxy Il n’est pas très fiable. Si on Plus robuste au changement modifie le firewall il est possible que du firewall. le tunneling ne fonctionne plus. Ces performances sont faibles.Il Les requêtes sont des requêtes encode beaucoup d’informations dans RMI sans information chaque message de requête. Il doit supplémentaire. créer une nouvelle socket pour chaque requête qu’il effectue. Il ne fonctionne pas à travers tous Fonctionne avec tout type de les types de firewall. firewall. Inefficacité de la connexion, il ne Connexion ne se referme pas peut pas créer un connexion et la après chaque requête. laisser ouverte pour pouvoir la réutiliser, il doit créer une nouvelle socket pour chaque requête qu’il effectue. 6. Conclusion Le RMI Proxy est certainement une méthode bien meilleur que le HTTP tunneling, car elle ne comporte que des avantages sur cette dernière. Malheureusement je n’ai pas testée. La configuration de Apache afin qu’il supporte Tomcat et qu’il puisse utiliser une servlet à la place du fichier cgi-bin m’a pris énormément de temps, pour que je puisse finalement montrer un exemple utilisant le HTTP tunneling. L’emplacement des fichiers servlet dans le serveur n’est pas si évident non plus. Il est finalement intéressant de s’apercevoir qu’avec le HTTP tunneling il est possible de contourner une permission qui n’a volontairement pas été donnée par au firewall en « marshallisant » un requête et en l’a « démarshallisant » de l’autre coté du firewall. Même si j’ai trouvé un peu moins intéressant la technique du RMIProxy car elle ne contourne pas vraiment une interdiction que l’on a définit (elle laisse ouvert un port tout en contrôlant ce qui y passe), c’est une technique qui me paraît comme expliquée dans ce document plus stable et plus robuste que le HTTP tunneling. 23
Master Seminar RMI Application over the Internet 7. Annexes : Configurations 7.1 Installation du seveur Apache Il suffit simplement de télécharger le fichier binaire Win32 apache_1.3.27- win32-x86-no_src.msi à l’adresse http://httpd.apache.org/download.cgi. Puis lancer ce fichier en double cliquant dessus et l’installation commencera. 7.2 Installation de Tomcat Télécharger le fichier jakarta-tomcat-4.0.6.exe à l’adresse http://jakarta.apache.org/builds/jakarta-tomcat-4.0/release/v4.0.6/bin/ , puis exécuter l’installation en double-cliquant sur ce fichier. 7.3 Configuration du serveur Apache Tomcat doit être intégré à Apache afin de pouvoir interpréter les servlets qui sont nécessaire au HTTP tunneling. Pour mettre en oeuvre une telle architecture, il faudra donc disposer d'un serveur Apache "up and running" et de Tomcat. Pour vérifier que Tomcat est convenablement installé, ouvrez votre navigateur et saisissez comme URL http://nomduserveur:8080/. Essayez de naviguer dans la section "Web Applications" et testez les exemples "Servlet" et "JSP". En cas de problème, vous pouvez consulter les journaux de Tomcat dans le sous-répertoire logs de Tomcat. L'intégration de Tomcat et d'Apache se fait par l'intermédiaire du module mod_webapp. Assurez-vous tout d'abord de disposer d'une installation fonctionnelle d'Apache. Ouvrer votre navigateur et à l’adresse http://nomduserveur la page principale d’apache doit apparaître. Télécharger ensuite mod_webapp à l'URL suivante : http://jakarta.apache.org/builds/jakarta-tomcat-4.0/release/v4.0.1/ bin/win32/webapp-module-1.0-tc40-windows.zip. Décompactez le contenu de l'archive téléchargée dans le sous-répertoire modules d'Apache. Editez le fichier httpd.conf et, dans la section 1, dans la partie relative aux modules, ajoutez la ligne suivante après toute autre concernant le chargement de modules optionnels : LoadModule webapp_module modules/mod_webapp.so Ajoutez ensuite les lignes suivantes juste avant la section trois (consacrée aux hôtes virtuels) : 24
Master Seminar RMI Application over the Internet # Tomcat WebAppConnection warpConnection warp localhost:8008 WebAppDeploy examples warpConnection /examples/ WebAppInfo /webapp-info Voici une description des directives utilisées : WebAppConnection warpConnection warp localhost:8008 Indique à Apache d'établir une connexion avec Tomcat par l'intermédiaire d'un processus spécifique, Warp, écoutant sur le port 8008. Un bref aperçu de la directive ci-dessus : WebAppConnection [connection name] [provider] [host:port] [connection name] Un nom qui va être utilisé pour décrire la connexion entre Apache et Tomcat. [provider] Le nom du fournisseur de service permettant de se connecter à Tomcat. Actuellement, seul le mot-clé "warp" est utilisable. [host:port] Le couple nom d'hôte:port décrivant le nom du serveur Tomcat et le port utilisé pour se connecter au fournisseur de service décrit ci-dessus. Il faut remplacer localhost par le nom du serveur Tomcat si Apache et Tomcat ne sont pas sur la même machine. Le port utilisé pour la connexion entre Apache et Tomcat est défini dans le fichier server.xml, dans la section "org.apache.catalina.connectors.warp.WarpConnector", pas la section HTTP. Ce fichier peut être trouvé dans le sous-répertoire conf de Tomcat. WebAppDeploy examples warpConnection /examples/ Cette directive indique de "déployer", c'est-à-dire mettre à disposition une application Web. Dans le cas présent, nous déployons une application nommée "examples" (à laquelle correspond un sous-répertoire examples dans le répertoire webapps de Tomcat), accédée par Apache au travers de la connexion "warpConnection" et disponible pour l'utilisateur à l'URL "/examples/". Voici le détail de la directive ci-dessus : WebAppDeploy [application name] [connection name] [url path] [application name] Le nom de l'application Web correspondant à un répertoire dans le sous- répertoire webapps de Tomcat. Celui-ci contient une structure complète décrivant l'application. Si l'application se présente sous la forme de fichier portant l'extension "war", il convient de spécifier le nom du fichier, par exemple "monApplication.war". [connection name] Le nom de connexion précédemment déclarée au niveau de la directive WebAppConnection. 25
Master Seminar RMI Application over the Internet [url path] L'URL par laquelle l'application Web sera joignable. La directive "WebAppDeploy" est liée à la déclaration d'un hôte. Ainsi, si une directive "WebAppDeploy" se trouve déclarée au niveau d'une section , l'application Web ne sera disponible que pour cet hôte virtuel. WebAppInfo /webapp-info Cette dernière directive permet de connaître le statut de toutes les connexions configurées et des applications déployées. Son utilisation est très simple : WebAppInfo /URL Le statut du serveur Tomcat pourra être consulté à l'URL http://server.name:port/webapp-info/ (le "/" à la fin est indispensable). Après avoir modifié le fichier httpd.conf, stoppez et rédemarrez Apache. Rendez-vous ensuite à l'URL http://nomduserveurApache/examples/jsp/index.html. Après ceci vous devriez disposer d'une plate-forme permettant de développer des applications Web basées sur Java. 7.4 Redirection du script CGI Puisque l’on n’utilise pas le script CGI mais un servlet pour effectuer le tunneling il faudra donc effectuer une redirection à l’intérieur du serveur, du script forme java-rmi.cgi afin que ce soit le servlet SimplifiedServletHandler qui soit exécuté lorsque le serveur appellera le script avec l’adresse /cgi-bin/java- rmi.cgi ?forward=[port numbe]. Deux modules sont utilisés pour la redirection ce sont mod_alias et mod_rewrite. Il faut éditer le fichier httpd.conf d’Apache et ajouter le script ci-dessous juste après les directives concernant les Alias. • RewriteEngine on RewriteLog /usr/local/Apache/logs/rewrite.log RewriteLogLevel 9 RewriteRule ^/cgi-bin/java\-rmi\.cgi$ http://localhost/rmi/servlet/ServletHandler [P] • L’option [P] force la redirection. 26
Master Seminar RMI Application over the Internet 8. Bibliographie [1] William Grosso. Java RMI, O’Reilly & Associates, Inc. 2002 [2] Esmond Pitt and Neil Belford. The RMI Proxy White Paper, 2002 [3] JGuru, Remote Methode Invocation (RMI), http://developer.java.sun.com/developer/onlineTraining/rmi/RMI.html#Fir ewallIssues, 1996-2000 (accédée le 10. 06. 03) [4] David M. Howard, Apache – Tomcat – RMI HTTP TUNNELING HOW-TO or 10 Steps to RMI Tunneling, http://shannon.informatik.fh- wiesbaden.de/jsp/jsp/docs/ApacheTomcatRMI.html (accédée le 10. 06. 03) [5] Yun, Young-Sik. Frequently Asked Questions RMI and Object Serialization. http://caic.chungnam.ac.kr/youngsik/rmi/sun/start_tutorial/faq.html, 1999 (accédée le 10. 06. 03) [6] M.Geldenhuys, Le Guide d'installation de Tomcat pour Windows, http://www.apachefrance.com/Articles/5/, 2001 (accédée le 10. 06. 03) [7] Sun Microsystems, Inc. Java Security Architecture. http://java.sun.com/products/jdk/1.2/docs/guide/security/spec/security- specTOC.fm.html, 1997-1998 (accédée le 10. 06. 03) 27
Vous pouvez aussi lire