Netzprogrammierung: Java RMI - Remote Method Invocation (Teil 2) Robert Tolksdorf und Peter Löhr Überblick 1. 2. 3. 4. Auffinden von .class-Dateien Serializable Parameter Sicherheit Code laden übers Web Zur Erinnerung - Dokumentation RMI: http://java.sun.com/javase/6/docs/platform/rmi/spec/rmiTOC.html http://java.sun.com/docs/books/tutorial/rmi/ Robert Tolksdorf und Peter Löhr 2 Auffinden von .class-Dateien Robert Tolksdorf und Peter Löhr Nochmal Beispiel Counter Zur Erinnerung - Beispiel Counter: Schnittstelle Counter.java Anbieter CounterImpl.java Klient Inc.java ann@server:~/anncount ann@server:~/anncount ann@server:~/anncount Counter.class CounterImpl.class ann@server:~/anncount bob@client:~/bobcount bob@client:~/bobcount bob@client:~/bobcount Counter.class Inc.class bob@client:~/bobcount $ javac CounterImpl.java $ rm *.java $ ls|more $ $ javac Inc.java $ rm *.java $ ls|more (... getRegistry("server"); ...) $ Robert Tolksdorf und Peter Löhr 4 Nochmal Beispiel Counter ann@server:~ann/anncount $ rmiregistry & [1] 8774 ann@server:~ann/anncount $ java CounterImpl & [1] 8777 Dies scheitert, wenn im Klassenpfad ann@server:~ann/anncount $ des rmiregistry (!) die Counter.class nicht gefunden wird ! bob@client:~/bobcount $ java Inc 5 counter is 5 bob@client:~/bobcount $ Robert Tolksdorf und Peter Löhr 5 .class-Dateien über das Web laden Saubere Lösung mit vollständiger Entkoppelung des Registry von den zu verwaltenden Objekten: eve@server:~ $ rmiregistry & [1] 8774 eve@server:~ $ (am besten ohne CLASSPATH) ann@server:~ann/anncount $ java \ > -Djava.rmi.server.codebase=http://page.mi.fu-berlin.de/~ann/classes/ \ > CounterImpl & [1] 8777 ann@server:~ann/anncount $ Codebasis für Registry („server“) wird durch URL identifiziert. Counter.class wird in classes vorgehalten. Robert Tolksdorf und Peter Löhr 6 Serializable Parameter Robert Tolksdorf und Peter Löhr Remote versus Serializable Zur Erinnerung - Fernaufruf-Parameter mit Verweistyp: • Wenn der aktuelle Parameter Remote ist: Übergabe eines Fernverweises. • Wenn der aktuelle Parameter nicht Remote, aber Serializable ist: Übergabe einer Objektkopie. • Sonst: Laufzeitfehler MarshalException. Robert Tolksdorf und Peter Löhr 8 Code nachladen Übergabe einer Kopie eines Serializable Objekts wie kommt der Empfänger zum Code des Objekts? • Kein Problem, wenn Klasse beim Empfänger vorhanden, genauer: wenn dort über CLASSPATH erreichbar; • wenn nicht, eventuell java -cp <classpath> einsetzbar, wenn Sender und Empfänger im gleichen Dateisystem. • Fernladen: Häufig liegt die Klasse in einem fremden Dateisystem auf einem anderem Rechner, typischerweise am Ort des entfernten Objekts. Dann kann der Code mit einer URL identifiziert und über das Web geladen werden (s.u.). • Achtung - Sicherheitsprobleme bei Verwendung fremden Codes! Robert Tolksdorf und Peter Löhr 9 Beispiel: Fernaufrufbares Factory-Objekt zur Erzeugung von Serializable Objekten, die die Klienten sich per Fernaufruf beschaffen können. Die Klienten wissen nichts von der Implementierung - und wollen auch nichts davon wissen. ... implements Factory ... new ... data data code code ? Netzdateisystem: Robert Tolksdorf und Peter Löhr code 10 Beispiel: Fabrik für Echo-Objekte interface Echo { // is not Remote ! void print(); // print echo } import java.rmi.*; interface Factory extends Remote { Echo create(String s) throws RemoteException; } // creates an echo of s anna als Anbieter der Fabrik stellt den Benutzern lediglich diese zwei Schnittstellen zur Verfügung. import java.io.Serializable; public class EchoImpl implements Echo, Serializable { public EchoImpl(String s) { echo = s; } String echo; import java.rmi.server.UnicastRemoteObject; public void print() { import java.rmi.registry.LocateRegistry; System.out.println(echo); import java.rmi.registry.Registry; public class EchoFactory implements Factory { } public Echo create(String s) { } Robert Tolksdorf und Peter Löhr return new EchoImpl(s.substring(1,s.length())); } public static void main(String[] arg) throws Exception { Factory f = new EchoFactory(); 11 Factory stub = (Factory) UnicastRemoteObject.exportObje Registry registry = LocateRegistry.getRegistry(); registry.bind("factory", stub); Beispiel: Fabrik für Echo-Objekte Mit Kenntnis der Schnittstellen (und des Namens der Fabrik im Registry) verfasst der Benutzer paul ein Klientenprogramm: import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Test { public static void main(String[] arg) throws Exception{ Registry registry = LocateRegistry.getRegistry(); Factory f = (Factory) registry.lookup("factory"); Echo x = f.create(arg[0]); // delivers copy ! x.print(); // local call ! } } Robert Tolksdorf und Peter Löhr 12 Test mit Netzdateisystem anna@human:~/echo/classes Echo.class EchoFactory.class EchoImpl.class Factory.class anna@human:~/echo/classes [1] 1373 anna@human:~/echo/classes [2] 1374 anna@human:~/echo/classes $ ls | more $ rmiregistry & $ java EchoFactory & $ paul@troll:~/test/classes $ ls | more Echo.class Factory.class Test.class paul@troll:~/test/classes $ java Test wesel Exception in thread "main" java.rmi.UnmarshalException: error unmarshalling return; nested exception is: java.lang.ClassNotFoundException: EchoImpl ..................... Robert Tolksdorf und Peter Löhr 13 Test mit Netzdateisystem Reparieren: paul@troll:~/test/classes $ ls | more Echo.class Factory.class Test.class paul@troll:~/test/classes $ java -cp .:/home/anna/echo/classes Test wesel esel paul@troll:~/test/classes $ (Damit sind übrigens auch diese entbehrlich!) Achtung: paul muss auf die Dateien in /home/anna/echo/classes Lesezugriff haben - sonst gibt es wiederum eine java.lang.ClassNotFoundException: EchoImpl Robert Tolksdorf und Peter Löhr 14 Entfernte Benutzung der Fabrik Bei Benutzung der Fabrik von einem fremden Dateisystem aus (evtl. Firewall mit VPN umgehen) muss der Code von EchoImpl mit dem RMI-Klassenlader über das Web heruntergeladen werden. Die URL der entsprechenden Codebasis wird von der Fabrik mit dem EchoImpl-Objekt mitgeliefert (s.u.). paul@xyz:~/test/classes $ java Test wesel Exception in thread "main" java.rmi.UnmarshalException: error unmarshalling return; nested exception is: java.lang.ClassNotFoundException: EchoImpl (no security manager: RMI class loader disabled) ........... Das Nachladen von Code über das Web wird verweigert, wenn kein Security Manager installiert ist! Robert Tolksdorf und Peter Löhr 15 Sicherheit http://java.sun.com/javase/6/docs/technotes/guides/security/ http://java.sun.com/docs/books/tutorial/security/ Robert Tolksdorf und Peter Löhr Vorsicht vor Schadcode! • Fremder Code verhält sich vielleicht anders als erwartet! • Schadcode (malware) hat unerwünschte Funktionalität, die dem Benutzer Schaden zufügen kann. Typisch sind: • Trojanische Pferde - haben zwar die gewünschte Funktionalität, aber zusätzlich (und verdeckt) eine schädliche Funktionalität; • Viren - sind Schadcode, der zusätzlich zu seiner schädlichen Funktionalität auch anderen - bislang unschädlichen - Code infiziert. • ! Sicherheitsbewusstes Verhalten: keinen Code benutzen, • den man nicht selbst geschrieben hat oder • dessen Autor man vertraut (?) oder • der erfahrungsgemäß richtig funktioniert (??). Robert Tolksdorf und Peter Löhr 17 EchoImpl ein Trojanisches Pferd? Zur Erinnerung (S. 14): bei der Ausführung des Programms Test paul@troll:~/test/classes $ java -cp .:/home/anna/echo/classes Test wesel esel holt paul sich eventuell ein Trojanisches Pferd von anna ins Haus: Der Code der von der Echo-Fabrik gelieferten EchoImpl-Objektkopie produziert zwar das erwartete Echo, könnte aber auch Schadcode enthalten, der Dateien zerstört, Dateiinhalte kopiert, etc. etc. Robert Tolksdorf und Peter Löhr 18 Security Manager Schutz vor fremdem Schadcode wird durch Mitlaufenlassen eines Security Manager ermöglicht: $ java -Djava.security.manager ..... Security Manager folgt benutzerdefinierten Richtlinien (policies), die in entsprechenden Richtlinien-Dateien bereitgestellt sind: ~/.java.policy (privat) $JAVAHOME/jre/lib/security/java.policy (global) ... und andere, explizit benannte Dateien Eine Richtlinien-Datei (policy file) enthält eine Menge von Berechtigungen (Erlaubissen, permissions), die für Code verschiedener Herkunft verschiedene Aktionen erlauben. ! Was nicht erlaubt ist, ist verboten. Wenn keine Richtlinien-Datei vorliegt, ist nichts erlaubt ! Robert Tolksdorf und Peter Löhr 19 Richtlinien-Datei Beispiel für eine einfache Richtlinien-Datei mypolicy von bob: grant codeBase "file:${user.home}/-" { permission java.security.AllPermission; }; grant codeBase "file:/usr/ann/classes/*" { permission java.io.FilePermission "/usr/bob/help/*", "read"; permission java.net.SocketPermission "localhost:1024-", "connect, accept"; } grant { // allows any code to read in pub subtree permission java.io.FilePermission "/usr/bob/pub/-", "read"; }; Benutzung z.B. so: bob: java -Djava.security.manager \ > -Djava.security.policy=mypolicy .... Robert Tolksdorf und Peter Löhr 20 Richtlinien-Datei bearbeiten Fensterbasierte Bearbeitung von Richtlinien-Dateien: lohr: policytool --> http://java.sun.com/javase/6/docs/technotes/guides/security/PolicyFiles.html http://java.sun.com/javase/6/docs/technotes/guides/security/permissions.html Robert Tolksdorf und Peter Löhr 21 Beispiel: System Property erfragen import java.lang.*; import java.security.*; class GetProps { // from http://java.sun.com/docs/books/tutorial/security/ public static void main(String[] args) throws Exception { s = System.getProperty("user.home", "not specified"); System.out.println(" Your user home directory is: " + s); } } $ java GetProps Your user home directory is: /Users/lohr $ java -Djava.security.manager GetProps Exception in thread "main" java.security.AccessControlException: access denied (java.util.PropertyPermission user.home read) ....... $ Robert Tolksdorf und Peter Löhr 22 Beispiel: System Property erfragen Geeignete Richtlinien-Datei mypolicy in getprops einrichten: grant codeBase "file:/Users/lohr/samples/getprops/*" { permission java.util.PropertyPermission "user.home", "read"; }; Und dann: $ ls | more GetProps.class GetProps.java mypolicy $ java -Djava.security.manager -Djava.security.policy=mypolicy GetProps Your user home directory is: /Users/lohr $ Robert Tolksdorf und Peter Löhr 23 Security Manager programmatisch Ein Security Manager kann auch per Programm gesetzt werden: public static void main(String[] arg) { if (System.getSecurityManager() == null) System.setSecurityManager(new SecurityManager()); ....... } Damit ist sichergestellt, dass ein Security Manager garantiert vorhanden ist. Robert Tolksdorf und Peter Löhr 24 Code laden übers Web http://java.sun.com/javase/6/docs/technotes/guides/rmi/codebase.html Robert Tolksdorf und Peter Löhr Codebasis im Web • Zur Erinnerung: mit CLASSPATH oder mit der Option -cp kann man sich nur auf Dateien im eigenen Dateisystem (bzw. Netzdateisystem) beziehen. • Das Nachladen von Code (.class-Dateien oder .jar-Dateien) aus fremden Dateisystemen ist nur über das Web möglich: • Ausnutzung vorhandener Infrastruktur (HTTP-Protokoll) • keine Probleme mit Firewalls • Somit wird eine URL verwendet, um ein Verzeichnis, das als Codebasis (codebase) dienen soll, zu identifizieren. • Achtung: Da die Firewall zwischen Internet und Intranet i.a. nur den Verkehr über wenige Standard-Ports erlaubt, braucht man VPN für intranet-überschreitendes RMI ! Robert Tolksdorf und Peter Löhr 26 Entfernte Benutzung der Echo-Fabrik Revision des Ansatzes von S. 15: Anbieter: Benutzer lohr auf human.inf.fu-berlin.de Klient: „anderer“ lohr auf PC über VPN mit diesen Richtlinien testpolicy : grant codeBase "file:/Users/lohr/-" { permission java.security.AllPermission; }; grant codeBase "http://page.mi.fu-berlin.de/~lohr/classes/-" { permission . . . . . . . }; Hierfür sollten Berechtigungen nur sehr restriktiv vergeben werden! In unserem Fall sollte der Eintrag sogar ganz weggelassen werden, weil die print-Methode von EchoImpl keine Berechtigungen benötigt; damit sind wir vor Trojanischem Code geschützt! Robert Tolksdorf und Peter Löhr 27 Randbedingungen • Die URL einer Codebasis im Web bezeichnet ein Verzeichnis, • das entweder die Klassendateien (.class) in Unterverzeichnissen gemäß der Paketstruktur enthält • oder eine entsprechende Archivdatei (.jar) enthält. • Für das Echo-Beispiel wählen wir eine einfache Paketisierung: • echo enthält die Schnittstellen Echo und Factory ; • factory enthält die Klassen EchoImpl und EchoFactory ; • echotest enthält die Klasse Test. • Beim Start der Fabrik EchoFactory muss die Codebasis angegeben werden, über die der Empfänger eines EchoObjekts sich den zugehörigen Code beschaffen kann (vgl.S.6): -Djava.rmi.server.codebase=http://page.mi.fu-berlin.de/~lohr/classes/ Achtung: eine solche Angabe ist bei allen Programmen erforderlich, die Serializable Objekte verschicken, sei es als Argument oder als Ergebnis! Robert Tolksdorf und Peter Löhr 28 Anbieterseitige Situation Die Codebasis im Web: human: cd /web/page.mi.fu-berlin.de/web-home/lohr/public_html/classes human: ls * echo: Factory.class (die beim Registry benötigte Schnittstelle - vgl. S. 6) factory: EchoImpl.class (die bei Klienten benötigte Klasse) Der vollständige Code für die Echo-Fabrik: human: cd ............ human: ls * echo: Echo.class Factory.class factory: EchoFactory.class EchoImpl.class Robert Tolksdorf und Peter Löhr 29 Starten der Echo-Fabrik human : rmiregistry & [1] 24365 human: java -Djava.rmi.server.codebase=\ > http://page.mi.fu-berlin.de/~lohr/classes/ \ > factory/EchoFactory & [2] 24395 human: Robert Tolksdorf und Peter Löhr 30 Test der Echo-Fabrik lohr@lohr: cd ....... lohr@lohr: ls * testpolicy echo: Echo.class Factory.class echotest: Test.class lohr@lohr: java -Djava.security.manager \ > -Djava.security.policy=testpolicy \ > echotest/Test wesel esel lohr@lohr: Robert Tolksdorf und Peter Löhr 31 Ein weiteres Beispiel (aus http://java.sun.com/docs/books/tutorial/rmi/ ) Rechenintensiven Code execute eines Klienten von einem Anbieter auf leistungsfähiger Maschine ausführen lassen: public interface Task<T> { T execute(); } // not Remote Anbieter kann über generische Methode executeTask angesprochen werden: public interface Compute extends Remote { <T> T executeTask(Task<T> t) throws RemoteException; } Robert Tolksdorf und Peter Löhr 32 ... und der Klient Die Rechenaufgabe des Klienten: public class Heavy implements Task<Result>, Serializable {...} ... und das Rahmenprogramm: public class HeavyComputing { public static void main(String arg[]) { ..... Compute comp = (Compute)registry.lookup("compute"); Result result = comp.executeTask(new Heavy(...)); ..... } } Robert Tolksdorf und Peter Löhr 33 Hier statt Berechnung von Heavy Berechnung von Pi Code-Übertragung in andere Richtung als bei Echo-Beispiel! Robert Tolksdorf und Peter Löhr mit HeavyComputing mit ComputePi implements Compute 34 Allgemeine Situation Robert Tolksdorf und Peter Löhr 35 Zusammenfassung Robert Tolksdorf und Peter Löhr Zusammenfassung • Auffinden von .class-Dateien • classpath bei rmiregistry, Anbieter und Klient beachten! • ggfls. Codebasis im Web für rmiregistry vorhalten • vorzugsweise mit .jar-Dateien arbeiten • Serialisierbare Parameter • Empfänger erhält Verweis auf lokale Kopie • eventuell muss auch Code übertragen werden • Sicherheit • Schutz vor Trojanischem Code • Security Manager und Richtlinien-Dateien • Code laden übers Web • nur mit Security Manager Robert Tolksdorf und Peter Löhr 37 Literatur Sun Microsystems: Security http://java.sun.com/javase/6/docs/technotes/guides/security/ http://java.sun.com/javase/6/docs/technotes/guides/security/PolicyFiles.html http://java.sun.com/javase/6/docs/technotes/guides/security/permissions.html Sun Microsystems: Security Tutorial http://java.sun.com/docs/books/tutorial/security/ Robert Tolksdorf und Peter Löhr 38