# AWS Config Partner Hackathon - Guide
**Hackathon FAQ added!** Please check the section on bottom of the doc.
**Step 1** - Get the schema for the custom resource you'll be working with.
For this example let's assume the custom resource schema you'll be working is an Azure Security Group.
We can describe the schema for this resource with the Azure cli command below:
`az network nsg show --name <YOUR AZURE SECURITY GROUP NAME> --resource-group <YOUR AZURE RESOURCE GROUP FOR THE SECURITY GROUP>`
The output for the above command should be a json similar to this:
```
{
"etag": "W/\"etag-value\"",
"id": "/subscriptions/subscription-id/resourceGroups/resource-group-name/providers/Microsoft.Network/networkSecurityGroups/network-security-group-name",
"location": "westus",
"name": "network-security-group-name",
"resourceGroup": "resource-group-name",
"securityRules": [
{
"access": "Allow",
"description": "Rule description",
"destinationAddressPrefix": "*",
"destinationPortRange": "22",
"direction": "Inbound",
"name": "SSH",
"priority": 100,
"protocol": "Tcp",
"sourceAddressPrefix": "192.168.0.0/24",
"sourcePortRange": "*"
},
{
"access": "Deny",
"description": "Rule description",
"destinationAddressPrefix": "10.0.0.0/16",
"destinationPortRange": "*",
"direction": "Outbound",
"name": "BlockOutbound",
"priority": 200,
"protocol": "*",
"sourceAddressPrefix": "*",
"sourcePortRange": "*"
}
],
"subnets": [
{
"id": "/subscriptions/subscription-id/resourceGroups/resource-group-name/providers/Microsoft.Network/virtualNetworks/virtual-network-name/subnets/subnet-name"
}
],
"tags": {},
"type": "Microsoft.Network/networkSecurityGroups"
}
```
From this schema we can start working in the Cloudformation Registry Private Extension Resource schema that would match the information we need from the schema of the Azure Security Group we got above.
**Step 2** - Start building the Cloudformation Registry Resource Private Extension Resource schema.
Reference doc [[here](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-walkthrough.html)]
*Pre-requirements: You need to have the AWS CLi installed and have a profile configured for your AWS Account, the AWS Cloudformation CLi also installed and docker installed.*
Detailed example steps below:
2.1 Create a directory where you'll store the Cloudformation Registry Resource Schema.
Create a python virtual environment and install AWS Cloudformation CLi
```
python3 -m venv env
source env/bin/activate
pip3 install cloudformation-cli cloudformation-cli-python-plugin
```
2.2 Enter the following command to generate the boilerplate code.
`cfn init`
You’ll be prompted to enter details for the new resource. Enter r to indicate you want to develop a new resource.
```
Initializing new project
Do you want to develop a new resource(r) or a module(m)?.
>> r
```
When you’re prompted for the name of the resource type, enter the name your want following the convention myORG::myService::myResource
Example:
```
Initializing new project
What's the name of your resource type?
(Organization::Service::Resource)
>> AzureTest::VM::SecurityGroup
```
Select the language for code generation:
```
Select a language for code generation:
[1] python36
[2] python37
(enter an integer):
>> 2
```
When you’re prompted to use Docker for platform-independent packaging, enter Y.
```
Use docker for platform-independent packaging (Y/n)?
This is highly recommended unless you are experienced
with cross-platform Python packaging.
>> Y
```
This will create all the required files. You should see the following message to confirm a new project has been initialized.
The init process created a file that defines the schema of the resource you want to register. This file follows the format of the name of the resource (in this case, azuretest-vm-securitygroup.json)
This is the file you'll need to edit to match the schema of your custom resource.
You can check a reference documentation for the schema accepted in Cloudformation Registry [[here](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html)].
As an example this would be the file edited to match the data fiels from Azure Security Group schema we saw before that I want to store in my Cloudformation Registry Private Extension resource.
```
{
"typeName": "AzureTest::VM::SecurityGroup",
"description": "An example CloudFormation Registry resource schema for an Azure Network Security Group",
"definitions": {
"Rule": {
"type": "object",
"additionalProperties": false,
"properties": {
"SecurityRuleId": {
"type": "string",
"description": "The id of the rule"
},
"SecurityRuleName": {
"type": "string",
"description": "The name of the rule"
},
"SecurityRuleType": {
"type": "string"
},
"SecurityRuleProtocol": {
"type": "string",
"description": "The network protocol (Tcp, Udp, or All)"
},
"SecurityRuleSourcePortRange": {
"type": "string",
"description": "The source port range"
},
"SecurityRuleDestinationPortRange": {
"type": "string",
"description": "The destination port range"
},
"SecurityRuleSourceAddressPrefix": {
"type": "string",
"description": "The source address prefix"
},
"SecurityRuleSourceAddressPrefixes": {
"type": "array",
"insertionOrder": false
},
"SecurityRuleDestinationAddressPrefix": {
"type": "string",
"description": "The destination address prefix"
},
"SecurityRuleDestinationAddressPrefixes": {
"type": "array",
"insertionOrder": false
},
"SecurityRuleSourcePortRanges": {
"type": "array",
"insertionOrder": false
},
"SecurityRuleDestinationPortRanges": {
"type": "array",
"insertionOrder": false
},
"SecurityRuleAccess": {
"type": "string",
"description": "The access type (Allow or Deny)"
},
"SecurityRulePriority": {
"type": "integer",
"description": "The priority of the rule"
},
"SecurityRuleDirection": {
"type": "string",
"description": "The direction of the rule (Inbound or Outbound)"
},
"SecurityRuleProvisioningState": {
"type": "string"
}
}
}
},
"properties": {
"SecurityGroupName": {
"type": "string"
},
"SecurityGroupId": {
"type": "string"
},
"AzureResourceType": {
"type": "string"
},
"SecurityGroupLocation": {
"type": "string"
},
"SecurityGroupTags": {
"type": "object"
},
"SecurityRules": {
"type": "array",
"insertionOrder": true,
"items": {
"$ref": "#/definitions/Rule"
},
"description": "The security rules for the security group"
}
},
"additionalProperties": false,
"required": [ "SecurityGroupName", "SecurityGroupLocation", "SecurityRules" ],
"readOnlyProperties": ["/properties/SecurityGroupId"],
"primaryIdentifier": ["/properties/SecurityGroupId"],
"handlers": {
"create": {
"permissions": ["lambda:InvokeFunction"]
},
"read": {
"permissions": ["lambda:InvokeFunction"]
},
"update": {
"permissions": ["lambda:InvokeFunction"]
},
"delete": {
"permissions": ["lambda:InvokeFunction"]
},
"list": {
"permissions": ["lambda:InvokeFunction"]
}
}
}
```
Save the file. Use the following command to validate its content is correct.
`cfn validate`
You should see the following message:
`Resource Schema is valid`
Now enter the following command to generate the files required for the registration.
`cfn generate`
You should see a message similar to this:
`Generated files for AzureTest::VM::SecurityGroup`
Submit your new custom resource to be created in the CloudFormation registry. Enter the following command to register the resource.
`cfn submit`
This command packages the required files, including your newly defined schema, and submits the request to CloudFormation to create this resource in your account. It will take a few minutes because the Docker image must be pulled. You should see a Registration complete message at the end of the process.
*PS: Remember that if you change your Cloudformarion Registry schema file and re-submit it (cfn submit) Cloudformation Registry will generate an auto-incrementing versionId of the schema (e.g. 00000002) that you'll need to refer to when calling AWS Config PutResourceConfig API in your Lambda code with the **SchemaVersionId** parameter.*
*PS-2: If you find any error during `cfn submit` command please check the CloudFormation console to check the events for any errors like permission erros for example and remediate them.

**Step 3** - Now that we have the Cloudformation Registry Private Extension Resource created we need to create the AWS Lambda that will capture the custom resource configuration data from the source API (in this example from Azure API to describe the specific Security Group we want to record) and record it to AWS Config using the destination schema from the Cloudformation Registry we just created.
You can create your Lambda directly through the console or using AWS SAM. Check tutorial [[here](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html)].
This is an example Lambda that retrieves a specific Azure Security Group data by receiving as an event with the name of the Azure Security Group, the name for an AWS Secrets Manager holding my Azure Client ID and Secret, the Azure Subscription ID, Azure Resource Group Name and Azure Tenant ID.
```
import json
import boto3
import botocore.exceptions
from azure.identity import ClientSecretCredential
from azure.mgmt.network import NetworkManagementClient
def lambda_handler(event, context):
try:
# Create a session with the desired region
session = boto3.session.Session(region_name='us-east-1')
# Initialize AWS Config client
config = session.client('config')
# Initialize Azure client variables from os environment
secret_name = event['SECRETS_MANAGER_AZURE_SECRET_NAME'] # Get AWS Secrets Manager secret name for Azure Client from Lambda event payload
azure_subscription_id = event['AZURE_SUBSCRIPTION_ID'] # Get Azure Subscription ID from Lambda event payload
azure_resource_group_name = event['AZURE_RESOURCE_GROUP_NAME'] # Get Azure Resource Group Name from Lambda event payload
azure_tenant_id = event['AZURE_TENANT_ID'] # Set this environment variable in your Lambda function
# Create a Secrets Manager client
secretsmanager = session.client('secretsmanager')
try:
get_secret_value_response = secretsmanager.get_secret_value(
SecretId=secret_name
)
except botocore.exceptions.ClientError as e:
# For a list of exceptions thrown, see
# https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
raise e
# Decrypts Azure Client ID and Secret using the associated KMS key.
secret = get_secret_value_response['SecretString']
azure_client_secret = json.loads(secret)['azure_client_secret']
azure_client_id = json.loads(secret)['azure_client_id']
credential = ClientSecretCredential(
tenant_id = azure_tenant_id,
client_id = azure_client_id,
client_secret = azure_client_secret
)
network_client = NetworkManagementClient(credential, azure_subscription_id)
# Get properties from the event
group_name = event['GroupName']
# Read the security group
security_group = network_client.network_security_groups.get(azure_resource_group_name, group_name)
# Rename properties from Azure SG to match AWS Config custom resource schema name
security_rules_new_array_of_dictionaries = []
for rule in security_group.security_rules:
new_rule = {
'SecurityRuleId' : rule.id,
'SecurityRuleName' : rule.name,
'SecurityRuleType' : rule.type,
'SecurityRuleProtocol' : rule.protocol,
'SecurityRuleSourcePortRange' : rule.source_port_range,
'SecurityRuleDestinationPortRange' : rule.destination_port_range,
'SecurityRuleSourceAddressPrefix' : rule.source_address_prefix,
'SecurityRuleSourceAddressPrefixes' : rule.source_address_prefixes,
'SecurityRuleDestinationAddressPrefix' : rule.destination_address_prefix,
'SecurityRuleDestinationAddressPrefixes' : rule.destination_address_prefixes,
'SecurityRuleSourcePortRanges' : rule.source_port_ranges,
'SecurityRuleDestinationPortRanges' : rule.destination_port_ranges,
'SecurityRuleAccess' : rule.access,
'SecurityRulePriority' : rule.priority,
'SecurityRuleDirection' : rule.direction,
'SecurityRuleProvisioningState' : rule.provisioning_state,
}
security_rules_new_array_of_dictionaries.append(new_rule)
# Prepare the custom resource configuration item data
resource_details = {
'SecurityGroupName': security_group.name,
'SecurityGroupId': security_group.id,
'AzureResourceType': security_group.type,
'SecurityGroupLocation': security_group.location,
'SecurityGroupTags': security_group.tags,
'SecurityRules': security_rules_new_array_of_dictionaries
}
#configurationprint = json.dumps(resource_details)
# Send the custom resource configuration item data to AWS Config
config.put_resource_config(
ResourceType='AzureTest::VM::SecurityGroup',
ResourceName=resource_details['SecurityGroupName'],
ResourceId=resource_details['SecurityGroupId'],
Configuration=json.dumps(resource_details),
Tags={},
SchemaVersionId='00000001'
)
except Exception as e:
print(f"An unexpected error occurred: {e}")
raise e
```
This is an example of the event json payload that this Lambda would need to receive and that we will be automating in the next step through an Amazon Eventbridge Scheduler that will be responsible to trigger this Lambda every x minutes while passing these parameters as an event json payload.
```
{
"GroupName": "YOUR-AZURE-SECURITY-GROUP-NAME",
"SECRETS_MANAGER_AZURE_SECRET_NAME": "YOUR-AWS-SECRETS-MANAGER-NAME",
"AZURE_SUBSCRIPTION_ID": "YOUR-AZURE-SUBSCRIPTION-ID",
"AZURE_RESOURCE_GROUP_NAME": "YOUR-AZURE-RESOURCE-GROUP-NAME",
"AZURE_TENANT_ID": "YOUR-AZURE-TENANT-ID"
}
```
The above Lambda also needs the following permissions set in its Execution Role. Reference doc on how to [[here](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html)]:
```
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"config:PutResourceConfig",
"secretsmanager:GetSecretValue",
"kms:Decrypt"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
```
*Tip: To help you speed up the development your Lambda code for your use case you can also check Amazon CodeWhisperer which is an AI coding companion that you can integrate in your IDE to provide you with code recommendations in real-time.
https://docs.aws.amazon.com/codewhisperer/latest/userguide/what-is-cwspr.html*
**Step 4** - Create an Amazon Eventbridge Scheduler to trigger the above Lambda based on the schedule you want.
You can check the documentation [[here](https://docs.aws.amazon.com/scheduler/latest/UserGuide/getting-started.html)].
You should now be able to see your AWS Config custom resource with its updated configuration data.

**Optional step** - You can create an AWS Config Custom Rule to evaluate a target configuration state of your AWS Config Custom resource by triggering an AWS Lambda.
You can check the public documentation [[here](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config_develop-rules.html)].
As an example this is a Lambda code for our Config Custom Rule to check if there's any SSH port open to any IP.
This would be useful for a CIS Framework evaluation.
This Lambda can be targetted by our Config Custom Rule based on a periodic schedule or just when the configuration changes for our Config Custom resource (in this example when our Azure Security Group configuration changes).
```
import json
import boto3
from datetime import datetime
def lambda_handler(event, context):
# Print the entire event payload
#print(json.dumps(event))
# Parse the invoking event string as JSON
invoking_event = json.loads(event['invokingEvent'])
#print(invoking_event)
try:
# Initialize AWS Config client
config = boto3.client('config')
# Get properties from the event
group_name = invoking_event['configurationItem']['resourceId']
# Get configuration history for the Azure Security Group
response = config.get_resource_config_history(
resourceType='AzureTest::VM::SecurityGroup',
resourceId=group_name
)
# Get the most recent configuration
current_config = json.loads(response['configurationItems'][0]['configuration'])
# Check the security group's rules
for rule in current_config['SecurityRules']:
if (rule['SecurityRuleDestinationPortRange'] == '22' and (rule['SecurityRuleSourceAddressPrefix'] == '*' or rule['SecurityRuleSourceAddressPrefix'] == '0.0.0.0/0') and
rule['SecurityRuleAccess'] == 'Allow' and rule['SecurityRuleDirection'] == 'Inbound'):
# If the rule matches, the resource is not compliant
config.put_evaluations(
Evaluations=[
{
'ComplianceResourceType': invoking_event['configurationItem']['resourceType'],
'ComplianceResourceId': group_name,
'ComplianceType': 'NON_COMPLIANT',
'Annotation': 'The security group contains a rule that allows ingress from * (any) to port 22.',
'OrderingTimestamp': datetime.now()
}
],
ResultToken=event['resultToken']
)
return
# If no matching rule was found, the resource is compliant
config.put_evaluations(
Evaluations=[
{
'ComplianceResourceType': invoking_event['configurationItem']['resourceType'],
'ComplianceResourceId': group_name,
'ComplianceType': 'COMPLIANT',
'Annotation': 'The security group does not contain any rules that allow ingress from * (any) to port 22.',
'OrderingTimestamp': datetime.now()
}
],
ResultToken=event['resultToken']
)
except Exception as e:
print(f"An unexpected error occurred: {e}")
raise e
```
And this would be a sample IAM Execution Role needed for the above Lambda:
```
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"config:GetResourceConfigHistory",
"config:PutEvaluations"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
```
# **Submission of your creation for the Hackathon**:
To submit your AWS Config Custom resource creation for a chance to win the prizes please submit your custom resource schema and Lambda code files along with your updated Idea document to the Box folder that was shared with you on your e-mail.
If you haven't received your Box folder invitation to your e-mail please send us an email at aws-config-partner-hackathon@amazon.com
# Hackathon FAQ:
• **Can other people still participate who haven’t registered yet?**
A: Yes! They just need to send an email to aws-config-partner-hackathon@amazon.com and we’ll share with them the resources.
• **When does the Hackathon ends?**
A: The Hackathon will close for the submissions on June 23 at 23:59PM PST.
Winners will be announced on July 7 via e-mail.
• **The winners need to submit working code, not just an idea, right?**
A: Correct. To compete for the Amazon.com gift card winning prizes, you need to submit not only the idea, but also functional code, being the schema for the Cloudformation Registry resource and the Lambda code that will be retrieving the configuration data from the custom resource and ingesting into AWS Config.
All of these files should be submitted to the Box folder that was shared with you.
• **What happen to the AWS credits or the final prize if two parties submit a similar idea?**
A: The AWS Credits ($50) will be provided to everyone that has submitted the idea to your shared Box folder until the close date - June 23 23:59PM PST.
The AWS Credits will be provided through e-mail to you by July 7.
If we have two parties that submit same idea + functional code independently and they are selected as a winner submission, we’ll give each one a separate prize on the same amount.
• **What types of ideas are applicable for the Hackathon?**
A: You can submit an idea for any custom resource on Config, meaning any resource that isn’t already available natively in Config (You can check the current one’s [here](https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html)).
This can be an AWS Service that isn’t already available in Config, a 3rd party application including SaaS or a Multicloud/Hybrid resource for example.
Sky’s the limit, work backwards from what your customers have been requesting or what you think would be beneficial to be available in AWS Config for customers.
• **Do we need CloudFormation Registry to build a Config rule?**
A: You need CloudFormation Registry to ingest a custom resource into Config.
Custom Config Rules are not required for the scope of this Hackathon, but basically these are Config rules that you can create either with a Lambda or with the recently launched Guard, which is simplified way to create these rules without a Lambda and by using a specific Guard language.
• **What is the sandbox AWS Account for testing/building for this Hackathon?**
A: You’ll need to use your own AWS Account.
You can either check with your company on the process for this or create one of your own.
We’ll be providing USD 50 of AWS Credits to everyone that submits at least an idea to the Box shared folder (you don’t need to submit functional code, just your idea on what is the Config custom resource).
• **Is docker needed?**
A: Yes, you’ll need Docker installed to be able to submit your Cloudformation Registry to your sandbox AWS Account so you can test the custom Config resource.
• **Is there a sample submission I can check?**
A: You can check a sample submission from the same example shown in the Hackathon webinar kickoff on this link:
https://amazoncorporate.box.com/s/t9qp688scpfceyovsfb6kke6fahgutfz
# ADDITIONAL RESOURCES:
Using AWS Config custom resources to track any resource on AWS:
https://aws.amazon.com/blogs/mt/using-aws-config-custom-resources-to-track-any-resource-on-aws/
Getting Started with AWS Config – Free training on AWS Skill Builder:https://explore.skillbuilder.aws/learn/course/external/view/elearning/12609/getting-started-with-aws-config
AWS Config Partner Hackathon - Presentation - https://amazoncorporate.box.com/s/0k0uo95lmqobta5dd6peel8rkwc4gp56
Amazon CodeWhisperer - AI Coding companion with free user
https://docs.aws.amazon.com/codewhisperer/latest/userguide/what-is-cwspr.html
AWS Config supported resources - https://docs.aws.amazon.com/config/latest/developerguide/resource-config-reference.html
**E-mail support**: aws-config-partner-hackathon@amazon.com