AWS Cloud Operations Blog
How to download your AWS Resilience Hub assessment results
AWS Resilience Hub provides a central place to define, validate, and track the resilience of your application on AWS. It can help in assessing impact of every application change on resiliency by automatically running the assessment on a daily basis or as part of CI/CD pipeline.
With AWS Resilience Hub, you can easily create resiliency policies which help you better understand your resilience posture, and whether your application can recover from different disruption types (e.g., software, hardware, Availability Zone, or Regional disruptions). Policies can be applied to a single application or multiple applications, and do not change or affect your applications.
When creating a resilience policy, you are asked to define your Recovery Point Objectives (RPO) and Recovery Time Objectives (RTO). Once your resilience objectives are defined, you can use Resilience Hub to run an assessment against your established RPO and RTO. This assessment helps you understand whether your application is estimated to meet your resilience objectives, and also provides actionable recommendations to improve resilience.
The assessment also generates code snippets that help you create recovery procedures as AWS Systems Manager documents for your applications, referred to as Standard Operating Procedures (SOPs). AWS Resilience Hub generates a list of recommended Amazon CloudWatch Alarms to help the operator identify any changes to the application’s resilience posture once deployed.
In this blog post, we will walk you through a solution that will allow users to automate the process of generating and downloading these assessment reports for multiple Resilience Hub Applications, reducing the operational overhead and making it more efficient to run assessments instead of requiring to generate individual assessments for each Resilience Hub Application from AWS console.
Prerequisites
- You have the AWS CLI If you do not have the AWS CLI installed, follow the steps here: Installing or Updating the latest version of the AWS CLI
- Create Named Profiles
- Install jq
- You have a Resilience Hub application(s) for which you would want to download the assessments results
- You have setup necessary roles and permissions in IAM following the steps here: How AWS Resilience Hub works with IAM
- You have executed the resiliency assessment for the application(s) in AWS Resilience Hub
Solution overview
We will be using the AWS CLI command to download the assessment details. With Resilience Hub, you can choose the level of detail you’d like to see, such as component-level compliance (resilience application component categories are Compute, Database, Networking, Notification, Queue and Storage), resiliency score and alarm recommendations against the defined resilience policy.
“describe-app-assessment” command provides the compliance details on resilience application components defined in your resilience applications.
The recommendations are provided on the component level and this can be downloaded using AWS CLI commands.
describe-app-assessment
You can use the “describe-app-assessment” command to get the details of a particular assessment. For example, the following command will return the results for the assessment ARN provided:
Note that you need to provide the ARN of the assessment (highlighted in yellow), not the application ARN. To get the list of assessments, you can use the command “aws resiliencehub list-app-assessments”.
Output of the “describe-app-assessment” contains the details of Resilience application ARN, assessment ARN (passed in value) and assessment status such as Success or Failed.
Compliance section provides the status as “PolicyMet” or “PolicyBreached” for three sections “AZ”, “Hardware”, “Software” and optionally for “Region” if it is specified in your target resilience policy.
“assessment”: {
“appArn”: “arn:aws:resiliencehub:us-east-1:123456789:app/5afdca92-d2dc-4da6-8cf2-005d3c7597ea”,
“appVersion”: “release”,
“assessmentArn”: “arn:aws:resiliencehub:us-east-1:123456789:app-assessment/4393bf7f-1b94-44a1-b7a2-2a021bd11bba”,
“assessmentName”: “Assessment-report-gxzn2v6ipoi”,
“assessmentStatus”: “Success”,
“compliance“: {
“AZ”: {
“achievableRpoInSecs”: 0,
“achievableRtoInSecs”: 0,
“complianceStatus”: “PolicyBreached”,
“currentRpoInSecs”: 0,
“currentRtoInSecs”: 2592196
},
“Hardware”: {
“achievableRpoInSecs”: 0,
“achievableRtoInSecs”: 0,
“complianceStatus”: “PolicyMet”,
“currentRpoInSecs”: 0,
“currentRtoInSecs”: 195
},
“Software”: {
“achievableRpoInSecs”: 0,
“achievableRtoInSecs”: 0,
“complianceStatus”: “PolicyBreached”,
“currentRpoInSecs”: 2592001,
“currentRtoInSecs”: 5187302
}
The policy section shows the target policy used for this assessment:
“policy”: {
“dataLocationConstraint”: “AnyLocation”,
“policy”: {
“AZ”: {
“rpoInSecs”: 300,
“rtoInSecs”: 300
},
“Hardware”: {
“rpoInSecs”: 300,
“rtoInSecs”: 300
},
“Software”: {
“rpoInSecs”: 900,
“rtoInSecs”: 3600
}},
“policyArn”: “arn:aws:resiliencehub:us-east-1:123456789:resiliency-policy/c533466c-51d1-4c97-ac48-93d06bdaa9b5”,
“policyName”: “critical-policy-1”
},
Resilience Score section provides the resiliency score for Availability Zones Hardware, Software and Region.
“resiliencyScore”: {
“disruptionScore”: {
“AZ”: 0.0,
“Hardware”: 0.38,
“Region”: 0.0,
“Software”: 0.0
},
list-app-component-compliances
Execute the following AWS CLI command the get the component level compliance details:
Output of this command contains the following sections for each of the resilience application components. In the below snippet, we show the database component details. It provides the details on the AZ, Hardware, Software and optionally Region-level compliance. In this example the “Software” compliance is not met and the recommendation is to set up read-replica or the backup with retention.
{
“appComponentName”: “databaseappcomponent-qjh”,
“compliance”: {
“AZ”: {
“achievableRpoInSecs”: 0,
“achievableRtoInSecs”: 0,
“complianceStatus”: “PolicyMet”,
“currentRpoInSecs”: 0,
“currentRtoInSecs”: 120,
“rpoDescription”: “RDS Multi-AZ configuration is synchronously replicated across Availability Zones to a standby replica”,
“rpoReferenceId”: “rds:rpo:has-maz”,
“rtoDescription”: “Estimated time to promote a secondary instance.”,
“rtoReferenceId”: “rds:rto:multiple-azs”
},
“Hardware”: {
“achievableRpoInSecs”: 0,
“achievableRtoInSecs”: 0,
“complianceStatus”: “PolicyMet”,
“currentRpoInSecs”: 0,
“currentRtoInSecs”: 120,
“rpoDescription”: “RDS Multi-AZ configuration is synchronously replicated across Availability Zones to a standby replica”,
“rpoReferenceId”: “rds:rpo:has-maz”,
“rtoDescription”: “Estimated time to promote a secondary instance.”,
“rtoReferenceId”: “rds:rto:multiple-azs”
},
“Software”: {
“achievableRpoInSecs”: 0,
“achievableRtoInSecs”: 0,
“complianceStatus”: “PolicyBreached”,
“currentRpoInSecs”: 2592001,
“currentRtoInSecs”: 2592001,
“rpoDescription”: “No recovery option configured (i.e. replica or backups).”,
“rpoRe
“rtoDescription”: “No recovery option configured (i.e. replica or backups).”,
“rtoReferenceId”: “rds:rto:unrecoverable”
}
},
“cost”: {
“amount”: 0.0,
“currency”: “USD”,
“frequency”: “Monthly”
},
“resiliencyScore”: {
“disruptionScore”: {
“AZ”: 0.53,
“Hardware”: 0.39,
“Region”: 0.0,
“Software”: 0.0
},
“score”: 0.22
},
“status”: “PolicyBreached”
}
List-app-component-recommendations
Use “list-app-component-recommendations” command to get the Resilience Hub recommendations at the component level.
Output of this command contains prescriptive recommendations for resilience application component categories. For example, for the database component, it is recommended to change the MySQL instance to Amazon Aurora and enable read-replica in the same region. In this case, Aurora provides “backtracking” capability. Another recommendation is to take advantage of Amazon Route 53 Application Recovery Controller (ARC).
{
“appComponentName”: “databaseappcomponent-qjh”,
“cost”: {
“amount”: 36.4,
“currency”: “USD”,
“frequency”: “Monthly”
},
“description”: “Aurora database cluster with one read replica, with backtracking window of 24 hours.”,
“haArchitecture”: “WarmStandby”,
“optimizationType”: “BestAZRecovery”,
“recommendationCompliance”: {
“AZ”: {
“expectedComplianceStatus”: “PolicyMet”,
“expectedRpoDescription”: “Aurora data is automatically replicated across multiple Availability Zones in a Region.”,
“expectedRpoInSecs”: 0,
“expectedRtoDescription”: “Estimated time to promote a secondary instance.”,
“expectedRtoInSecs”: 120
},
“Hardware”: {
“expectedComplianceStatus”: “PolicyMet”,
“expectedRpoDescription”: “Aurora data is automatically replicated across multiple Availability Zones in a Region.”,
“expectedRpoInSecs”: 0,
“expectedRtoDescription”: “Estimated time to promote a secondary instance.”,
“expectedRtoInSecs”: 120
},
“Software”: {
“expectedComplianceStatus”: “PolicyMet”,
“expectedRpoDescription”: “Estimate for latest restorable time for point in time recovery.”,
“expectedRpoInSecs”: 300,
“expectedRtoDescription”: “Estimate time to backtrack to a stable state.”,
“expectedRtoInSecs”: 900
}
},
“referenceId”: “rds:config:aurora-backtracking”,
“suggestedChanges”: [
“Add read replica in the same region”,
“Change to Aurora”,
“Enable Amazon RDS cluster backtracking.”,
“Enable instance backup with retention period 7”,
“Optional: For increased availability, we recommend using Amazon Route 53 Application Recovery Controller readiness check. (Note that you will
incur an hourly cost of $0.045 for each Amazon Route 53 Application Recovery Controller readiness check that you configured.)”
]
}
Automating report download using a script
As demonstrated above, the AWS CLI provides an option to download Resilience Hub assessment reports without using the AWS Console. We want to provide the ability to consolidate the assessment of all the resilience applications in your account into a friendly HTML format report. Let’s look at how this works:
- Clone the repo
git clone git@github.com:aws-samples/aws-resilience-hub-samples.git
cd aws-resilience-hub-samples/automate-resilience-assessment-report/scripts
- Run the script
./aws-rh-download-assessment.sh <profile name>
The script will accept “profile name” as an input parameter. If not specified, the script will use the default profile. The script will also expect a Region (default is set to us-east-1) and S3 bucket as inputs.
The Script will generate assessment details in json format and a summary html report with applications, assessments, compliance status, resiliency score, components along with estimated and target RTO/RPO.Script:
#!/usr/bin/env bash # Author : Madhu Balaji # Description : This script will look for all the assessment reports for each application in a # specific region , describe the assessment , fetch reports, create summary report (html) and upload the artifacts to S3 bucket specified # Pre-req : Required Roles assigned to the user/profile used for execution, AWS CLI and jq Region="x" S3Bucket="x" HTMLContent="" # Exception - Throw error function throw(){ tput setaf 1; echo "Failure: $*" && tput sgr0 exit 1 } # Input var0 if [[ "$Region" == "x" ]]; then read -r -p "Region [us-east-1] : " Region fi if [ -z "$Region" ]; then Region="us-east-1" fi # Input var1 # Validate Input params if [[ "$S3Bucket" == "x" ]]; then read -r -p "S3 Bucket Name : " S3Bucket fi if [ -z "$S3Bucket" ]; then throw "S3 bucket name is mandatory." fi # Check Command to validate required libraries function check_command { type -P $1 &>/dev/null || throw "Unable to find $1, please install it and run this script again." } # Verify if AWS CLI Credentials are setup if ! grep -q aws_access_key_id ~/.aws/config; then if ! grep -q aws_access_key_id ~/.aws/credentials; then throw "AWS config not found or CLI not installed. Please run \"aws configure\"." fi fi # Verify AWS CLI profile if [ $# -eq 0 ]; then scriptname=`basename "$0"` echo "Usage: ./$scriptname profile" echo "Where profile is the AWS CLI profile name" echo "Using default profile" echo profile=default else profile=$1 fi # Check required commands check_command "aws" check_command "jq" #Not working 100%- Fix this function createbucket(){ aws s3 mb s3://$S3Bucket --region $Region } #Delete local JSON file (optional) function deleteJSONFile(){ rm -rf ./*.json } #Delete local report file (optional) function deleteHTMLFile(){ rm -rf ./*.html } function createHTMLTemplate(){ HEADER="<!DOCTYPE html><html><head> <style>#application{font-family: Arial, Helvetica, sans-serif; border-collapse: collapse; width: 100%;}#application td{font-weight: bold;}#application th{border: 1px solid #ddd; padding: 8px;}#application tr:nth-child(even){background-color: #f2f2f2;}#application tr:hover{background-color: #ddd;}#application th{padding-top: 12px; padding-bottom: 12px; text-align: left; background-color: #ff9900; color: white;}#assessment{font-family: Arial, Helvetica, sans-serif; border-collapse: collapse; width: 100%;}#assessment td, #assessment th{border: 1px solid #ddd; padding: 8px;}#assessment tr:nth-child(even){background-color: #f2f2f2;}#assessment tr:hover{background-color: #ddd;}#assessment th{padding-top: 12px; padding-bottom: 12px; text-align: left; background-color: #e7cca3;color: black;font-style: italic;text-align: center;}</style></head>" BODY="<body> <h2> AWS Resilience Hub Assessment Report " TABLE=" region </h2><table id='application'> <tr> <th>App Name</th><th>App Compliance Status</th><th>Assessment Name</th> <th>Assessment Compliance Status</th><th>Resiliency Score</th><th>Start Time</th><th>End Time</th> </tr>" FOOTER="</table></body></html>" TR="<tr>" TD="<td>" CLSTD="</td>" CLSTR="</tr>" EMPTYTD="<td> </td>" TDCOL4="<td colspan=4>" } #Main Function to downnload assessment reports function GetAppAssessments(){ # Create S3 bucket if it does't exists createbucket # List all the app assessments in a region Assessments=$(aws resiliencehub list-app-assessments --region $Region --output=json --profile $profile --query 'sort_by(assessmentSummaries, &appArn)[].{appArn: appArn,assessmentArn: assessmentArn, complianceStatus: complianceStatus,resiliencyScore: resiliencyScore}' 2>&1) #echo $Assessments > "applicationSummaries.json" #Initialize HTMl content createHTMLTemplate #Loop thru all the assessments to extract content, create JSON and Summary HTML, upload to S3 bucket counter=0 SUMMARYTBL="" FolderName=$(date +%Y%m%d%H%M) echo $Assessments >> "assessment_list.json" for row in $(echo "${Assessments}" | jq -r '.[] | @base64'); do _jq() { echo "${row}" | base64 --decode | jq -r "${1}" } apparn=$(_jq '.appArn') assessmentArn=$(_jq '.assessmentArn') resiliencyScore=$(_jq '.resiliencyScore') AppCompStatus=$(_jq '.complianceStatus') if [ "$AppCompStatus" == 'PolicyBreached' ]; then AppCompStatus="<font color='red'>$AppCompStatus</font>" elif [ "$AppCompStatus" == 'PolicyMet' ]; then AppCompStatus="<font color='green'>$AppCompStatus</font>" fi ROWCONTENT="" AppName=$(aws resiliencehub describe-app --app-arn $apparn --region $Region --profile $profile --query 'app.name' 2>&1) AName=${AppName:1:${#AppName}-2} DescribeAssessment=$(aws resiliencehub describe-app-assessment --assessment-arn $assessmentArn --region $Region --profile $profile 2>&1) AssessName=$(echo "$DescribeAssessment" | jq -r '.assessment | .assessmentName') CompStatus=$(echo "$DescribeAssessment" | jq -r '.assessment | .complianceStatus') if [ "$CompStatus" == 'PolicyBreached' ]; then CompStatus="<font color='red'>$CompStatus</font>" elif [ "$CompStatus" == 'PolicyMet' ]; then CompStatus="<font color='green'>$CompStatus</font>" fi StartTime=$(echo "$DescribeAssessment" | jq -r '.assessment | .startTime') EndTime=$(echo "$DescribeAssessment" | jq -r '.assessment | .endTime') AZRTOTarget=$(echo "$DescribeAssessment" | jq -r '.assessment | .policy | .policy | .AZ | .rtoInSecs') AZRPOTarget=$(echo "$DescribeAssessment" | jq -r '.assessment | .policy | .policy | .AZ | .rpoInSecs') HWRTOTarget=$(echo "$DescribeAssessment" | jq -r '.assessment | .policy | .policy | .Hardware | .rtoInSecs') HWRPOTarget=$(echo "$DescribeAssessment" | jq -r '.assessment | .policy | .policy | .Hardware | .rpoInSecs') SWRTOTarget=$(echo "$DescribeAssessment" | jq -r '.assessment | .policy | .policy | .Software | .rtoInSecs') SWRPOTarget=$(echo "$DescribeAssessment" | jq -r '.assessment | .policy | .policy | .Software | .rpoInSecs') REGRTOTarget=$(echo "$DescribeAssessment" | jq -r '.assessment | .policy | .policy | .Region | .rtoInSecs') REGRPOTarget=$(echo "$DescribeAssessment" | jq -r '.assessment | .policy | .policy | .Region | .rpoInSecs') ROWCONTENT=$TR$TD$AName$CLSTD$TD$ROWCONTENT$AppCompStatus$CLSTD$TD$ROWCONTENT$AssessName$CLSTD$TD$ROWCONTENT$CompStatus$CLSTDSTD$TD$ROWCONTENT$resiliencyScore$CLSTD$TD$ROWCONTENT$StartTime$CLSTD$TD$ROWCONTENT$EndTime$CLSTD$CLSTR SUBROW="" UNREC="2592001" Results=$(aws resiliencehub list-app-component-compliances --assessment-arn $assessmentArn --region $Region --profile $profile 2>&1) arnname=${assessmentArn:(-36)} echo $Results >> "assessment-components-"$arnname".json" Output=$(echo "$Results" | jq -r '.componentCompliances') ROWCONTENT=$ROWCONTENT$TR$EMPTYTD"<td colspan=6>" SUBROW="<table id='assessment'><tr><th colspan=4>Component Name</th><th colspan=4>Application</th><th colspan=4>Infrastructure</th><th colspan=4>Availability Zone</th><th colspan=4>Region</th></tr>" ADDSUBROW="<tr><td colspan=4> </td><td>Targeted RTO(s)</td><td>Estimated RTO(s)</td><td>Targeted RPO(s)</td><td>Estimated RPO(s)</td><td>Targeted RTO(s)</td><td>Estimated RTO(s)</td><td>Targeted RPO(s)</td><td>Estimated RPO(s)</td><td>Targeted RTO(s)</td><td>Estimated RTO(s)</td><td>Targeted RPO(s)</td><td>Estimated RPO(s)</td><td>Targeted RTO(s)</td><td>Estimated RTO(s)</td><td>Targeted RPO(s)</td><td>Estimated RPO(s)</td></tr>" SUBROW=$SUBROW$ADDSUBROW for row in $(echo "${Output}" | jq -r '.[] | @base64'); do _jqr() { echo "${row}" | base64 --decode | jq -r "${1}" } componentName=$(_jqr '.appComponentName') AZRTOEstimate=$(_jqr '.compliance.AZ.currentRtoInSecs') if [ "$AZRTOEstimate" == "$UNREC" ]; then AZRTOEstimate="<font color='red'>unrecoverable</font>" fi AZRTODesc=$(_jqr '.compliance.AZ.rtoDescription') AZRPODesc=$(_jqr '.compliance.AZ.rpoDescription') AZRPOEstimate=$(_jqr '.compliance.AZ.currentRpoInSecs') if [ "$AZRPOEstimate" == "$UNREC" ]; then AZRPOEstimate="<font color='red'>unrecoverable</font>" fi HWRTOEstimate=$(_jqr '.compliance.Hardware.currentRtoInSecs') if [ "$HWRTOEstimate" == "$UNREC" ]; then HWRTOEstimate="<font color='red'>unrecoverable</font>" fi HWRTODesc=$(_jqr '.compliance.Hardware.rtoDescription') HWRPODesc=$(_jqr '.compliance.Hardware.rpoDescription') HWRPOEstimate=$(_jqr '.compliance.Hardware.currentRpoInSecs') if [ "$HWRPOEstimate" == "$UNREC" ]; then HWRPOEstimate="<font color='red'>unrecoverable</font>" fi SWRTOEstimate=$(_jqr '.compliance.Software.currentRtoInSecs') if [ "$SWRTOEstimate" == "$UNREC" ]; then SWRTOEstimate="<font color='red'>unrecoverable</font>" fi SWRTODesc=$(_jqr '.compliance.Software.rtoDescription') SWRPODesc=$(_jqr '.compliance.Software.rpoDescription') SWRPOEstimate=$(_jqr '.compliance.Software.currentRpoInSecs') if [ "$SWRPOEstimate" == "$UNREC" ]; then SWRPOEstimate="<font color='red'>unrecoverable</font>" fi REGRTOEstimate=$(_jqr '.compliance.Region.currentRtoInSecs') if [ "$REGRTOEstimate" == "$UNREC" ]; then REGRTOEstimate="<font color='red'>unrecoverable</font>" fi REGRTODesc=$(_jqr '.compliance.Region.rtoDescription') REGRPODesc=$(_jqr '.compliance.Region.rpoDescription') REGRPOEstimate=$(_jqr '.compliance.Region.currentRpoInSecs') if [ "$REGRPOEstimate" == "$UNREC" ]; then REGRPOEstimate="<font color='red'>unrecoverable</font>" fi SUBROW=$SUBROW$TR$TDCOL4$componentName$CLSTD$TD$SWRTOTarget$CLSTD$TD$SWRTOEstimate$CLSTD$TD$SWRPOTarget$CLSTD$TD$SWRPOEstimate$CLSTD SUBROW=$SUBROW$TD$HWRTOTarget$CLSTD$TD$HWRTOEstimate$CLSTD$TD$HWRPOTarget$CLSTD$TD$HWRPOEstimate$CLSTD SUBROW=$SUBROW$TD$AZRTOTarget$CLSTD$TD$AZRTOEstimate$CLSTD$TD$AZRPOTarget$CLSTD$TD$AZRPOEstimate$CLSTD SUBROW=$SUBROW$TD$REGRTOTarget$CLSTD$TD$REGRTOEstimate$CLSTD$TD$REGRPOTarget$CLSTD$TD$REGRPOEstimate$CLSTD$CLSTR done SUBROW=$SUBROW"</table>" ROWCONTENT=$ROWCONTENT$SUBROW$CLSTD$CLSTR Filename="assessment-"$arnname echo $DescribeAssessment > $Filename.json aws s3 cp ./$Filename'.json' 's3://'$S3Bucket'/reports'$FolderName'/'$Filename'.json' --region $Region --profile $profile 2>&1 EMPTYROW="<tr><td colspan=7> </td></tr>" SUMMARYTBL=$SUMMARYTBL$ROWCONTENT$EMPTYROW$EMPTYROW counter=$[$counter +1] done HTMLFILE=$HEADER$BODY$Region$TABLE$SUMMARYTBL$FOOTER echo $HTMLFILE > 'report.html' aws s3 cp ./report.html 's3://'$S3Bucket'/reports'$FolderName'/report.html' --region $Region #Cleanup - Delete generated JSON annd HTML files deleteJSONFile deleteHTMLFile } GetAppAssessments
- Sample report:
Complete source code is available here: https://github.com/aws-samples/aws-resilience-hub-samples
Conclusion
AWS Resilience Hub provides the ability to manage the resilience of your applications. It is recommended to run resiliency assessment whenever your application or resiliency policy changes. In this post, we have seen an easier way to download the consolidated assessment reports using AWS CLI instead of logging into AWS console to access the report. This approach can be scaled up for enterprise use by scheduling the assessments to be run daily or integrating as part of your CI/CD pipeline. You can execute the script provided to retrieve the resilience assessment of your applications and take actions based on the prescriptive guidance provided by AWS Resilience Hub.