/*************************************************************************
* Copyright 2009-2013 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.cloudformation.resources.standard.actions;
import com.eucalyptus.auth.euare.CreateAccessKeyResponseType;
import com.eucalyptus.auth.euare.CreateAccessKeyType;
import com.eucalyptus.auth.euare.DeleteAccessKeyResponseType;
import com.eucalyptus.auth.euare.DeleteAccessKeyType;
import com.eucalyptus.auth.euare.UpdateAccessKeyResponseType;
import com.eucalyptus.auth.euare.UpdateAccessKeyType;
import com.eucalyptus.cloudformation.ValidationErrorException;
import com.eucalyptus.cloudformation.resources.IAMHelper;
import com.eucalyptus.cloudformation.resources.ResourceAction;
import com.eucalyptus.cloudformation.resources.ResourceInfo;
import com.eucalyptus.cloudformation.resources.ResourceProperties;
import com.eucalyptus.cloudformation.resources.standard.info.AWSIAMAccessKeyResourceInfo;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSIAMAccessKeyProperties;
import com.eucalyptus.cloudformation.template.JsonHelper;
import com.eucalyptus.cloudformation.util.MessageHelper;
import com.eucalyptus.cloudformation.workflow.steps.Step;
import com.eucalyptus.cloudformation.workflow.steps.StepBasedResourceAction;
import com.eucalyptus.cloudformation.workflow.steps.UpdateStep;
import com.eucalyptus.cloudformation.workflow.updateinfo.UpdateType;
import com.eucalyptus.cloudformation.workflow.updateinfo.UpdateTypeAndDirection;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.component.id.Euare;
import com.eucalyptus.util.async.AsyncRequests;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.collect.Maps;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Objects;
/**
* Created by ethomas on 2/3/14.
*/
public class AWSIAMAccessKeyResourceAction extends StepBasedResourceAction {
private AWSIAMAccessKeyProperties properties = new AWSIAMAccessKeyProperties();
private AWSIAMAccessKeyResourceInfo info = new AWSIAMAccessKeyResourceInfo();
public AWSIAMAccessKeyResourceAction() {
super(fromEnum(CreateSteps.class), fromEnum(DeleteSteps.class), fromUpdateEnum(UpdateNoInterruptionSteps.class), null);
// In this case, update with replacement has a precondition check before essentially the same steps as "create". We add both.
Map<String, UpdateStep> updateWithReplacementMap = Maps.newLinkedHashMap();
updateWithReplacementMap.putAll(fromUpdateEnum(UpdateWithReplacementPreCreateSteps.class));
updateWithReplacementMap.putAll(createStepsToUpdateWithReplacementSteps(fromEnum(CreateSteps.class)));
setUpdateSteps(UpdateTypeAndDirection.UPDATE_WITH_REPLACEMENT, updateWithReplacementMap);
}
@Override
public UpdateType getUpdateType(ResourceAction resourceAction, boolean stackTagsChanged) {
UpdateType updateType = info.supportsTags() && stackTagsChanged ? UpdateType.NO_INTERRUPTION : UpdateType.NONE;
AWSIAMAccessKeyResourceAction otherAction = (AWSIAMAccessKeyResourceAction) resourceAction;
if (!Objects.equals(properties.getUserName(), otherAction.properties.getUserName())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
if (!Objects.equals(properties.getStatus(), otherAction.properties.getStatus())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getSerial(), otherAction.properties.getSerial())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
return updateType;
}
private enum CreateSteps implements Step {
CREATE_KEY {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSIAMAccessKeyResourceAction action = (AWSIAMAccessKeyResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
if (action.properties.getStatus() == null) action.properties.setStatus("Active");
if (!"Active".equals(action.properties.getStatus()) && !"Inactive".equals(action.properties.getStatus())) {
throw new ValidationErrorException("Invalid status " + action.properties.getStatus());
}
CreateAccessKeyType createAccessKeyType = MessageHelper.createMessage(CreateAccessKeyType.class, action.info.getEffectiveUserId());
createAccessKeyType.setUserName(action.properties.getUserName());
CreateAccessKeyResponseType createAccessKeyResponseType = AsyncRequests.<CreateAccessKeyType,CreateAccessKeyResponseType> sendSync(configuration, createAccessKeyType);
// access key id = physical resource id
action.info.setPhysicalResourceId(createAccessKeyResponseType.getCreateAccessKeyResult().getAccessKey().getAccessKeyId());
action.info.setCreatedEnoughToDelete(true);
action.info.setSecretAccessKey(JsonHelper.getStringFromJsonNode(new TextNode(createAccessKeyResponseType.getCreateAccessKeyResult().getAccessKey().getSecretAccessKey())));
action.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(action.info.getPhysicalResourceId())));
return action;
}
},
SET_STATUS {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSIAMAccessKeyResourceAction action = (AWSIAMAccessKeyResourceAction) resourceAction;
if (action.properties.getStatus() == null) action.properties.setStatus("Active");
ServiceConfiguration configuration = Topology.lookup(Euare.class);
UpdateAccessKeyType updateAccessKeyType = MessageHelper.createMessage(UpdateAccessKeyType.class, action.info.getEffectiveUserId());
updateAccessKeyType.setUserName(action.properties.getUserName());
updateAccessKeyType.setAccessKeyId(action.info.getPhysicalResourceId());
updateAccessKeyType.setStatus(action.properties.getStatus());
AsyncRequests.<UpdateAccessKeyType,UpdateAccessKeyResponseType> sendSync(configuration, updateAccessKeyType);
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum DeleteSteps implements Step {
DELETE_KEY {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSIAMAccessKeyResourceAction action = (AWSIAMAccessKeyResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action;
if (action.properties.getStatus() == null) action.properties.setStatus("Active");
// if no user, bye.
if (!IAMHelper.userExists(configuration, action.properties.getUserName(), action.info.getEffectiveUserId())) return action;
if (!IAMHelper.accessKeyExists(configuration, action.info.getPhysicalResourceId(),
action.properties.getUserName(), action.info.getEffectiveUserId())) return action;
DeleteAccessKeyType deleteAccessKeyType = MessageHelper.createMessage(DeleteAccessKeyType.class, action.info.getEffectiveUserId());
deleteAccessKeyType.setUserName(action.properties.getUserName());
deleteAccessKeyType.setAccessKeyId(action.info.getPhysicalResourceId());
AsyncRequests.<DeleteAccessKeyType,DeleteAccessKeyResponseType> sendSync(configuration, deleteAccessKeyType);
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
@Override
public ResourceProperties getResourceProperties() {
return properties;
}
@Override
public void setResourceProperties(ResourceProperties resourceProperties) {
properties = (AWSIAMAccessKeyProperties) resourceProperties;
}
@Override
public ResourceInfo getResourceInfo() {
return info;
}
@Override
public void setResourceInfo(ResourceInfo resourceInfo) {
info = (AWSIAMAccessKeyResourceInfo) resourceInfo;
}
private enum UpdateNoInterruptionSteps implements UpdateStep {
UPDATE_STATUS {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMAccessKeyResourceAction oldAction = (AWSIAMAccessKeyResourceAction) oldResourceAction;
AWSIAMAccessKeyResourceAction newAction = (AWSIAMAccessKeyResourceAction) newResourceAction;
if (newAction.properties.getStatus() == null) newAction.properties.setStatus("Active");
ServiceConfiguration configuration = Topology.lookup(Euare.class);
UpdateAccessKeyType updateAccessKeyType = MessageHelper.createMessage(UpdateAccessKeyType.class, newAction.info.getEffectiveUserId());
updateAccessKeyType.setUserName(newAction.properties.getUserName());
updateAccessKeyType.setAccessKeyId(newAction.info.getPhysicalResourceId());
updateAccessKeyType.setStatus(newAction.properties.getStatus());
AsyncRequests.<UpdateAccessKeyType,UpdateAccessKeyResponseType> sendSync(configuration, updateAccessKeyType);
return newAction;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum UpdateWithReplacementPreCreateSteps implements UpdateStep {
CHECK_SERIAL {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMAccessKeyResourceAction oldAction = (AWSIAMAccessKeyResourceAction) oldResourceAction;
AWSIAMAccessKeyResourceAction newAction = (AWSIAMAccessKeyResourceAction) newResourceAction;
int oldSerial = oldAction.properties.getSerial() != null ? oldAction.properties.getSerial().intValue() : 0;
int newSerial = oldAction.properties.getSerial() != null ? newAction.properties.getSerial().intValue() : 0;
if (newSerial < oldSerial && Objects.equals(oldAction.properties.getUserName(), newAction.properties.getUserName())) {
throw new ValidationErrorException("AccessKey Serial cannot be decreased");
}
return newAction;
}
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
}
}