關於hadoop登陸kerberos時設置環境變量問題的思考


  中心思想,設置kerberos環境變量時,發現JDK源碼當中的一個問題,故描述如下。

  在平時的使用中,如果hadoop集群配置kerberos認證的話,使用java訪問hdfs或者hive時,需要先進行認證登陸,之后才可以訪問。登陸代碼大致如下:

package demo.kerberos;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.UserGroupInformation;

public class KerberosLoginTest {

	public static void main(String[] args) throws IOException {
		// 設置kerberos相關登陸信息
		// 方法1:設置krb5.conf配置文件
		System.setProperty("java.security.krb5.conf", "/home/eabour/hadoop/conf/krb5.conf");
		// 方法2:設置kerberos環境變量
		// System.setProperty("java.security.krb5.realm", "HADOOP.COM");
		// System.setProperty("java.security.krb5.kdc", "kdc.server.com");
		// 開啟登陸調試日志
		System.setProperty("sun.security.krb5.debug", "true");
		Configuration conf = new Configuration();
		conf.addResource(new Path("/home/eabour/hadoop/conf/core-site.xml"));
		conf.addResource(new Path("/home/eabour/hadoop/conf/hdfs-site.xml"));
		UserGroupInformation.setConfiguration(conf);
		// 登陸kerberos
		UserGroupInformation.loginUserFromKeytab("test@HADOOP.COM", "/home/eabour/test.keytab");
		// TODO
		// 訪問HDFS
	}

}

  設置krb5.conf和core-site.xml、hdfs-site.xml相關大數據平台的配置文件,用來初始化相關配置信息。使用krb5.conf登陸沒有問題,不管設置多個kdc server還是單個,jdk中的相關模塊都會解析出來,jdk源碼位置為openjdk\jdk\src\share\classes\sun\security\krb5,其中openjdk的源碼地址為http://hg.openjdk.java.net/jdk7u/jdk7u60/jdk/file/33c1eee28403/src/share/classes,其他版本的類似,解析類為sun.security.krb5.Config,部分代碼如下:

    /**
     * Private constructor - can not be instantiated externally.
     */
    private Config() throws KrbException {
        /*
         * If either one system property is specified, we throw exception.
         */
        String tmp = getProperty("java.security.krb5.kdc");
        if (tmp != null) {
            // The user can specify a list of kdc hosts separated by ":"
            defaultKDC = tmp.replace(':', ' ');
        } else {
            defaultKDC = null;
        }
        defaultRealm = getProperty("java.security.krb5.realm");
        if ((defaultKDC == null && defaultRealm != null) ||
            (defaultRealm == null && defaultKDC != null)) {
            throw new KrbException
                ("System property java.security.krb5.kdc and " +
                 "java.security.krb5.realm both must be set or " +
                 "neither must be set.");
        }

        // Always read the Kerberos configuration file
        try {
            Vector<String> configFile;
            String fileName = getJavaFileName();
            if (fileName != null) {
                configFile = loadConfigFile(fileName);
                stanzaTable = parseStanzaTable(configFile);
                if (DEBUG) {
                    System.out.println("Loaded from Java config");
                }
            } else {
                boolean found = false;
                if (isMacosLionOrBetter()) {
                    try {
                        stanzaTable = SCDynamicStoreConfig.getConfig();
                        if (DEBUG) {
                            System.out.println("Loaded from SCDynamicStoreConfig");
                        }
                        found = true;
                    } catch (IOException ioe) {
                        // OK. Will go on with file
                    }
                }
                if (!found) {
                    fileName = getNativeFileName();
                    configFile = loadConfigFile(fileName);
                    stanzaTable = parseStanzaTable(configFile);
                    if (DEBUG) {
                        System.out.println("Loaded from native config");
                    }
                }
            }
        } catch (IOException ioe) {
            // No krb5.conf, no problem. We'll use DNS or system property etc.
        }
    }

  

  從代碼可以看出,先從環境變量中獲取java.security.krb5.kdc和java.security.krb5.realm,然后在讀取配置文件java.security.krb5.conf。如果同時設置java.security.krb5.kdc、java.security.krb5.realm和java.security.krb5.conf,在登陸的時候,會使用java.security.krb5.kdc,除非登陸的realm與defaultRealm不一致,從代碼中可以看出來。

  那么,我要說的問題來了,在某些場景下,沒有設置java.security.krb5.conf變量,而使用java.security.krb5.kdc、java.security.krb5.realm來登陸。在1個或多個kdc server情況下,這樣的設置沒有問題。我們知道kdc server的默認端口為88,使用的時UDP協議,當然可以為TCP協議,服務端口也可以修改,這時候大致為:

kdc=kdc.server.com:88

  或者

kdc=kdc.server.com:1088

  krb5.conf文件配置

[realms]
    HADOOP.COM = {
    kdc = kdc.server.com:1088
}

  此時,設置環境變量:

System.setProperty("java.security.krb5.kdc", "kdc.server.com:1088");

  那么,真正的問題就來了,執行登陸的話,會出現如下報錯:

Caused by: GSSException: No valid credentials provided (Mechanism level: ICMP Port Unreachable)
at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:775)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:248)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179)
at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:192)
… 76 more
Caused by: java.net.PortUnreachableException: ICMP Port Unreachable
at java.net.PlainDatagramSocketImpl.receive0(Native Method)
at java.net.AbstractPlainDatagramSocketImpl.receive(AbstractPlainDatagramSocketImpl.java:143)
at java.net.DatagramSocket.receive(DatagramSocket.java:812)
at sun.security.krb5.internal.UDPClient.receive(NetClient.java:206)
at sun.security.krb5.KdcComm$KdcCommunication.run(KdcComm.java:411)
at sun.security.krb5.KdcComm$KdcCommunication.run(KdcComm.java:364)
at java.security.AccessController.doPrivileged(Native Method)
at sun.security.krb5.KdcComm.send(KdcComm.java:348)
at sun.security.krb5.KdcComm.sendIfPossible(KdcComm.java:253)
at sun.security.krb5.KdcComm.send(KdcComm.java:229)
at sun.security.krb5.KdcComm.send(KdcComm.java:200)
at sun.security.krb5.KrbTgsReq.send(KrbTgsReq.java:254)
at sun.security.krb5.KrbTgsReq.sendAndGetCreds(KrbTgsReq.java:269)
at sun.security.krb5.internal.CredentialsUtil.serviceCreds(CredentialsUtil.java:302)
at sun.security.krb5.internal.CredentialsUtil.acquireServiceCreds(CredentialsUtil.java:120)
at sun.security.krb5.Credentials.acquireServiceCreds(Credentials.java:458)
at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:693)

 

  為什么會報錯呢?因為在解析環境變量時,解析出問題了,現在,再來看看解析代碼:

    /**
     * Private constructor - can not be instantiated externally.
     */
    private Config() throws KrbException {
        /*
         * If either one system property is specified, we throw exception.
         */
        String tmp = getProperty("java.security.krb5.kdc");
        if (tmp != null) {
            // The user can specify a list of kdc hosts separated by ":"
            defaultKDC = tmp.replace(':', ' ');
        } else {
            defaultKDC = null;
        }
        defaultRealm = getProperty("java.security.krb5.realm");
        if ((defaultKDC == null && defaultRealm != null) ||
            (defaultRealm == null && defaultKDC != null)) {
            throw new KrbException
                ("System property java.security.krb5.kdc and " +
                 "java.security.krb5.realm both must be set or " +
                 "neither must be set.");
        }

  請看紅色黃底的代碼 defaultKDC = tmp.replace(':', ' ')對,就是這句代碼的問題,他將kdc.server.com:1088分割為kdc.server.com和1088了,認為時兩個kdc server。我的心崩潰呀,為什么要用冒號來分割多個server的配置,如果在使用默認端口的話,這樣也沒問題,但是,如果kdc修改了端口,這種通過環境變量設置kdc server的方式就沒法用了。

  到最后,原來時jdk源碼的問題,正常的途徑怕是設置不了了。但是,不是不能修改了,我們可以用反射來修改ConfigdefaultKDC的值,雖然說defaultKDC為final String ,到那時它是private final String defaultKDC;,所以還是可以修改的。

  以上就是我分析的問題,在實際項目中是真實遇到過,因為涉及JDK底層代碼,所以請大家來參詳一下。

 

 

附:

1.kerberos配置文件設置:https://www.ibm.com/support/knowledgecenter/zh/SSAW57_9.0.0/com.ibm.websphere.nd.multiplatform.doc/ae/tsec_kerb_create_conf.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM