org.apache.hadoop.yarn.server.resourcemanager.security.RMAppSecurityManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.yarn.server.resourcemanager.security.RMAppSecurityManager.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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 org.apache.hadoop.yarn.server.resourcemanager.security;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.math3.util.Pair;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.security.ssl.X509SecurityMaterial;
import org.apache.hadoop.service.AbstractService;
import org.apache.hadoop.util.BackOff;
import org.apache.hadoop.util.ExponentialBackOff;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.event.EventHandler;
import org.apache.hadoop.yarn.server.resourcemanager.RMContext;
import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppEvent;
import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppEventType;
import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppSecurityMaterialGeneratedEvent;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.Security;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RMAppSecurityManager extends AbstractService implements EventHandler<RMAppSecurityManagerEvent> {
    private final static Log LOG = LogFactory.getLog(RMAppSecurityManager.class);
    private final static Map<String, ChronoUnit> CHRONOUNITS = new HashMap<>();
    static {
        CHRONOUNITS.put("MS", ChronoUnit.MILLIS);
        CHRONOUNITS.put("S", ChronoUnit.SECONDS);
        CHRONOUNITS.put("M", ChronoUnit.MINUTES);
        CHRONOUNITS.put("H", ChronoUnit.HOURS);
        CHRONOUNITS.put("D", ChronoUnit.DAYS);
    }
    private static final Pattern CONF_TIME_PATTERN = Pattern.compile("(^[0-9]+)(\\p{Alpha}+)");

    private RMContext rmContext;
    private Configuration conf;
    private EventHandler handler;
    private RMAppSecurityActions rmAppCertificateActions;
    private boolean isRPCTLSEnabled = false;
    private Map<Class, RMAppSecurityHandler> securityHandlersMap;

    private static final int RENEWAL_EXECUTOR_SERVICE_POOL_SIZE = 10;
    private ScheduledExecutorService renewalExecutorService;

    public RMAppSecurityManager(RMContext rmContext) {
        super(RMAppSecurityManager.class.getName());
        Security.addProvider(new BouncyCastleProvider());
        this.rmContext = rmContext;
        securityHandlersMap = new HashMap();
    }

    @Override
    protected void serviceInit(Configuration conf) throws Exception {
        LOG.debug("Initializing RMAppSecurityManager");
        this.conf = conf;
        this.handler = rmContext.getDispatcher().getEventHandler();
        rmAppCertificateActions = RMAppSecurityActionsFactory.getInstance().getActor(conf);
        isRPCTLSEnabled = conf.getBoolean(CommonConfigurationKeys.IPC_SERVER_SSL_ENABLED,
                CommonConfigurationKeys.IPC_SERVER_SSL_ENABLED_DEFAULT);

        renewalExecutorService = Executors.newScheduledThreadPool(RENEWAL_EXECUTOR_SERVICE_POOL_SIZE,
                new ThreadFactoryBuilder().setDaemon(true).setNameFormat("RMApp Security Material Renewer #%d")
                        .build());
        for (RMAppSecurityHandler handler : securityHandlersMap.values()) {
            handler.init(conf);
        }

        super.serviceInit(conf);
    }

    public void registerRMAppSecurityHandler(RMAppSecurityHandler securityHandler) {
        registerRMAppSecurityHandlerWithType(securityHandler, securityHandler.getClass());
    }

    @InterfaceAudience.Private
    @VisibleForTesting
    public void registerRMAppSecurityHandlerWithType(RMAppSecurityHandler securityHandler, Class type) {
        if (securityHandler != null) {
            securityHandlersMap.put(type, securityHandler);
        }
    }

    protected Pair<Long, TemporalUnit> parseInterval(String intervalFromConf, String confKey) {
        Matcher matcher = CONF_TIME_PATTERN.matcher(intervalFromConf);
        if (matcher.matches()) {
            Long interval = Long.parseLong(matcher.group(1));
            String unitStr = matcher.group(2);
            TemporalUnit unit = CHRONOUNITS.get(unitStr.toUpperCase());
            if (unit == null) {
                final StringBuilder validUnits = new StringBuilder();
                for (String key : CHRONOUNITS.keySet()) {
                    validUnits.append(key).append(", ");
                }
                validUnits.append("\b\b");
                throw new IllegalArgumentException(
                        "Could not parse ChronoUnit: " + unitStr + ". Valid values are " + validUnits.toString());
            }
            return new Pair<>(interval, unit);
        } else {
            throw new IllegalArgumentException("Could not parse value " + intervalFromConf + " of " + confKey);
        }
    }

    @Override
    protected void serviceStart() throws Exception {
        LOG.info("Starting RMAppSecurityManager");
        for (RMAppSecurityHandler handler : securityHandlersMap.values()) {
            handler.start();
        }

        super.serviceStart();
    }

    @Override
    protected void serviceStop() throws Exception {
        LOG.info("Stopping RMAppCertificateManager");
        for (RMAppSecurityHandler handler : securityHandlersMap.values()) {
            handler.stop();
        }
        if (renewalExecutorService != null) {
            try {
                renewalExecutorService.shutdown();
                if (!renewalExecutorService.awaitTermination(2L, TimeUnit.SECONDS)) {
                    renewalExecutorService.shutdownNow();
                }
                clearRMAppSecurityActionsFactory();
            } catch (InterruptedException ex) {
                renewalExecutorService.shutdownNow();
                if (rmAppCertificateActions != null) {
                    rmAppCertificateActions.destroy();
                }
                Thread.currentThread().interrupt();
            }
        }
    }

    @InterfaceAudience.Private
    @VisibleForTesting
    protected void clearRMAppSecurityActionsFactory() {
        RMAppSecurityActionsFactory.getInstance().clear();
    }

    protected ScheduledExecutorService getRenewalExecutorService() {
        return renewalExecutorService;
    }

    protected BackOff createBackOffPolicy() {
        return new ExponentialBackOff.Builder().setInitialIntervalMillis(200).setMaximumIntervalMillis(5000)
                .setMultiplier(1.5).setMaximumRetries(4).build();
    }

    @Override
    public void handle(RMAppSecurityManagerEvent event) {
        ApplicationId applicationId = event.getApplicationId();
        LOG.info("Processing event type: " + event.getType() + " for application: " + applicationId);
        if (event.getType().equals(RMAppSecurityManagerEventType.GENERATE_SECURITY_MATERIAL)) {
            generateSecurityMaterial(event);
        } else if (event.getType().equals(RMAppSecurityManagerEventType.REVOKE_SECURITY_MATERIAL)) {
            revokeSecurityMaterial(event);
        } else if (event.getType().equals(RMAppSecurityManagerEventType.REVOKE_CERTIFICATE_AFTER_ROTATION)) {
            revokeX509Only(event);
        } else if (event.getType().equals(RMAppSecurityManagerEventType.REVOKE_GENERATE_MATERIAL)) {
            revokeAndGenerateMaterial(event.getSecurityMaterial());
        } else {
            LOG.warn("Unknown event type " + event.getType());
        }
    }

    public <P extends SecurityManagerMaterial> void registerWithMaterialRenewers(P parameter) {
        if (parameter instanceof X509SecurityHandler.X509MaterialParameter) {
            X509SecurityHandler handler = (X509SecurityHandler) securityHandlersMap.get(X509SecurityHandler.class);
            if (handler != null) {
                handler.registerRenewer((X509SecurityHandler.X509MaterialParameter) parameter);
            } else {
                String errorMsg = "Tried to register with X.509 renewer but there is no handler";
                LOG.error(errorMsg);
                throw new NullPointerException(errorMsg);
            }
        } else if (parameter instanceof JWTSecurityHandler.JWTMaterialParameter) {
            JWTSecurityHandler handler = (JWTSecurityHandler) securityHandlersMap.get(JWTSecurityHandler.class);
            if (handler != null) {
                handler.registerRenewer((JWTSecurityHandler.JWTMaterialParameter) parameter);
            } else {
                String errorMsg = "Tried to register with JWT renewer but there is no handler";
                throw new NullPointerException(errorMsg);
            }
        }
    }

    @VisibleForTesting
    public RMAppSecurityActions getRmAppCertificateActions() {
        return rmAppCertificateActions;
    }

    @VisibleForTesting
    public RMAppSecurityHandler getSecurityHandler(Class type) {
        return securityHandlersMap.get(type);
    }

    @VisibleForTesting
    protected RMContext getRmContext() {
        return rmContext;
    }

    private void generateSecurityMaterial(RMAppSecurityManagerEvent event) {
        ApplicationId appId = event.getApplicationId();
        RMAppSecurityMaterial rmAppMaterial = new RMAppSecurityMaterial();
        try {
            // This could be a little bit more elegant
            for (RMAppSecurityHandler handler : securityHandlersMap.values()) {
                if (handler instanceof X509SecurityHandler) {
                    X509SecurityHandler.X509MaterialParameter x509Param = (X509SecurityHandler.X509MaterialParameter) event
                            .getSecurityMaterial().getMaterial(X509SecurityHandler.X509MaterialParameter.class);
                    if (x509Param == null) {
                        throw new NullPointerException(
                                "Hops TLS is enabled but X.509 parameter is null for " + appId);
                    }
                    SecurityManagerMaterial material = handler.generateMaterial(x509Param);
                    if (material != null) {
                        rmAppMaterial.addMaterial(material);
                    }
                } else if (handler instanceof JWTSecurityHandler) {
                    JWTSecurityHandler.JWTMaterialParameter jwtParam = (JWTSecurityHandler.JWTMaterialParameter) event
                            .getSecurityMaterial().getMaterial(JWTSecurityHandler.JWTMaterialParameter.class);
                    if (jwtParam == null) {
                        throw new NullPointerException(
                                "JWT on Yarn is enabled but JWT parameter is null for " + appId);
                    }
                    SecurityManagerMaterial material = handler.generateMaterial(jwtParam);
                    if (material != null) {
                        rmAppMaterial.addMaterial(material);
                    }
                }
            }
            if (rmAppMaterial.isEmpty()) {
                handler.handle(new RMAppEvent(appId, RMAppEventType.SECURITY_MATERIAL_GENERATED));
            } else {
                handler.handle(new RMAppSecurityMaterialGeneratedEvent(appId, rmAppMaterial,
                        RMAppEventType.SECURITY_MATERIAL_GENERATED));
            }
        } catch (Exception ex) {
            LOG.error("Error while generating RMApp security material", ex);
            handler.handle(new RMAppEvent(appId, RMAppEventType.KILL, "Error while generating application security "
                    + "material for " + appId + " - " + ex.getMessage()));
        }
    }

    private void revokeX509Only(RMAppSecurityManagerEvent event) {
        RMAppSecurityHandler x509Handler = securityHandlersMap.get(X509SecurityHandler.class);
        if (x509Handler == null && isRPCTLSEnabled()) {
            LOG.error("Hops TLS is enabled but there is no X509SecurityHandler registered");
        } else {
            revokeX509(event, x509Handler);
        }
    }

    @InterfaceAudience.Private
    @VisibleForTesting
    public void revokeSecurityMaterial(RMAppSecurityManagerEvent event) {
        for (RMAppSecurityHandler handler : securityHandlersMap.values()) {
            if (handler instanceof X509SecurityHandler) {
                revokeX509(event, handler);
            } else if (handler instanceof JWTSecurityHandler) {
                revokeJWT(event, handler);
            }
        }
    }

    private void revokeX509(RMAppSecurityManagerEvent event, RMAppSecurityHandler securityHandler) {
        ApplicationId appId = event.getApplicationId();
        X509SecurityHandler.X509MaterialParameter x509Param = (X509SecurityHandler.X509MaterialParameter) event
                .getSecurityMaterial().getMaterial(X509SecurityHandler.X509MaterialParameter.class);
        if (x509Param != null) {
            securityHandler.revokeMaterial(x509Param, false);
            LOG.debug("Revoked X.509 material for " + appId);
        }
    }

    private void revokeJWT(RMAppSecurityManagerEvent event, RMAppSecurityHandler securityHandler) {
        JWTSecurityHandler.JWTMaterialParameter jwtParam = (JWTSecurityHandler.JWTMaterialParameter) event
                .getSecurityMaterial().getMaterial(JWTSecurityHandler.JWTMaterialParameter.class);
        if (jwtParam != null) {
            securityHandler.revokeMaterial(jwtParam, false);
            LOG.debug("Revoked JWT material for " + jwtParam.getApplicationId());
        }
    }

    public <P extends SecurityManagerMaterial> void revokeSecurityMaterialSync(P parameter) {
        if (parameter instanceof X509SecurityHandler.X509MaterialParameter) {
            X509SecurityHandler handler = (X509SecurityHandler) securityHandlersMap.get(X509SecurityHandler.class);
            handler.revokeMaterial((X509SecurityHandler.X509MaterialParameter) parameter, true);
        }
    }

    // Special case when an RMApp is recovering and crypto material was not recovered for whatever reason
    public void revokeAndGenerateMaterial(RMAppSecurityMaterial securityMaterial) {
        X509SecurityHandler.X509MaterialParameter x509Param = (X509SecurityHandler.X509MaterialParameter) securityMaterial
                .getMaterial(X509SecurityHandler.X509MaterialParameter.class);
        boolean exceptionThrown = false;
        boolean x509Revoked = false;
        ApplicationId applicationId = null;
        RMAppSecurityMaterial newSecurityMaterial = new RMAppSecurityMaterial();

        X509SecurityHandler x509Handler = (X509SecurityHandler) securityHandlersMap.get(X509SecurityHandler.class);
        JWTSecurityHandler jwtHandler = (JWTSecurityHandler) securityHandlersMap.get(JWTSecurityHandler.class);

        // X.509 material
        if (x509Param != null) {
            applicationId = x509Param.getApplicationId();
            x509Revoked = x509Handler.revokeMaterial(x509Param, true);
        }
        if (x509Revoked && !exceptionThrown) {
            try {
                X509SecurityHandler.X509SecurityManagerMaterial newX509 = x509Handler.generateMaterial(x509Param);
                if (newX509 != null) {
                    newSecurityMaterial.addMaterial(newX509);
                }
            } catch (Exception ex) {
                LOG.error("Error when generating X.509 material for " + x509Param.getApplicationId(), ex);
                exceptionThrown = true;
            }

            // Add more security materials here

            // For JWT we do not need to invalidate the previous
            JWTSecurityHandler.JWTMaterialParameter jwtParam = (JWTSecurityHandler.JWTMaterialParameter) securityMaterial
                    .getMaterial(JWTSecurityHandler.JWTMaterialParameter.class);
            if (!exceptionThrown) {
                try {
                    if (jwtParam != null) {
                        if (applicationId == null) {
                            applicationId = jwtParam.getApplicationId();
                        }
                        JWTSecurityHandler.JWTSecurityManagerMaterial newJwt = jwtHandler
                                .generateMaterial(jwtParam);
                        if (newJwt != null) {
                            newSecurityMaterial.addMaterial(newJwt);
                        }
                    }
                } catch (Exception ex) {
                    LOG.error("Error when generating JWT material for " + applicationId, ex);
                    exceptionThrown = true;
                }
            }
            if (exceptionThrown) {
                /**
                 * You should revoke all security materials if something goes wrong
                 */
                X509SecurityHandler.X509SecurityManagerMaterial generatedX509 = (X509SecurityHandler.X509SecurityManagerMaterial) newSecurityMaterial
                        .getMaterial(X509SecurityHandler.X509SecurityManagerMaterial.class);
                if (generatedX509 != null) {
                    // X.509 was generated correctly but something went wrong later
                    // We should revoke it
                    int version2revoke = x509Param.getCryptoMaterialVersion() + 1;
                    X509SecurityHandler.X509MaterialParameter x5092revoke = new X509SecurityHandler.X509MaterialParameter(
                            x509Param.getApplicationId(), x509Param.getAppUser(), version2revoke);
                    x509Handler.revokeMaterial(x5092revoke, false);
                }

                // JWT was generated correctly but something else went wrong
                JWTSecurityHandler.JWTSecurityManagerMaterial generatedJWT = (JWTSecurityHandler.JWTSecurityManagerMaterial) newSecurityMaterial
                        .getMaterial(JWTSecurityHandler.JWTSecurityManagerMaterial.class);
                if (generatedJWT != null) {
                    jwtHandler.revokeMaterial(jwtParam, false);
                }

                handler.handle(new RMAppEvent(applicationId, RMAppEventType.KILL,
                        "Error while revoking and generating new security " + "material for " + applicationId));
            } else {
                if (newSecurityMaterial.isEmpty()) {
                    handler.handle(new RMAppEvent(applicationId, RMAppEventType.SECURITY_MATERIAL_GENERATED));
                } else {
                    handler.handle(new RMAppSecurityMaterialGeneratedEvent(applicationId, newSecurityMaterial,
                            RMAppEventType.SECURITY_MATERIAL_GENERATED));
                }
            }
        }
    }

    @VisibleForTesting
    @InterfaceAudience.Private
    public boolean isRPCTLSEnabled() {
        return isRPCTLSEnabled;
    }

    public abstract static class SecurityManagerMaterial {
        private final ApplicationId applicationId;

        protected SecurityManagerMaterial(ApplicationId applicationId) {
            this.applicationId = applicationId;
        }

        public ApplicationId getApplicationId() {
            return applicationId;
        }
    }
}