org.apache.hadoop.mapred.PriorityAuthorization.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.mapred.PriorityAuthorization.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 org.apache.hadoop.mapred;

import java.security.SignatureException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.File;
import java.net.URLDecoder;

import java.util.HashMap;

import org.apache.hadoop.conf.Configuration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.commons.codec.binary.Base64;

/**
 * This class implements symmetric key HMAC/SHA1 signature
 * based authorization of users and admins.
 */
public class PriorityAuthorization {
    public static final int USER = 0;
    public static final int ADMIN = 1;
    public static final int NO_ACCESS = 2;
    private HashMap<String, UserACL> acl = new HashMap<String, UserACL>();
    private long lastSuccessfulReload = 0;
    public static final long START_TIME = System.currentTimeMillis();
    private String aclFile;
    private static final Log LOG = LogFactory.getLog(PriorityAuthorization.class);
    private static final boolean debug = LOG.isDebugEnabled();
    private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";

    /**
     * Initializes authorization configuration
     * @param conf MapReduce configuration handle 
     */
    public void init(Configuration conf) {
        aclFile = conf.get("mapred.priority-scheduler.acl-file", "/etc/hadoop.acl");
    }

    /**
     * Adapted from AWS Query Authentication cookbook:
     * Computes RFC 2104-compliant HMAC signature.
     *
     * @param data
     *     The data to be signed.
     * @param key
     *     The signing key.
     * @return
     *     The base64-encoded RFC 2104-compliant HMAC signature.
     * @throws
     *     java.security.SignatureException when signature generation fails
     */
    public static String hmac(String data, String key) throws java.security.SignatureException {
        String result;
        try {
            // get an hmac_sha1 key from the raw key bytes
            SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);

            // get an hmac_sha1 Mac instance and initialize with the signing key
            Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
            mac.init(signingKey);

            // compute the hmac on input data bytes
            byte[] rawHmac = mac.doFinal(data.getBytes());

            // base64-encode the hmac
            result = new String(Base64.encodeBase64(rawHmac));
        } catch (Exception e) {
            throw new SignatureException("Failed to generate HMAC : " + e, e);
        }
        return result;
    }

    class UserACL {
        String user;
        String role;
        String key;
        // for replay detection
        long lastTimestamp = START_TIME;

        UserACL(String user, String role, String key) {
            this.user = user;
            this.role = role;
            this.key = key;
        }
    }

    private void reloadACL() {
        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(aclFile));
            String line = in.readLine();
            while (line != null) {
                String[] nameValue = line.split(" ");
                if (nameValue.length != 3) {
                    continue;
                }
                acl.put(nameValue[0], new UserACL(nameValue[0], nameValue[1], nameValue[2]));
                if (debug) {
                    LOG.debug("Loading " + line);
                }
                line = in.readLine();
            }
        } catch (Exception e) {
            LOG.error(e);
        }
        try {
            in.close();
        } catch (Exception e) {
            LOG.error(e);
        }
    }

    private void loadACL() {
        long time = System.currentTimeMillis();
        try {
            File file = new File(aclFile);
            long lastModified = file.lastModified();
            if (lastModified > lastSuccessfulReload) {
                reloadACL();
                lastSuccessfulReload = time;
            }
        } catch (Exception e) {
            LOG.error("Failed to reload acl file", e);
        }
    }

    private boolean isReplay(String timestamp, String signature, UserACL userACL) {
        long signatureTime = Long.parseLong(timestamp);
        if (debug) {
            LOG.debug("signaturetime: " + Long.toString(signatureTime));
            LOG.debug("lasttime: " + Long.toString(userACL.lastTimestamp));
        }
        if (signatureTime <= userACL.lastTimestamp) {
            return true;
        }
        userACL.lastTimestamp = signatureTime;
        return false;
    }

    /**
     * Returns authorized role for user.
     * Checks whether signature obtained by user was made by key stored in local acl.
     * Also checks for replay attacks.
     * @param data data that was signed by user
     * @param signature user-provided signature
     * @param user-provided nonce/timestamp of signature 
     * @return the authorized role of the user:
     *   ADMIN, USER or NO_ACCESS
     */
    public int authorize(String data, String signature, String user, String timestamp) {
        try {
            signature = URLDecoder.decode(signature, "UTF-8");
        } catch (Exception e) {
            LOG.error("Authorization exception:", e);
            return NO_ACCESS;
        }
        if (debug) {
            LOG.debug(data + " sig: " + signature + " user: " + user + " time: " + timestamp);
        }
        try {
            loadACL();
            UserACL userACL = acl.get(user);
            if (userACL == null) {
                return NO_ACCESS;
            }
            String signatureTest = hmac(data, userACL.key);
            if (debug) {
                LOG.debug("SignatureTest " + signatureTest);
                LOG.debug("Signature " + signature);
            }
            if (signatureTest.equals(signature) && !isReplay(timestamp, signature, userACL)) {
                return (userACL.role.equals("admin")) ? ADMIN : USER;
            }
        } catch (Exception e) {
            LOG.error("Athorization exception:", e);
        }
        return NO_ACCESS;
    }
}