├── images ├── org-agregator-query.png ├── flagged-ca-config-query.png └── flagged-ca-security-hub.png ├── requirements.txt ├── CODE_OF_CONDUCT.md ├── LICENSE ├── acm_flag_ca_issuer.py ├── CONTRIBUTING.md ├── README.md └── acm_flag_ca_issuer.yaml /images/org-agregator-query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ACM-flag-CA-issuer/main/images/org-agregator-query.png -------------------------------------------------------------------------------- /images/flagged-ca-config-query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ACM-flag-CA-issuer/main/images/flagged-ca-config-query.png -------------------------------------------------------------------------------- /images/flagged-ca-security-hub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ACM-flag-CA-issuer/main/images/flagged-ca-security-hub.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | boto3==1.33.13 2 | botocore==1.33.13 3 | jmespath==1.0.1 4 | python-dateutil==2.9.0.post0 5 | s3transfer==0.8.2 6 | six==1.16.0 7 | urllib3==1.26.19 -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | -------------------------------------------------------------------------------- /acm_flag_ca_issuer.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | import botocore 3 | import json 4 | import argparse 5 | import csv 6 | args = argparse.ArgumentParser() 7 | args.add_argument( 8 | '--profile', 9 | type=str, 10 | default=None, 11 | help="Use the specified AWS credentials profile. By default not used") 12 | args.add_argument( 13 | '--output', 14 | type=str, 15 | default='text', 16 | choices=[ 17 | 'json', 18 | 'csv', 19 | 'text'], 20 | help="By Default results are printed to the console. You can also save them to json or csv") 21 | args.add_argument( 22 | '--regions', 23 | type=list, 24 | default=None, 25 | help="Comma separated list of AWS regions to iterate over. Default is all") 26 | args.add_argument( 27 | '--flagged-ca', 28 | type=str, 29 | required=True, 30 | help="Comma separated list of flagged CAs") 31 | 32 | args = args.parse_args() 33 | 34 | args.flagged_ca_list = args.flagged_ca.split(',') 35 | 36 | if args.profile is None: 37 | session = boto3.Session() 38 | else: 39 | session = boto3.Session(profile_name=args.profile) 40 | 41 | if args.regions is None: 42 | args.regions = session.get_available_regions('acm') 43 | 44 | results = [] 45 | for region_name in args.regions: 46 | acm_client = session.client('acm', region_name=region_name) 47 | try: 48 | certificates = acm_client.list_certificates()['CertificateSummaryList'] 49 | except botocore.exceptions.ClientError as err: 50 | if err.response['Error']['Code'] == 'UnrecognizedClientException': 51 | continue 52 | for certificate in certificates: 53 | certificate_arn = certificate['CertificateArn'] 54 | 55 | certificate_detail = acm_client.describe_certificate(CertificateArn=certificate_arn)['Certificate'] 56 | cert_issuer = certificate_detail.get('Issuer', "") 57 | issued_at = certificate_detail.get('IssuedAt',"") 58 | if issued_at: 59 | issued_at = issued_at.strftime('%Y-%m-%d %H:%M:%S') 60 | results.append({ 61 | "domain_name": certificate_detail.get('DomainName', ""), 62 | "alternate_domain_names": ','.join(certificate_detail.get('SubjectAlternativeNames', [])), 63 | "issued_at": issued_at, 64 | "not_before": certificate_detail.get('NotBefore').strftime('%Y-%m-%d %H:%M:%S'), 65 | "cert_issuer": cert_issuer, 66 | "flagged_ca": cert_issuer in args.flagged_ca_list 67 | }) 68 | if args.output == 'csv': 69 | with open('flagged_acm_cs.csv', 'w', newline='') as csvfile: 70 | fieldnames = ['domain_name', 'alternate_domain_names', 'issued_at', 'not_before', 'cert_issuer', 'flagged_ca'] 71 | writer = csv.DictWriter(csvfile, fieldnames=fieldnames) 72 | writer.writeheader() 73 | for result in results: 74 | writer.writerow(result) 75 | elif args.output == 'json': 76 | with open('flagged_acm_cs.json', 'w') as jsonfile: 77 | json.dump(results, jsonfile, indent=4, default=str) 78 | elif args.output == 'text': 79 | print(json.dumps(results, indent=4)) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Certificate Manager - Flag Certificates by CA Issuer 2 | 3 | ## Overview 4 | 5 | Three solutions/approaches allow you to audit the Issuer of certificates stored in AWS Certificate Manager. The most common reasons you might do this include: 6 | * You have an allowed/preferred list of Certificate Authorities and need to audit this across your Organization 7 | * You have a list of Certificate Authorities you do NOT to be used or need to flag where they are being used 8 | * This could be company policy or as a result of a upcoming client/browser trusted CA store change to identify impact/risk. 9 | 10 | Please see this [AWS Security Blog post](https://aws.amazon.com/blogs/security/options-for-aws-customers-who-use-entrust-issued-certificates/) for more information about how to use this project, and why it might be useful for you. 11 | 12 | ## Approaches 13 | 14 | The right approach depends on what you already have in place, level of effort you can take, and if you need an ad-hoc report or require ongoing visibility. 15 | 16 | * [Python Script](#python-script) 17 | No AWS Account setup required. Prints or saves report to JSON/CSV for multiple accounts. 18 | 19 | * [AWS Config Query](#aws-config-query) 20 | If you already use AWS Config, use an AWS Config Aggregator (easy to setup) to create a report across any number of accounts/regions at once. 21 | 22 | * [AWS Config Custom Rule](#aws-config-custom-rule) 23 | Deploy an AWS Config Custom Rule to flag non-compliance certificates in near real time across any number of accounts/regions at once. Query with AWS Config or AWS Security Hub! 24 | 25 | --- 26 | ### Python Script 27 | 28 | Simple python script evaluates all ACM certificates for a given AWS Account. By default evaluates all regions for a provided list of CA Issuer values. 29 | 30 | #### Prerequisites 31 | 32 | AWS Account Configurations: 33 | * None 34 | 35 | Client: 36 | * [python3.10+](https://www.python.org/downloads/) 37 | * [git](https://git-scm.com/downloads) 38 | 39 | #### How to Use 40 | 41 | 42 | ``` 43 | # Clone GitHub repo 44 | git clone https://github.com/aws-samples/acm-flag-ca-issuer.git 45 | 46 | #Create a virtual Environment 47 | python -m venv .venv 48 | 49 | # Activate virtual Environment 50 | source .venv/bin/activate 51 | 52 | # Install required python modules for script 53 | pip install -r requirements.txt --no-cache-dir 54 | 55 | # Example execution, assumes credentials are already configured 56 | python3 acm_flag_ca_issuer.py --flagged-ca MYCA1, MYCA2 57 | 58 | # Example execution, specifies an AWS Credential profile to use 59 | python3 acm_flag_ca_issuer.py --flagged-ca MYCA1, MYCA2 --profile MyCredentialsProfile 60 | ``` 61 | Example output 62 | ``` 63 | [ 64 | { 65 | "domain_name": "anycompany.com", 66 | "alternate_domain_names": "foo.anycompany.com", 67 | "issued_at": "", 68 | "not_before": "2024-08-07 11:22:3", 69 | "cert_issuer": "Issuer Name Is Here", 70 | "flagged_ca": false 71 | }, 72 | { 73 | "domain_name": "anycompany.com", 74 | "alternate_domain_names": "foo.anycompany.com", 75 | "issued_at": "", 76 | "not_before": "2024-08-07 11:22:3", 77 | "cert_issuer": "Flagged Issuer Goes Here", 78 | "flagged_ca": true 79 | } 80 | ] 81 | ``` 82 | 83 | 84 | #### Help 85 | Full help for script and available arguments 86 | ``` 87 | usage: acm_flag_ca_issuer.py [-h] [--profile PROFILE] 88 | [--output {json,csv,text}] [-regions REGIONS] 89 | --flagged-ca FLAGGED_CA 90 | 91 | optional arguments: 92 | -h, --help show this help message and exit 93 | --profile PROFILE Use the specified AWS credentials profile. By default 94 | not used 95 | --output {json,csv,text} 96 | By Default results are printed to the console. You can 97 | also save them to json or csv 98 | --regions REGIONS Comma separated list of AWS regions to iterate over. 99 | Default is all 100 | --flagged-ca FLAGGED_CA 101 | ``` 102 | 103 | 104 | --- 105 | 106 | 107 | ### AWS Config Query 108 | 109 | AWS Config Advanced Queries provide an ad-hoc report of all ACM certificates and the relevant data to identify and flag by Certificate Issuer. Using an AWS Config Aggregator, this includes results for all accounts and regions in scope to the Aggregator. 110 | 111 | **Pros**: Out of box multi-account reporting, easily customizable report. 112 | **Cons**: Requires specific AWS Services already configured: AWS Config with an Aggregator 113 | 114 | #### Prerequisites 115 | 116 | AWS Account Configurations: 117 | * AWS Config recorder in all member accounts with at least AWS::ACM::Certificates configured as a resource 118 | * AWS Config Aggregator covering all Accounts in scope. e.g. [Org Aggregator](https://aws.amazon.com/blogs/mt/org-aggregator-delegated-admin/) 119 | 120 | #### Query 121 | 122 | ``` 123 | select 124 | accountId, 125 | awsRegion, 126 | resourceId, 127 | configuration.issuer, 128 | configuration.NotBeforeDate, 129 | configuration.issuedAt 130 | where 131 | resourceType = 'AWS::ACM::Certificate' 132 | ``` 133 | ![AWS Config Advanced Query showing all ACM certificates](./images/org-agregator-query.png) 134 | 135 | 136 | --- 137 | 138 | 139 | ### AWS Config Custom Rule 140 | 141 | Deploy an AWS Config Custom Rule flagging (non-compliant) certificates based on Issuer to any number of AWS accounts/regions. 142 | 143 | #### Prerequisites 144 | 145 | AWS Account Configurations: 146 | * AWS Config with at least AWS::ACM::Certificates configured in the recorder 147 | * [AWS CloudFormation Service-Managed StackSets](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacksets-orgs-activate-trusted-access.html) is enabled 148 | 149 | Client: 150 | * python3.10+ 151 | * git 152 | 153 | #### How to Deploy 154 | 155 | The below instructions assume you have configured credentials and are deploying from your payer account. 156 | 157 | ``` 158 | # Clone github code 159 | git clone https://github.com/aws-samples/acm-flag-ca-issuer.git 160 | # Define stackset name 161 | export StackName=acm-flag-ca-issuer 162 | aws cloudformation create-stack-set \ 163 | --stack-set-name $StackName \ 164 | --template-body file://acm_flag_ca_issuer.yaml \ 165 | --permission-model SERVICE_MANAGED \ 166 | --capabilities CAPABILITY_IAM \ 167 | --auto-deployment Enabled=True,RetainStacksOnAccountRemoval=False \ 168 | --parameters ParameterKey=MatchLogic,ParameterValue='exact' \ 169 | ParameterKey=FlagIndicator,ParameterValue=NON_COMPLIANT 170 | 171 | #Specify the OU (or in this case root ID) to deploy to 172 | export OrgId=`aws organizations list-roots --query 'Roots'[0].Id` 173 | # Uncomment below to specify custom OU(s) 174 | #export OrgId=ou-1234-12341234,ou-abcd-abcdabcd 175 | 176 | 177 | #Deploy StackSet, Default is all accounts and all default regions 178 | 179 | aws cloudformation create-stack-instances \ 180 | --stack-set-name $StackName \ 181 | --deployment-targets OrganizationalUnitIds=$OrgId \ 182 | --regions ap-south-1 ca-central-1 eu-central-1 us-west-1 \ 183 | us-west-2 eu-north-1 eu-west-3 eu-west-2 eu-west-1 \ 184 | ap-northeast-3 ap-northeast-2 ap-northeast-1 sa-east-1 \ 185 | ap-southeast-1 ap-southeast-2 us-east-1 us-east-2 \ 186 | --operation-preferences RegionConcurrencyType=PARALLEL 187 | 188 | ``` 189 | 190 | #### Consolidated Report 191 | 192 | ##### AWS Security Hub 193 | 194 | 195 | AWS Security Hub by default shows non-compliant resources for any AWS Config rules. AWS Security Hub provides a central location to view security findings. With this CloudFormation Template deployed, non-compliant ACM certificates can be viewed and used to trigger alerts (if desired). 196 | ![AWS Config Advanced Query showing all ACM certificates](./images/flagged-ca-security-hub.png) 197 | 198 | ##### AWS Config Query 199 | 200 | If you don't use AWS SEcurity Hub, you can still see compliance information created by the deployed AWS Config rules 201 | 202 | #### Query 203 | 204 | ``` 205 | SELECT 206 | configuration.targetResourceId, 207 | configuration.complianceType 208 | WHERE 209 | configuration.configRuleList.configRuleName = 'flagged_ca_check' 210 | and configuration.targetResourceType = 'AWS::ACM::Certificate' 211 | ``` 212 | ![AWS Config Advanced Query showing all ACM certificates](./images/flagged-ca-config-query.png) 213 | -------------------------------------------------------------------------------- /acm_flag_ca_issuer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: 2010-09-09 3 | Description: Flags ACM Certificates based on the issuing CA (uksb-qwu6c9fai4) 4 | Parameters: 5 | MatchLogic: 6 | Type: String 7 | Default: "exact" 8 | AllowedValues: 9 | - "exact" 10 | - "regex" 11 | FlagIndicator: 12 | Type: String 13 | Description: > 14 | If resource that meet MatchLogic/vales are COMPLIANT or NOT_COMPLIANT. 15 | Default: NON_COMPLIANT 16 | AllowedValues: 17 | - NON_COMPLIANT 18 | - COMPLIANT 19 | Resources: 20 | LambdaRole: 21 | Type: AWS::IAM::Role 22 | Properties: 23 | AssumeRolePolicyDocument: 24 | Version: 2012-10-17 25 | Statement: 26 | - Effect: Allow 27 | Principal: 28 | Service: 29 | - lambda.amazonaws.com 30 | Action: 31 | - 'sts:AssumeRole' 32 | Path: / 33 | LambdaRolePolicy: 34 | Metadata: 35 | cfn_nag: 36 | rules_to_suppress: 37 | - id: W12 38 | reason: Wildcard resource required for ACM and config API actions 39 | - id: W91 40 | reason: Access to CWL is granted, cdn_nag not detecting 41 | Type: 'AWS::IAM::Policy' 42 | Properties: 43 | PolicyName: Policy 44 | PolicyDocument: 45 | Version: 2012-10-17 46 | Statement: 47 | - Effect: Allow 48 | Action: 49 | - "logs:CreateLogGroup" 50 | - "logs:CreateLogStream" 51 | - "logs:PutLogEvents" 52 | Resource: "arn:aws:logs:*:*:*" 53 | - Effect: Allow 54 | Action: 55 | - "acm:ListCertificates" 56 | - "acm:DescribeCertificate" 57 | - "config:PutEvaluations" 58 | Resource: "*" 59 | - Effect: Allow 60 | Action: 61 | - "ssm:GetParameter" 62 | Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${FlaggedCAListParameter}" 63 | Roles: 64 | - !Ref LambdaRole 65 | 66 | FlaggedCAListParameter: 67 | Type: AWS::SSM::Parameter 68 | Properties: 69 | Name: flagged-ca-list 70 | Type: StringList 71 | Value: !Join 72 | - "," 73 | - 74 | - Entrust Root Certification Authority 75 | - AffirmTrust Commercial 76 | - AffirmTrust Networking 77 | - AffirmTrust Premium 78 | - AffirmTrust Premium ECC 79 | - Entrust Root Certification Authority 80 | - Entrust Root Certification Authority - EC1 81 | - Entrust Root Certification Authority - G2 82 | - Entrust Root Certification Authority - G4 83 | - Entrust.net Certification Authority (2048) 84 | Description: List of Flagged CAs 85 | 86 | ConfigPermissionToCallLambda: 87 | Type: AWS::Lambda::Permission 88 | Properties: 89 | FunctionName: 90 | Fn::GetAtt: 91 | - ConfigRuleLambdaFunction 92 | - Arn 93 | Action: "lambda:InvokeFunction" 94 | Principal: "config.amazonaws.com" 95 | SourceAccount: !Ref 'AWS::AccountId' 96 | ConfigRuleLambdaFunction: 97 | Metadata: 98 | cfn_nag: 99 | rules_to_suppress: 100 | - id: W58 101 | reason: Access to CWL is granted, cdn_nag not detecting 102 | - id: W89 103 | reason: Not relevant for use case 104 | - id: W92 105 | reason: Not relevant for use case 106 | Type: AWS::Lambda::Function 107 | Properties: 108 | Code: 109 | ZipFile: | 110 | import json 111 | import boto3 112 | import re 113 | import os 114 | import botocore 115 | from datetime import datetime 116 | 117 | config_client = boto3.client('config') 118 | acm_client = boto3.client('acm') 119 | ssm_client = boto3.client('ssm') 120 | MATCH_LOGIC = os.environ.get('MatchLogic', 'exact') 121 | FLAG_INDICATOR = os.environ.get('FlagIndicator', 'NON_COMPLIANT') 122 | FLAGGED_CA_LIST = ssm_client.get_parameter(Name='flagged-ca-list').get('Parameter').get('Value').split(',') 123 | 124 | if FLAG_INDICATOR == 'NON_COMPLIANT': 125 | DEFAULT_RESULT_CONFIG = 'COMPLIANT' 126 | else: 127 | DEFAULT_RESULT_CONFIG = 'NOT_COMPLIANT' 128 | # Define the default Config result. 129 | #DEFAULT_RESULT_CONFIG = 'COMPLIANT' 130 | #DEFAULT_RESULT_CONFIG = 'NOT_COMPLIANT' 131 | #DEFAULT_RESULT_CONFIG = 'NOT_APPLICABLE' 132 | 133 | class ConfigResult(): 134 | ### Define and send AWS Config resource compliance 135 | def __init__(self): 136 | self.client = boto3.client('config') 137 | self.config_result = DEFAULT_RESULT_CONFIG 138 | self._annotation = [] 139 | self.resource_type = None 140 | self.resource_id = None 141 | self.result_token = None 142 | 143 | def annotation(self, value:str = None): 144 | """Append a value to the eventual annotation string""" 145 | self._annotation.append(value) 146 | 147 | def _build(self): 148 | """ Set default or construct annotation String """ 149 | if self._annotation == []: 150 | self.annotation_value = ' ' 151 | else: 152 | self.annotation_value = ' | '.join(self._annotation) 153 | 154 | def configure(self, event:dict = None): 155 | """Extract details from AWS Config > Lambda event to send compliance later.""" 156 | invoking_event = json.loads(event.get('invokingEvent',"{}")) 157 | configuration_item = invoking_event.get('configurationItem') 158 | self.resource_type = configuration_item.get('resourceType', None) 159 | self.resource_id = configuration_item.get('resourceId', None) 160 | self.result_token = event.get('resultToken', None) 161 | 162 | def send(self): 163 | """Respond to AWS Config with Resource Results.""" 164 | self._build() 165 | print(f"Sending config result: {self.resource_id} | {self.config_result} | {self.annotation_value}") 166 | if self.config_result in ['COMPLIANT','NOT_COMPLIANT']: 167 | self.client.put_evaluations( 168 | Evaluations=[ 169 | { 170 | 'ComplianceResourceType': self.resource_type, 171 | 'ComplianceResourceId': self.resource_id, 172 | 'ComplianceType': self.config_result, 173 | 'Annotation': self.annotation_value, 174 | 'OrderingTimestamp': datetime.now() 175 | }, 176 | ], 177 | ResultToken=self.result_token 178 | ) 179 | 180 | def lambda_handler(event, context): 181 | ### Identify ACM Certificates Issued by specific CAs### 182 | config_result = ConfigResult() 183 | config_result.configure(event) 184 | invoking_event = json.loads(event.get('invokingEvent',"{}")) 185 | configuration_item = invoking_event.get('configurationItem') 186 | certificate_issuer = configuration_item.get('configuration').get('issuer') 187 | if configuration_item.get('configurationItemStatus', None) in ['ResourceDeleted', None]: 188 | config_result.config_result = 'NOT_APPLICABLE' 189 | return 190 | else: 191 | match MATCH_LOGIC: 192 | case 'exact': 193 | if certificate_issuer in FLAGGED_CA_LIST: 194 | config_result.config_result = FLAG_INDICATOR 195 | config_result.annotation(f"Certificate issues by {certificate_issuer}") 196 | case 'regex': 197 | for flagged_ca_pattern in FLAGGED_CA_LIST: 198 | if re.match(flagged_ca_pattern, certificate_issuer): 199 | print(f"regex match: {flagged_ca_pattern} || {certificate_issuer}") 200 | config_result.config_result = FLAG_INDICATOR 201 | config_result.annotation(f"Certificate issues by {certificate_issuer}") 202 | config_result.send() 203 | 204 | Handler: "index.lambda_handler" 205 | Runtime: python3.12 206 | Timeout: 15 207 | Environment: 208 | Variables: 209 | MatchLogic: !Ref MatchLogic 210 | FlagIndicator: !Ref FlagIndicator 211 | Role: !GetAtt LambdaRole.Arn 212 | 213 | ConfigRule: 214 | Type: AWS::Config::ConfigRule 215 | Properties: 216 | ConfigRuleName: flagged_ca_check 217 | Description: Evaluate if certificates were issued by a list of provided CAs (via SSM Parameter). 218 | Scope: 219 | ComplianceResourceTypes: 220 | - "AWS::ACM::Certificate" 221 | Source: 222 | Owner: "CUSTOM_LAMBDA" 223 | SourceDetails: 224 | - 225 | EventSource: "aws.config" 226 | MessageType: "ConfigurationItemChangeNotification" 227 | - 228 | EventSource: aws.config 229 | MessageType: OversizedConfigurationItemChangeNotification 230 | SourceIdentifier: 231 | Fn::GetAtt: 232 | - ConfigRuleLambdaFunction 233 | - Arn 234 | DependsOn: ConfigPermissionToCallLambda 235 | --------------------------------------------------------------------------------