/*************************************************************************
* 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.AttachGroupPolicyType;
import com.eucalyptus.auth.euare.AttachedPolicyType;
import com.eucalyptus.auth.euare.CreateGroupResponseType;
import com.eucalyptus.auth.euare.CreateGroupType;
import com.eucalyptus.auth.euare.DeleteGroupPolicyResponseType;
import com.eucalyptus.auth.euare.DeleteGroupPolicyType;
import com.eucalyptus.auth.euare.DeleteGroupResponseType;
import com.eucalyptus.auth.euare.DeleteGroupType;
import com.eucalyptus.auth.euare.DetachGroupPolicyType;
import com.eucalyptus.auth.euare.GroupType;
import com.eucalyptus.auth.euare.ListAttachedGroupPoliciesResponseType;
import com.eucalyptus.auth.euare.ListAttachedGroupPoliciesType;
import com.eucalyptus.auth.euare.PutGroupPolicyResponseType;
import com.eucalyptus.auth.euare.PutGroupPolicyType;
import com.eucalyptus.auth.euare.UpdateGroupResponseType;
import com.eucalyptus.auth.euare.UpdateGroupType;
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.AWSIAMGroupResourceInfo;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSIAMGroupProperties;
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.MoreObjects;
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 AWSIAMGroupResourceAction extends StepBasedResourceAction {
private AWSIAMGroupProperties properties = new AWSIAMGroupProperties();
private AWSIAMGroupResourceInfo info = new AWSIAMGroupResourceInfo();
public AWSIAMGroupResourceAction() {
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);
}
private static final String DEFAULT_PATH = "/";
@Override
public UpdateType getUpdateType(ResourceAction resourceAction, boolean stackTagsChanged) {
UpdateType updateType = info.supportsTags() && stackTagsChanged ? UpdateType.NO_INTERRUPTION : UpdateType.NONE;
AWSIAMGroupResourceAction otherAction = (AWSIAMGroupResourceAction) resourceAction;
if (!Objects.equals(properties.getPath(), otherAction.properties.getPath())) {
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.getManagedPolicyArns(), otherAction.properties.getManagedPolicyArns())) {
updateType = UpdateType.max(updateType, UpdateType.NO_INTERRUPTION);
}
if (!Objects.equals(properties.getGroupName(), otherAction.properties.getGroupName())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
return updateType;
}
private enum CreateSteps implements Step {
CREATE_GROUP {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSIAMGroupResourceAction action = (AWSIAMGroupResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
String groupName = action.properties.getGroupName() != null ? action.properties.getGroupName() : action.getDefaultPhysicalResourceId();
CreateGroupType createGroupType = MessageHelper.createMessage(CreateGroupType.class, action.info.getEffectiveUserId());
createGroupType.setGroupName(groupName);
createGroupType.setPath(MoreObjects.firstNonNull(action.properties.getPath(), DEFAULT_PATH));
CreateGroupResponseType createGroupResponseType = AsyncRequests.<CreateGroupType,CreateGroupResponseType> sendSync(configuration, createGroupType);
String arn = createGroupResponseType.getCreateGroupResult().getGroup().getArn();
action.info.setPhysicalResourceId(groupName);
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 {
AWSIAMGroupResourceAction action = (AWSIAMGroupResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
if (action.properties.getPolicies() != null) {
for (EmbeddedIAMPolicy policy: action.properties.getPolicies()) {
PutGroupPolicyType putGroupPolicyType = MessageHelper.createMessage(PutGroupPolicyType.class, action.info.getEffectiveUserId());
putGroupPolicyType.setGroupName(action.info.getPhysicalResourceId());
putGroupPolicyType.setPolicyName(policy.getPolicyName());
putGroupPolicyType.setPolicyDocument(policy.getPolicyDocument().toString());
AsyncRequests.<PutGroupPolicyType,PutGroupPolicyResponseType> sendSync(configuration, putGroupPolicyType);
}
}
return action;
}
},
ADD_MANAGED_POLICIES {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSIAMGroupResourceAction action = (AWSIAMGroupResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
if (action.properties.getPolicies() != null) {
for (String managedPolicyArn: action.properties.getManagedPolicyArns()) {
AttachGroupPolicyType attachGroupPolicyType = MessageHelper.createMessage(AttachGroupPolicyType.class, action.info.getEffectiveUserId());
attachGroupPolicyType.setGroupName(action.info.getPhysicalResourceId());
attachGroupPolicyType.setPolicyArn(managedPolicyArn);
AsyncRequests.sendSync(configuration, attachGroupPolicyType);
}
}
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum DeleteSteps implements Step {
DELETE_GROUP {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSIAMGroupResourceAction action = (AWSIAMGroupResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action;
// if no group, bye...
if (!IAMHelper.groupExists(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()) {
DeleteGroupPolicyType deleteGroupPolicyType = MessageHelper.createMessage(DeleteGroupPolicyType.class, action.info.getEffectiveUserId());
deleteGroupPolicyType.setGroupName(action.info.getPhysicalResourceId());
deleteGroupPolicyType.setPolicyName(policy.getPolicyName());
AsyncRequests.<DeleteGroupPolicyType,DeleteGroupPolicyResponseType> sendSync(configuration, deleteGroupPolicyType);
}
}
DeleteGroupType deleteGroupType = MessageHelper.createMessage(DeleteGroupType.class, action.info.getEffectiveUserId());
deleteGroupType.setGroupName(action.info.getPhysicalResourceId());
AsyncRequests.<DeleteGroupType,DeleteGroupResponseType> sendSync(configuration, deleteGroupType);
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
@Override
public ResourceProperties getResourceProperties() {
return properties;
}
@Override
public void setResourceProperties(ResourceProperties resourceProperties) {
properties = (AWSIAMGroupProperties) resourceProperties;
}
@Override
public ResourceInfo getResourceInfo() {
return info;
}
@Override
public void setResourceInfo(ResourceInfo resourceInfo) {
info = (AWSIAMGroupResourceInfo) resourceInfo;
}
private static Set<String> getManagedPolicyArns(AWSIAMGroupResourceAction 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(AWSIAMGroupResourceAction newAction) throws Exception {
ServiceConfiguration configuration = Topology.lookup(Euare.class);
ListAttachedGroupPoliciesType listAttachedGroupPoliciesType = MessageHelper.createMessage(ListAttachedGroupPoliciesType.class, newAction.info.getEffectiveUserId());
listAttachedGroupPoliciesType.setGroupName(newAction.info.getPhysicalResourceId());
ListAttachedGroupPoliciesResponseType listAttachedGroupPoliciesResponseType = AsyncRequests.sendSync(configuration, listAttachedGroupPoliciesType);
Set<String> result = Sets.newHashSet();
if (listAttachedGroupPoliciesResponseType != null && listAttachedGroupPoliciesResponseType.getListAttachedGroupPoliciesResult() != null &&
listAttachedGroupPoliciesResponseType.getListAttachedGroupPoliciesResult().getAttachedPolicies() != null) {
for (AttachedPolicyType attachedPolicyType : listAttachedGroupPoliciesResponseType.getListAttachedGroupPoliciesResult().getAttachedPolicies()) {
result.add(attachedPolicyType.getPolicyArn());
}
}
return result;
}
private enum UpdateNoInterruptionSteps implements UpdateStep {
UPDATE_GROUP {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMGroupResourceAction oldAction = (AWSIAMGroupResourceAction) oldResourceAction;
AWSIAMGroupResourceAction newAction = (AWSIAMGroupResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
String groupName = newAction.info.getPhysicalResourceId();
UpdateGroupType updateGroupType = MessageHelper.createMessage(UpdateGroupType.class, newAction.info.getEffectiveUserId());
updateGroupType.setGroupName(groupName);
updateGroupType.setNewPath(MoreObjects.firstNonNull(newAction.properties.getPath(), DEFAULT_PATH));
AsyncRequests.<UpdateGroupType, UpdateGroupResponseType>sendSync(configuration, updateGroupType);
return newAction;
}
},
UPDATE_ARN {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMGroupResourceAction oldAction = (AWSIAMGroupResourceAction) oldResourceAction;
AWSIAMGroupResourceAction newAction = (AWSIAMGroupResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
String groupName = newAction.info.getPhysicalResourceId();
GroupType group = IAMHelper.getGroup(configuration, groupName, newAction.info.getEffectiveUserId());
newAction.info.setArn(JsonHelper.getStringFromJsonNode(new TextNode(group.getArn())));
return newAction;
}
},
UPDATE_POLICIES {
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMGroupResourceAction oldAction = (AWSIAMGroupResourceAction) oldResourceAction;
AWSIAMGroupResourceAction newAction = (AWSIAMGroupResourceAction) newResourceAction;
ServiceConfiguration configuration = Topology.lookup(Euare.class);
Set<String> oldPolicyNames = IAMHelper.getPolicyNames(oldAction.properties.getPolicies());
Set<String> newPolicyNames = IAMHelper.getPolicyNames(newAction.properties.getPolicies());
if (newAction.properties.getPolicies() != null) {
for (EmbeddedIAMPolicy policy : newAction.properties.getPolicies()) {
PutGroupPolicyType putGroupPolicyType = MessageHelper.createMessage(PutGroupPolicyType.class, newAction.info.getEffectiveUserId());
putGroupPolicyType.setGroupName(newAction.info.getPhysicalResourceId());
putGroupPolicyType.setPolicyName(policy.getPolicyName());
putGroupPolicyType.setPolicyDocument(policy.getPolicyDocument().toString());
AsyncRequests.<PutGroupPolicyType, PutGroupPolicyResponseType>sendSync(configuration, putGroupPolicyType);
}
}
// delete all the old policies not in the new set (remember deleting policies that don't exist doesn't do anything)
for (String oldPolicyName : Sets.difference(oldPolicyNames, newPolicyNames)) {
DeleteGroupPolicyType deleteGroupPolicyType = MessageHelper.createMessage(DeleteGroupPolicyType.class, newAction.info.getEffectiveUserId());
deleteGroupPolicyType.setGroupName(newAction.info.getPhysicalResourceId());
deleteGroupPolicyType.setPolicyName(oldPolicyName);
AsyncRequests.<DeleteGroupPolicyType, DeleteGroupPolicyResponseType>sendSync(configuration, deleteGroupPolicyType);
}
return newAction;
}
},
UPDATE_MANAGED_POLICIES {
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMGroupResourceAction oldAction = (AWSIAMGroupResourceAction) oldResourceAction;
AWSIAMGroupResourceAction newAction = (AWSIAMGroupResourceAction) 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) {
AttachGroupPolicyType attachGroupPolicyType = MessageHelper.createMessage(AttachGroupPolicyType.class, newAction.info.getEffectiveUserId());
attachGroupPolicyType.setGroupName(newAction.info.getPhysicalResourceId());
attachGroupPolicyType.setPolicyArn(managedPolicyArn);
AsyncRequests.sendSync(configuration, attachGroupPolicyType);
}
// 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) {
DetachGroupPolicyType detachGroupPolicyType = MessageHelper.createMessage(DetachGroupPolicyType.class, newAction.info.getEffectiveUserId());
detachGroupPolicyType.setGroupName(newAction.info.getPhysicalResourceId());
detachGroupPolicyType.setPolicyArn(managedPolicyArn);
try {
AsyncRequests.sendSync(configuration, detachGroupPolicyType);
} 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_GROUP_NAME {
@Override
public ResourceAction perform(ResourceAction oldResourceAction, ResourceAction newResourceAction) throws Exception {
AWSIAMGroupResourceAction oldAction = (AWSIAMGroupResourceAction) oldResourceAction;
AWSIAMGroupResourceAction newAction = (AWSIAMGroupResourceAction) newResourceAction;
if (Objects.equals(oldAction.properties.getGroupName(), newAction.properties.getGroupName()) && oldAction.properties.getGroupName() != null) {
throw new ValidationErrorException("CloudFormation cannot update a stack when a custom-named resource requires replacing. Rename "+oldAction.properties.getGroupName()+" and update the stack again.");
}
return newAction;
}
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
}
}