/*
* Copyright (c) 2013 Denis Mikhalkin.
*
* This software is provided 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.denismo.apacheds.auth;
import com.denismo.aws.iam.*;
import org.apache.directory.server.core.api.LdapPrincipal;
import org.apache.directory.server.core.api.entry.ClonedServerEntry;
import org.apache.directory.server.core.api.interceptor.context.BindOperationContext;
import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
import org.apache.directory.server.core.authn.AbstractAuthenticator;
import org.apache.directory.server.core.authn.SimpleAuthenticator;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.api.ldap.model.constants.AuthenticationLevel;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.mina.core.session.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.Properties;
/**
* User: Denis Mikhalkin
* Date: 30/03/13
* Time: 10:31 PM
*/
public class AWSIAMAuthenticator extends AbstractAuthenticator {
private static final Logger LOG = LoggerFactory.getLogger(AWSIAMAuthenticator.class);
public static class Config {
public static final String PASSWORD_VALIDATOR = "iam_password";
public static final String SECRET_KEY_VALIDATOR = "iam_secret_key";
public static final String DUAL_VALIDATOR = "iam_dual";
public String rootDN = "dc=iam,dc=aws,dc=org";
public int pollPeriod = 600;
public String validator = "iam_secret_key";
public boolean isPasswordLogin() { return PASSWORD_VALIDATOR.equals(validator); }
public boolean isSecretKeyLogin() { return SECRET_KEY_VALIDATOR.equals(validator); }
public boolean isDualLogin() { return DUAL_VALIDATOR.equals(validator); }
}
private static Config s_config;
public static void setConfig(Config config) {
s_config = config;
}
public static Config getConfig() {
return s_config;
}
private _IAMPasswordValidator validator;
private LDAPIAMPoller poller;
private SimpleAuthenticator delegatedAuth;
private boolean disabled;
public AWSIAMAuthenticator() {
super(AuthenticationLevel.SIMPLE);
delegatedAuth = new SimpleAuthenticator();
LOG.info("AWSIAMAuthenticator has been created");
}
@Override
protected void doInit() {
super.doInit();
LOG.debug("Init called");
if (getDirectoryService() != null) {
try {
delegatedAuth.init(getDirectoryService());
readIAMProperties();
poller = new LDAPIAMPoller(getDirectoryService());
poller.start();
} catch (Exception e) {
LOG.error("Exception initializing AWSIAMAuthenticator", e);
disabled=true;
}
} else {
LOG.debug("doInit without directory service");
}
}
private void readIAMProperties() throws LdapException {
String propsPath = System.getProperty("iamLdapPropertiesPath", "/etc/iam_ldap.conf");
File propsFile = new File(propsPath);
// Read the config file if exists
if (propsFile.exists()) {
try {
Properties props = new Properties();
props.load(new FileInputStream(propsFile));
AWSIAMAuthenticator.Config config = new AWSIAMAuthenticator.Config();
if (props.containsKey("pollPeriod")) config.pollPeriod = Integer.parseInt(props.getProperty("pollPeriod"));
if (props.containsKey("rootDN")) config.rootDN = props.getProperty("rootDN");
if (props.containsKey("validator")) config.validator = props.getProperty("validator");
AWSIAMAuthenticator.setConfig(config);
} catch (IOException e) {
LOG.error("Unable to read IAM LDAP config file");
AWSIAMAuthenticator.setConfig(new AWSIAMAuthenticator.Config());
}
} else {
// Populate from defaults
AWSIAMAuthenticator.setConfig(new AWSIAMAuthenticator.Config());
}
createValidator();
}
private void createValidator() throws LdapException {
Config config = getConfig();
if (config.isPasswordLogin()) {
validator = new IAMAccountPasswordValidator();
} else if (config.isSecretKeyLogin()) {
validator = new IAMSecretKeyValidator();
} else if (config.isDualLogin()) {
validator = new IAMDualValidator();
} else {
throw new LdapException("Unsupported validator mode: " + config.validator);
}
}
@Override
public LdapPrincipal authenticate(BindOperationContext bindContext) throws Exception {
if (!isAWSAccount(bindContext) || disabled) {
LOG.debug("Skipping " + bindContext.getDn() + " - not an AWS account");
if (delegatedAuth == null) {
LOG.error("Delegated auth is null");
return null;
}
return delegatedAuth.authenticate(bindContext);
}
LOG.debug("Authenticating " + bindContext.getDn());
byte[] password = bindContext.getCredentials();
LookupOperationContext lookupContext = new LookupOperationContext( getDirectoryService().getAdminSession(),
bindContext.getDn(), SchemaConstants.ALL_USER_ATTRIBUTES, SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES);
Entry userEntry = getDirectoryService().getPartitionNexus().lookup( lookupContext );
if (validator.verifyIAMPassword(userEntry, new String(password))) {
LdapPrincipal principal = new LdapPrincipal( getDirectoryService().getSchemaManager(), bindContext.getDn(),
AuthenticationLevel.SIMPLE, password);
IoSession session = bindContext.getIoSession();
if ( session != null )
{
SocketAddress clientAddress = session.getRemoteAddress();
principal.setClientAddress( clientAddress );
SocketAddress serverAddress = session.getServiceAddress();
principal.setServerAddress( serverAddress );
}
bindContext.setEntry( new ClonedServerEntry( userEntry ) );
return principal;
} else {
// Bad password ...
String message = I18n.err( I18n.ERR_230, bindContext.getDn().getName() );
LOG.info( message );
throw new LdapAuthenticationException( message );
}
}
private boolean isAWSAccount(BindOperationContext bindContext) throws LdapException {
LookupOperationContext lookupContext = new LookupOperationContext( getDirectoryService().getAdminSession(),
bindContext.getDn(), SchemaConstants.ALL_USER_ATTRIBUTES, SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES);
Entry userEntry = getDirectoryService().getPartitionNexus().lookup( lookupContext );
return userEntry.hasObjectClass("iamaccount");
}
}