com.huawei.streaming.storm.KerberosSecurity.java Source code

Java tutorial

Introduction

Here is the source code for com.huawei.streaming.storm.KerberosSecurity.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.huawei.streaming.storm;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Properties;
import java.util.UUID;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.huawei.streaming.config.StreamingConfig;
import com.huawei.streaming.exception.ErrorCode;
import com.huawei.streaming.exception.StreamingException;

/**
 * StreamingZookeeper
 *
 */
public class KerberosSecurity implements StreamingSecurity {
    /**
     * The jass.conf for zookeeper client security login.
     */
    public static final String ZOOKEEPER_AUTH_JASSCONF = "java.security.auth.login.config";

    /**
     * Zookeeper quorum principal.
     */
    public static final String ZOOKEEPER_AUTH_PRINCIPAL = "zookeeper.server.principal";

    /**
     * java security krb5 file path
     */
    public static final String JAVA_SECURITY_KRB5_CONF = "java.security.krb5.conf";

    private static final Logger LOG = LoggerFactory.getLogger(KerberosSecurity.class);

    private static final String DEFAULT_STRING_CHARSET = "UTF-8";

    private static final Charset DEFAULT_CHARSET = Charset.forName(DEFAULT_STRING_CHARSET);

    private static final String STREAMING_JAAS_POSTFIX = ".streaming.jaas.conf";

    private static final String LINE_SEPARATOR = IOUtils.LINE_SEPARATOR;

    private boolean useKeyTab = true;

    private String keyTabPath;

    private String userPrincipal;

    private String zookeeperPrincipal;

    private String stormPrincipal;

    private boolean useTicketCache = false;

    private boolean storeKey = true;

    private boolean debug = false;

    private String jaasPath;

    private StreamingConfig conf;

    private Properties backupSecurityConf = null;

    private String krbFilePath = null;

    /**
     * JaasConf
     *
     * @param config ?
     * @throws StreamingException jaas
     */
    public KerberosSecurity(StreamingConfig config) throws StreamingException {
        initParameters(config);
        resetSecurityType();

        this.conf = config;
        this.jaasPath = createJaasPath();
        this.backupSecurityConf = new Properties();

        backupSecurityConfs();
    }

    private void backupSecurityConfs() {
        this.backupSecurityProperties(ZOOKEEPER_AUTH_PRINCIPAL);
        this.backupSecurityProperties(ZOOKEEPER_AUTH_JASSCONF);
        this.backupSecurityProperties(JAVA_SECURITY_KRB5_CONF);
    }

    private void initParameters(StreamingConfig config) throws StreamingException {
        readZooKeeperPrincipal(config);
        readStormPrincipal(config);
        readUserPrincipal(config);
        readKeyTabPath(config);
        readKrbConfPath(config);
    }

    private void resetSecurityType() throws StreamingException {
        if (userPrincipal == null && keyTabPath == null) {
            LOG.info("Use ticket cache security.");
            this.useKeyTab = false;
            this.useTicketCache = true;
            return;
        }

        LOG.info("Use keytab security.");
        if (Strings.isNullOrEmpty(userPrincipal)) {
            StreamingException exception = new StreamingException(ErrorCode.CONFIG_NOT_FOUND,
                    StreamingConfig.STREAMING_SECURITY_USER_PRINCIPAL);
            LOG.error("User principal error.", exception);
            throw exception;
        }

        if (Strings.isNullOrEmpty(keyTabPath)) {
            StreamingException exception = new StreamingException(ErrorCode.CONFIG_NOT_FOUND,
                    StreamingConfig.STREAMING_SECURITY_KEYTAB_PATH);
            LOG.error("Keytab file error.", exception);
            throw exception;
        }
    }

    /**
     * ??
     *
     * @throws StreamingException ?
     */
    @Override
    public void initSecurity() throws StreamingException {
        writeJaasFile();
        initAuthToSysProperty();
    }

    /**
     * ??
     *
     * @throws StreamingException ?
     */
    @Override
    public void destroySecurity() throws StreamingException {
        restoreSecurityConf();
        deleteJaasFile();
    }

    private void restoreSecurityConf() {
        this.restoreSecurityProperties(ZOOKEEPER_AUTH_PRINCIPAL);
        this.restoreSecurityProperties(ZOOKEEPER_AUTH_JASSCONF);
        this.restoreSecurityProperties(JAVA_SECURITY_KRB5_CONF);
    }

    /**
     * jaas
     *
     * @throws StreamingException 
     */
    private void deleteJaasFile() throws StreamingException {
        try {
            String tmpDir = new File(conf.getStringValue(StreamingConfig.STREAMING_TEMPLATE_DIRECTORY))
                    .getCanonicalPath();
            File file = new File(jaasPath).getCanonicalFile();
            if (!file.getPath().startsWith(tmpDir)) {
                LOG.error("Invalid jaas path, not in config tmp path.");
                throw new StreamingException(ErrorCode.SECURITY_INNER_ERROR);
            }

            if (file.isFile()) {
                FileUtils.forceDelete(new File(jaasPath));
            }

        } catch (IOException e) {
            LOG.error("Failed to delete jaas file.");
            throw new StreamingException(ErrorCode.UNKNOWN_SERVER_COMMON_ERROR);
        } catch (SecurityException e1) {
            StreamingException exception = new StreamingException(ErrorCode.SECURITY_INNER_ERROR);
            LOG.error("Failed to get canonical pathname for cannot be accessed.", exception);
            throw exception;
        }
    }

    /**
     * ???
     *
     * @throws StreamingException
     */
    private void initAuthToSysProperty() throws StreamingException {
        String zkJassConf = decodePath();
        System.setProperty(ZOOKEEPER_AUTH_PRINCIPAL, zookeeperPrincipal);
        System.setProperty(ZOOKEEPER_AUTH_JASSCONF, zkJassConf);

        if (this.krbFilePath != null) {
            System.setProperty(JAVA_SECURITY_KRB5_CONF, krbFilePath);
        }
    }

    /**
     * ????
     *
     * @return ???
     * @throws StreamingException
     */
    private String decodePath() throws StreamingException {
        try {
            return URLDecoder.decode(jaasPath, DEFAULT_STRING_CHARSET);
        } catch (UnsupportedEncodingException e) {
            LOG.error("Unsupported encode, failed to decode jaas path {}.", jaasPath);
            throw new StreamingException(ErrorCode.UNKNOWN_SERVER_COMMON_ERROR);
        }
    }

    /**
     * jaas
     *
     * @throws StreamingException 
     */
    private void writeJaasFile() throws StreamingException {
        try {
            Files.write(createJaasContext(), new File(jaasPath), DEFAULT_CHARSET);
        } catch (IOException e) {
            LOG.error("Failed to create jaas file.");
            throw new StreamingException(ErrorCode.UNKNOWN_SERVER_COMMON_ERROR);
        }
    }

    /**
     * jaas
     *
     * @return jaas
     */
    private String createJaasContext() {
        if (useKeyTab) {
            return createKeyTabContext();
        }
        return createCacheContext();
    }

    /*
     * jaas
     */
    private String createCacheContext() {
        StringBuilder sb = new StringBuilder();
        sb.append("Client {").append(LINE_SEPARATOR);
        sb.append("com.sun.security.auth.module.Krb5LoginModule required").append(LINE_SEPARATOR);
        sb.append("useKeyTab=" + useKeyTab).append(LINE_SEPARATOR);
        sb.append("useTicketCache=" + useTicketCache + ";").append(LINE_SEPARATOR);
        sb.append("};").append(LINE_SEPARATOR);
        sb.append("StormClient {").append(LINE_SEPARATOR);
        sb.append("com.sun.security.auth.module.Krb5LoginModule required").append(LINE_SEPARATOR);
        sb.append("useKeyTab=" + useKeyTab).append(LINE_SEPARATOR);
        sb.append("useTicketCache=" + useTicketCache).append(LINE_SEPARATOR);
        sb.append("serviceName=\"" + splitKerberosName(stormPrincipal)[0] + "\";").append(LINE_SEPARATOR);
        sb.append("};");
        return sb.toString();
    }

    /*
     * jaas
     */
    private String createKeyTabContext() {
        StringBuilder sb = new StringBuilder();
        sb.append("Client {").append(LINE_SEPARATOR);
        sb.append("com.sun.security.auth.module.Krb5LoginModule required").append(LINE_SEPARATOR);
        sb.append("useKeyTab=" + useKeyTab).append(LINE_SEPARATOR);
        sb.append("keyTab=\"" + keyTabPath + "\"").append(LINE_SEPARATOR);
        sb.append("principal=\"" + userPrincipal + "\"").append(LINE_SEPARATOR);
        sb.append("useTicketCache=" + useTicketCache).append(LINE_SEPARATOR);
        sb.append("storeKey=" + storeKey).append(LINE_SEPARATOR);
        sb.append("debug=" + debug + ";").append(LINE_SEPARATOR);
        sb.append("};").append(LINE_SEPARATOR);
        sb.append("StormClient {").append(LINE_SEPARATOR);
        sb.append("com.sun.security.auth.module.Krb5LoginModule required").append(LINE_SEPARATOR);
        sb.append("useKeyTab=" + useKeyTab).append(LINE_SEPARATOR);
        sb.append("keyTab=\"" + keyTabPath + "\"").append(LINE_SEPARATOR);
        sb.append("principal=\"" + userPrincipal + "\"").append(LINE_SEPARATOR);
        sb.append("useTicketCache=" + useTicketCache).append(LINE_SEPARATOR);
        sb.append("storeKey=" + storeKey).append(LINE_SEPARATOR);
        sb.append("serviceName=\"" + splitKerberosName(stormPrincipal)[0] + "\"").append(LINE_SEPARATOR);
        sb.append("debug=" + debug + ";").append(LINE_SEPARATOR);
        sb.append("};");
        return sb.toString();
    }

    /**
     * ?stormprincipal
     *
     * @param config ?
     * @throws StreamingException ??
     */
    private void readStormPrincipal(StreamingConfig config) throws StreamingException {
        Object stormPrincipal = config.get(StreamingConfig.STREAMING_SECURITY_STORM_PRINCIPAL);

        if (stormPrincipal == null) {
            StreamingException exception = new StreamingException(ErrorCode.CONFIG_NOT_FOUND,
                    StreamingConfig.STREAMING_SECURITY_STORM_PRINCIPAL);
            LOG.error("Can't find storm principal in config.", exception);
            throw exception;
        }

        this.stormPrincipal = stormPrincipal.toString();
    }

    /**
     * ?zookeeperprincipal
     *
     * @param config ?
     * @throws StreamingException ??
     */
    private void readZooKeeperPrincipal(StreamingConfig config) throws StreamingException {
        Object zkPrincipal = config.get(StreamingConfig.STREAMING_SECURITY_ZOOKEEPER_PRINCIPAL);

        if (zkPrincipal == null) {
            StreamingException exception = new StreamingException(ErrorCode.CONFIG_NOT_FOUND,
                    StreamingConfig.STREAMING_SECURITY_ZOOKEEPER_PRINCIPAL);
            LOG.error("Can't find zk principal in config.", exception);
            throw exception;
        }

        this.zookeeperPrincipal = zkPrincipal.toString();
    }

    /**
     * ?principal??
     *
     * @param config ?
     */
    private void readUserPrincipal(StreamingConfig config) throws StreamingException {
        Object principal = config.get(StreamingConfig.STREAMING_SECURITY_USER_PRINCIPAL);
        this.userPrincipal = principal == null ? null : principal.toString();
    }

    /**
     * ??keytab?
     * keytab?
     *
     * @param config ?
     * @throws StreamingException ??
     */
    private void readKeyTabPath(StreamingConfig config) throws StreamingException {
        Object keyTablePath = config.get(StreamingConfig.STREAMING_SECURITY_KEYTAB_PATH);
        this.keyTabPath = keyTablePath == null ? null : formatPath(getKeyTablePath(keyTablePath.toString()));
    }

    /**
     * ??krb.conf?
     *
     * @param config ?
     * @throws StreamingException ??
     */
    private void readKrbConfPath(StreamingConfig config) throws StreamingException {
        Object krbPath = config.get(StreamingConfig.STREAMING_SECURITY_KRBCONF_PATH);
        this.krbFilePath = krbPath == null ? null : formatPath(getKeyTablePath(krbPath.toString()));
    }

    private String createJaasPath() throws StreamingException {
        UUID uuid = UUID.randomUUID();
        String randomName = uuid.toString().replace("-", "");
        String tmpDir = conf.getStringValue(StreamingConfig.STREAMING_TEMPLATE_DIRECTORY);

        try {
            tmpDir = new File(tmpDir).getCanonicalPath();
        } catch (IOException e) {
            StreamingException exception = new StreamingException(ErrorCode.SECURITY_INNER_ERROR);
            LOG.error("Failed to get canonical pathname for io error.", exception);
            throw exception;
        } catch (SecurityException e1) {
            StreamingException exception = new StreamingException(ErrorCode.SECURITY_INNER_ERROR);
            LOG.error("Failed to get canonical pathname for cannot be accessed.", exception);
            throw exception;
        }

        String jaasPath = tmpDir + File.separator + randomName + STREAMING_JAAS_POSTFIX;
        return jaasPath;
    }

    private String getKeyTablePath(String keyTabPath) throws StreamingException {
        try {
            return new File(keyTabPath).getCanonicalPath();
        } catch (IOException e) {
            StreamingException exception = new StreamingException(ErrorCode.SECURITY_KEYTAB_PATH_ERROR, keyTabPath);
            LOG.error("Failed to get canonical pathname for io error.", exception);
            throw exception;
        } catch (SecurityException e1) {
            StreamingException exception = new StreamingException(ErrorCode.SECURITY_KEYTAB_PATH_ERROR, keyTabPath);
            LOG.error("Failed to get canonical pathname for cannot be accessed.", exception);
            throw exception;
        }
    }

    private String formatPath(String path) {
        return path.replace("\\", "\\\\");
    }

    private String[] splitKerberosName(String fullName) {
        return fullName.split("[/@]");
    }

    private void backupSecurityProperties(String propertyKey) {
        if (System.getProperty(propertyKey) == null) {
            backupSecurityConf.put(propertyKey, "");
        } else {
            backupSecurityConf.put(propertyKey, System.getProperty(propertyKey));
        }
    }

    private void restoreSecurityProperties(String propertyKey) {
        if (Strings.isNullOrEmpty(backupSecurityConf.getProperty(propertyKey))) {
            System.clearProperty(propertyKey);
        } else {
            System.setProperty(propertyKey, backupSecurityConf.getProperty(propertyKey));
        }
    }
}