9. Januar 2012

Login als User "A" - mit dem Password von "B": Proxy Authentication!

Log in as user "A" - with the password of "B": Proxy Authentication
Es ist in der Tat möglich: Ich kann mich an der Oracle-Datenbank als User "A" mit dem Passwort des Benutzers "B" anmelden - und das ist keine Sicherheitslücke. Ich möchte dieses Blog-Posting dem Thema Proxy Authentication widmen. Das kann man ganz besonders in einer dreischichtigen Webarchitektur gebrauchen. Denn dort werden Datenbankverbindungen nahezu immer statisch im Application Server konfiguriert ( data-sources.xml). Wenn man in einer solchem Umgebung mit den Nutzerkonten der Datenbank arbeiten möchte, hat man ein Problem: Man kann sie nicht alle in der data-sources.xml einrichten; erstmal sind es oft zu viele und zweitens kann man die data-sources.xml nicht so einfach im laufenden Betrieb ändern. Die Lösung ist Proxy Authentication: Dabei wird die Datenbankverbidnung im Application Server fest mit einem technischen User und einem Passwort eingerichtet; der tatsächliche Datenbankuser, nach dem sich auch die Privilegien richten, wird zur Laufzeit festgelegt. Und das ganze funktioniert wie folgt: Zuerst brauchen wir einen technischen User TECHUSER (mit Passwort TECHUSER) und zwei "echte" User (REALUSER1 und REALUSER2).
create user techuser identified by techuser
/

grant connect to techuser
/

reate user realuser1 identified by realuser1
/

grant connect, resource to realuser1
/

reate user realuser2 identified by realuser2
/

grant connect, resource to realuser2
/
Und damit die User in einer Anwendung unterscheidbar werden, bekommen Sie nun eine Kopie der EMP-Tabelle, aber mit unterschiedlichen Inhalten ...
craete table realuser1.emp as select * from scott.emp where deptno = 10
/

craete table realuser2.emp as select * from scott.emp where deptno = 20
/
Und jetzt geht es los: Zuerst muss man der Datenbank sagen, dass TECHUSER die Erlaubnis bekommt, sich mit seinem eigenen Passwort als REALUSER1 oder REALUSER2 "auszugeben". Man kann auch sagen: TECHUSER wird der Proxy User für die Clients REALUSER1 und REALUSER2.
alter user realuser1 grant connect through techuser
/

alter user realuser2 grant connect through techuser
/
Der Setup lässt sich in der Dictionary View PROXY_USERS auch überprüfen:
SQL> select * from proxy_users;

PROXY           CLIENT          AUT FLAGS
--------------- --------------- --- -----------------------------------
TECHUSER        REALUSER1       NO  PROXY MAY ACTIVATE ALL CLIENT ROLES
TECHUSER        REALUSER2       NO  PROXY MAY ACTIVATE ALL CLIENT ROLES
Mit SQL*Plus kann man das jetzt schon ausprobieren:
D:\>sqlplus.exe techuser[realuser1]/techuser

SQL*Plus: Release 11.1.0.6.0 - Production on Do Dez 22 16:17:30 2011

Copyright (c) 1982, 2007, Oracle.  All rights reserved.


Verbunden mit:
Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options

SQL> select user from dual;

USER
------------------------------
REALUSER1
Man sieht sehr schön, dass Username und Passwort des TECHUSER zum Login verwendet werden; in eckigen Klammern wird aber festgelegt, für wen die Datenbankverbindung eigentlich aufgemacht werden soll.
So weit, so gut, so einfach. Aber für eine dreischichtige Applikation nutzt das bis jetzt noch gar nichts. Schließlich kann man nicht einfach den "echten" User in eckige Klammern in die Definition der Datenbankverbindung eintragen, denn diese ist ja statisch. Der User, für den der Connect gelten soll, soll sich aber beliebig ändern können - also dynamisch sein. Also müssen wir in der Lage sein, den Usernamen, den man bei SQL*Plus in eckige Klammern setzt, per Java-Code zu setzen ... Schauen wir uns zunächst ein kleines Java-Testprogramm an: Es gibt zuerst den Namen des angemeldeten Users aus ( select user from dual) und dann die Inhalte der EMP Tabelle. Achtet darauf, dass Username und Passwort des TECHUSER hart kodiert sind - und das wird so bleiben!
import java.sql.*;
import oracle.jdbc.*;
import java.io.*;
import java.util.*;

public class proxyConnect {
  public static void main(String args[]) throws Exception {
    DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
    Connection con = DriverManager.getConnection("jdbc:oracle:thin:@sccloud030:1521/orcl","techuser","techuser");
    
/*
 * Code zum Setzen des Client-Usernamens HIER !!!
 */

    Statement stmt = null;
    ResultSet rs1 = null;
    stmt = con.createStatement();
    rs1 = stmt.executeQuery("select user from dual");
    while (rs1.next()) {
      System.out.println("Database userid: " + rs1.getString(1));
    }  
    rs1.close();
    stmt.close();

    stmt = con.createStatement();
    rs1 = stmt.executeQuery("select * from emp");
    while (rs1.next()) {
      System.out.println("EMPNO: " + rs1.getString(1) + " ["+rs1.getString(2)+"] - DEPTNO: "+rs1.getString("DEPTNO"));
    }  
    rs1.close();
    stmt.close();
    con.close();
  }
} 
Beim ersten Test sagt uns das Programm nur, dass wir als TECHUSER verbunden sind und dass es keine EMP-Tabelle gibt ...
D:\> java.exe proxyConnect realuser1
Database userid: TECHUSER
Exception in thread "main" java.sql.SQLSyntaxErrorException: 
ORA-00942: Tabelle oder View nicht vorhanden
        at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:91)
        at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:112)
        at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:173)
        :
Dann bauen wir jetzt den Code ein, der den "richtigen" User setzt. Tauscht die Zeilen ...
/*
 * Code zum Setzen des Client-Usernamens HIER !!!
 */
... durch diese hier aus: Als Usernamen nehmen wir den ersten Parameter der Kommandozeile.
    Properties props = new Properties();
    props.put("PROXY_USER_NAME", args[0]);
    ((OracleConnection)con).openProxySession(OracleConnection.PROXYTYPE_USER_NAME, props);  
Neu kompilieren und wieder ausführen. Und .. voilá:
D:\> java proxyConnect realuser1
Database userid: REALUSER1
EMPNO: 7782 [CLARK] - DEPTNO: 10
EMPNO: 7839 [KING] - DEPTNO: 10
EMPNO: 7934 [MILLER] - DEPTNO: 10

D:\> java proxyConnect realuser2
Database userid: REALUSER2
EMPNO: 7369 [SMITH] - DEPTNO: 20
EMPNO: 7566 [JONES] - DEPTNO: 20
EMPNO: 7788 [SCOTT] - DEPTNO: 20
EMPNO: 7876 [ADAMS] - DEPTNO: 20
EMPNO: 7902 [FORD] - DEPTNO: 20
Nur durch die Angabe des Namens wird nun festgelegt, als welcher User die Datenbanksession laufen soll. Damit kann ein Java-Programm (bspw. in einem Application Server) mit einer statischen Datasource-Definition dennoch dynamisch den Datenbankuser wechseln. Denn eine bestehende Proxy-Verbindung kann geschlossen werden, um danach auf der gleichen "physikalischen" Datenbankverbindung eine neue Proxy-Verbindung für einen anderen User zu öffnen. Im folgenden Code habe ich das mal illustriert - zur besseren Übersicht habe ich die Datenbankaktionen entfernt und gegen den Pseudocall auf erledigeDatenbankaktionen() ausgetauscht.
  public static void main(String args[]) throws Exception {
    // Physikalische Verbindung öffnen als TECHUSER
    DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
    Connection con = DriverManager.getConnection("jdbc:oracle:thin:@sccloud030:1521/orcl","techuser","techuser");
    
    // Proxy-Verbindung für User REALUSER1 öffnen, etwas tun und schließen
    Properties props = new Properties();
    props.put("PROXY_USER_NAME", "realuser1");
    ((OracleConnection)con).openProxySession(OracleConnection.PROXYTYPE_USER_NAME, props);  
    erledigeDatenbankaktionen(con);
    ((OracleConnection)con).close(OracleConnection.PROXY_SESSION); 

    // Die physikalische Verbindung ist immer noch offen ...
    // Proxy-Verbindung für User REALUSER2 öffnen, etwas tun und schließen
    props.put("PROXY_USER_NAME", "realuser2");
    ((OracleConnection)con).openProxySession(OracleConnection.PROXYTYPE_USER_NAME, props);  
    erledigeDatenbankaktionen(con);
    ((OracleConnection)con).close(OracleConnection.PROXY_SESSION); 

    // Physikalische Verbindung schließen
    con.close();
  }
Beim Umstieg von Client/Server- zu Webanwendungen ist ein solches Vorgehen hochinteressant.
Wichtig ist, dass REALUSER1 bzw. REALUSER2 immer noch das CREATE SESSION-Privileg brauchen; auch darf der Account nicht gelockt sein. Es ist aber durchaus möglich, direkte Connects zu unterbinden - dazu muss man "nur" das Passwort auf einen nicht ermittelbaren Wert setzen ...
SQL> alter user realuser1 identified by values 'Hallo'
Nun wird der Text "Hallo" als Hashwert in die Oracle-Passworttabelle geschrieben - ein Connect als REALUSER1 ist nun theoretisch möglich: wenn man das Passwort herausbekommt, dessen Hashwert "Hallo" ergibt. Defacto jedoch kann man sich nur noch über den TECHUSER als REALUSER1 anmelden. Aber auch ein Parallelbetrieb mit direkten und Proxy-Verbindungen ist natürlich problemlos machbar.
Mehr zur Proxy Authentication in der Dokumentation: Dazu sind zwei Links wichtig:
Yes - it is possible: You can connect to the Oracle database as say: User "A" with the password of User "B" - and that is not a security vulnerability. In this blog posting I'd like to elaborate a bit on proxy authentication - this feature is particular useful in three-tier-applications. Database connections are typically part of the static application server configuration ("data-sources.xml"). And when such a java environments want to use the user accounts in the database there is a problem: We cannot add each individual user to the datasource configuration. The first arguments is, that there might be just too many users - the second one is that changes to data-sources.xml are often not possible without downtime. So we need a connection using one single technical user's credentials and the ability to set the desired username at runtime. And that is all what proxy authentication is about. We'll start with creating the technical user (TECHUSER) and two "real" user accounts (REALUSER1 and REALUSER2).
create user techuser identified by techuser
/

grant connect to techuser
/

reate user realuser1 identified by realuser1
/

grant connect, resource to realuser1
/

reate user realuser2 identified by realuser2
/

grant connect, resource to realuser2
/
REALUSER1 and REALUSER2 now get a copy of the EMP table with different contents: we want to differentiate between the two later on.
craete table realuser1.emp as select * from scott.emp where deptno = 10
/

craete table realuser2.emp as select * from scott.emp where deptno = 20
/
Now we declare TECHUSER as the proxy user for REALUSER1 and REALUSER2. This is kind of a GRANT statement. The privilege to act as (the clients) REALUSER1 or REALUSER2 is granted to TECHUSER.
alter user realuser1 grant connect through techuser
/

alter user realuser2 grant connect through techuser
/
You might review this in the dictionary view PROXY_USERS:
SQL> select * from proxy_users;

PROXY           CLIENT          AUT FLAGS
--------------- --------------- --- -----------------------------------
TECHUSER        REALUSER1       NO  PROXY MAY ACTIVATE ALL CLIENT ROLES
TECHUSER        REALUSER2       NO  PROXY MAY ACTIVATE ALL CLIENT ROLES
A first test with SQL*Plus:
D:\>sqlplus.exe techuser[realuser1]/techuser

SQL*Plus: Release 11.1.0.6.0 - Production on Do Dez 22 16:17:30 2011

Copyright (c) 1982, 2007, Oracle.  All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options

SQL> select user from dual;

USER
------------------------------
REALUSER1
TECHUSER used his own credentials to log in. But the user as which the connection should be established was provided within the brackets. In SQL*Plus it is that easy: We logged in as TECHUSER/techuser and we now are REALUSER1.
So far - so good. But for real world environments this is virtually useless - no end user connects with SQL*Plus. And we cannot add the brackets to the static datasource definition in the application server: we need to set the client user dynamically with some code - and the following example will show how to do this. First I have a little testing program in java language. It first connects to the database with hardcoded TECHUSER credentials, then it looks up "as who" it is connected and finally it shows the contents of the EMP table. Again: The username and password arguments in the call to DriverManager.getConnection are hard coded and this will not change!
import java.sql.*;
import oracle.jdbc.*;
import java.io.*;
import java.util.*;

public class proxyConnect {
  public static void main(String args[]) throws Exception {
    DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
    Connection con = DriverManager.getConnection("jdbc:oracle:thin:@sccloud030:1521/orcl","techuser","techuser");
    
/*
 * The java code to set the client username goes here !!!
 */

    Statement stmt = null;
    ResultSet rs1 = null;
    stmt = con.createStatement();
    rs1 = stmt.executeQuery("select user from dual");
    while (rs1.next()) {
      System.out.println("Database userid: " + rs1.getString(1));
    }  
    rs1.close();
    stmt.close();

    stmt = con.createStatement();
    rs1 = stmt.executeQuery("select * from emp");
    while (rs1.next()) {
      System.out.println("EMPNO: " + rs1.getString(1) + " ["+rs1.getString(2)+"] - DEPTNO: "+rs1.getString("DEPTNO"));
    }  
    rs1.close();
    stmt.close();
    con.close();
  }
} 
This program so far does not know anything about proxy connections - the results are therefore straightforward: We are connected as TECHUSER and there is no EMP table.
D:\> java.exe proxyConnect realuser1
Database userid: TECHUSER
Exception in thread "main" java.sql.SQLSyntaxErrorException: 
ORA-00942: table or view does not exist.
        at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:91)
        at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:112)
        at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:173)
        :
Now we'll add the "magic" lines of code to set the client username. Exchange those three lines ...
/*
 * The java code to set the client username goes here !!!
 */
... with these - we'll take the first command line argument as the username.
    Properties props = new Properties();
    props.put("PROXY_USER_NAME", args[0]);
    ((OracleConnection)con).openProxySession(OracleConnection.PROXYTYPE_USER_NAME, props);  
Recompile and test again - and ... it works.
D:\> java proxyConnect realuser1
Database userid: REALUSER1
EMPNO: 7782 [CLARK] - DEPTNO: 10
EMPNO: 7839 [KING] - DEPTNO: 10
EMPNO: 7934 [MILLER] - DEPTNO: 10

D:\> java proxyConnect realuser2
Database userid: REALUSER2
EMPNO: 7369 [SMITH] - DEPTNO: 20
EMPNO: 7566 [JONES] - DEPTNO: 20
EMPNO: 7788 [SCOTT] - DEPTNO: 20
EMPNO: 7876 [ADAMS] - DEPTNO: 20
EMPNO: 7902 [FORD] - DEPTNO: 20
Only by giving the username on the command line we connected as REALUSER1 or REALUSER2 - the password always was TECHUSER's one. With this approach a java program can obtain a database connection from its application server, set the client username and connect to the database as another "real" user. And this proxy connection can be changed while the main "physical" connection remains open. The following code shows this - for clarity I have removed the actual database actions and replaced them with the pseudo method doDatabaseActions().
  public static void main(String args[]) throws Exception {
    // open the physical connection as TECHUSER
    DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
    Connection con = DriverManager.getConnection("jdbc:oracle:thin:@sccloud030:1521/orcl","techuser","techuser");
    
    // open the proxy connection for REALUSER1, do something and close
    Properties props = new Properties();
    props.put("PROXY_USER_NAME", "realuser1");
    ((OracleConnection)con).openProxySession(OracleConnection.PROXYTYPE_USER_NAME, props);  
    doDatabaseActions(con);
    ((OracleConnection)con).close(OracleConnection.PROXY_SESSION); 

    // the physical connection is still open ...
    // now open the proxy connection for REALUSER2, do something and close
    props.put("PROXY_USER_NAME", "realuser2");
    ((OracleConnection)con).openProxySession(OracleConnection.PROXYTYPE_USER_NAME, props);  
    doDatabaseActions(con);
    ((OracleConnection)con).close(OracleConnection.PROXY_SESSION); 

    // finally close the physical connection
    con.close();
  }
When client/server applications are moving to a web environment, this technology gets highly interesting - because the application can keep its existing user, role and security model and move on to the web technology where all database connections are done with one technical user.
REALUSER1 or REALUSER2 still need their CREATE SESSION privilege - the accounts also must exist in the database and must not be locked. But (if needed) you actually can "forbid" direct connections by setting the password to an "impossible" value ...
SQL> alter user realuser1 identified by values 'Hello'
The REALUSER1 account is still open and connects are possible in theory. But since the IDENTIFIED BY VALUES command directly wrote "Hello" into the password table, one would have to find out one of the password - which evaluate to "Hello" during the hashing process - I'll say it that way: at least very difficult. Connections as REALUSER1 now are only possible as connections through TECHUSER. But it is also possible to have both direct and proxy connections in parallel.
More about proxy authentication is in the documentation:

Kommentare:

Anonym hat gesagt…

Hallo,
gehe ich recht in der Annahme, dass bei einer Neuentwicklung dieses Konzept nicht verfolgt werden sollte ?

Stattdessen verbindet sich der Appserver mit einem user.
Die Application speichert dann den "realuser" in einem Context.

Heutzutage möchte man ja nicht mehr 1000e User in der DB kreiern.

Carsten Czarski hat gesagt…

Hallo,

das sehe ich genauso. Das Konzept eignet sich sehr gut, wenn es darum geht, die in der Datenbank vorhandenen Nutzer (mitsamt ihrer Rollen und Privilegien) in der Anwendung zu nutzen, den Login aber über einen anderen User durchzuführen. Sicherlich kommt das am ehesten in Frage, wenn die DB-User aus "historischen" Gründen bereits alle vorhanden sind, man aber von einem Appserver aus auf die DB zugreifen möchte. Eine neue Anwendung würde man wahrscheinlich eher mit einem einfachen technischen User, dem RealUser im "Context" und dann einer Zugriffskontrolle mit Virtual Private Database realisieren ...

Beste Grüße

-Carsten

Beliebte Postings