FinalDivvyCloud

Certain actions within the product have the ability to leverage Jinja2 templating in the message body. This enables bot authors to insert a variety of useful data about resources into the message. As of this writing, the following actions contain support for this capability:

  • Send Delayed Email (see example messages below)
  • Post Request To URL
  • Send HipChat Message
  • Send Slack Message
  • Set Container Policy (see custom policies examples below)

When using these actions, you can access resource properties by using the following syntax:

{{resource.<attribute>}} or {{resource.<function>}}

Resource Attributes

Available attributes vary based on the resource type (e.g., instances). A listing of available attributes based on the resource type can be found by clicking the links below:

Compute Resources

Storage Resources

Network Resources

Identity & Management Resources

Other

  • Availability Zone
  • DNS Zone Record
  • Memcache Instance
  • Resource Access List
  • Private Network
  • Service Alarm
  • Service User
  • Service Certificate

Resource Functions

Available resource functions are listed below:

Get Resource Name

This walks the resource to the database object and returns the name. For resources which do not have a name, the primary key value is returned. With public IP addresses, for example, the resource’s IP address will be returned.

{{resource.get_resource_name()}}

Get Organization Service Name and Account ID

These walk the resource to the parent organization service (cloud account) to retrieve the name and account number assigned to the account (e.g., Acmecorp AWS Production and 123456789012).

{{resource.get_organization_service_name()}}

{{resource.get_organization_service().account_id}}

Get Owner Name

In DivvyCloud you can assign ownership to resources. Doing this grants the owner full permission to that resource, and designates a point of contact (POC) for questions about the resource. This function will return the name of the POC if one is assigned, and None if one is not.

{{resource.get_owner_name()}}

Get Daily Cost

Calculate the daily cost for the resource in question. If the resource does not support cost calculation then this function will return None.

{{resource.get_daily_cost()}}

Get Monthly Cost

Calculate the monthly cost for the resource in question. If the resource does not support cost calculation then this function will return None.

{{resource.get_monthly_cost()}}

Get Tag Value

Get the value of a particular tag key. Only the tag key is required; however, you can optionally set the case_insensitive and default_return_str kwargs to force case sensitivity and/or return a specific value if the tag key does not exist.

{{resource.get_tag_value(tag_key, case_insensitive=True, default_return_str=None)}}

Jinja2 Examples

Here we provide an example of using Jinja2 to configure a 'Send Delayed Email' notification. This action will send an email to the admin team when a new instance is provisioned, allowing the admin team to see any new resource that can impact the monthly bill. The image below shows how to add a customized message body for each new instance.

Configuring a 'Send Delayed Email' Bot Action Using Jinja2

Configuring a 'Send Delayed Email' Bot Action Using Jinja2

Next the Slack notification is configured to send a customized message when new volumes are provisioned. The message will contain the account name and availability zone where the volume resides.

Configuring a Slack Message Using Jinja2

Configuring a Slack Message Using Jinja2

Following are some additional examples of dynamic messages you can use in emails, Slack, and other messaging tools.

Custom Message

Ensure Tag Strategy is Enforced

A resource of type ```{{resource.get_resource_type()}}``` was discovered at
```{{resource.common.creation_timestamp}}``` without the required *owner*
or *contact-email* tags. The resource name is ```{{resource.get_resource_name()}}```.
It lives in account ```{{resource.get_organization_service_name()}}```.

Instance Has More Than 4 Cores

```{{resource.name}}``` has been created or discovered with more than 4 cores. This resource will cost ```${{resource.get_daily_cost()}}```/day. The details are ```{{resource.serialize(indent=2)}}```. If this instance isn't downsized or cleaned up, it will be deleted within 6 hours.

A Resource You Own Was Removed

The resource ```{{resource.name}}``` which belongs to you, has been deleted. Details below:
```{{resource.serialize(indent=2)}}

Instance With Public IP Has SSH Open To The World

Instance ```{{resource.instance_id}}``` is ```{{resource.state}}``` and has a
public IP address of ```{{resource.public_ip_address}}```. This instance has exposed
port 22 (SSH) open to the world. This instance is not within our security policy. Please
lock down the attached ACLs ```{{resource.access_lists}}```, or this instance will be
deleted in 4 hours.

Bad ACL

Bad ACL found on `{{resource.name}}`. Please login to investigate!

Expensive Instance was created or discovered

A very expensive instance `{{resource.get_resource_name()}}` created at
`{{resource.common.creation_timestamp}}` was found in account
`{{resource. get_organization_service_name()}}` which costs
`${{resource.get_daily_cost()}}/day` and `${{resource.get_monthly_cost()}}/mo`.

Database Open to the World

An insecure database was identified with access open to the World.
The details of this resource are:
```{{resource.serialize(indent=2)}}```.

Storage Container Public Access

An insecure storage container was identified with public permissions to the world.
The details of this resource are:
```{{resource.serialize(indent=2)}}```.
The resource has the following tags
```{{resource.serialize_tags(indent=2)}}```This storage container is scheduled for deletion in 24 hours.
Please update security access rules to avoid the scheduled deletion.

CloudWatch Alarm has triggered

A CloudWatch Alarm has triggered. The data on the alarm is:
ID of the parent organization service (cloud): `{{resource.organization_service_id}}`
ID of alarm: `{{resource. alarm_id}}`
Name of Alarm: `{{resource.name}}`
ARN for the account where this alarm resides: `{{resource.provider_resource_id}}`
An optional, brief description of this alarm: `{{resource.description}}`
Namespace (container for CloudWatch metrics) of the alarm: `{{resource.namespace}}`
Name of the metric this alarm checks for: `{{resource.metric_name}}`
User defined threshold for this alarm: `{{resource.threshold}}`
Amount of times this data will be evaluated before arriving at conclusion for alarm: `{{resource.evaluation_periods}}`
Alarm Type: `{{resource.state_value}}`
Alarm Reason: `{{resource.state_reason}}`
More data regarding this alarm: `{{resource.state_reason_data}}`

Database Instance has had Zero Connections in 14 days

Database instance `{{resource.name}}` has had no connections within 14 days. This database instance
resides in `{{resource.get_organization_service_name()}}` account and has an endpoint of
`{{resource.endpoint_address}}`.
This resource will have a snapshot taken, will be stopped, and finally deleted after 7 days.

Instance Provisioned outside of AMI Whitelist

{{resource.image_id}} has been used to provision instance `{{resource.instance_id}}`
in `{{resource.organization_service_name}}`. This machine image is not permitted.
This instance will have a snapshot taken, and be terminated immediately.

User without MFA Enabled

`{{resource.get_resource_type()}}: {{resource.get_resource_name()}}` in account `{{resource. get_organization_service_name()}}` with account # `{{resource.get_organization_service().account_id}}` does not have MFA enabled.

Custom Policies Using Jinja2

You can also use Jinja2 templating to add or update S3 bucket policies. Two examples of updating bucket policy follow--one to enforce encryption, and one to restrict access.

Bucket Encryption Policy
If you would like to enforce encryption via bucket policy, you can create a bot that uses the "Set Container Policy" action and the following template to enforce encryption.

{
    "Version": "2012-10-17",
    "Id": "bucketpolicyid:{{event.resource.get_resource_name()}}",
    "Statement": [
        {
            "Sid": "DenyIncorrectEncryptionHeader",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::{{event.resource.get_resource_name()}}/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:x-amz-server-side-encryption": "AES256"
                }
            }
        },
        {
            "Sid": "DenyUnEncryptedObjectUploads",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::{{event.resource.get_resource_name()}}/*",
            "Condition": {
                "Null": {
                    "s3:x-amz-server-side-encryption": "true"
                }
            }
        }
    ]
}

Bucket Lockdown Policy
If you would like to restrict bucket access to certain IP ranges or block specific IP ranges, you can use "Set Container Policy" and the following template to "lock down" the bucket to those IP ranges.

{
    "Version": "2012-10-17",
    "Id": "bucketpolicyid:{{event.resource.get_resource_name()}}",
    "Statement": [
        {
            "Sid": "IPAllow",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::{{event.resource.get_resource_name()}}/*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "<insert IP range to block>"
                },
                "IpAddress": {
                    "aws:SourceIp": "<or insert IP range to allow>"
                }
            }
        }
    ]
}