/*************************************************************************
* 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.AttachRolePolicyType;
import com.eucalyptus.auth.euare.AttachUserPolicyType;
import com.eucalyptus.auth.euare.AttachedPolicyType;
import com.eucalyptus.auth.euare.CreateRoleResponseType;
import com.eucalyptus.auth.euare.CreateRoleType;
import com.eucalyptus.auth.euare.DeleteRolePolicyResponseType;
import com.eucalyptus.auth.euare.DeleteRolePolicyType;
import com.eucalyptus.auth.euare.DeleteRoleResponseType;
import com.eucalyptus.auth.euare.DeleteRoleType;
import com.eucalyptus.auth.euare.DetachRolePolicyType;
import com.eucalyptus.auth.euare.ListAttachedGroupPoliciesResponseType;
import com.eucalyptus.auth.euare.ListAttachedRolePoliciesResponseType;
import com.eucalyptus.auth.euare.ListAttachedRolePoliciesType;
import com.eucalyptus.auth.euare.PutRolePolicyResponseType;
import com.eucalyptus.auth.euare.PutRolePolicyType;
import com.eucalyptus.auth.euare.RemoveUserFromGroupResponseType;
import com.eucalyptus.auth.euare.RemoveUserFromGroupType;
import com.eucalyptus.auth.euare.UpdateAssumeRolePolicyResponseType;
import com.eucalyptus.auth.euare.UpdateAssumeRolePolicyType;
import com.eucalyptus.cloudformation.CloudFormationException;
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.AWSIAMRoleResourceInfo;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSIAMRoleProperties;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.EmbeddedIAMPolicy;
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.AsyncExceptions;
import com.eucalyptus.util.async.AsyncRequests;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Created by ethomas on 2/3/14.
*/
public class AWSIAMRoleResourceAction extends StepBasedResourceAction {
private AWSIAMRoleProperties properties = new AWSIAMRoleProperties();
private AWSIAMRoleResourceInfo info = new AWSIAMRoleResourceInfo();
public AWSIAMRoleResourceAction() {
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;
AWSIAMRoleResourceAction otherAction = (AWSIAMRoleResourceAction) resourceAction;
if (!Objects.equals(properties.getAssumeRolePolicyDocument(), otherAction.properties.getAssumeRolePolicyDocument())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getPath(), otherAction.properties.getPath())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
if (!Objects.equals(properties.getManagedPolicyArns(), otherAction.properties.getManagedPolicyArns())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getPolicies(), otherAction.properties.getPolicies())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getRoleName(), otherAction.properties.getRoleName())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
return updateType;
}
private enum CreateSteps implements Step {
CREATE_ROLE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSIAMRoleResourceAction action = (AWSIAMRoleResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
String roleName = action.properties.getRoleName() != null ? action.properties.getRoleName() : action.getDefaultPhysicalResourceId();
CreateRoleType createRoleType = MessageHelper.createMessage(CreateRoleType.class, action.info.getEffectiveUserId());
createRoleType.setRoleName(roleName);
createRoleType.setPath(action.properties.getPath());
createRoleType.setAssumeRolePolicyDocument(action.properties.getAssumeRolePolicyDocument().toString());
CreateRoleResponseType createRoleResponseType = AsyncRequests.<CreateRoleType,CreateRoleResponseType> sendSync(configuration, createRoleType);
String arn = createRoleResponseType.getCreateRoleResult().getRole().getArn();
action.info.setPhysicalResourceId(roleName);
action.info.setCreatedEnoughToDelete(true);
action.info.setArn(JsonHelper.getStringFromJsonNode(new TextNode(arn)));
action.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(action.info.getPhysicalResourceId())));
return action;
}
},
ADD_POLICIES {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSIAMRoleResourceAction action = (AWSIAMRoleResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
if (action.properties.getPolicies() != null) {
for (EmbeddedIAMPolicy policy: action.properties.getPolicies()) {
PutRolePolicyType putRolePolicyType = MessageHelper.createMessage(PutRolePolicyType.class, action.info.getEffectiveUserId());
putRolePolicyType.setRoleName(action.info.getPhysicalResourceId());
putRolePolicyType.setPolicyName(policy.getPolicyName());
putRolePolicyType.setPolicyDocument(policy.getPolicyDocument().toString());
AsyncRequests.<PutRolePolicyType,PutRolePolicyResponseType> sendSync(configuration, putRolePolicyType);
}
}
return action;
}
},
ADD_MANAGED_POLICIES {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSIAMRoleResourceAction action = (AWSIAMRoleResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
if (action.properties.getPolicies() != null) {
for (String managedPolicyArn: action.properties.getManagedPolicyArns()) {
AttachRolePolicyType attachRolePolicyType = MessageHelper.createMessage(AttachRolePolicyType.class, action.info.getEffectiveUserId());
attachRolePolicyType.setRoleName(action.info.getPhysicalResourceId());
attachRolePolicyType.setPolicyArn(managedPolicyArn);
AsyncRequests.sendSync(configuration, attachRolePolicyType);
}
}
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum DeleteSteps implements Step {
DELETE_ROLE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSIAMRoleResourceAction action = (AWSIAMRoleResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action;
// if no role, bye...
if (!IAMHelper.roleExists(configuration, action.info.getPhysicalResourceId(), action.info.getEffectiveUserId())) return action;
// remove all policies added by us. (Note: this could cause issues if an admin added some, but we delete what we create)
// Note: deleting a non-existing policy doesn't do anything so we just delete them all...
if (action.properties.getPolicies() != null) {
for (EmbeddedIAMPolicy policy: action.properties.getPolicies()) {
DeleteRolePolicyType deleteRolePolicyType = MessageHelper.createMessage(DeleteRolePolicyType.class, action.info.getEffectiveUserId());
deleteRolePolicyType.setRoleName(action.info.getPhysicalResourceId());
deleteRolePolicyType.setPolicyName(policy.getPolicyName());
AsyncRequests.<DeleteRolePolicyType,DeleteRolePolicyResponseType> sendSync(configuration, deleteRolePolicyType);
}
}
DeleteRoleType deleteRoleType = MessageHelper.createMessage(DeleteRoleType.class, action.info.getEffectiveUserId());
deleteRoleType.setRoleName(action.info.getPhysicalResourceId());
AsyncRequests.<DeleteRoleType,DeleteRoleResponseType> sendSync(configuration, deleteRoleType);
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
@Override
public ResourceProperties getResourceProperties() {
return properties;
}
@Override
public void setResourceProperties(ResourceProperties resourceProperties) {
properties = (AWSIAMRoleProperties) resourceProperties;
}
@Override
public ResourceInfo getResourceInfo() {
return info;
}
@Override
public void setResourceInfo(ResourceInfo resourceInfo) {
info = (AWSIAMRoleResourceInfo) resourceInfo;
}
private static Set<String> getPolicyNames(AWSIAMRoleResourceAction action) {
Set<String> policyNames = Sets.newLinkedHashSet();
if (action.properties.getPolicies() != null) {
for (EmbeddedIAMPolicy policy : action.properties.getPolicies()) {
policyNames.add(policy.getPolicyName());
}
}
return policyNames;
}
private static Set<String> getManagedPolicyArns(AWSIAMRoleResourceAction action) {
Set<String> managedPolicyArns = Sets.newHashSet();
if (action != null && action.properties != null && action.properties.getManagedPolicyArns() != null) {
managedPolicyArns.addAll(action.properties.getManagedPolicyArns());
}
return managedPolicyArns;
}
private static Set<String> getExistingManagedPolicyArns(AWSIAMRoleResourceAction newAction) throws Exception {
ServiceConfiguration configuration = Topology.lookup(Euare.class);
ListAttachedRolePoliciesType listAttachedRolePoliciesType = MessageHelper.createMessage(ListAttachedRolePoliciesType.class, newAction.info.getEffectiveUserId());
listAttachedRolePoliciesType.setRoleName(newAction.info.getPhysicalResourceId());
ListAttachedRolePoliciesResponseType listAttachedRolePoliciesResponseType = AsyncRequests.sendSync(configuration, listAttachedRolePoliciesType);
Set<String> result = Sets.newHashSet();
if (listAttachedRolePoliciesResponseType != null && listAttachedRolePoliciesResponseType.getListAttachedRolePoliciesResult() != null &&
listAttachedRolePoliciesResponseType.getListAttachedRolePoliciesResult().getAttachedPolicies() != null) {
for (AttachedPolicyType attachedPolicyType : listAttachedRolePoliciesResponseType.getListAttachedRolePoliciesResult().getAttachedPolicies()) {
result.add(attachedPolicyType.getPolicyArn());
}
}
return result;
}
private enum UpdateNoInterruptionSteps implements UpdateStep {
UPDATE_ASSUME_ROLE_DOCUMENT {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMRoleResourceAction oldAction = (AWSIAMRoleResourceAction) oldResourceAction;
AWSIAMRoleResourceAction newAction = (AWSIAMRoleResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
UpdateAssumeRolePolicyType updateAssumeRolePolicyType = MessageHelper.createMessage(UpdateAssumeRolePolicyType.class, newAction.info.getEffectiveUserId());
updateAssumeRolePolicyType.setRoleName(newAction.info.getPhysicalResourceId());
updateAssumeRolePolicyType.setPolicyDocument(newAction.properties.getAssumeRolePolicyDocument().toString());
AsyncRequests.<UpdateAssumeRolePolicyType,UpdateAssumeRolePolicyResponseType> sendSync(configuration, updateAssumeRolePolicyType);
return newAction;
}
},
UPDATE_POLICIES {
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMRoleResourceAction oldAction = (AWSIAMRoleResourceAction) oldResourceAction;
AWSIAMRoleResourceAction newAction = (AWSIAMRoleResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
Set<String> oldPolicyNames = getPolicyNames(oldAction);
Set<String> newPolicyNames = getPolicyNames(newAction);
if (newAction.properties.getPolicies() != null) {
for (EmbeddedIAMPolicy policy: newAction.properties.getPolicies()) {
PutRolePolicyType putRolePolicyType = MessageHelper.createMessage(PutRolePolicyType.class, newAction.info.getEffectiveUserId());
putRolePolicyType.setRoleName(newAction.info.getPhysicalResourceId());
putRolePolicyType.setPolicyName(policy.getPolicyName());
putRolePolicyType.setPolicyDocument(policy.getPolicyDocument().toString());
AsyncRequests.<PutRolePolicyType,PutRolePolicyResponseType> sendSync(configuration, putRolePolicyType);
}
}
// delete all the old policies not in the new set
// Note: deleting a non-existing policy doesn't do anything so we just delete them all...
for (String oldPolicyName : Sets.difference(oldPolicyNames, newPolicyNames)) {
DeleteRolePolicyType deleteRolePolicyType = MessageHelper.createMessage(DeleteRolePolicyType.class, newAction.info.getEffectiveUserId());
deleteRolePolicyType.setRoleName(newAction.info.getPhysicalResourceId());
deleteRolePolicyType.setPolicyName(oldPolicyName);
AsyncRequests.<DeleteRolePolicyType,DeleteRolePolicyResponseType> sendSync(configuration, deleteRolePolicyType);
}
return newAction;
}
},
UPDATE_MANAGED_POLICIES {
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMRoleResourceAction oldAction = (AWSIAMRoleResourceAction) oldResourceAction;
AWSIAMRoleResourceAction newAction = (AWSIAMRoleResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
Set<String> oldManagedPolicyArns = getManagedPolicyArns(oldAction);
Set<String> newManagedPolicyArns = getManagedPolicyArns(newAction);
Set<String> existingManagedPolicyArns = getExistingManagedPolicyArns(newAction);
// the policies to add are the new policies that are not old and not existing
Set<String> managedPolicyArnsToAdd = Sets.difference(newManagedPolicyArns, Sets.union(oldManagedPolicyArns, existingManagedPolicyArns));
for (String managedPolicyArn: managedPolicyArnsToAdd) {
AttachRolePolicyType attachRolePolicyType = MessageHelper.createMessage(AttachRolePolicyType.class, newAction.info.getEffectiveUserId());
attachRolePolicyType.setRoleName(newAction.info.getPhysicalResourceId());
attachRolePolicyType.setPolicyArn(managedPolicyArn);
AsyncRequests.sendSync(configuration, attachRolePolicyType);
}
// the policies to remove from the resource are the old policies that are not new, but they must also exist.
Set<String> managedPolicyArnsToRemove = Sets.difference(Sets.intersection(existingManagedPolicyArns, oldManagedPolicyArns), newManagedPolicyArns);
for (String managedPolicyArn: managedPolicyArnsToRemove) {
DetachRolePolicyType detachRolePolicyType = MessageHelper.createMessage(DetachRolePolicyType.class, newAction.info.getEffectiveUserId());
detachRolePolicyType.setRoleName(newAction.info.getPhysicalResourceId());
detachRolePolicyType.setPolicyArn(managedPolicyArn);
try {
AsyncRequests.sendSync(configuration, detachRolePolicyType);
} catch ( final Exception e ) {
final Optional<AsyncExceptions.AsyncWebServiceError> error = AsyncExceptions.asWebServiceError(e);
if (error.isPresent() && Strings.nullToEmpty(error.get().getCode()).equals("NoSuchEntity")) {
// we don't care. (already deleted or never there)
} else throw e;
}
}
return newAction;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum UpdateWithReplacementPreCreateSteps implements UpdateStep {
CHECK_CHANGED_ROLE_NAME {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMRoleResourceAction oldAction = (AWSIAMRoleResourceAction) oldResourceAction;
AWSIAMRoleResourceAction newAction = (AWSIAMRoleResourceAction) newResourceAction;
if (Objects.equals(oldAction.properties.getRoleName(), newAction.properties.getRoleName()) && oldAction.properties.getRoleName() != null) {
throw new ValidationErrorException("CloudFormation cannot update a stack when a custom-named resource requires replacing. Rename "+oldAction.properties.getRoleName()+" and update the stack again.");
}
return newAction;
}
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
}
}