Blog do projektu Open Source JavaHotel

wtorek, 24 maja 2011

Trusted context, DB2, Spring

The problem
The problem is described here. Keeping long story short - in three-tiered solutions the user does not interact directly with the database. The middle-tier (application server) on behalf of the user is connected to the database and send there requests. It has advantages and disadvantages. The main disadvantage is that it is very difficult to track and audit the user activities from the point of the database because it is done by the application server and we cannot tell the request from one user from the requests of other user. Also - in case of the users with the different level of privileges - it is very difficult to provide the granularity of privileges as required.

Bad news and good news
The bad news is that JEE standard does not provide a consistent approach to this problem. The good news is that DB2 provides a solution although this solution is specific to the DB2 only.

Trusted connection
This good news is trusted context and trusted connection. General idea:
Trusted context is the database object which allows setting a special connection (trusted) between database server and specified (by IP address) client (application server).
Application server connects to the database server and establishes the trusted connection.
Then, by virtue of this connection, it is possible to reuse this connection without authentication and change the authentication id (user id). It could be the id of the user who's just logged in to the application server. Also privileges assigned to this  authentication id will be changed accordingly (if not inherited implicitly).

How to accomplish it (more detail)

1. Create a trusted user
It is the user who will establish a trusted connection. In default DB2 security implementation system (Linux for instance) authentication is utilized. So it is enough to add a new user to the system e.g U/P trust/trustme. No additional DB2 privileges or authorities are required, this user should be only able to connect to the database which is granted for all system users.

2. Create roles
Assume we want to split our users into two groups. Some users (a smaller group) would be able to modify the data (dataaccess authority), the rest (default) are allowed only to read the data without modifying.
The first role (group) could be created with the command:
{ connect to SAMPLE database } 
  db2 create role SAMPLEFULLACCESS
  db2 grant dataaccess on database  to samplefullaccess
The task of creating the second role (read only) is a bit more complicated because in DB2 there is no general read only authority like "dataaccess". This privilege should be performed on table by table basis.
db2 create role SAMPLEREADACCESS
Then run the following command:
db2 "select 'grant select on table ' || tabname || ' to samplereadaccess;' from syscat.tables where tabschema='DB2INST1'" >grant.sql
This command prepares a sql script file giving a right to select data from every table. After removing the first and the list line from this script it is enough to run the script:
db2 -tvf grant.sql
More information on DB2 security model 

3. Create trusted context
CREATE TRUSTED CONTEXT MYTCX  
       BASED UPON CONNECTION USING SYSTEM AUTHID trust
       ATTRIBUTES (ADDRESS   '192.168.1.4')  
       DEFAULT ROLE samplereadaccess
       WITH USE FOR PUBLIC WITHOUT AUTHENTICATION,  
                    JOHN ROLE samplefullaccess WITHOUT AUTHENTICATION 
       ENABLE;

The trusted context object named MYTCX is created. What does it mean ?
  • The client running on machine 192.166.1.4 can establish trusted connection to database SAMPLE as user trust. 
  • All requests for trusted connection from other machines or request from 192.168.1.4 but as a different user or request from 192.168.1.4 to connect to database other than SAMPLE will be rejected. 
  • Having trusted connection established the client can switch this connection  to other user id (without authentication) and this new user is only able to read data from SAMPLE database. 
  • Only the user 'JOHN' is a proud keeper of the special privilege on database SAMPLE - he can also modify data.

4. Java code
Java code for creating trusted connection and switching (reusing) this connection to the other (non-authenticated) user looks like.
(it is based on this code sample).

TrustedConnection.java

package com.db2.trusted;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.ibm.db2.jcc.DB2ConnectionPoolDataSource;
import com.ibm.db2.jcc.DB2PooledConnection;

public class TrustedConnection {

    private final int portNumber;
    private final String databaseName;
    private final String userName;
    private final String password;
    private final String hostName;
    private final String defaultSchema;
    private Object[] objects;
    private final DB2ConnectionPoolDataSource dataSource;

    /**
     * Constructor, provide all details necessary to estalish trusted connecton
     * @param portNumber  port number (most common is 50000)
     * @param databaseName e.g SAMPLE
     * @param userName e.g trust
     * @param password e.g trustme
     * @param hostName IP address or hostname
     * @param defaultSchema e.g DB2INST1 (important: case sensitive !)
     */
            
    public TrustedConnection(int portNumber, String databaseName,
            String userName, String password, String hostName,
            String defaultSchema) {
        this.portNumber = portNumber;
        this.databaseName = databaseName;
        this.userName = userName;
        this.password = password;
        this.hostName = hostName;
        this.defaultSchema = defaultSchema;
        dataSource = new DB2ConnectionPoolDataSource();
    }

    /**
     * Establishes trusted connection, should be called only once
     * @throws SQLException
     */
    public void trustConnect() throws SQLException {
        dataSource.setDatabaseName(databaseName);
        dataSource.setServerName(hostName);
        dataSource.setDriverType(4);
        dataSource.setPortNumber(portNumber);
        dataSource.setCurrentSchema(defaultSchema);
        // Call getDB2TrustedPooledConnection to get the trusted connection
        // instance and the cookie for the connection
        objects = dataSource.getDB2TrustedPooledConnection(userName, password,
                new java.util.Properties());
    }

    /**
     * Switch and reuse trusted connection as different user (without authentication)
     * @param newUser id of the new user
     * @return Connection (java.sqlx)
     * @throws SQLException
     */
    public Connection useConnection(String newUser) throws SQLException {
        DB2PooledConnection pooledCon = (DB2PooledConnection) objects[0];
        Properties properties = new Properties();
        byte[] cookie = (byte[]) (objects[1]);
        String userRegistry = null;
        byte[] userSecTkn = null;
        String originalUser = null;
        Connection con = pooledCon.getDB2Connection(cookie, newUser, null,
                userRegistry, userSecTkn, originalUser, properties);
        return con;
    }

    /**
     * For the purpose of JdbcTemplate. 
     * DataSource with getConnection method override
     * New user id as constructor parameter
     * @author sbartkowski
     *
     */
    private class TrustedDataSource implements DataSource {

        private final String newUser;

        public TrustedDataSource(String newUser) {
            this.newUser = newUser;
        }

        @Override
        public PrintWriter getLogWriter() throws SQLException {
            return dataSource.getLogWriter();
        }

        @Override
        public void setLogWriter(PrintWriter out) throws SQLException {
            dataSource.setLogWriter(out);
        }

        @Override
        public void setLoginTimeout(int seconds) throws SQLException {
            dataSource.setLoginTimeout(seconds);
        }

        @Override
        public int getLoginTimeout() throws SQLException {
            return dataSource.getLoginTimeout();
        }

        @Override
        public  T unwrap(Class iface) throws SQLException {
            return null;
        }

        @Override
        public boolean isWrapperFor(Class iface) throws SQLException {
            return false;
        }

        @Override
        public Connection getConnection() throws SQLException {
            return useConnection(newUser);
        }

        @Override
        public Connection getConnection(String username, String password)
                throws SQLException {
            return getConnection();
        }

    }

    public DataSource constructReuseDataSource(String newUser) {
        return new TrustedDataSource(newUser);
    }

}

Usage example

TrustedConnection trust = new TrustedConnection(50000, "SAMPLE", "trust",
                "trustme", "192.168.1.6", "DB2INST1");
        trust.trustConnect(); // should be done once only
        Connection con = trus.useConnection("John");
        // some database activity as user 'John'
        con = trus.useConnection("Jack");
        // some database activity as user 'Jack'
        //etc 

Spring JdbcTemplate

JdbcTemplate uses DataSource to extract Connection interface. Unfortunately  I cannot use DB2ConnectionPoolDataSource directly because JdbcTemplate uses getConnection method (without parameters). In order to overcome this problem I applied Decorator pattern to DB2ConnectionPoolDataSource class just overriding only getConnection method and providing new user id in class constructor.

The usage example:
TrustedConnection trust;
   ....
    JdbcTemplate jTemplate = new JdbcTemplate(trust.constructReuseDataSource('John'));
    jTemplate.update(...);
    jTemplate // any other method. 
 
Last but not least - is it really works ???

In order to answer this question I created a simple standalone Java program which uses TrustedContext class (described above), retrieves trusted connection to the SAMPLE database, switches to different user and run some simple SQL statements.
Main.java

import java.sql.SQLException;

import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.core.JdbcTemplate;

import com.db2.trusted.TrustedConnection;

public class Main {

    private static void goTrusted() throws SQLException {
        TrustedConnection trust = new TrustedConnection(50000, "SAMPLE", "trust",
                "trustme", "192.168.1.6", "DB2INST1");
        trust.trustConnect();
        JdbcTemplate jTemplate = new JdbcTemplate(
                trust.constructReuseDataSource("john"));
        int no = jTemplate.queryForInt("SELECT COUNT(*) FROM ACT");
        String sql = "update employee set bonus=30000 where lastname='WONG'";
        jTemplate.update(sql);
        
        jTemplate = new JdbcTemplate(trust.constructReuseDataSource("mary"));
        no = jTemplate.queryForInt("SELECT COUNT(*) FROM EMPLOYEE");
        
        sql = "update employee set bonus=30000 where lastname='WONG'";
        try {
          jTemplate.update(sql);
        } catch (BadSqlGrammarException e) {
            // as expected
            // SQL0551N code
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        try {
            goTrusted();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

Firstly we can debug this code and set breakpoint at three places (14, 17 and 29). At the same time connect to SAMPLE database and run
db2 list applications
command when Java program breaks.
The first time something like this will be retrieved:

Auth Id  Application    Appl.      Application Id                                                 DB       # of
         Name           Handle                                                                    Name    Agents
-------- -------------- ---------- -------------------------------------------------------------- -------- -----
TRUST    db2jcc_applica 144        C0A80103.88C9.110524112902                                     SAMPLE   1    
DB2INST1 db2bp          63         *LOCAL.db2inst1.110524104758                                   SAMPLE   1    

the second:

Auth Id  Application    Appl.      Application Id                                                 DB       # of
         Name           Handle                                                                    Name    Agents
-------- -------------- ---------- -------------------------------------------------------------- -------- -----
JOHN     db2jcc_applica 144        C0A80103.88C9.110524113623                                     SAMPLE   1    
DB2INST1 db2bp          63         *LOCAL.db2inst1.110524104758                                   SAMPLE   1  

and third:
 
Auth Id  Application    Appl.      Application Id                                                 DB       # of
         Name           Handle                                                                    Name    Agents
-------- -------------- ---------- -------------------------------------------------------------- -------- -----
MARY     db2jcc_applica 144        C0A80103.88C9.110524113623                                     SAMPLE   1    
DB2INST1 db2bp          63         *LOCAL.db2inst1.110524104758                                   SAMPLE   1  

Pay attention that every time application handle (Appl. Handle) which is set at the moment when application connects to the database server is the same but Authentication Id is different - it is changing according to the current Connection user id. It means that different users shares the same connection one after one.

Secondly we can check if our sophisticated security policy is in force. As we see  - user John is able to update the table but when user Mary is trying to modify the bonus of her college - she is rejected.

Next step is to verify if we are able to track the user activities for auditing purspose.

Brak komentarzy:

Prześlij komentarz