Skip to content

Commit

Permalink
support for putting configuration in parameter store to enable Lambda…
Browse files Browse the repository at this point in the history
… faults via libraries
  • Loading branch information
adhorn committed Jan 13, 2022
1 parent a38382a commit 5fa5065
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 19 deletions.
3 changes: 2 additions & 1 deletion cdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"eks_cluster_name": "test-cluster-chaos",
"security_group_id": "sg-022eb488dbd1655b3",
"target_role_name": "SSM-Chaos",
"s3-bucket-to-deny": "chaos-ssm-documents/*"
"s3-bucket-to-deny": "chaos-ssm-documents/*",
"ssm_parameter_name": "chaoslambda.config"
}
}
68 changes: 68 additions & 0 deletions lib/fis-experiments/lambda-faults/experiments-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { StackProps, Stack } from "aws-cdk-lib";
import { aws_fis as fis } from "aws-cdk-lib";
import { aws_iam as iam } from "aws-cdk-lib";

export class LambdaChaosExperiments extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);

// Import FIS Role, Stop Condition, and other required parameters
const importedFISRoleArn = cdk.Fn.importValue("FISIamRoleArn");
const importedStopConditionArn = cdk.Fn.importValue("StopConditionArn");
const importedSSMAPutParameterStoreRoleArn = cdk.Fn.importValue(
"SSMAPutParameterStoreRoleArn"
);
const importedPutParameterStoreSSMADocName = cdk.Fn.importValue(
"PutParameterStoreSSMADocName"
);

const importedParameterName = this.node.tryGetContext("ssm_parameter_name");

// Targets - empty since SSMA defines its own targets

// Actions
const startAutomation = {
actionId: "aws:ssm:start-automation-execution",
description: "Put config into parameter store to enable Lambda Chaos.",
parameters: {
documentArn: `arn:aws:ssm:${this.region}:${
this.account
}:document/${importedPutParameterStoreSSMADocName.toString()}`,
documentParameters: JSON.stringify({
DurationMinutes: "PT1M",
AutomationAssumeRole: importedSSMAPutParameterStoreRoleArn.toString(),
ParameterName: importedParameterName.toString(),
ParameterValue: "{ \"delay\": 500, \"is_enabled\": true, \"error_code\": 404, \"exception_msg\": \"This is chaos\", \"rate\": 1, \"fault_type\": \"exception\"}",
RollbackValue: "{ \"delay\": 500, \"is_enabled\": false, \"error_code\": 404, \"exception_msg\": \"This is chaos\", \"rate\": 1, \"fault_type\": \"exception\"}"
}),
maxDuration: "PT5M",
},
};

// Experiments
const templateInjectS3AccessDenied = new fis.CfnExperimentTemplate(
this,
"fis-template-inject-lambda-fault",
{
description: "Inject faults into Lambda function using chaos-lambda library",
roleArn: importedFISRoleArn.toString(),
stopConditions: [
{
source: "aws:cloudwatch:alarm",
value: importedStopConditionArn.toString(),
},
],
tags: {
Name: "Inject fault to Lambda functions",
Stackname: this.stackName,
},
actions: {
ssmaAction: startAutomation,
},
targets: {},
}
);
}
}
114 changes: 114 additions & 0 deletions lib/fis-upload-ssm-docs/documents/ssma-put-config-parameterstore.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
#==================================================
# SSM Automation Document / Runbook:
# Defines the configuration as well as the
# the steps to be run by SSM Automation
#==================================================

description: |
### Document Name - ParameterStore-FIS-Automation
## What does this document do?
This document stores a particular configuration to SSM Parameter store.
https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html
## Security Risk
Low: This is not a fault per se, but a configuration change.The change should be restricted by a strict IAM role that only allows changing a particular ParameterName. https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-access.html
## Input Parameters
* AutomationAssumeRole: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
* ParameterName: (Required) The name of the parameter to modify.
* ParameterValue: (Required) The value of the parameter.
* RollbackValue: (Required) The value of the parameter to roll-back to.
* Type: (Optional) The type of parameter. String, StringList, or SecureString. Default String.
* DurationMinutes: (Optional) ** Default 1 minute ** Maximum duration the fault can exist for.
## Supports Rollback
Yes. The configuration is reverted to a .
## Cancellation behaviour
The parameter value is rollback to RollbackValue.
## Output Parameters
This document has no outputs.
## Minimum Permissions Required
* ssm:PutParameter
## Additional Permissions for logging
* logs:CreateLogStream
* logs:CreateLogGroup
* logs:PutLogEvents
* logs:DescribeLogGroups
* logs:DescribeLogStreams
schemaVersion: "0.3"

#==================================================
# Role assumed my the automation document / runbook
#==================================================
assumeRole: "{{ AutomationAssumeRole }}"

#==================================================
# SSM automation document parameters
#==================================================

parameters:
ParameterName:
type: String
description: "(Required) The name of the parameter to modify."
ParameterValue:
type: String
description: "(Required) The value of the parameter."
RollbackValue:
type: String
description: "(Required) The value of the parameter to roll-back to."
ParameterType:
type: String
description: "(Optional) The type of parameter. String, StringList, or SecureString."
default: "String"
DurationMinutes:
type: String
description: "The duration - in ISO-8601 format - until rollback. (Required)"
default: "PT1M"
AutomationAssumeRole:
type: String
description:
"(Optional) The ARN of the role that allows Automation to perform
the actions on your behalf."

#==================================================
# Automation steps
#==================================================

mainSteps:
- name: putParameter
description: Adding value to a particular parameter
onFailure: "step:rollback"
onCancel: "step:rollback"
action: "aws:executeAwsApi"
inputs:
Service: ssm
Api: PutParameter
Name: '{{ ParameterName }}'
Value: '{{ ParameterValue }}'
Type: '{{ ParameterType }}'
Overwrite: true

- name: sleep
action: aws:sleep
onFailure: "step:rollback"
onCancel: "step:rollback"
inputs:
Duration: "{{ DurationMinutes }}"

- name: rollback
description: Rolling back value to a particular parameter
action: "aws:executeAwsApi"
inputs:
Service: ssm
Api: PutParameter
Name: '{{ ParameterName }}'
Value: '{{ RollbackValue }}'
Type: '{{ ParameterType }}'
Overwrite: true
75 changes: 57 additions & 18 deletions lib/fis-upload-ssm-docs/ssm-upload-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@ export class FisSsmDocs extends Stack {
}
);

// Deploy the SSMA document to modify a parameter store value
let parameterstore_file = path.join(
__dirname,
"documents/ssma-put-config-parameterstore.yml"
);

const parameterstore_content = fs
.readFileSync(parameterstore_file)
.toString();

const parameterstore_cfnDocument = new ssm.CfnDocument(
this,
`ParameterStore-SSM-Document`,
{
content: yaml.load(parameterstore_content),
documentType: "Automation",
documentFormat: "YAML",
}
);

// SSMA Role for SSMA Documents fault

const iamAccessFaultRole = this.node.tryGetContext("target_role_name");
Expand Down Expand Up @@ -187,32 +207,39 @@ export class FisSsmDocs extends Stack {
})
);

//AllowOnlyTargetResourcePolicies
ssmaIamAccessRole.addToPolicy(
new iam.PolicyStatement({
resources: ["*"],
actions: ["iam:DetachRolePolicy", "iam:AttachRolePolicy"],
conditions: {
ArnEquals: {
"iam:PolicyARN": [`arn:aws:iam::${this.account}:policy/*`],
},
},
})
// IAM role access faults 'ssma-put-config-parameterstore.yml'
const ssmParameterName = this.node.tryGetContext("ssm_parameter_name");

const ssmaPutParameterStoreRole = new iam.Role(
this,
"ssma-put-parameterstore-role",
{
assumedBy: new iam.CompositePrincipal(
new iam.ServicePrincipal("iam.amazonaws.com"),
new iam.ServicePrincipal("ssm.amazonaws.com")
),
}
);

// DenyAttachDetachAllRolesExceptApplicationRole
ssmaIamAccessRole.addToPolicy(
const ssmaPutParameterStoreRoleAsCfn = ssmaPutParameterStoreRole.node
.defaultChild as iam.CfnRole;
ssmaPutParameterStoreRoleAsCfn.addOverride(
"Properties.AssumeRolePolicyDocument.Statement.0.Principal.Service",
["ssm.amazonaws.com", "iam.amazonaws.com"]
);

// GetRoleandPolicyDetails
ssmaPutParameterStoreRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.DENY,
actions: ["iam:DetachRolePolicy", "iam:AttachRolePolicy"],
notResources: [
`arn:aws:iam::${this.account}:role/${iamAccessFaultRole}`,
resources: [
`arn:aws:ssm:${this.region}:${this.account}:parameter/${ssmParameterName}`,
],
actions: ["ssm:PutParameter"],
})
);

// Additional Permissions for logging
ssmaIamAccessRole.addToPolicy(
ssmaPutParameterStoreRole.addToPolicy(
new iam.PolicyStatement({
resources: ["*"],
actions: [
Expand Down Expand Up @@ -244,6 +271,12 @@ export class FisSsmDocs extends Stack {
exportName: "IamAccessSSMADocName",
});

new cdk.CfnOutput(this, "PutParameterStoreSSMADocName", {
value: parameterstore_cfnDocument.ref!,
description: "The name of the SSM Doc",
exportName: "PutParameterStoreSSMADocName",
});

new cdk.CfnOutput(this, "SSMANaclRoleArn", {
value: ssmaNaclRole.roleArn,
description: "The Arn of the IAM role",
Expand All @@ -261,5 +294,11 @@ export class FisSsmDocs extends Stack {
description: "The Arn of the IAM role",
exportName: "SSMAIamAccessRoleArn",
});

new cdk.CfnOutput(this, "SSMAPutParameterStoreRoleArn", {
value: ssmaPutParameterStoreRole.roleArn,
description: "The Arn of the IAM role",
exportName: "SSMAPutParameterStoreRoleArn",
});
}
}
8 changes: 8 additions & 0 deletions lib/parent-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AsgExperiments } from "./fis-experiments/asg-faults/experiments-stack";
import { EksExperiments } from "./fis-experiments/eks-faults/experiments-stack";
import { SecGroupExperiments } from "./fis-experiments/security-groups-faults/experiments-stack";
import { IamAccessExperiments } from "./fis-experiments/iam-access-faults/experiments-stack";
import { LambdaChaosExperiments } from "./fis-experiments/lambda-faults/experiments-stack";

export class FIS extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
Expand Down Expand Up @@ -45,6 +46,11 @@ export class FIS extends Stack {
this,
"IamAccExp"
);
const LambdaFaultExperimentStack = new LambdaChaosExperiments(
this,
'LambdaExp'
)


Ec2InstancesExperimentStack.node.addDependency(IamRoleStack);
Ec2InstancesExperimentStack.node.addDependency(StopConditionStack);
Expand All @@ -60,5 +66,7 @@ export class FIS extends Stack {
SecGroupExperimentsStack.node.addDependency(StopConditionStack);
IamAccessExperimentsStack.node.addDependency(IamRoleStack);
IamAccessExperimentsStack.node.addDependency(StopConditionStack);
LambdaFaultExperimentStack.addDependency(IamRoleStack);
LambdaFaultExperimentStack.addDependency(StopConditionStack);
}
}

0 comments on commit 5fa5065

Please sign in to comment.