diff --git a/.github/scripts/sync-copywriter-changes/sync_md_to_metadata.py b/.github/scripts/sync-copywriter-changes/sync_md_to_metadata.py index 713ccd40..b8ee3c36 100755 --- a/.github/scripts/sync-copywriter-changes/sync_md_to_metadata.py +++ b/.github/scripts/sync-copywriter-changes/sync_md_to_metadata.py @@ -19,7 +19,7 @@ def extract_informations_from_md( content: str, -) -> tuple[str | None, str | None, str | None, str | None]: +) -> tuple[str | None, str | None, str | None, str | None, str | None]: """ Extract the informations from markdown content. @@ -27,7 +27,7 @@ def extract_informations_from_md( content: Markdown content (full file including frontmatter) Returns: - Query name, description, severity and category + Query name, description, severity, category and provider_url """ # Extract query name from title in frontmatter @@ -64,7 +64,15 @@ def extract_informations_from_md( if category_match: category = category_match.group(1).strip() - return query_name, description, severity, category + # Extract Provider Reference URL from Learn More section + provider_url = None + provider_url_pattern = r'\[Provider Reference\]\((https?://[^)]+)\)' + provider_url_match = re.search(provider_url_pattern, content) + + if provider_url_match: + provider_url = provider_url_match.group(1).strip() + + return query_name, description, severity, category, provider_url def find_json_paths( @@ -130,6 +138,7 @@ def update_metadata_json( description: str | None, severity: str | None, category: str | None, + provider_url: str | None = None, dry_run: bool = False, ) -> bool: """ @@ -186,6 +195,13 @@ def update_metadata_json( if not dry_run: metadata["category"] = category + # Update providerUrl + if provider_url and metadata.get("providerUrl") != provider_url: + old_provider_url = metadata.get("providerUrl", "") + changes.append(f" providerUrl: '{old_provider_url}' -> '{provider_url}'") + if not dry_run: + metadata["providerUrl"] = provider_url + if changes: print(f"\n{'[DRY RUN] ' if dry_run else ''}Updating {metadata_path}:") for change in changes: @@ -308,7 +324,7 @@ def sync_md_to_metadata( content = f.read() # Extract information from full markdown content - name, description, severity, category = extract_informations_from_md( + name, description, severity, category, provider_url = extract_informations_from_md( content ) @@ -334,6 +350,7 @@ def sync_md_to_metadata( description, severity, category, + provider_url, dry_run, ) positive_updated = update_positive_expected_result_json( diff --git a/assets/queries/ansible/aws/alb_listening_on_http/metadata.json b/assets/queries/ansible/aws/alb_listening_on_http/metadata.json index 85f21135..6dacd7de 100644 --- a/assets/queries/ansible/aws/alb_listening_on_http/metadata.json +++ b/assets/queries/ansible/aws/alb_listening_on_http/metadata.json @@ -1,14 +1,14 @@ { "id": "f81d63d2-c5d7-43a4-a5b5-66717a41c895", - "queryName": "ALB Listening on HTTP", + "queryName": "ALB listening on HTTP", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "AWS Application Load Balancer (alb) should not listen on HTTP", + "descriptionText": "Application Load Balancers (ALB) must terminate TLS and use HTTPS listeners to protect traffic in transit and prevent interception or downgrade attacks. Serving application traffic over plain HTTP exposes credentials and sensitive data to eavesdropping.\n\nFor Ansible ALB resources (modules `amazon.aws.elb_application_lb` and `elb_application_lb`), ensure the `listeners[].Protocol` property is set to `\"HTTPS\"`. Resources missing the `Protocol` property or with `Protocol` set to any value other than `\"HTTPS\"` are flagged. When using HTTPS, also configure a valid TLS certificate (for example via `Certificates: - CertificateArn: ...`) or implement an HTTP listener only to perform redirects to HTTPS rather than serving plaintext.\n\nSecure configuration example:\n\n```yaml\n- name: Create ALB with HTTPS listener\n amazon.aws.elb_application_lb:\n name: my-alb\n state: present\n listeners:\n - Protocol: HTTPS\n Port: 443\n Certificates:\n - CertificateArn: arn:aws:acm:us-east-1:123456789012:certificate/abcdef01-2345-6789-abcd-ef0123456789\n DefaultActions:\n - Type: forward\n TargetGroupArn: arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/abcdef0123456789\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/alb_listening_on_http", "platform": "Ansible", "descriptionID": "3a7576e5", "cloudProvider": "aws", "cwe": "319", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/elb_application_lb_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/elb_application_lb_module.html" } diff --git a/assets/queries/ansible/aws/alb_listening_on_http/test/positive_expected_result.json b/assets/queries/ansible/aws/alb_listening_on_http/test/positive_expected_result.json index 40450933..7bca43df 100644 --- a/assets/queries/ansible/aws/alb_listening_on_http/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/alb_listening_on_http/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "ALB Listening on HTTP", + "queryName": "ALB listening on HTTP", "severity": "MEDIUM", "line": 11 }, { - "queryName": "ALB Listening on HTTP", + "queryName": "ALB listening on HTTP", "severity": "MEDIUM", "line": 29 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/ami_not_encrypted/metadata.json b/assets/queries/ansible/aws/ami_not_encrypted/metadata.json index 0339907d..6495b604 100644 --- a/assets/queries/ansible/aws/ami_not_encrypted/metadata.json +++ b/assets/queries/ansible/aws/ami_not_encrypted/metadata.json @@ -1,9 +1,9 @@ { "id": "97707503-a22c-4cd7-b7c0-f088fa7cf830", - "queryName": "AMI Not Encrypted", + "queryName": "AMI not encrypted", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "AWS AMI Encryption is not enabled", + "descriptionText": "AMIs must have their block device mappings encrypted to protect data at rest and prevent sensitive information from being exposed if snapshots are copied, shared, or recovered on different storage.\n\nFor Ansible tasks using the `amazon.aws.ec2_ami` or `ec2_ami` modules, each entry in the `device_mapping` must include `encrypted: true`. Resources missing the `encrypted` attribute or with `encrypted: false` are flagged. Ensure every device mapping explicitly sets `encrypted: true` so AMI snapshots and derived volumes remain encrypted.\n\nSecure configuration example:\n\n```yaml\n- name: Create AMI with encrypted device mapping\n amazon.aws.ec2_ami:\n name: my-encrypted-ami\n device_mapping:\n - device_name: /dev/sda1\n encrypted: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ami_not_encrypted", "platform": "Ansible", "descriptionID": "a4342f08", diff --git a/assets/queries/ansible/aws/ami_not_encrypted/test/positive_expected_result.json b/assets/queries/ansible/aws/ami_not_encrypted/test/positive_expected_result.json index d31968ad..9199a3a8 100644 --- a/assets/queries/ansible/aws/ami_not_encrypted/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ami_not_encrypted/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "AMI Not Encrypted", + "queryName": "AMI not encrypted", "severity": "MEDIUM", "line": 6 }, { - "queryName": "AMI Not Encrypted", + "queryName": "AMI not encrypted", "severity": "MEDIUM", "line": 13 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/ami_shared_with_multiple_accounts/metadata.json b/assets/queries/ansible/aws/ami_shared_with_multiple_accounts/metadata.json index f7cef722..bd859791 100644 --- a/assets/queries/ansible/aws/ami_shared_with_multiple_accounts/metadata.json +++ b/assets/queries/ansible/aws/ami_shared_with_multiple_accounts/metadata.json @@ -1,9 +1,9 @@ { "id": "a19b2942-142e-4e2b-93b7-6cf6a6c8d90f", - "queryName": "AMI Shared With Multiple Accounts", + "queryName": "AMI shared with multiple accounts", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "Limits access to AWS AMIs by checking if more than one account is using the same image", + "descriptionText": "AMIs must not be broadly shared. Granting multiple AWS accounts or group-based access increases the attack surface and can expose embedded credentials, custom configurations, or vulnerable images to unintended parties.\n\nFor Ansible tasks using the `amazon.aws.ec2_ami` or `ec2_ami` modules, `launch_permissions` should be restricted to at most one explicit AWS account and must not include `group_names`. This rule flags tasks where `launch_permissions.group_names` is present or where `launch_permissions.user_ids` contains more than one entry. \n\nSecure example with a single allowed account:\n\n```yaml\n- name: Register AMI with restricted launch permissions\n amazon.aws.ec2_ami:\n name: my-ami\n image_id: ami-0123456789abcdef0\n launch_permissions:\n user_ids:\n - \"123456789012\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ami_shared_with_multiple_accounts", "platform": "Ansible", "descriptionID": "2117f1c7", diff --git a/assets/queries/ansible/aws/ami_shared_with_multiple_accounts/test/positive_expected_result.json b/assets/queries/ansible/aws/ami_shared_with_multiple_accounts/test/positive_expected_result.json index 62c69ac9..836586b7 100644 --- a/assets/queries/ansible/aws/ami_shared_with_multiple_accounts/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ami_shared_with_multiple_accounts/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "AMI Shared With Multiple Accounts", + "queryName": "AMI shared with multiple accounts", "severity": "MEDIUM", "line": 6 }, { - "queryName": "AMI Shared With Multiple Accounts", + "queryName": "AMI shared with multiple accounts", "severity": "MEDIUM", "line": 13 } diff --git a/assets/queries/ansible/aws/api_gateway_endpoint_config_is_not_private/metadata.json b/assets/queries/ansible/aws/api_gateway_endpoint_config_is_not_private/metadata.json index 10e8fd0c..fc158f63 100644 --- a/assets/queries/ansible/aws/api_gateway_endpoint_config_is_not_private/metadata.json +++ b/assets/queries/ansible/aws/api_gateway_endpoint_config_is_not_private/metadata.json @@ -1,13 +1,13 @@ { "id": "559439b2-3e9c-4739-ac46-17e3b24ec215", - "queryName": "API Gateway Endpoint Config is Not Private", + "queryName": "API Gateway endpoint config is not private", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "The API Endpoint type in API Gateway should be set to PRIVATE so it's not exposed to the public internet", + "descriptionText": "API Gateway endpoint type must be set to `PRIVATE` to prevent the API from being exposed to the public internet, which increases attack surface and can enable unauthorized access or data exfiltration.\n\nFor Ansible tasks using the `community.aws.api_gateway` or `api_gateway` modules, the `endpoint_type` property must be defined and set to `PRIVATE`. Tasks missing this property or with `endpoint_type` not set to `PRIVATE` are flagged. A `PRIVATE` endpoint restricts access to VPC endpoints, so ensure the required VPC endpoint and networking is configured to allow authorized clients to reach the API.\n\nSecure Ansible task example:\n\n```yaml\n- name: Create private API Gateway\n community.aws.api_gateway:\n name: my-private-api\n endpoint_type: PRIVATE\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/api_gateway_endpoint_config_is_not_private", "platform": "Ansible", "descriptionID": "42fabc16", "cloudProvider": "aws", "cwe": "285", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_api_gateway_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/api_gateway_module.html" } diff --git a/assets/queries/ansible/aws/api_gateway_endpoint_config_is_not_private/test/positive_expected_result.json b/assets/queries/ansible/aws/api_gateway_endpoint_config_is_not_private/test/positive_expected_result.json index bdba1c0e..ce889dee 100644 --- a/assets/queries/ansible/aws/api_gateway_endpoint_config_is_not_private/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/api_gateway_endpoint_config_is_not_private/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "API Gateway Endpoint Config is Not Private", + "queryName": "API Gateway endpoint config is not private", "severity": "MEDIUM", "line": 9 } diff --git a/assets/queries/ansible/aws/api_gateway_with_cloudwatch_logging_disabled/metadata.json b/assets/queries/ansible/aws/api_gateway_with_cloudwatch_logging_disabled/metadata.json index 6f37a396..47ad15e4 100644 --- a/assets/queries/ansible/aws/api_gateway_with_cloudwatch_logging_disabled/metadata.json +++ b/assets/queries/ansible/aws/api_gateway_with_cloudwatch_logging_disabled/metadata.json @@ -1,13 +1,13 @@ { "id": "72a931c2-12f5-40d1-93cc-47bff2f7aa2a", - "queryName": "API Gateway With CloudWatch Logging Disabled", + "queryName": "API Gateway with CloudWatch Logs disabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "AWS CloudWatch Logs for APIs is not enabled", + "descriptionText": "APIs must send request logs and execution traces to CloudWatch Logs so activity, errors, and suspicious behavior can be detected and investigated. Without a configured log group, you lose critical visibility for incident response and troubleshooting.\n\nIn Ansible, tasks using the `amazon.aws.cloudwatchlogs_log_group` or `cloudwatchlogs_log_group` modules must include the `log_group_name` property to create or reference a specific CloudWatch Logs group. Tasks missing `log_group_name` (or with it unset) are flagged. Set `log_group_name` to a stable, descriptive string and ensure API Gateway access logging or tracing is pointed to that group.\n\nSecure configuration example:\n\n```yaml\n- name: Create CloudWatch log group for API Gateway\n amazon.aws.cloudwatchlogs_log_group:\n log_group_name: \"/aws/apigateway/my-api\"\n state: present\n retention_in_days: 30\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/api_gateway_with_cloudwatch_logging_disabled", "platform": "Ansible", "descriptionID": "313709e8", "cloudProvider": "aws", "cwe": "778", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/cloudwatchlogs_log_group_module.html#ansible-collections-community-aws-cloudwatchlogs-log-group-module" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudwatchlogs_log_group_module.html#ansible-collections-community-aws-cloudwatchlogs-log-group-module" } diff --git a/assets/queries/ansible/aws/api_gateway_with_cloudwatch_logging_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/api_gateway_with_cloudwatch_logging_disabled/test/positive_expected_result.json index aef74f95..ad0385e9 100644 --- a/assets/queries/ansible/aws/api_gateway_with_cloudwatch_logging_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/api_gateway_with_cloudwatch_logging_disabled/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "API Gateway With CloudWatch Logging Disabled", + "queryName": "API Gateway with CloudWatch Logs disabled", "severity": "MEDIUM", "line": 3 } diff --git a/assets/queries/ansible/aws/api_gateway_without_configured_authorizer/metadata.json b/assets/queries/ansible/aws/api_gateway_without_configured_authorizer/metadata.json index e8e44e37..3d763995 100644 --- a/assets/queries/ansible/aws/api_gateway_without_configured_authorizer/metadata.json +++ b/assets/queries/ansible/aws/api_gateway_without_configured_authorizer/metadata.json @@ -1,13 +1,13 @@ { "id": "b16cdb37-ce15-4ab2-8401-d42b05d123fc", - "queryName": "API Gateway Without Configured Authorizer", + "queryName": "API Gateway without configured authorizer", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "API Gateway REST API should have an API Gateway Authorizer", + "descriptionText": "API Gateway REST APIs must have an API Gateway authorizer configured so that requests are authenticated before reaching backend integrations. Without an authorizer, APIs can be invoked anonymously, increasing the risk of unauthorized access, data exposure, and abuse of backend services.\n\nFor Ansible resources using `community.aws.api_gateway` or `api_gateway`, ensure the API's Swagger/OpenAPI definition—provided via the `swagger_file`, `swagger_dict`, or `swagger_text` property—includes an `x-amazon-apigateway-authorizer` entry in `components.securitySchemes` and that operations reference the authorizer (via `security` at the operation or global level).\n\nResources that omit all three swagger properties, or whose Swagger/OpenAPI content does not contain `x-amazon-apigateway-authorizer`, are flagged as missing an authorizer. Include a valid authorizer definition and reference it from your paths to remediate the finding.\n\nSecure example with an OpenAPI components authorizer and operation-level security:\n\n```yaml\nopenapi: \"3.0.1\"\ncomponents:\n securitySchemes:\n MyLambdaAuthorizer:\n x-amazon-apigateway-authorizer:\n type: token\n authorizerUri: arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:MyAuthFunction/invocations\nsecurity:\n - MyLambdaAuthorizer: []\npaths:\n /resource:\n get:\n security:\n - MyLambdaAuthorizer: []\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/api_gateway_without_configured_authorizer", "platform": "Ansible", "descriptionID": "e7b28671", "cloudProvider": "aws", "cwe": "284", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_api_gateway_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/api_gateway_module.html" } diff --git a/assets/queries/ansible/aws/api_gateway_without_configured_authorizer/test/positive_expected_result.json b/assets/queries/ansible/aws/api_gateway_without_configured_authorizer/test/positive_expected_result.json index 59480dd2..bc99a4f3 100644 --- a/assets/queries/ansible/aws/api_gateway_without_configured_authorizer/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/api_gateway_without_configured_authorizer/test/positive_expected_result.json @@ -1,24 +1,24 @@ [ { - "queryName": "API Gateway Without Configured Authorizer", + "queryName": "API Gateway without configured authorizer", "severity": "MEDIUM", "line": 4, "fileName": "positive1.yaml" }, { - "queryName": "API Gateway Without Configured Authorizer", + "queryName": "API Gateway without configured authorizer", "severity": "MEDIUM", "line": 2, "fileName": "positive2.yaml" }, { - "queryName": "API Gateway Without Configured Authorizer", + "queryName": "API Gateway without configured authorizer", "severity": "MEDIUM", "line": 4, "fileName": "positive3.yaml" }, { - "queryName": "API Gateway Without Configured Authorizer", + "queryName": "API Gateway without configured authorizer", "severity": "MEDIUM", "line": 4, "fileName": "positive4.yaml" diff --git a/assets/queries/ansible/aws/api_gateway_without_ssl_certificate/metadata.json b/assets/queries/ansible/aws/api_gateway_without_ssl_certificate/metadata.json index 4e497d7f..1b2875c1 100644 --- a/assets/queries/ansible/aws/api_gateway_without_ssl_certificate/metadata.json +++ b/assets/queries/ansible/aws/api_gateway_without_ssl_certificate/metadata.json @@ -1,13 +1,13 @@ { "id": "b47b98ab-e481-4a82-8bb1-1ab39fd36e33", - "queryName": "API Gateway Without SSL Certificate", + "queryName": "API Gateway without SSL certificate", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "SSL Client Certificate should be enabled", + "descriptionText": "API Gateway integrations must validate TLS/SSL certificates to ensure backend endpoints are authentic and prevent man-in-the-middle attacks that can expose credentials or sensitive data.\n\nThe `validate_certs` property in Ansible `community.aws.api_gateway` and `api_gateway` tasks must be defined and set to a truthy value (Ansible `yes` or `true`). Resources missing this property or with `validate_certs` set to `no` or `false` are flagged.\n\nIf your backend uses self-signed certificates, prefer adding the CA to a trusted store or using proper certificate management rather than disabling certificate validation.\n\nSecure example Ansible task:\n\n```yaml\n- name: Create API Gateway with TLS validation\n community.aws.api_gateway:\n name: my-api\n state: present\n validate_certs: yes\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/api_gateway_without_ssl_certificate", "platform": "Ansible", "descriptionID": "82608f36", "cloudProvider": "aws", "cwe": "295", - "providerUrl": "https://docs.ansible.com/ansible/2.8/modules/aws_api_gateway_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/api_gateway_module.html" } diff --git a/assets/queries/ansible/aws/api_gateway_without_ssl_certificate/test/positive_expected_result.json b/assets/queries/ansible/aws/api_gateway_without_ssl_certificate/test/positive_expected_result.json index ae68bdf7..312ef63f 100644 --- a/assets/queries/ansible/aws/api_gateway_without_ssl_certificate/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/api_gateway_without_ssl_certificate/test/positive_expected_result.json @@ -1,25 +1,22 @@ [ - { - "queryName": "API Gateway Without SSL Certificate", - "severity": "MEDIUM", - "line": 7 - }, - - { - "queryName": "API Gateway Without SSL Certificate", - "severity": "MEDIUM", - "line": 9 - }, - - { - "queryName": "API Gateway Without SSL Certificate", - "severity": "MEDIUM", - "line": 24 - }, - - { - "queryName": "API Gateway Without SSL Certificate", - "severity": "MEDIUM", - "line": 26 - } + { + "queryName": "API Gateway without SSL certificate", + "severity": "MEDIUM", + "line": 7 + }, + { + "queryName": "API Gateway without SSL certificate", + "severity": "MEDIUM", + "line": 9 + }, + { + "queryName": "API Gateway without SSL certificate", + "severity": "MEDIUM", + "line": 24 + }, + { + "queryName": "API Gateway without SSL certificate", + "severity": "MEDIUM", + "line": 26 + } ] diff --git a/assets/queries/ansible/aws/api_gateway_without_waf/metadata.json b/assets/queries/ansible/aws/api_gateway_without_waf/metadata.json index 6e06bbc6..5f7f2eee 100644 --- a/assets/queries/ansible/aws/api_gateway_without_waf/metadata.json +++ b/assets/queries/ansible/aws/api_gateway_without_waf/metadata.json @@ -3,7 +3,7 @@ "queryName": "API Gateway without WAF", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "API Gateway should have WAF (Web Application Firewall) enabled", + "descriptionText": "API Gateway stages should be protected by an AWS WAF Web ACL to block common web threats (for example SQL injection, XSS, and malicious request patterns) before they reach backend services. Ensure your IaC defines a WAFv2 WebACLAssociation that links a Web ACL to the API Gateway stage. The association's `ResourceArn` (or Terraform `resource_arn`) must reference the API Gateway stage ARN (for REST APIs: arn:aws:apigateway:::/restapis//stages/).\n\nThis rule checks Ansible API Gateway resources (modules `community.aws.api_gateway` or `api_gateway`) and expects a corresponding WAFv2 association (for example, `community.aws.wafv2_resources`/`wafv2_resources`) that targets the same stage. Resources missing a WebACLAssociation or where `ResourceArn` does not point to the stage are flagged.\n\nSecure CloudFormation example:\n\n```yaml\nWebACLAssociation:\n Type: AWS::WAFv2::WebACLAssociation\n Properties:\n ResourceArn: !Sub \"arn:aws:apigateway:${AWS::Region}::/restapis/${ApiId}/stages/${StageName}\"\n WebACLArn: !Ref MyWebACL\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/api_gateway_without_waf", "platform": "Ansible", "descriptionID": "8e789062", diff --git a/assets/queries/ansible/aws/api_gateway_xray_disabled/metadata.json b/assets/queries/ansible/aws/api_gateway_xray_disabled/metadata.json index 450fa1c6..41d7f650 100644 --- a/assets/queries/ansible/aws/api_gateway_xray_disabled/metadata.json +++ b/assets/queries/ansible/aws/api_gateway_xray_disabled/metadata.json @@ -1,14 +1,14 @@ { "id": "2059155b-27fd-441e-b616-6966c468561f", - "queryName": "API Gateway X-Ray Disabled", + "queryName": "API Gateway X-Ray disabled", "severity": "LOW", "category": "Observability", - "descriptionText": "API Gateway should have X-Ray Tracing enabled", + "descriptionText": "API Gateway resources should have AWS X-Ray tracing enabled to provide end-to-end request visibility and support detection of anomalous or malicious activity. For Ansible tasks that use the `community.aws.api_gateway` or `api_gateway` modules, set the `tracing_enabled` property to `true`. Tasks missing `tracing_enabled` or with `tracing_enabled: false` are flagged because they disable observability needed for effective incident response and root-cause analysis.\n\nSecure Ansible task example:\n\n```yaml\n- name: Configure API Gateway with X-Ray tracing\n community.aws.api_gateway:\n name: my-api\n tracing_enabled: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/api_gateway_xray_disabled", "platform": "Ansible", "descriptionID": "57da10ee", "cloudProvider": "aws", "cwe": "778", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_api_gateway_module.html#parameter-tracing_enabled" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/api_gateway_module.html#parameter-tracing_enabled" } diff --git a/assets/queries/ansible/aws/api_gateway_xray_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/api_gateway_xray_disabled/test/positive_expected_result.json index 9c13d8cd..500011bc 100644 --- a/assets/queries/ansible/aws/api_gateway_xray_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/api_gateway_xray_disabled/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "API Gateway X-Ray Disabled", + "queryName": "API Gateway X-Ray disabled", "severity": "LOW", "line": 9 }, { - "queryName": "API Gateway X-Ray Disabled", + "queryName": "API Gateway X-Ray disabled", "severity": "LOW", "line": 13 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/authentication_without_mfa/metadata.json b/assets/queries/ansible/aws/authentication_without_mfa/metadata.json index 07886e1b..fe6d6bd0 100644 --- a/assets/queries/ansible/aws/authentication_without_mfa/metadata.json +++ b/assets/queries/ansible/aws/authentication_without_mfa/metadata.json @@ -1,9 +1,9 @@ { "id": "eee107f9-b3d8-45d3-b9c6-43b5a7263ce1", - "queryName": "Authentication Without MFA", + "queryName": "Authentication without MFA", "severity": "LOW", "category": "Access Control", - "descriptionText": "Users should authenticate with MFA (Multi-factor Authentication) to ensure an extra layer of protection when authenticating", + "descriptionText": "Assume-role operations should require multi-factor authentication (MFA) to provide a second authentication factor and reduce the risk that compromised credentials or automated workflows can silently assume privileged roles.\n\nIn Ansible, tasks using the `amazon.aws.sts_assume_role` or `sts_assume_role` modules must define both `mfa_serial_number` (the IAM MFA device ARN or serial) and `mfa_token` (the one-time MFA code). Tasks missing either property or with those properties undefined are flagged.\n\nSupply `mfa_token` securely at runtime (for example via Ansible Vault, environment variables, or an interactive prompt) and ensure `mfa_serial_number` references the correct MFA device ARN (for example, `arn:aws:iam::123456789012:mfa/username`).", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/authentication_without_mfa", "platform": "Ansible", "descriptionID": "36040ce0", diff --git a/assets/queries/ansible/aws/authentication_without_mfa/test/positive_expected_result.json b/assets/queries/ansible/aws/authentication_without_mfa/test/positive_expected_result.json index 27bfe61d..c4d858bd 100644 --- a/assets/queries/ansible/aws/authentication_without_mfa/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/authentication_without_mfa/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "Authentication Without MFA", + "queryName": "Authentication without MFA", "severity": "LOW", "line": 2 }, { - "queryName": "Authentication Without MFA", + "queryName": "Authentication without MFA", "severity": "LOW", "line": 9 }, { - "queryName": "Authentication Without MFA", + "queryName": "Authentication without MFA", "severity": "LOW", "line": 9 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/auto_scaling_group_with_no_associated_elb/metadata.json b/assets/queries/ansible/aws/auto_scaling_group_with_no_associated_elb/metadata.json index edf983d6..a1a5cb15 100644 --- a/assets/queries/ansible/aws/auto_scaling_group_with_no_associated_elb/metadata.json +++ b/assets/queries/ansible/aws/auto_scaling_group_with_no_associated_elb/metadata.json @@ -1,13 +1,13 @@ { "id": "050f085f-a8db-4072-9010-2cca235cc02f", - "queryName": "Auto Scaling Group With No Associated ELB", + "queryName": "Auto Scaling Group with no associated ELB", "severity": "MEDIUM", "category": "Availability", - "descriptionText": "AWS Auto Scaling Groups must have associated ELBs to ensure high availability and improve application performance. This means the attribute 'load_balancers' must be defined and not empty.", + "descriptionText": "Auto Scaling Groups must be associated with a load balancer so new instances receive traffic and health checks can detect and replace unhealthy instances. Without a load balancer, instances may not serve requests, and application availability and scaling behavior can be impacted.\n\nFor Ansible `autoscaling_group` tasks (modules `amazon.aws.autoscaling_group` and `autoscaling_group`), the `load_balancers` property must be defined and set to a non-empty list of Classic ELB names. Tasks missing the `load_balancers` property or with `load_balancers: []` are flagged. If you use Application Load Balancers with target groups instead of Classic ELBs, configure `target_group_arns` accordingly—this rule only validates the `load_balancers` attribute.\n\nSecure example:\n\n```yaml\n- name: Create Auto Scaling Group with ELB\n amazon.aws.autoscaling_group:\n name: my-asg\n launch_template: my-launch-template\n min_size: 2\n max_size: 5\n load_balancers:\n - my-classic-elb\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/auto_scaling_group_with_no_associated_elb", "platform": "Ansible", "descriptionID": "57b5fbca", "cloudProvider": "aws", "cwe": "400", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/ec2_asg_module.html#parameter-load_balancers" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/autoscaling_group_module.html#parameter-load_balancers" } diff --git a/assets/queries/ansible/aws/auto_scaling_group_with_no_associated_elb/test/positive_expected_result.json b/assets/queries/ansible/aws/auto_scaling_group_with_no_associated_elb/test/positive_expected_result.json index ba9b8c74..31981c04 100644 --- a/assets/queries/ansible/aws/auto_scaling_group_with_no_associated_elb/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/auto_scaling_group_with_no_associated_elb/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Auto Scaling Group With No Associated ELB", + "queryName": "Auto Scaling Group with no associated ELB", "severity": "MEDIUM", "line": 4, "fileName": "positive1.yaml" }, { - "queryName": "Auto Scaling Group With No Associated ELB", + "queryName": "Auto Scaling Group with no associated ELB", "severity": "MEDIUM", "line": 2, "fileName": "positive2.yaml" diff --git a/assets/queries/ansible/aws/automatic_minor_upgrades_disabled/metadata.json b/assets/queries/ansible/aws/automatic_minor_upgrades_disabled/metadata.json index cb0b1e1c..b7a1a850 100644 --- a/assets/queries/ansible/aws/automatic_minor_upgrades_disabled/metadata.json +++ b/assets/queries/ansible/aws/automatic_minor_upgrades_disabled/metadata.json @@ -1,13 +1,13 @@ { "id": "857f8808-e96a-4ba8-a9b7-f2d4ec6cad94", - "queryName": "Automatic Minor Upgrades Disabled", + "queryName": "Automatic minor upgrades disabled", "severity": "LOW", "category": "Best Practices", - "descriptionText": "RDS instance should have automatic minor upgrades enabled, which means the attribute 'auto_minor_version_upgrade' must be set to true.", + "descriptionText": "RDS instances should have automatic minor engine upgrades enabled so critical security patches and bug fixes are applied promptly, preventing exposure to known vulnerabilities or compliance drift.\n\nFor Ansible RDS tasks using the `amazon.aws.rds_instance` or `rds_instance` modules, the `auto_minor_version_upgrade` property must be defined and set to `true`. Tasks that omit this property or set `auto_minor_version_upgrade: false` are flagged. Enabling this setting ensures minor engine patches are applied automatically during the instance's maintenance window.\n\nSecure Ansible example:\n\n```yaml\n- name: create RDS instance with automatic minor upgrades\n amazon.aws.rds_instance:\n name: mydb\n engine: postgres\n instance_type: db.t3.medium\n auto_minor_version_upgrade: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/automatic_minor_upgrades_disabled", "platform": "Ansible", "descriptionID": "7734a8b1", "cloudProvider": "aws", "cwe": "665", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/rds_instance_module.html#parameter-auto_minor_version_upgrade" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-auto_minor_version_upgrade" } diff --git a/assets/queries/ansible/aws/automatic_minor_upgrades_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/automatic_minor_upgrades_disabled/test/positive_expected_result.json index 2cff287e..5fc8d93c 100644 --- a/assets/queries/ansible/aws/automatic_minor_upgrades_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/automatic_minor_upgrades_disabled/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "Automatic Minor Upgrades Disabled", + "queryName": "Automatic minor upgrades disabled", "severity": "LOW", "line": 10 }, { - "queryName": "Automatic Minor Upgrades Disabled", + "queryName": "Automatic minor upgrades disabled", "severity": "LOW", "line": 12 } diff --git a/assets/queries/ansible/aws/aws_password_policy_with_unchangeable_passwords/metadata.json b/assets/queries/ansible/aws/aws_password_policy_with_unchangeable_passwords/metadata.json index 754ab62b..bdbfad90 100644 --- a/assets/queries/ansible/aws/aws_password_policy_with_unchangeable_passwords/metadata.json +++ b/assets/queries/ansible/aws/aws_password_policy_with_unchangeable_passwords/metadata.json @@ -1,14 +1,14 @@ { "id": "e28ceb92-d588-4166-aac5-766c8f5b7472", - "queryName": "AWS Password Policy With Unchangeable Passwords", + "queryName": "AWS password policy with unchangeable passwords", "severity": "LOW", "category": "Insecure Configurations", - "descriptionText": "Unchangeable passwords in AWS password policy", + "descriptionText": "IAM password policies must permit users to change their own passwords so compromised, expired, or weak credentials can be rotated and account recovery workflows remain effective. In Ansible tasks using the `amazon.aws.iam_password_policy` or `iam_password_policy` modules, the boolean property controlling this must be defined and set to `true` — either `allow_pw_change` or `allow_password_change` depending on module version.\n\nTasks that omit these properties or set them to `false`/`no` are flagged because disabling password changes prevents credential rotation and hampers incident response and account hygiene.\n\nSecure Ansible example:\n\n```yaml\n- name: Ensure IAM password policy allows user password changes\n amazon.aws.iam_password_policy:\n allow_password_change: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/aws_password_policy_with_unchangeable_passwords", "platform": "Ansible", "descriptionID": "5a7cf92f", "cloudProvider": "aws", "cwe": "620", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_password_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_password_policy_module.html" } diff --git a/assets/queries/ansible/aws/aws_password_policy_with_unchangeable_passwords/test/positive_expected_result.json b/assets/queries/ansible/aws/aws_password_policy_with_unchangeable_passwords/test/positive_expected_result.json index 85872501..3e692f4e 100644 --- a/assets/queries/ansible/aws/aws_password_policy_with_unchangeable_passwords/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/aws_password_policy_with_unchangeable_passwords/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "AWS Password Policy With Unchangeable Passwords", + "queryName": "AWS password policy with unchangeable passwords", "severity": "LOW", "line": 9 }, { - "queryName": "AWS Password Policy With Unchangeable Passwords", + "queryName": "AWS password policy with unchangeable passwords", "severity": "LOW", "line": 21 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/batch_job_definition_with_privileged_container_properties/metadata.json b/assets/queries/ansible/aws/batch_job_definition_with_privileged_container_properties/metadata.json index e0945138..ecf4a4c9 100644 --- a/assets/queries/ansible/aws/batch_job_definition_with_privileged_container_properties/metadata.json +++ b/assets/queries/ansible/aws/batch_job_definition_with_privileged_container_properties/metadata.json @@ -1,13 +1,13 @@ { "id": "defe5b18-978d-4722-9325-4d1975d3699f", - "queryName": "Batch Job Definition With Privileged Container Properties", + "queryName": "Batch job definition with privileged container properties", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "Batch Job Definition should not have Privileged Container Properties", + "descriptionText": "Batch job definitions must not enable privileged containers. Privileged mode weakens container isolation and can allow containers to access host resources or escalate privileges, increasing the risk of host compromise and lateral movement.\n\nFor Ansible, tasks using the `community.aws.batch_job_definition` or `aws_batch_job_definition` modules must not set the `privileged` parameter to `true`. The `privileged` setting should be omitted or explicitly set to `false` in the job definition's container properties. Resources with `privileged: true` are flagged. Only enable privileged mode when absolutely required and after applying additional host hardening, access controls, and justification.\n\nSecure example:\n\n```yaml\n- name: Register Batch job definition without privileged mode\n community.aws.batch_job_definition:\n name: my-batch-job\n container_properties:\n image: my-image:latest\n vcpus: 1\n memory: 1024\n privileged: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/batch_job_definition_with_privileged_container_properties", "platform": "Ansible", "descriptionID": "9683e81a", "cloudProvider": "aws", "cwe": "250", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_batch_job_definition_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/batch_job_definition_module.html" } diff --git a/assets/queries/ansible/aws/batch_job_definition_with_privileged_container_properties/test/positive_expected_result.json b/assets/queries/ansible/aws/batch_job_definition_with_privileged_container_properties/test/positive_expected_result.json index f1d91515..b1dfc0b5 100644 --- a/assets/queries/ansible/aws/batch_job_definition_with_privileged_container_properties/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/batch_job_definition_with_privileged_container_properties/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { "line": 9, - "queryName": "Batch Job Definition With Privileged Container Properties", + "queryName": "Batch job definition with privileged container properties", "severity": "HIGH" } ] diff --git a/assets/queries/ansible/aws/ca_certificate_identifier_is_outdated/metadata.json b/assets/queries/ansible/aws/ca_certificate_identifier_is_outdated/metadata.json index 2a8a4eab..24f67286 100644 --- a/assets/queries/ansible/aws/ca_certificate_identifier_is_outdated/metadata.json +++ b/assets/queries/ansible/aws/ca_certificate_identifier_is_outdated/metadata.json @@ -1,14 +1,14 @@ { "id": "5eccd62d-8b4d-46d3-83ea-1879f3cbd3ce", - "queryName": "CA Certificate Identifier Is Outdated", + "queryName": "CA certificate identifier is outdated", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "The CA certificate Identifier must be 'rds-ca-2019'.", + "descriptionText": "RDS instances must specify a CA certificate identifier so the database uses a known AWS CA for TLS connections and avoids broken or insecure certificate chains during CA rotations. For Ansible RDS resources (modules `amazon.aws.rds_instance` and `rds_instance`), the `ca_certificate_identifier` property must be defined and set to `rds-ca-2019`. Resources missing this property or specifying a different value are flagged. Update the value if AWS publishes a newer CA identifier.\n\nSecure Ansible task example:\n\n```yaml\n- name: create RDS instance with CA\n amazon.aws.rds_instance:\n db_instance_identifier: my-db\n engine: mysql\n instance_class: db.t3.medium\n allocated_storage: 20\n username: admin\n password: secret\n ca_certificate_identifier: rds-ca-2019\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ca_certificate_identifier_is_outdated", "platform": "Ansible", "descriptionID": "d92aa922", "cloudProvider": "aws", "cwe": "295", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/rds_instance_module.html#parameter-ca_certificate_identifier" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-ca_certificate_identifier" } diff --git a/assets/queries/ansible/aws/ca_certificate_identifier_is_outdated/test/positive_expected_result.json b/assets/queries/ansible/aws/ca_certificate_identifier_is_outdated/test/positive_expected_result.json index 222b4d17..0379db79 100644 --- a/assets/queries/ansible/aws/ca_certificate_identifier_is_outdated/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ca_certificate_identifier_is_outdated/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "CA Certificate Identifier Is Outdated", + "queryName": "CA certificate identifier is outdated", "severity": "MEDIUM", "line": 10 }, { - "queryName": "CA Certificate Identifier Is Outdated", + "queryName": "CA certificate identifier is outdated", "severity": "MEDIUM", "line": 12 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/cdn_configuration_is_missing/metadata.json b/assets/queries/ansible/aws/cdn_configuration_is_missing/metadata.json index d8ed8300..282e0cf3 100644 --- a/assets/queries/ansible/aws/cdn_configuration_is_missing/metadata.json +++ b/assets/queries/ansible/aws/cdn_configuration_is_missing/metadata.json @@ -1,9 +1,9 @@ { "id": "b25398a2-0625-4e61-8e4d-a1bb23905bf6", - "queryName": "CDN Configuration Is Missing", + "queryName": "CDN configuration is missing", "severity": "LOW", "category": "Best Practices", - "descriptionText": "Content Delivery Network (CDN) service is used within an AWS account to secure and accelerate the delivery of websites. The use of a CDN can provide a layer of security between your origin content and the destination.", + "descriptionText": "CloudFront distributions must be enabled and include at least one origin so traffic is routed through the CDN. This ensures requests benefit from CloudFront protections such as caching, TLS termination, WAF rules, and DDoS mitigation. A disabled or origin-less distribution can cause traffic to bypass the CDN and expose origin servers.\n\nThis rule inspects Ansible tasks using the `community.aws.cloudfront_distribution` or `cloudfront_distribution` modules. It requires the `enabled` property to be present and set to `true`, and the `origins` property to be defined with at least one origin entry. Tasks missing `enabled` or `origins`, or with `enabled: false`, are flagged as misconfigured.\n\nSecure example:\n\n```yaml\n- name: create cloudfront distribution\n community.aws.cloudfront_distribution:\n enabled: true\n comment: \"Secure distribution\"\n origins:\n - id: my-origin\n domain_name: origin.example.com\n custom_origin_config:\n origin_protocol_policy: https-only\n http_port: 80\n https_port: 443\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cdn_configuration_is_missing", "platform": "Ansible", "descriptionID": "c8ae0f38", diff --git a/assets/queries/ansible/aws/cdn_configuration_is_missing/test/positive_expected_result.json b/assets/queries/ansible/aws/cdn_configuration_is_missing/test/positive_expected_result.json index b448a833..edfb520a 100644 --- a/assets/queries/ansible/aws/cdn_configuration_is_missing/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cdn_configuration_is_missing/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "CDN Configuration Is Missing", + "queryName": "CDN configuration is missing", "severity": "LOW", "line": 2 }, { - "queryName": "CDN Configuration Is Missing", + "queryName": "CDN configuration is missing", "severity": "LOW", "line": 23 } diff --git a/assets/queries/ansible/aws/certificate_has_expired/metadata.json b/assets/queries/ansible/aws/certificate_has_expired/metadata.json index fd3c4b86..8fc9bcce 100644 --- a/assets/queries/ansible/aws/certificate_has_expired/metadata.json +++ b/assets/queries/ansible/aws/certificate_has_expired/metadata.json @@ -1,13 +1,13 @@ { "id": "5a443297-19d4-4381-9e5b-24faf947ec22", - "queryName": "Certificate Has Expired", + "queryName": "Certificate has expired", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "Expired SSL/TLS certificates should be removed", + "descriptionText": "Expired SSL/TLS certificates cause service outages by breaking TLS handshakes and undermine trust in encrypted connections. This can result in failed client connections and compliance or security issues. In Ansible, tasks using the `community.aws.acm_certificate` module must reference a certificate whose `certificate.expiration_date` is a future date. This rule flags `community.aws.acm_certificate` tasks where `certificate.expiration_date` is in the past. Renew or replace any expired certificates—for example, request a new ACM certificate or update the task to point to a renewed certificate—so `certificate.expiration_date` reflects a valid future date.", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/certificate_has_expired", "platform": "Ansible", "descriptionID": "3f6a85e8", "cloudProvider": "aws", "cwe": "298", - "providerUrl": "https://docs.ansible.com/ansible/2.10/collections/community/aws/aws_acm_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/acm_certificate_module.html" } diff --git a/assets/queries/ansible/aws/certificate_has_expired/test/positive_expected_result.json b/assets/queries/ansible/aws/certificate_has_expired/test/positive_expected_result.json index 58dbbac7..7e0f4cbb 100644 --- a/assets/queries/ansible/aws/certificate_has_expired/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/certificate_has_expired/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Certificate Has Expired", + "queryName": "Certificate has expired", "severity": "MEDIUM", "line": 3 } diff --git a/assets/queries/ansible/aws/certificate_rsa_key_bytes_lower_than_256/metadata.json b/assets/queries/ansible/aws/certificate_rsa_key_bytes_lower_than_256/metadata.json index 70cb97c6..40657329 100644 --- a/assets/queries/ansible/aws/certificate_rsa_key_bytes_lower_than_256/metadata.json +++ b/assets/queries/ansible/aws/certificate_rsa_key_bytes_lower_than_256/metadata.json @@ -1,13 +1,13 @@ { "id": "d5ec2080-340a-4259-b885-f833c4ea6a31", - "queryName": "Certificate RSA Key Bytes Lower Than 256", + "queryName": "Certificate RSA key bytes lower than 256", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "The certificate should use a RSA key with a length equal to or higher than 256 bytes", + "descriptionText": "Certificates must use sufficiently strong RSA keys to prevent cryptographic compromise. RSA keys smaller than 2048 bits can be factored with modern compute, enabling certificate impersonation and decryption of TLS traffic.\n\nFor Ansible tasks using the `community.aws.acm_certificate` module, ensure the `certificate.rsa_key_bytes` property is defined and set to at least `256` (bytes), which corresponds to 2048 bits. Resources missing this property or with `rsa_key_bytes < 256` are flagged as insecure. Larger values (for example, `rsa_key_bytes: 512` for 4096-bit keys) are acceptable.\n\nSecure example:\n\n```yaml\n- name: Request ACM certificate with 2048-bit RSA key\n community.aws.acm_certificate:\n name: example-cert\n certificate:\n rsa_key_bytes: 256\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/certificate_rsa_key_bytes_lower_than_256", "platform": "Ansible", "descriptionID": "97dc7eba", "cloudProvider": "aws", "cwe": "295", - "providerUrl": "https://docs.ansible.com/ansible/2.10/collections/community/aws/aws_acm_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/acm_certificate_module.html" } diff --git a/assets/queries/ansible/aws/certificate_rsa_key_bytes_lower_than_256/test/positive_expected_result.json b/assets/queries/ansible/aws/certificate_rsa_key_bytes_lower_than_256/test/positive_expected_result.json index 26c7b277..7eb44422 100644 --- a/assets/queries/ansible/aws/certificate_rsa_key_bytes_lower_than_256/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/certificate_rsa_key_bytes_lower_than_256/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Certificate RSA Key Bytes Lower Than 256", + "queryName": "Certificate RSA key bytes lower than 256", "severity": "MEDIUM", "line": 3 } diff --git a/assets/queries/ansible/aws/cloudfront_logging_disabled/metadata.json b/assets/queries/ansible/aws/cloudfront_logging_disabled/metadata.json index 445e3479..c500c190 100644 --- a/assets/queries/ansible/aws/cloudfront_logging_disabled/metadata.json +++ b/assets/queries/ansible/aws/cloudfront_logging_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "d31cb911-bf5b-4eb6-9fc3-16780c77c7bd", - "queryName": "CloudFront Logging Disabled", + "queryName": "CloudFront logging disabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "AWS CloudFront distributions should have logging enabled to collect all viewer requests, which means the attribute 'logging' should be defined with 'enabled' set to true", + "descriptionText": "CloudFront distributions must have access logging enabled to record viewer requests for incident investigation and auditing. Without logs, you cannot reliably detect abuse, investigate incidents, or meet audit requirements.\n\nFor Ansible CloudFront distribution resources (modules `community.aws.cloudfront_distribution` and `cloudfront_distribution`), the `logging` property must be defined and `logging.enabled` set to `true`. Tasks missing `logging` or with `logging.enabled: false` are flagged. Ensure a valid S3 bucket is specified in `logging.bucket` as the log destination.\n\nSecure configuration example:\n\n```yaml\n- name: Create CloudFront distribution with logging enabled\n community.aws.cloudfront_distribution:\n origin:\n - id: my-origin\n domain_name: origin.example.com\n enabled: yes\n logging:\n enabled: true\n bucket: my-log-bucket.s3.amazonaws.com\n include_cookies: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cloudfront_logging_disabled", "platform": "Ansible", "descriptionID": "1bfc2dfd", diff --git a/assets/queries/ansible/aws/cloudfront_logging_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/cloudfront_logging_disabled/test/positive_expected_result.json index 45d5f284..b66c7902 100644 --- a/assets/queries/ansible/aws/cloudfront_logging_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cloudfront_logging_disabled/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "CloudFront Logging Disabled", + "queryName": "CloudFront logging disabled", "severity": "MEDIUM", "line": 2 }, { - "queryName": "CloudFront Logging Disabled", + "queryName": "CloudFront logging disabled", "severity": "MEDIUM", "line": 62 } diff --git a/assets/queries/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2/metadata.json b/assets/queries/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2/metadata.json index f89166b2..ae61d5b3 100644 --- a/assets/queries/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2/metadata.json +++ b/assets/queries/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2/metadata.json @@ -1,9 +1,9 @@ { "id": "d0c13053-d2c8-44a6-95da-d592996e9e67", - "queryName": "CloudFront Without Minimum Protocol TLS 1.2", + "queryName": "CloudFront without minimum protocol TLS 1.2", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "CloudFront Minimum Protocol version should be at least TLS 1.2", + "descriptionText": "CloudFront distributions must enforce modern TLS for viewer connections to prevent interception and protocol downgrades. The Ansible `community.aws.cloudfront_distribution` (or `cloudfront_distribution`) resource must include a `viewer_certificate` block with `minimum_protocol_version` set to a TLS 1.2 variant (for example, `TLSv1.2_2018` or `TLSv1.2_2019`).\n\nTasks that omit `viewer_certificate` or specify a `minimum_protocol_version` that is not a TLS 1.2 variant are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create CloudFront distribution with TLS 1.2 minimum\n community.aws.cloudfront_distribution:\n state: present\n enabled: yes\n origins:\n - id: myOrigin\n domain_name: origin.example.com\n viewer_certificate:\n acm_certificate_arn: arn:aws:acm:us-east-1:123456789012:certificate/abcd-ef01-2345-6789\n ssl_support_method: sni-only\n minimum_protocol_version: TLSv1.2_2018\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2", "platform": "Ansible", "descriptionID": "b0a58f2f", diff --git a/assets/queries/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2/test/positive_expected_result.json b/assets/queries/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2/test/positive_expected_result.json index f5d18279..b954e2ff 100644 --- a/assets/queries/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "CloudFront Without Minimum Protocol TLS 1.2", + "queryName": "CloudFront without minimum protocol TLS 1.2", "severity": "MEDIUM", "line": 18 }, { - "queryName": "CloudFront Without Minimum Protocol TLS 1.2", + "queryName": "CloudFront without minimum protocol TLS 1.2", "severity": "MEDIUM", "line": 37 }, { "line": 40, - "queryName": "CloudFront Without Minimum Protocol TLS 1.2", + "queryName": "CloudFront without minimum protocol TLS 1.2", "severity": "MEDIUM" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/cloudfront_without_waf/metadata.json b/assets/queries/ansible/aws/cloudfront_without_waf/metadata.json index 25352977..aab3833e 100644 --- a/assets/queries/ansible/aws/cloudfront_without_waf/metadata.json +++ b/assets/queries/ansible/aws/cloudfront_without_waf/metadata.json @@ -1,9 +1,9 @@ { "id": "22c80725-e390-4055-8d14-a872230f6607", - "queryName": "CloudFront Without WAF", + "queryName": "CloudFront without WAF", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "All AWS CloudFront distributions should be integrated with the Web Application Firewall (AWS WAF) service", + "descriptionText": "CloudFront distributions must be associated with an AWS WAF Web ACL to filter malicious HTTP traffic and reduce the risk of application-layer attacks such as SQL injection, cross-site scripting, and automated bot abuse.\n\nFor Ansible tasks using the `community.aws.cloudfront_distribution` or `cloudfront_distribution` module, the `web_acl_id` property must be defined and set to the ARN of a WAFv2 Web ACL (global scope). This rule flags distributions where `web_acl_id` is missing or undefined. Ensure the attached WAFv2 Web ACL ARN is compatible with CloudFront.\n\nSecure example (Ansible):\n\n```yaml\n- name: create cloudfront distribution with WAF\n community.aws.cloudfront_distribution:\n state: present\n alias:\n - example.com\n web_acl_id: arn:aws:wafv2:global:123456789012:regional/webacl/example-web-acl/abcd1234-ef56-7890-gh12-ijklmnopqrst\n # other required distribution properties...\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cloudfront_without_waf", "platform": "Ansible", "descriptionID": "7fd7e5c0", diff --git a/assets/queries/ansible/aws/cloudfront_without_waf/test/positive_expected_result.json b/assets/queries/ansible/aws/cloudfront_without_waf/test/positive_expected_result.json index f4dbbfce..12a0a063 100644 --- a/assets/queries/ansible/aws/cloudfront_without_waf/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cloudfront_without_waf/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "CloudFront Without WAF", + "queryName": "CloudFront without WAF", "severity": "MEDIUM", "line": 2 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/cloudtrail_log_file_validation_disabled/metadata.json b/assets/queries/ansible/aws/cloudtrail_log_file_validation_disabled/metadata.json index 56c8df42..9bdfd51e 100644 --- a/assets/queries/ansible/aws/cloudtrail_log_file_validation_disabled/metadata.json +++ b/assets/queries/ansible/aws/cloudtrail_log_file_validation_disabled/metadata.json @@ -1,13 +1,13 @@ { "id": "4d8681a2-3d30-4c89-8070-08acd142748e", - "queryName": "CloudTrail Log File Validation Disabled", + "queryName": "CloudTrail log file validation disabled", "severity": "LOW", "category": "Observability", - "descriptionText": "CloudTrail log file validation should be enabled to determine whether a log file has not been tampered", + "descriptionText": "CloudTrail log file validation must be enabled to detect tampering of delivered log files and preserve the integrity of audit data used for incident response and compliance.\n\nFor Ansible tasks using the `amazon.aws.cloudtrail` or `cloudtrail` module, one of the properties `enable_log_file_validation` or `log_file_validation_enabled` must be defined and set to `true` (or `yes`). Resources missing both properties or with these properties set to `false`, `no`, or any non-`true` value are flagged as insecure.\n\nSecure Ansible example:\n\n```yaml\n- name: Create CloudTrail with log file validation enabled\n amazon.aws.cloudtrail:\n name: my-trail\n s3_bucket_name: my-trail-bucket\n enable_log_file_validation: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cloudtrail_log_file_validation_disabled", "platform": "Ansible", "descriptionID": "04302074", "cloudProvider": "aws", "cwe": "778", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/cloudtrail_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html" } diff --git a/assets/queries/ansible/aws/cloudtrail_log_file_validation_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/cloudtrail_log_file_validation_disabled/test/positive_expected_result.json index 4e971a4b..58ca71ce 100644 --- a/assets/queries/ansible/aws/cloudtrail_log_file_validation_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cloudtrail_log_file_validation_disabled/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "CloudTrail Log File Validation Disabled", + "queryName": "CloudTrail log file validation disabled", "severity": "LOW", "line": 2 }, { - "queryName": "CloudTrail Log File Validation Disabled", + "queryName": "CloudTrail log file validation disabled", "severity": "LOW", "line": 21 } diff --git a/assets/queries/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms/metadata.json b/assets/queries/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms/metadata.json index 9797e53c..933882dc 100644 --- a/assets/queries/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms/metadata.json +++ b/assets/queries/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms/metadata.json @@ -1,13 +1,13 @@ { "id": "f5587077-3f57-4370-9b4e-4eb5b1bac85b", - "queryName": "CloudTrail Log Files Not Encrypted With KMS", + "queryName": "CloudTrail log files not encrypted with KMS", "severity": "LOW", "category": "Encryption", - "descriptionText": "Logs delivered by CloudTrail should be encrypted using KMS to increase security of your CloudTrail", + "descriptionText": "CloudTrail log deliveries must be encrypted with an AWS KMS customer-managed key to protect audit logs at rest and ensure strict key access control, rotation, and usage auditing. In Ansible tasks using the `amazon.aws.cloudtrail` or `cloudtrail` module, the `kms_key_id` parameter must be defined and set to a KMS key ARN or alias (for example `arn:aws:kms:region:account-id:key/KEY-ID` or `alias/my-key`).\n\nTasks missing `kms_key_id` are flagged. Without a customer-managed key, you lose control over key access, rotation, and usage auditing.\n\nSecure configuration example:\n\n```yaml\n- name: Create CloudTrail with KMS encryption\n amazon.aws.cloudtrail:\n name: my-trail\n s3_bucket_name: my-cloudtrail-bucket\n kms_key_id: arn:aws:kms:us-east-1:123456789012:key/abcd1234-ef56-7890-abcd-EXAMPLE\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms", "platform": "Ansible", "descriptionID": "d3b81fde", "cloudProvider": "aws", "cwe": "311", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/cloudtrail_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html" } diff --git a/assets/queries/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms/test/positive_expected_result.json b/assets/queries/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms/test/positive_expected_result.json index f8ba4b80..18f75f58 100644 --- a/assets/queries/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "CloudTrail Log Files Not Encrypted With KMS", + "queryName": "CloudTrail log files not encrypted with KMS", "severity": "LOW", "line": 2 } diff --git a/assets/queries/ansible/aws/cloudtrail_logging_disabled/metadata.json b/assets/queries/ansible/aws/cloudtrail_logging_disabled/metadata.json index 5607f7e3..f7f9b09c 100644 --- a/assets/queries/ansible/aws/cloudtrail_logging_disabled/metadata.json +++ b/assets/queries/ansible/aws/cloudtrail_logging_disabled/metadata.json @@ -1,14 +1,14 @@ { "id": "d4a73c49-cbaa-4c6f-80ee-d6ef5a3a26f5", - "queryName": "CloudTrail Logging Disabled", + "queryName": "CloudTrail logging disabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Checks if logging is enabled for CloudTrail.", + "descriptionText": "CloudTrail logging must be enabled to record AWS API activity for detection, auditing, and forensic investigations, and to meet compliance requirements. Disabling logging can allow malicious or accidental changes to go undetected.\n\nIn Ansible, tasks using the `amazon.aws.cloudtrail` or `cloudtrail` modules must have the `enable_logging` property set to `true`. This rule flags tasks where `enable_logging` is explicitly set to `false`. Ensure the property is present and set to `true` to enable delivery of management events and logs. Example secure Ansible task:\n\n```yaml\n- name: Ensure CloudTrail logging is enabled\n amazon.aws.cloudtrail:\n name: my-trail\n s3_bucket_name: my-cloudtrail-bucket\n enable_logging: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cloudtrail_logging_disabled", "platform": "Ansible", "descriptionID": "c29f6786", "cloudProvider": "aws", "cwe": "778", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/cloudtrail_module.html#parameter-enable_logging" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html#parameter-enable_logging" } diff --git a/assets/queries/ansible/aws/cloudtrail_logging_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/cloudtrail_logging_disabled/test/positive_expected_result.json index 8e57b93d..dbea991e 100644 --- a/assets/queries/ansible/aws/cloudtrail_logging_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cloudtrail_logging_disabled/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "CloudTrail Logging Disabled", + "queryName": "CloudTrail logging disabled", "severity": "MEDIUM", "line": 5 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/cloudtrail_multi_region_disabled/metadata.json b/assets/queries/ansible/aws/cloudtrail_multi_region_disabled/metadata.json index ba6d0480..d7896e90 100644 --- a/assets/queries/ansible/aws/cloudtrail_multi_region_disabled/metadata.json +++ b/assets/queries/ansible/aws/cloudtrail_multi_region_disabled/metadata.json @@ -1,14 +1,14 @@ { "id": "6ad087d7-a509-4b20-b853-9ef6f5ebaa98", - "queryName": "CloudTrail Multi Region Disabled", + "queryName": "CloudTrail multi-region is disabled", "severity": "LOW", "category": "Observability", - "descriptionText": "CloudTrail multi region should be enabled, which means attribute 'is_multi_region_trail' should be set to true", + "descriptionText": "CloudTrail must be configured as a multi-region trail so that API activity across all AWS regions is captured. This ensures comprehensive auditing and timely incident response. Without multi-region logging, cross-region activity can be missed, hindering detection, forensics, and compliance.\n\nFor Ansible CloudTrail resources (modules `amazon.aws.cloudtrail` or `cloudtrail`), the `is_multi_region_trail` property must be defined and set to `true`. Resources that omit `is_multi_region_trail` or have `is_multi_region_trail: false` are flagged.\n\nSecure example (Ansible):\n\n```yaml\n- name: Create multi-region CloudTrail\n amazon.aws.cloudtrail:\n name: my-trail\n s3_bucket_name: my-trail-bucket\n is_multi_region_trail: true\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cloudtrail_multi_region_disabled", "platform": "Ansible", "descriptionID": "8c4873bf", "cloudProvider": "aws", "cwe": "778", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/cloudtrail_module.html#parameter-is_multi_region_trail" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html#parameter-is_multi_region_trail" } diff --git a/assets/queries/ansible/aws/cloudtrail_multi_region_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/cloudtrail_multi_region_disabled/test/positive_expected_result.json index c758ab31..cbdc066d 100644 --- a/assets/queries/ansible/aws/cloudtrail_multi_region_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cloudtrail_multi_region_disabled/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "CloudTrail Multi Region Disabled", + "queryName": "CloudTrail multi-region is disabled", "severity": "LOW", "line": 7 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/cloudtrail_not_integrated_with_cloudwatch/metadata.json b/assets/queries/ansible/aws/cloudtrail_not_integrated_with_cloudwatch/metadata.json index 1d8589e3..8dccf965 100644 --- a/assets/queries/ansible/aws/cloudtrail_not_integrated_with_cloudwatch/metadata.json +++ b/assets/queries/ansible/aws/cloudtrail_not_integrated_with_cloudwatch/metadata.json @@ -1,14 +1,14 @@ { "id": "ebb2118a-03bc-4d53-ab43-d8750f5cb8d3", - "queryName": "CloudTrail Not Integrated With CloudWatch", + "queryName": "CloudTrail not integrated with CloudWatch", "severity": "LOW", "category": "Observability", - "descriptionText": "CloudTrail should be integrated with CloudWatch", + "descriptionText": "CloudTrail must be integrated with CloudWatch Logs so events are available for real-time detection, alerting, and centralized log analysis, and so forensic evidence is retained for incident investigation.\n\nFor Ansible tasks using the `amazon.aws.cloudtrail` or `cloudtrail` modules, the `cloudwatch_logs_role_arn` and `cloudwatch_logs_log_group_arn` properties must be defined. `cloudwatch_logs_role_arn` should be an IAM role ARN that allows CloudTrail to publish to CloudWatch Logs. `cloudwatch_logs_log_group_arn` should reference the destination Log Group ARN. Tasks missing either property are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create CloudTrail with CloudWatch Logs integration\n amazon.aws.cloudtrail:\n name: my-trail\n s3_bucket_name: my-bucket\n is_multi_region_trail: yes\n cloudwatch_logs_role_arn: arn:aws:iam::123456789012:role/CloudTrail_CloudWatch_Logs_Role\n cloudwatch_logs_log_group_arn: arn:aws:logs:us-east-1:123456789012:log-group:/aws/cloudtrail\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cloudtrail_not_integrated_with_cloudwatch", "platform": "Ansible", "descriptionID": "fbc987a2", "cloudProvider": "aws", "cwe": "778", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/cloudtrail_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html" } diff --git a/assets/queries/ansible/aws/cloudtrail_not_integrated_with_cloudwatch/test/positive_expected_result.json b/assets/queries/ansible/aws/cloudtrail_not_integrated_with_cloudwatch/test/positive_expected_result.json index 1a0aa93a..7acf1162 100644 --- a/assets/queries/ansible/aws/cloudtrail_not_integrated_with_cloudwatch/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cloudtrail_not_integrated_with_cloudwatch/test/positive_expected_result.json @@ -2,21 +2,21 @@ { "severity": "LOW", "line": 2, - "queryName": "CloudTrail Not Integrated With CloudWatch" + "queryName": "CloudTrail not integrated with CloudWatch" }, { "severity": "LOW", "line": 2, - "queryName": "CloudTrail Not Integrated With CloudWatch" + "queryName": "CloudTrail not integrated with CloudWatch" }, { - "queryName": "CloudTrail Not Integrated With CloudWatch", + "queryName": "CloudTrail not integrated with CloudWatch", "severity": "LOW", "line": 14 }, { - "queryName": "CloudTrail Not Integrated With CloudWatch", + "queryName": "CloudTrail not integrated with CloudWatch", "severity": "LOW", "line": 27 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/cloudtrail_sns_topic_name_undefined/metadata.json b/assets/queries/ansible/aws/cloudtrail_sns_topic_name_undefined/metadata.json index 214ba6e6..93a09a12 100644 --- a/assets/queries/ansible/aws/cloudtrail_sns_topic_name_undefined/metadata.json +++ b/assets/queries/ansible/aws/cloudtrail_sns_topic_name_undefined/metadata.json @@ -1,14 +1,14 @@ { "id": "5ba316a9-c466-4ec1-8d5b-bc6107dc9a92", - "queryName": "CloudTrail SNS Topic Name Undefined", + "queryName": "CloudTrail SNS topic name undefined", "severity": "LOW", "category": "Observability", - "descriptionText": "Check if SNS topic name is set for CloudTrail", + "descriptionText": "CloudTrail should be configured to publish notifications to an SNS topic so trail events and log delivery issues can trigger alerts and automated responses. Without an SNS target, you may miss timely notifications about suspicious activity or failures.\n\nFor Ansible CloudTrail tasks (modules `amazon.aws.cloudtrail` or `cloudtrail`), the `sns_topic_name` property must be defined and non-null. Tasks missing `sns_topic_name` or with it set to `null`/empty are flagged. Ensure the value references an existing SNS topic (or create one in the same playbook) so CloudTrail can publish notifications.\n\nSecure example:\n\n```yaml\n- name: Create CloudTrail with SNS notifications\n amazon.aws.cloudtrail:\n name: my-trail\n s3_bucket_name: my-cloudtrail-bucket\n sns_topic_name: my-cloudtrail-topic\n is_multi_region_trail: true\n include_global_service_events: true\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cloudtrail_sns_topic_name_undefined", "platform": "Ansible", "descriptionID": "de97fa1a", "cloudProvider": "aws", "cwe": "703", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/cloudtrail_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html" } diff --git a/assets/queries/ansible/aws/cloudtrail_sns_topic_name_undefined/test/positive_expected_result.json b/assets/queries/ansible/aws/cloudtrail_sns_topic_name_undefined/test/positive_expected_result.json index 85466e93..4be2d477 100644 --- a/assets/queries/ansible/aws/cloudtrail_sns_topic_name_undefined/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cloudtrail_sns_topic_name_undefined/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "CloudTrail SNS Topic Name Undefined", + "queryName": "CloudTrail SNS topic name undefined", "severity": "LOW", "line": 2 }, { - "queryName": "CloudTrail SNS Topic Name Undefined", + "queryName": "CloudTrail SNS topic name undefined", "severity": "LOW", "line": 15 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/cloudwatch_without_retention_period_specified/metadata.json b/assets/queries/ansible/aws/cloudwatch_without_retention_period_specified/metadata.json index d2fc33a4..74ccc2c8 100644 --- a/assets/queries/ansible/aws/cloudwatch_without_retention_period_specified/metadata.json +++ b/assets/queries/ansible/aws/cloudwatch_without_retention_period_specified/metadata.json @@ -1,14 +1,14 @@ { "id": "e24e18d9-4c2b-4649-b3d0-18c088145e24", - "queryName": "CloudWatch Without Retention Period Specified", + "queryName": "CloudWatch without retention period specified", "severity": "LOW", "category": "Observability", - "descriptionText": "AWS CloudWatch should have CloudWatch Logs enabled in order to monitor, store, and access log events", + "descriptionText": "CloudWatch Log Groups must have a defined retention period to retain logs for incident investigation and regulatory compliance. Without one, indefinite retention increases storage costs and the risk of long-term data exposure.\n\nFor Ansible tasks using `amazon.aws.cloudwatchlogs_log_group` or `cloudwatchlogs_log_group`, the `retention` property must be set to one of the AWS-supported retention periods: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653]. Resources missing `retention` or with a value not in this list are flagged as misconfigured.\n\nSecure configuration example:\n\n```\n- name: Create CloudWatch log group with retention\n amazon.aws.cloudwatchlogs_log_group:\n name: my-log-group\n retention: 365\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cloudwatch_without_retention_period_specified", "platform": "Ansible", "descriptionID": "c48a227e", "cloudProvider": "aws", "cwe": "778", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/cloudwatchlogs_log_group_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudwatchlogs_log_group_module.html" } diff --git a/assets/queries/ansible/aws/cloudwatch_without_retention_period_specified/test/positive_expected_result.json b/assets/queries/ansible/aws/cloudwatch_without_retention_period_specified/test/positive_expected_result.json index 55994f0c..4c6d0b8b 100644 --- a/assets/queries/ansible/aws/cloudwatch_without_retention_period_specified/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cloudwatch_without_retention_period_specified/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "CloudWatch Without Retention Period Specified", + "queryName": "CloudWatch without retention period specified", "severity": "LOW", "line": 2 }, { - "queryName": "CloudWatch Without Retention Period Specified", + "queryName": "CloudWatch without retention period specified", "severity": "LOW", "line": 7 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/cmk_is_unusable/metadata.json b/assets/queries/ansible/aws/cmk_is_unusable/metadata.json index 3de37275..39800071 100644 --- a/assets/queries/ansible/aws/cmk_is_unusable/metadata.json +++ b/assets/queries/ansible/aws/cmk_is_unusable/metadata.json @@ -1,13 +1,13 @@ { "id": "133fee21-37ef-45df-a563-4d07edc169f4", - "queryName": "CMK Is Unusable", + "queryName": "CMK is unusable", "severity": "MEDIUM", "category": "Availability", - "descriptionText": "AWS Key Management Service (KMS) must only possess usable Customer Master Keys (CMK), which means the CMKs must have the attribute 'enabled' set to true and the attribute 'pending_window' must be undefined.", + "descriptionText": "KMS Customer Master Keys (CMKs) must be usable, as disabled or scheduled-for-deletion keys cannot decrypt data and may cause service outages or data inaccessibility.\n\nIn Ansible `amazon.aws.kms_key` tasks, ensure `enabled` is defined and set to `true`, and that `pending_window` is not defined. Tasks with `enabled` set to `false` or with `enabled` undefined are flagged. Any task that sets `pending_window` (scheduling the key for deletion) is also flagged because it renders the key unusable after the pending window expires.\n\nSecure example for Ansible:\n\n```yaml\n- name: create KMS key\n amazon.aws.kms_key:\n name: my-key\n description: \"Key for encrypting secrets\"\n state: present\n enabled: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cmk_is_unusable", "platform": "Ansible", "descriptionID": "cb70e349", "cloudProvider": "aws", "cwe": "693", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_kms_module.html#parameter-enabled" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/kms_key_module.html#parameter-enabled" } diff --git a/assets/queries/ansible/aws/cmk_is_unusable/test/positive_expected_result.json b/assets/queries/ansible/aws/cmk_is_unusable/test/positive_expected_result.json index e29e5d54..40335767 100644 --- a/assets/queries/ansible/aws/cmk_is_unusable/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cmk_is_unusable/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "CMK Is Unusable", + "queryName": "CMK is unusable", "severity": "MEDIUM", "line": 6, "fileName": "positive1.yaml" }, { - "queryName": "CMK Is Unusable", + "queryName": "CMK is unusable", "severity": "MEDIUM", "line": 6, "fileName": "positive2.yaml" diff --git a/assets/queries/ansible/aws/cmk_rotation_disabled/metadata.json b/assets/queries/ansible/aws/cmk_rotation_disabled/metadata.json index af8391ab..8aaaa8c4 100644 --- a/assets/queries/ansible/aws/cmk_rotation_disabled/metadata.json +++ b/assets/queries/ansible/aws/cmk_rotation_disabled/metadata.json @@ -1,14 +1,14 @@ { "id": "af96d737-0818-4162-8c41-40d969bd65d1", - "queryName": "CMK Rotation Disabled", + "queryName": "CMK rotation disabled", "severity": "LOW", "category": "Observability", - "descriptionText": "Customer Master Keys (CMK) must have rotation enabled, which means the attribute 'enable_key_rotation' must be set to 'true' when the key is enabled.", + "descriptionText": "Customer Master Keys (CMKs) must have automatic key rotation enabled to limit how long a compromised key can be used and to meet key lifecycle and compliance requirements.\n\nIn Ansible, for tasks using the `amazon.aws.kms_key` module, when `enabled: true` and the key is not scheduled for deletion (no `pending_window` defined), the `enable_key_rotation` property must be present and set to `true`. Resources missing `enable_key_rotation` or with `enable_key_rotation: false` are flagged as misconfigured.\n\nSecure configuration example:\n\n```\n- name: Create CMK with rotation enabled\n amazon.aws.kms_key:\n name: my-key\n enabled: true\n enable_key_rotation: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cmk_rotation_disabled", "platform": "Ansible", "descriptionID": "177ee908", "cloudProvider": "aws", "cwe": "326", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_kms_module.html#parameter-enable_key_rotation" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/kms_key_module.html#parameter-enable_key_rotation" } diff --git a/assets/queries/ansible/aws/cmk_rotation_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/cmk_rotation_disabled/test/positive_expected_result.json index 1515aadb..515c7d9a 100644 --- a/assets/queries/ansible/aws/cmk_rotation_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cmk_rotation_disabled/test/positive_expected_result.json @@ -1,14 +1,14 @@ [ { - "queryName": "CMK Rotation Disabled", + "queryName": "CMK rotation disabled", "severity": "LOW", "line": 2, "fileName": "positive1.yaml" }, { - "queryName": "CMK Rotation Disabled", + "queryName": "CMK rotation disabled", "severity": "LOW", "line": 7, "fileName": "positive2.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/codebuild_not_encrypted/metadata.json b/assets/queries/ansible/aws/codebuild_not_encrypted/metadata.json index 414500cd..9fdfd2bc 100644 --- a/assets/queries/ansible/aws/codebuild_not_encrypted/metadata.json +++ b/assets/queries/ansible/aws/codebuild_not_encrypted/metadata.json @@ -1,13 +1,13 @@ { "id": "a1423864-2fbc-4f46-bfe1-fbbf125c71c9", - "queryName": "CodeBuild Not Encrypted", + "queryName": "CodeBuild project is not encrypted", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "CodeBuild Project should be encrypted", + "descriptionText": "CodeBuild projects must have a KMS encryption key configured so build artifacts, cached data, and logs are protected at rest.\n\nFor Ansible resources using the `community.aws.codebuild_project` or `aws_codebuild` modules, the `encryption_key` property must be defined and set to a valid AWS KMS key ARN or alias (for example `arn:aws:kms:...` or `alias/your-key-alias`). Resources missing `encryption_key` or with it undefined are flagged. \n\nExample secure task:\n\n```yaml\n- name: create codebuild project\n community.aws.codebuild_project:\n name: my-build\n encryption_key: arn:aws:kms:us-east-1:123456789012:key/abcd1234-ef56-7890-abcd-123456ef7890\n # other required properties...\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/codebuild_not_encrypted", "platform": "Ansible", "descriptionID": "7d51416a", "cloudProvider": "aws", "cwe": "311", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_codebuild_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/codebuild_project_module.html" } diff --git a/assets/queries/ansible/aws/codebuild_not_encrypted/test/positive_expected_result.json b/assets/queries/ansible/aws/codebuild_not_encrypted/test/positive_expected_result.json index 73a4efdc..b7955862 100644 --- a/assets/queries/ansible/aws/codebuild_not_encrypted/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/codebuild_not_encrypted/test/positive_expected_result.json @@ -1,8 +1,7 @@ [ - { - "queryName": "CodeBuild Not Encrypted", - "severity": "MEDIUM", - "line": 2 - } - + { + "queryName": "CodeBuild project is not encrypted", + "severity": "MEDIUM", + "line": 2 + } ] diff --git a/assets/queries/ansible/aws/config_configuration_aggregator_to_all_regions_disabled/metadata.json b/assets/queries/ansible/aws/config_configuration_aggregator_to_all_regions_disabled/metadata.json index 97912d17..5437bd63 100644 --- a/assets/queries/ansible/aws/config_configuration_aggregator_to_all_regions_disabled/metadata.json +++ b/assets/queries/ansible/aws/config_configuration_aggregator_to_all_regions_disabled/metadata.json @@ -1,14 +1,14 @@ { "id": "a2fdf451-89dd-451e-af92-bf6c0f4bab96", - "queryName": "Configuration Aggregator to All Regions Disabled", + "queryName": "Configuration aggregator to all regions disabled", "severity": "LOW", "category": "Observability", - "descriptionText": "AWS Config Configuration Aggregator All Regions must be set to True", + "descriptionText": "AWS Config aggregators must collect configuration data from all AWS Regions to provide centralized, complete visibility of resource state. This ensures cross-region misconfigurations and compliance violations are detected.\n\nFor Ansible tasks using the `community.aws.config_aggregator` or `aws_config_aggregator` modules, set the `all_aws_regions` property to `true` under the relevant `account_sources` entries or the `organization_source` block. Resources that omit `all_aws_regions` or have it set to `false` are flagged, as they do not provide full regional coverage.\n\nSecure examples for Ansible (account and organization sources):\n\n```yaml\n- name: Create AWS Config Aggregator (account sources)\n community.aws.config_aggregator:\n name: my-config-aggregator\n account_sources:\n - account_ids: ['123456789012']\n all_aws_regions: true\n\n- name: Create AWS Config Aggregator (organization source)\n community.aws.config_aggregator:\n name: org-config-aggregator\n organization_source:\n role_arn: arn:aws:iam::111122223333:role/ConfigAggregatorRole\n all_aws_regions: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/config_configuration_aggregator_to_all_regions_disabled", "platform": "Ansible", "descriptionID": "c6e4ac23", "cloudProvider": "aws", "cwe": "285", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_config_aggregator_module.html#parameter-organization_source" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/config_aggregator_module.html#parameter-organization_source" } diff --git a/assets/queries/ansible/aws/config_configuration_aggregator_to_all_regions_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/config_configuration_aggregator_to_all_regions_disabled/test/positive_expected_result.json index db2fb698..b6c43520 100644 --- a/assets/queries/ansible/aws/config_configuration_aggregator_to_all_regions_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/config_configuration_aggregator_to_all_regions_disabled/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Configuration Aggregator to All Regions Disabled", + "queryName": "Configuration aggregator to all regions disabled", "severity": "LOW", "line": 10 }, { - "queryName": "Configuration Aggregator to All Regions Disabled", + "queryName": "Configuration aggregator to all regions disabled", "severity": "LOW", "line": 24 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/config_rule_for_encrypted_volumes_is_disabled/metadata.json b/assets/queries/ansible/aws/config_rule_for_encrypted_volumes_is_disabled/metadata.json index 5e23f95a..411145fc 100644 --- a/assets/queries/ansible/aws/config_rule_for_encrypted_volumes_is_disabled/metadata.json +++ b/assets/queries/ansible/aws/config_rule_for_encrypted_volumes_is_disabled/metadata.json @@ -1,14 +1,14 @@ { "id": "7674a686-e4b1-4a95-83d4-1fd53c623d84", - "queryName": "Config Rule For Encrypted Volumes Disabled", + "queryName": "Config rule for encrypted volumes disabled", "severity": "HIGH", "category": "Encryption", - "descriptionText": "Check if AWS config rules do not identify Encrypted Volumes as a source.", + "descriptionText": "Missing an AWS Config rule for encrypted volumes prevents automated detection of unencrypted block storage and snapshots, leaving data at rest vulnerable to exposure if storage is compromised.\n\nFor Ansible-managed resources, define an `aws_config_rule` (module `community.aws.config_rule` or `aws_config_rule`) with `source.identifier` set to `ENCRYPTED_VOLUMES`. The check is case-insensitive. Tasks that omit this `aws_config_rule` or set `source.identifier` to a different value are flagged.\n\nSecure Ansible example:\n\n```yaml\n- name: Ensure AWS Config rule for encrypted volumes exists\n community.aws.config_rule:\n name: encrypted-volumes-rule\n source:\n owner: AWS\n identifier: ENCRYPTED_VOLUMES\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/config_rule_for_encrypted_volumes_is_disabled", "platform": "Ansible", "descriptionID": "5b434d3f", "cloudProvider": "aws", "cwe": "311", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_config_rule_module.html#parameter-source/identifier" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/config_rule_module.html#parameter-source/identifier" } diff --git a/assets/queries/ansible/aws/config_rule_for_encrypted_volumes_is_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/config_rule_for_encrypted_volumes_is_disabled/test/positive_expected_result.json index 8310f199..3b4f6650 100644 --- a/assets/queries/ansible/aws/config_rule_for_encrypted_volumes_is_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/config_rule_for_encrypted_volumes_is_disabled/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "Config Rule For Encrypted Volumes Disabled", + "queryName": "Config rule for encrypted volumes disabled", "severity": "HIGH", "line": 2 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa/metadata.json b/assets/queries/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa/metadata.json index 3fa56718..c9ef22c8 100644 --- a/assets/queries/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa/metadata.json +++ b/assets/queries/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa/metadata.json @@ -1,14 +1,14 @@ { "id": "af167837-9636-4086-b815-c239186b9dda", - "queryName": "Cross-Account IAM Assume Role Policy Without ExternalId or MFA", + "queryName": "Cross-account IAM assume role policy without ExternalId or MFA", "severity": "HIGH", "category": "Access Control", - "descriptionText": "Cross-Account IAM Assume Role Policy should require external ID or MFA to protect cross-account access", + "descriptionText": "Cross-account IAM role trust policies that allow `sts:AssumeRole` to external principals must require an `ExternalId` or MFA to prevent unintended or unauthorized access from third-party accounts. Without an `ExternalId` or a `Condition` requiring MFA, an external principal (including other-account root principals) that can assume the role may gain access to sensitive resources or perform privileged actions.\n\nIn Ansible `amazon.aws.iam_role` and `iam_role` tasks, the `assume_role_policy_document` `Statement` with `Effect: Allow` and `Action: sts:AssumeRole` that names a cross-account `Principal` (for example, an ARN that includes another account or `:root`) must include a `Condition` containing either `sts:ExternalId` (for example, `StringEquals`) or `aws:MultiFactorAuthPresent` set to `true`. Resources missing the required `Condition` or that allow cross-account assume-role without `ExternalId` or MFA are flagged.\n\nSecure trust policy examples:\n\n```json\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": { \"AWS\": \"arn:aws:iam::123456789012:root\" },\n \"Action\": \"sts:AssumeRole\",\n \"Condition\": {\n \"StringEquals\": { \"sts:ExternalId\": \"your-external-id-value\" }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": { \"AWS\": \"arn:aws:iam::123456789012:root\" },\n \"Action\": \"sts:AssumeRole\",\n \"Condition\": {\n \"Bool\": { \"aws:MultiFactorAuthPresent\": \"true\" }\n }\n }\n ]\n}\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa", "platform": "Ansible", "descriptionID": "54f0a7dd", "cloudProvider": "aws", "cwe": "285", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_role_module.html#parameter-assume_role_policy_document" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_role_module.html#parameter-assume_role_policy_document" } diff --git a/assets/queries/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa/test/positive_expected_result.json b/assets/queries/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa/test/positive_expected_result.json index a68d8c51..aeb3345a 100644 --- a/assets/queries/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa/test/positive_expected_result.json @@ -1,20 +1,20 @@ [ { - "queryName": "Cross-Account IAM Assume Role Policy Without ExternalId or MFA", + "queryName": "Cross-account IAM assume role policy without ExternalId or MFA", "severity": "HIGH", "line": 4, "fileName": "positive1.yaml" }, { - "queryName": "Cross-Account IAM Assume Role Policy Without ExternalId or MFA", + "queryName": "Cross-account IAM assume role policy without ExternalId or MFA", "severity": "HIGH", "line": 4, "fileName": "positive2.yaml" }, { - "queryName": "Cross-Account IAM Assume Role Policy Without ExternalId or MFA", + "queryName": "Cross-account IAM assume role policy without ExternalId or MFA", "severity": "HIGH", "line": 4, "fileName": "positive3.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/db_instance_storage_not_encrypted/metadata.json b/assets/queries/ansible/aws/db_instance_storage_not_encrypted/metadata.json index f8abeecf..df5f0522 100644 --- a/assets/queries/ansible/aws/db_instance_storage_not_encrypted/metadata.json +++ b/assets/queries/ansible/aws/db_instance_storage_not_encrypted/metadata.json @@ -1,13 +1,13 @@ { "id": "7dfb316c-a6c2-454d-b8a2-97f147b0c0ff", - "queryName": "DB Instance Storage Not Encrypted", + "queryName": "DB instance storage not encrypted", "severity": "HIGH", "category": "Encryption", - "descriptionText": "AWS DB Instance should have its storage encrypted by setting the parameter to 'true'. The storage_encrypted default value is 'false'.", + "descriptionText": "RDS instances must have storage encryption enabled to protect data at rest, including database files, automated backups, and snapshots. Without encryption, this data is exposed to unauthorized access if storage media or snapshots are compromised.\n\nFor Ansible resources using the `amazon.aws.rds_instance` or `rds_instance` modules, set `storage_encrypted` to `true`. If you are using a customer-managed key, also define `kms_key_id`. This rule flags instances where `storage_encrypted` is undefined or set to `false` and no `kms_key_id` is provided.\n\n```yaml\n- name: Create encrypted RDS instance\n amazon.aws.rds_instance:\n db_instance_identifier: mydb\n engine: mysql\n allocated_storage: 20\n master_username: admin\n master_user_password: secret\n storage_encrypted: true\n kms_key_id: arn:aws:kms:us-east-1:123456789012:key/abcd-ef01-2345-6789-abcd\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/db_instance_storage_not_encrypted", "platform": "Ansible", "descriptionID": "575cc1f4", "cloudProvider": "aws", "cwe": "311", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/rds_instance_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html" } diff --git a/assets/queries/ansible/aws/db_instance_storage_not_encrypted/test/positive_expected_result.json b/assets/queries/ansible/aws/db_instance_storage_not_encrypted/test/positive_expected_result.json index bd322037..dbe9c3d8 100644 --- a/assets/queries/ansible/aws/db_instance_storage_not_encrypted/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/db_instance_storage_not_encrypted/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ - { - "queryName": "DB Instance Storage Not Encrypted", - "severity": "HIGH", - "line": 8 - }, - { - "queryName": "DB Instance Storage Not Encrypted", - "severity": "HIGH", - "line": 19 - }, - { - "queryName": "DB Instance Storage Not Encrypted", - "severity": "HIGH", - "line": 25 - } + { + "queryName": "DB instance storage not encrypted", + "severity": "HIGH", + "line": 8 + }, + { + "queryName": "DB instance storage not encrypted", + "severity": "HIGH", + "line": 19 + }, + { + "queryName": "DB instance storage not encrypted", + "severity": "HIGH", + "line": 25 + } ] diff --git a/assets/queries/ansible/aws/db_security_group_open_to_large_scope/metadata.json b/assets/queries/ansible/aws/db_security_group_open_to_large_scope/metadata.json index 49ea15be..469a91a0 100644 --- a/assets/queries/ansible/aws/db_security_group_open_to_large_scope/metadata.json +++ b/assets/queries/ansible/aws/db_security_group_open_to_large_scope/metadata.json @@ -1,9 +1,9 @@ { "id": "ea0ed1c7-9aef-4464-b7c7-94c762da3640", - "queryName": "DB Security Group Open To Large Scope", + "queryName": "DB security group open to large scope", "severity": "HIGH", "category": "Networking and Firewall", - "descriptionText": "The IP address in a DB Security Group must not have more than 256 hosts.", + "descriptionText": "Security group rules that use CIDR blocks containing 256 or more IP addresses broaden the attack surface and make unauthorized access or lateral movement easier.\n\nFor Ansible EC2 security groups (modules `amazon.aws.ec2_group` and `ec2_group`), ensure each rule's `cidr_ip` is a CIDR with a prefix length greater than 24 (for example `/25`–`/32`) so the subnet contains fewer than 256 addresses. This rule flags any task where `rules[].cidr_ip` has a prefix length of 24 or less (for example, `10.0.0.0/24`, `10.0.0.0/16`, or `0.0.0.0/0`). If broader access is required, prefer tighter subnetting, explicit host IPs, or security-group references instead of large CIDR ranges.\n\nSecure Ansible example with a narrow CIDR (/32 single host):\n\n```yaml\n- name: create restrictive security group\n amazon.aws.ec2_group:\n name: my-sg\n rules:\n - proto: tcp\n from_port: 22\n to_port: 22\n cidr_ip: 10.0.0.5/32\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/db_security_group_open_to_large_scope", "platform": "Ansible", "descriptionID": "c7f9cb9f", diff --git a/assets/queries/ansible/aws/db_security_group_open_to_large_scope/test/positive_expected_result.json b/assets/queries/ansible/aws/db_security_group_open_to_large_scope/test/positive_expected_result.json index 0c0d773c..d928d68f 100644 --- a/assets/queries/ansible/aws/db_security_group_open_to_large_scope/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/db_security_group_open_to_large_scope/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "DB Security Group Open To Large Scope", + "queryName": "DB security group open to large scope", "severity": "HIGH", "line": 22 } diff --git a/assets/queries/ansible/aws/db_security_group_with_public_scope/metadata.json b/assets/queries/ansible/aws/db_security_group_with_public_scope/metadata.json index af45606c..ee64828a 100644 --- a/assets/queries/ansible/aws/db_security_group_with_public_scope/metadata.json +++ b/assets/queries/ansible/aws/db_security_group_with_public_scope/metadata.json @@ -1,9 +1,9 @@ { "id": "0956aedf-6a7a-478b-ab56-63e2b19923ad", - "queryName": "DB Security Group With Public Scope", + "queryName": "DB security group with public scope", "severity": "CRITICAL", "category": "Networking and Firewall", - "descriptionText": "The IP address in a DB Security Group should not be '0.0.0.0/0' (IPv4) or '::/0' (IPv6). If so, any IP can access it", + "descriptionText": "Security groups must not allow unrestricted IP ranges because a `cidr_ip` of `0.0.0.0/0` grants access from the entire Internet and exposes instances to unauthorized access, brute-force attacks, and data exfiltration.\n\nFor Ansible tasks using the `amazon.aws.ec2_group` or `ec2_group` modules, check the `rules` (ingress) and `rules_egress` (egress) entries and ensure each `cidr_ip` is not `0.0.0.0/0`. Prefer specific trusted CIDRs, private address ranges (for example `10.0.0.0/8`, `192.168.0.0/16`, `172.16.0.0/12`), or references to other security groups.\n\nThis rule flags any `ec2_group.rules[].cidr_ip` or `ec2_group.rules_egress[].cidr_ip` set to a public scope such as `0.0.0.0/0`. Review and replace wide-open CIDRs with least-privilege network ranges or security-group references.\n\nSecure Ansible example with restricted CIDRs:\n\n```yaml\n- name: Create internal security group\n amazon.aws.ec2_group:\n name: my-internal-sg\n description: Allow internal SSH only\n rules:\n - proto: tcp\n from_port: 22\n to_port: 22\n cidr_ip: 10.0.0.0/8\n rules_egress:\n - proto: -1\n from_port: 0\n to_port: 0\n cidr_ip: 10.0.0.0/8\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/db_security_group_with_public_scope", "platform": "Ansible", "descriptionID": "47a14ee4", diff --git a/assets/queries/ansible/aws/db_security_group_with_public_scope/test/positive_expected_result.json b/assets/queries/ansible/aws/db_security_group_with_public_scope/test/positive_expected_result.json index dc66872a..cb9a8b81 100644 --- a/assets/queries/ansible/aws/db_security_group_with_public_scope/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/db_security_group_with_public_scope/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "DB Security Group With Public Scope", + "queryName": "DB security group with public scope", "severity": "CRITICAL", "line": 22 }, { - "queryName": "DB Security Group With Public Scope", + "queryName": "DB security group with public scope", "severity": "CRITICAL", "line": 53 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/default_security_groups_with_unrestricted_traffic/metadata.json b/assets/queries/ansible/aws/default_security_groups_with_unrestricted_traffic/metadata.json index 70866869..76e12b61 100644 --- a/assets/queries/ansible/aws/default_security_groups_with_unrestricted_traffic/metadata.json +++ b/assets/queries/ansible/aws/default_security_groups_with_unrestricted_traffic/metadata.json @@ -1,9 +1,9 @@ { "id": "8010e17a-00e9-4635-a692-90d6bcec68bd", - "queryName": "Default Security Groups With Unrestricted Traffic", + "queryName": "Default security groups with unrestricted traffic", "severity": "HIGH", "category": "Networking and Firewall", - "descriptionText": "Check if default security group does not restrict all inbound and outbound traffic.", + "descriptionText": "Security groups that allow inbound or outbound CIDR ranges of `0.0.0.0/0` or `::/0` expose resources to the entire internet, increasing the risk of unauthorized access, brute-force attacks, and data exfiltration.\n\nFor Ansible `amazon.aws.ec2_group` or `ec2_group` tasks, inspect the `rules` and `rules_egress` entries and ensure the `cidr_ip` and `cidr_ipv6` properties are not set to `0.0.0.0/0` or `::/0`. Tasks containing `cidr_ip: 0.0.0.0/0` or `cidr_ipv6: ::/0` are flagged. Restrict access to specific CIDR ranges or reference other security groups instead of using global open CIDRs.\n\nSecure configuration example:\n\n```yaml\nmy_security_group:\n amazon.aws.ec2_group:\n name: my-sg\n rules:\n - proto: tcp\n from_port: 22\n to_port: 22\n cidr_ip: 203.0.113.0/24\n rules_egress:\n - proto: -1\n from_port: 0\n to_port: 0\n cidr_ip: 10.0.0.0/16\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/default_security_groups_with_unrestricted_traffic", "platform": "Ansible", "descriptionID": "6fd8f0e1", diff --git a/assets/queries/ansible/aws/default_security_groups_with_unrestricted_traffic/test/positive_expected_result.json b/assets/queries/ansible/aws/default_security_groups_with_unrestricted_traffic/test/positive_expected_result.json index fbf9d83d..c34c9970 100644 --- a/assets/queries/ansible/aws/default_security_groups_with_unrestricted_traffic/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/default_security_groups_with_unrestricted_traffic/test/positive_expected_result.json @@ -1,26 +1,26 @@ [ { - "queryName": "Default Security Groups With Unrestricted Traffic", + "queryName": "Default security groups with unrestricted traffic", "severity": "HIGH", "line": 17 }, { - "queryName": "Default Security Groups With Unrestricted Traffic", + "queryName": "Default security groups with unrestricted traffic", "severity": "HIGH", "line": 30 }, { - "queryName": "Default Security Groups With Unrestricted Traffic", + "queryName": "Default security groups with unrestricted traffic", "severity": "HIGH", "line": 48 }, { - "queryName": "Default Security Groups With Unrestricted Traffic", + "queryName": "Default security groups with unrestricted traffic", "severity": "HIGH", "line": 61 }, { - "queryName": "Default Security Groups With Unrestricted Traffic", + "queryName": "Default security groups with unrestricted traffic", "severity": "HIGH", "line": 83 } diff --git a/assets/queries/ansible/aws/ebs_volume_encryption_disabled/metadata.json b/assets/queries/ansible/aws/ebs_volume_encryption_disabled/metadata.json index 7c2e5830..b1899421 100644 --- a/assets/queries/ansible/aws/ebs_volume_encryption_disabled/metadata.json +++ b/assets/queries/ansible/aws/ebs_volume_encryption_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "4b6012e7-7176-46e4-8108-e441785eae57", - "queryName": "EBS Volume Encryption Disabled", + "queryName": "EBS volume encryption disabled", "severity": "HIGH", "category": "Encryption", - "descriptionText": "EBS volumes should be encrypted", + "descriptionText": "Encrypt EBS volumes to protect data at rest and ensure snapshots and backups are also encrypted. Unencrypted volumes and their snapshots risk exposure if storage media or backups are compromised. For Ansible, tasks using the `amazon.aws.ec2_vol` or legacy `ec2_vol` modules must define the `encrypted` property and set it to `true` (or `yes`). Tasks with `state` set to `absent` or `list` are ignored. Resources with `encrypted` set to `false` or missing the `encrypted` attribute are flagged.\n\nSecure Ansible example:\n\n```yaml\n- name: Create encrypted EBS volume\n amazon.aws.ec2_vol:\n volume_size: 10\n region: us-east-1\n encrypted: yes\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ebs_volume_encryption_disabled", "platform": "Ansible", "descriptionID": "06f72385", diff --git a/assets/queries/ansible/aws/ebs_volume_encryption_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/ebs_volume_encryption_disabled/test/positive_expected_result.json index ea450c2a..7643a60a 100644 --- a/assets/queries/ansible/aws/ebs_volume_encryption_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ebs_volume_encryption_disabled/test/positive_expected_result.json @@ -1,22 +1,22 @@ [ { - "queryName": "EBS Volume Encryption Disabled", + "queryName": "EBS volume encryption disabled", "severity": "HIGH", "line": 6 }, { - "queryName": "EBS Volume Encryption Disabled", + "queryName": "EBS volume encryption disabled", "severity": "HIGH", "line": 14 }, { - "queryName": "EBS Volume Encryption Disabled", + "queryName": "EBS volume encryption disabled", "severity": "HIGH", "line": 22 }, { - "queryName": "EBS Volume Encryption Disabled", + "queryName": "EBS volume encryption disabled", "severity": "HIGH", "line": 27 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/ec2_group_has_public_interface/metadata.json b/assets/queries/ansible/aws/ec2_group_has_public_interface/metadata.json index 6a922b14..5b7aa0ff 100644 --- a/assets/queries/ansible/aws/ec2_group_has_public_interface/metadata.json +++ b/assets/queries/ansible/aws/ec2_group_has_public_interface/metadata.json @@ -1,9 +1,9 @@ { "id": "5330b503-3319-44ff-9b1c-00ee873f728a", - "queryName": "EC2 Group Has Public Interface", + "queryName": "EC2 security group allows public access", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "The CIDR IP should not be a public interface", + "descriptionText": "Security group rules must not permit ingress from the public internet (`0.0.0.0/0` or `::/0`). Open rules expose instances to unauthorized access and automated attacks. In Ansible tasks using the `amazon.aws.ec2_group` or `ec2_group` modules, each entry in the `rules` list must not set `cidr_ip` to `0.0.0.0/0` or `cidr_ipv6` to `::/0`. This rule flags any `rules` item with those values. Instead, restrict access to specific CIDR ranges, reference other security groups, or require access via a bastion/VPN. \n\nSecure example with a restricted CIDR:\n\n```yaml\n- name: create ssh access for admin network\n amazon.aws.ec2_group:\n name: my-secgroup\n rules:\n - proto: tcp\n from_port: 22\n to_port: 22\n cidr_ip: 203.0.113.0/24\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ec2_group_has_public_interface", "platform": "Ansible", "descriptionID": "506f9dd8", diff --git a/assets/queries/ansible/aws/ec2_group_has_public_interface/test/positive_expected_result.json b/assets/queries/ansible/aws/ec2_group_has_public_interface/test/positive_expected_result.json index 422c66bd..b6f1307e 100644 --- a/assets/queries/ansible/aws/ec2_group_has_public_interface/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ec2_group_has_public_interface/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ - { - "queryName": "EC2 Group Has Public Interface", - "severity": "HIGH", - "line": 22, - "fileName": "positive.yaml" - } + { + "queryName": "EC2 security group allows public access", + "severity": "HIGH", + "line": 22, + "fileName": "positive.yaml" + } ] diff --git a/assets/queries/ansible/aws/ec2_instance_has_public_ip/metadata.json b/assets/queries/ansible/aws/ec2_instance_has_public_ip/metadata.json index 5f745f01..788fd085 100644 --- a/assets/queries/ansible/aws/ec2_instance_has_public_ip/metadata.json +++ b/assets/queries/ansible/aws/ec2_instance_has_public_ip/metadata.json @@ -1,14 +1,14 @@ { "id": "a8b0c58b-cd25-4b53-9ad0-55bca0be0bc1", - "queryName": "EC2 Instance Has Public IP", + "queryName": "EC2 instance has public IP", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "EC2 Instance should not have a public IP address.", + "descriptionText": "EC2 instances and launch templates that automatically receive a public IPv4 address are exposed directly to the internet, increasing the attack surface and the risk of unauthorized access or exploitation.\n\nFor Ansible tasks, check the following module properties:\n\n- For `amazon.aws.ec2_launch_template` / `ec2_launch_template`: `network_interfaces.associate_public_ip_address`\n- For `amazon.aws.ec2_instance` / `ec2_instance`: `network.assign_public_ip`\n\nEach property must be explicitly set to `false` (or `'no'`) or omitted. The rule flags resources where the property is truthy (for example, `true`, `yes`) because there is no safe default. \n\nSecure examples:\n\n```yaml\n- name: Launch instance without public IP (ec2_instance)\n amazon.aws.ec2_instance:\n name: my-instance\n network:\n assign_public_ip: false\n\n- name: Create launch template without public IP\n amazon.aws.ec2_launch_template:\n name: my-template\n network_interfaces:\n - device_index: 0\n associate_public_ip_address: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ec2_instance_has_public_ip", "platform": "Ansible", "descriptionID": "f32c5d88", "cloudProvider": "aws", "cwe": "200", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_module.html#parameter-assign_public_ip" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html" } diff --git a/assets/queries/ansible/aws/ec2_instance_has_public_ip/test/positive_expected_result.json b/assets/queries/ansible/aws/ec2_instance_has_public_ip/test/positive_expected_result.json index e1046525..eacd4dc9 100644 --- a/assets/queries/ansible/aws/ec2_instance_has_public_ip/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ec2_instance_has_public_ip/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "EC2 Instance Has Public IP", + "queryName": "EC2 instance has public IP", "severity": "MEDIUM", "line": 8 }, { - "queryName": "EC2 Instance Has Public IP", + "queryName": "EC2 instance has public IP", "severity": "MEDIUM", "line": 17 } diff --git a/assets/queries/ansible/aws/ec2_instance_using_default_security_group/metadata.json b/assets/queries/ansible/aws/ec2_instance_using_default_security_group/metadata.json index 31807de4..7b462c44 100644 --- a/assets/queries/ansible/aws/ec2_instance_using_default_security_group/metadata.json +++ b/assets/queries/ansible/aws/ec2_instance_using_default_security_group/metadata.json @@ -1,14 +1,14 @@ { "id": "8d03993b-8384-419b-a681-d1f55149397c", - "queryName": "EC2 Instance Using Default Security Group", + "queryName": "EC2 instance using default security group", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "EC2 instances should not use default security group(s)", + "descriptionText": "Using the default security group for EC2 instances is unsafe. The default group is shared across the VPC, often broadly permissive for intra-VPC traffic, and cannot be scoped to least-privilege rules. This increases the risk of lateral movement and unintended exposure.\n\nThis rule inspects Ansible tasks that use the `amazon.aws.ec2_instance` or `ec2_instance` module and flags `security_group` or `security_groups` properties that reference the default security group. Both string and list forms are evaluated. Any value containing the word \"default\" (case-insensitive) is flagged and should be replaced with explicit, purpose-built security group names or IDs that restrict ingress and egress to only the required sources and ports.\n\nSecure example using an explicit security group ID:\n\n```yaml\n- name: Launch EC2 with dedicated security group\n amazon.aws.ec2_instance:\n name: my-instance\n image_id: ami-0123456789abcdef0\n instance_type: t3.micro\n vpc_subnet_id: subnet-29e63245\n security_groups:\n - sg-0123456789abcdef0\n network:\n assign_public_ip: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ec2_instance_using_default_security_group", "platform": "Ansible", "descriptionID": "cc323109", "cloudProvider": "aws", "cwe": "732", "oldSeverity": "LOW", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_module.html#parameter-group" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html#parameter-security_group" } diff --git a/assets/queries/ansible/aws/ec2_instance_using_default_security_group/test/positive_expected_result.json b/assets/queries/ansible/aws/ec2_instance_using_default_security_group/test/positive_expected_result.json index c19040f6..a25ca63a 100644 --- a/assets/queries/ansible/aws/ec2_instance_using_default_security_group/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ec2_instance_using_default_security_group/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "EC2 Instance Using Default Security Group", + "queryName": "EC2 instance using default security group", "severity": "MEDIUM", "line": 8, "fileName": "positive1.yaml" }, { - "queryName": "EC2 Instance Using Default Security Group", + "queryName": "EC2 instance using default security group", "severity": "MEDIUM", "line": 9, "fileName": "positive2.yaml" diff --git a/assets/queries/ansible/aws/ec2_instance_using_default_vpc/metadata.json b/assets/queries/ansible/aws/ec2_instance_using_default_vpc/metadata.json index b35e3eda..fe6d6e53 100644 --- a/assets/queries/ansible/aws/ec2_instance_using_default_vpc/metadata.json +++ b/assets/queries/ansible/aws/ec2_instance_using_default_vpc/metadata.json @@ -1,13 +1,13 @@ { "id": "8833f180-96f1-46f4-9147-849aafa56029", - "queryName": "EC2 Instance Using Default VPC", + "queryName": "EC2 instance using default VPC", "severity": "LOW", "category": "Networking and Firewall", - "descriptionText": "EC2 Instances should not be configured under a default VPC network", + "descriptionText": "Launching EC2 instances into a default VPC increases exposure because default VPCs often have permissive networking defaults that are not tailored with least-privilege network controls. This makes it harder to enforce isolation and audit access. In Ansible playbooks using the `amazon.aws.ec2_instance` or `ec2_instance` module, the `vpc_subnet_id` parameter must not reference a subnet that belongs to a default VPC. This rule flags EC2 tasks where `vpc_subnet_id` is templated to a registered `amazon.aws.ec2_vpc_subnet`/`ec2_vpc_subnet` and the corresponding subnet's `vpc_id` contains the string \"default\". Ensure subnets referenced by `vpc_subnet_id` are created in a non-default VPC (for example, `vpc-0abc1234`) rather than a value containing \"default\".\n\nSecure example with a subnet in a non-default VPC:\n\n```yaml\n- name: create subnet in custom VPC\n amazon.aws.ec2_vpc_subnet:\n vpc_id: vpc-0abc1234\n cidr: 10.0.1.0/24\n state: present\n register: my_subnet\n\n- name: launch instance in the custom subnet\n amazon.aws.ec2_instance:\n name: my-instance\n image_id: ami-0123456789abcdef0\n instance_type: t3.micro\n vpc_subnet_id: \"{{ my_subnet.subnet.id }}\"\n wait: true\n network:\n assign_public_ip: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ec2_instance_using_default_vpc", "platform": "Ansible", "descriptionID": "701b1c92", "cloudProvider": "aws", "cwe": "200", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_module.html#parameter-vpc_subnet_id" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html#parameter-vpc_subnet_id" } diff --git a/assets/queries/ansible/aws/ec2_instance_using_default_vpc/test/positive_expected_result.json b/assets/queries/ansible/aws/ec2_instance_using_default_vpc/test/positive_expected_result.json index 8007e2c0..14dda86b 100644 --- a/assets/queries/ansible/aws/ec2_instance_using_default_vpc/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ec2_instance_using_default_vpc/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ - { - "queryName": "EC2 Instance Using Default VPC", - "severity": "LOW", - "line": 8, - "fileName": "positive.yaml" - } + { + "queryName": "EC2 instance using default VPC", + "severity": "LOW", + "line": 8, + "fileName": "positive.yaml" + } ] diff --git a/assets/queries/ansible/aws/ec2_not_ebs_optimized/metadata.json b/assets/queries/ansible/aws/ec2_not_ebs_optimized/metadata.json index 3d0abd8e..fd7b4d08 100644 --- a/assets/queries/ansible/aws/ec2_not_ebs_optimized/metadata.json +++ b/assets/queries/ansible/aws/ec2_not_ebs_optimized/metadata.json @@ -1,13 +1,13 @@ { "id": "338b6cab-961d-4998-bb49-e5b6a11c9a5c", - "queryName": "EC2 Not EBS Optimized", + "queryName": "EC2 instance is not EBS optimized", "severity": "LOW", "category": "Best Practices", - "descriptionText": "It's considered a best practice for an EC2 instance to use an EBS optimized instance. This provides the best performance for your EBS volumes by minimizing contention between Amazon EBS I/O and other traffic from your instance", + "descriptionText": "EC2 instances must be EBS-optimized to ensure consistent, high-performance EBS I/O and reduce contention between EBS traffic and other instance operations.\n\nFor Ansible EC2 tasks using the `amazon.aws.ec2_instance` or `ec2_instance` module, the `ebs_optimized` property must be defined and set to `true` for instance types that are not EBS-optimized by default. If `instance_type` is omitted, the default `t2.micro` is assumed. Instance types that are EBS-optimized by default are exempt and are not flagged. Tasks missing the `ebs_optimized` property or with `ebs_optimized: false` are reported.\n\nSecure configuration example:\n\n```yaml\n- name: Launch EBS-optimized EC2\n amazon.aws.ec2_instance:\n name: my-instance\n instance_type: m5.large\n image_id: ami-0123456789abcdef0\n vpc_subnet_id: subnet-29e63245\n ebs_optimized: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ec2_not_ebs_optimized", "platform": "Ansible", "descriptionID": "a99c1fe0", "cloudProvider": "aws", "cwe": "710", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_module.html#parameter-ebs_optimized" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html#parameter-ebs_optimized" } diff --git a/assets/queries/ansible/aws/ec2_not_ebs_optimized/test/positive_expected_result.json b/assets/queries/ansible/aws/ec2_not_ebs_optimized/test/positive_expected_result.json index e7d45e5d..d700374e 100644 --- a/assets/queries/ansible/aws/ec2_not_ebs_optimized/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ec2_not_ebs_optimized/test/positive_expected_result.json @@ -1,18 +1,18 @@ [ { - "queryName": "EC2 Not EBS Optimized", + "queryName": "EC2 instance is not EBS optimized", "severity": "LOW", "line": 2, "fileName": "positive1.yaml" }, { - "queryName": "EC2 Not EBS Optimized", + "queryName": "EC2 instance is not EBS optimized", "severity": "LOW", "line": 8, "fileName": "positive2.yaml" }, { - "queryName": "EC2 Not EBS Optimized", + "queryName": "EC2 instance is not EBS optimized", "severity": "LOW", "line": 2, "fileName": "positive3.yaml" diff --git a/assets/queries/ansible/aws/ecr_image_tag_not_immutable/metadata.json b/assets/queries/ansible/aws/ecr_image_tag_not_immutable/metadata.json index 09f2b49b..52456c45 100644 --- a/assets/queries/ansible/aws/ecr_image_tag_not_immutable/metadata.json +++ b/assets/queries/ansible/aws/ecr_image_tag_not_immutable/metadata.json @@ -1,9 +1,9 @@ { "id": "60bfbb8a-c72f-467f-a6dd-a46b7d612789", - "queryName": "ECR Image Tag Not Immutable", + "queryName": "ECR image tag not immutable", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "ECR should have an image tag be immutable. This prevents image tags from being overwritten.", + "descriptionText": "ECR repositories should enforce immutable image tags to prevent tags from being overwritten. Allowing mutable tags can enable accidental or malicious replacement of images, facilitating supply-chain tampering or execution of unexpected code. For Ansible tasks using the `community.aws.ecs_ecr` or `ecs_ecr` modules, the `image_tag_mutability` property must be defined and set to the literal string `\"immutable\"`. Resources missing this property or set to any other value are flagged.\n\nSecure Ansible task example:\n\n```yaml\n- name: Create ECR repository with immutable tags\n community.aws.ecs_ecr:\n name: my-repo\n image_tag_mutability: immutable\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ecr_image_tag_not_immutable", "platform": "Ansible", "descriptionID": "a9bdce24", diff --git a/assets/queries/ansible/aws/ecr_image_tag_not_immutable/test/positive_expected_result.json b/assets/queries/ansible/aws/ecr_image_tag_not_immutable/test/positive_expected_result.json index 738ab12a..d1780bfb 100644 --- a/assets/queries/ansible/aws/ecr_image_tag_not_immutable/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ecr_image_tag_not_immutable/test/positive_expected_result.json @@ -1,13 +1,12 @@ [ - { - "queryName": "ECR Image Tag Not Immutable", - "severity": "MEDIUM", - "line": 2 - }, - - { - "queryName": "ECR Image Tag Not Immutable", - "severity": "MEDIUM", - "line": 7 - } + { + "queryName": "ECR image tag not immutable", + "severity": "MEDIUM", + "line": 2 + }, + { + "queryName": "ECR image tag not immutable", + "severity": "MEDIUM", + "line": 7 + } ] diff --git a/assets/queries/ansible/aws/ecr_repository_is_publicly_accessible/metadata.json b/assets/queries/ansible/aws/ecr_repository_is_publicly_accessible/metadata.json index ada62a55..566bca3f 100644 --- a/assets/queries/ansible/aws/ecr_repository_is_publicly_accessible/metadata.json +++ b/assets/queries/ansible/aws/ecr_repository_is_publicly_accessible/metadata.json @@ -1,9 +1,9 @@ { "id": "fb5a5df7-6d74-4243-ab82-ff779a958bfd", - "queryName": "ECR Repository Is Publicly Accessible", + "queryName": "ECR repository is publicly accessible", "severity": "CRITICAL", "category": "Access Control", - "descriptionText": "Amazon ECR image repositories shouldn't have public access", + "descriptionText": "ECR repository policies must not grant Allow permissions to the wildcard principal (`*`). This makes repositories publicly accessible and allows unauthorized accounts to pull or push container images, increasing the risk of data exposure and supply-chain compromise.\n\nCheck Ansible ECS/ECR tasks using the `community.aws.ecs_ecr` or `ecs_ecr` modules: in the resource `policy` document, any statement with `\"Effect\": \"Allow\"` must not have `Principal` equal to `\"*\"`. Resources with an Allow statement whose `Principal` is `\"*\"` are flagged. Instead, specify explicit principals such as AWS account ARNs, IAM role ARNs, or service principals, or restrict access using condition keys (for example, `aws:PrincipalOrgID`).\n\nSecure example with an explicit AWS account principal:\n\n```json\n{\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": { \"AWS\": \"arn:aws:iam::123456789012:root\" },\n \"Action\": [ \"ecr:GetDownloadUrlForLayer\", \"ecr:BatchGetImage\" ],\n \"Resource\": \"arn:aws:ecr:us-east-1:123456789012:repository/my-repo\"\n }\n ]\n}\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ecr_repository_is_publicly_accessible", "platform": "Ansible", "descriptionID": "060d624f", diff --git a/assets/queries/ansible/aws/ecr_repository_is_publicly_accessible/test/positive_expected_result.json b/assets/queries/ansible/aws/ecr_repository_is_publicly_accessible/test/positive_expected_result.json index 5676de08..3edc95a8 100644 --- a/assets/queries/ansible/aws/ecr_repository_is_publicly_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ecr_repository_is_publicly_accessible/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "ECR Repository Is Publicly Accessible", + "queryName": "ECR repository is publicly accessible", "severity": "CRITICAL", "line": 4 }, { - "queryName": "ECR Repository Is Publicly Accessible", + "queryName": "ECR repository is publicly accessible", "severity": "CRITICAL", "line": 17 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/ecs_service_admin_role_is_present/metadata.json b/assets/queries/ansible/aws/ecs_service_admin_role_is_present/metadata.json index dcacf70a..7f1444fd 100644 --- a/assets/queries/ansible/aws/ecs_service_admin_role_is_present/metadata.json +++ b/assets/queries/ansible/aws/ecs_service_admin_role_is_present/metadata.json @@ -1,9 +1,9 @@ { "id": "7db727c1-1720-468e-b80e-06697f71e09e", - "queryName": "ECS Service Admin Role Is Present", + "queryName": "ECS service admin role is present", "severity": "HIGH", "category": "Access Control", - "descriptionText": "ECS Services must not have Admin roles, which means the attribute 'role' must not be an admin role", + "descriptionText": "ECS services must not be assigned administrative IAM roles. Admin-level privileges grant containers broad account-wide access and increase the risk of privilege escalation and lateral movement if the service is compromised. In Ansible tasks using `community.aws.ecs_service` or `ecs_service`, the `role` property must reference a least-privilege IAM role or ARN and must not contain the substring \"admin\" (case-insensitive). This rule flags tasks where `role` is a string that includes \"admin\". Roles omitted or defined via non-string constructs may not be detected and should be reviewed to ensure they do not attach the `AdministratorAccess` policy.\n\nSecure example referencing a non-admin role:\n\n```yaml\n- name: my-ecs-service\n community.aws.ecs_service:\n name: my-service\n cluster: my-cluster\n task_definition: my-task:1\n role: arn:aws:iam::123456789012:role/ecsTaskRole\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ecs_service_admin_role_is_present", "platform": "Ansible", "descriptionID": "32e74c18", diff --git a/assets/queries/ansible/aws/ecs_service_admin_role_is_present/test/positive_expected_result.json b/assets/queries/ansible/aws/ecs_service_admin_role_is_present/test/positive_expected_result.json index 0b9e1c22..06693f50 100644 --- a/assets/queries/ansible/aws/ecs_service_admin_role_is_present/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ecs_service_admin_role_is_present/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ - { - "queryName": "ECS Service Admin Role Is Present", - "severity": "HIGH", - "line": 9 - } + { + "queryName": "ECS service admin role is present", + "severity": "HIGH", + "line": 9 + } ] diff --git a/assets/queries/ansible/aws/ecs_service_without_running_tasks/metadata.json b/assets/queries/ansible/aws/ecs_service_without_running_tasks/metadata.json index 6049a99a..d4977635 100644 --- a/assets/queries/ansible/aws/ecs_service_without_running_tasks/metadata.json +++ b/assets/queries/ansible/aws/ecs_service_without_running_tasks/metadata.json @@ -1,9 +1,9 @@ { "id": "f5c45127-1d28-4b49-a692-0b97da1c3a84", - "queryName": "ECS Service Without Running Tasks", + "queryName": "ECS service without running tasks", "severity": "LOW", "category": "Availability", - "descriptionText": "ECS Service should have at least 1 task running", + "descriptionText": "ECS services must define a deployment configuration to avoid deployments or scaling events from temporarily leaving zero tasks running, which can cause application downtime and loss of availability.\n\nFor Ansible ECS tasks using the `community.aws.ecs_service` or `ecs_service` modules, the `deployment_configuration` property must be present and include the `minimum_healthy_percent` and `maximum_percent` keys. Resources missing `deployment_configuration` or missing either `minimum_healthy_percent` or `maximum_percent` are flagged. This rule checks for the presence of those keys and does not validate numeric ranges. Ensure `minimum_healthy_percent` is set so at least one task remains running during deployments according to your desired task count.\n\nSecure example (Ansible task):\n\n```yaml\n- name: my-ecs-service\n community.aws.ecs_service:\n name: my-service\n cluster: my-cluster\n task_definition: my-task:1\n desired_count: 2\n deployment_configuration:\n maximum_percent: 200\n minimum_healthy_percent: 50\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ecs_service_without_running_tasks", "platform": "Ansible", "descriptionID": "dce30fcb", diff --git a/assets/queries/ansible/aws/ecs_service_without_running_tasks/test/positive_expected_result.json b/assets/queries/ansible/aws/ecs_service_without_running_tasks/test/positive_expected_result.json index b8fd110d..b35e6bea 100644 --- a/assets/queries/ansible/aws/ecs_service_without_running_tasks/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ecs_service_without_running_tasks/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "ECS Service Without Running Tasks", + "queryName": "ECS service without running tasks", "severity": "LOW", "line": 2 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/ecs_services_assigned_with_public_ip_address/metadata.json b/assets/queries/ansible/aws/ecs_services_assigned_with_public_ip_address/metadata.json index 91c6498f..d3869f9d 100644 --- a/assets/queries/ansible/aws/ecs_services_assigned_with_public_ip_address/metadata.json +++ b/assets/queries/ansible/aws/ecs_services_assigned_with_public_ip_address/metadata.json @@ -1,9 +1,9 @@ { "id": "560f256b-0b45-4496-bcb5-733681e7d38d", - "queryName": "ECS Services assigned with public IP address", + "queryName": "ECS services should not be assigned public IP addresses", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Amazon ECS Services should not be assigned public IP addresses. Public IP assignment exposes services directly to the internet, increasing the attack surface and potential unauthorized access.", + "descriptionText": "Amazon ECS services should not be assigned public IP addresses. Attaching public IPs exposes tasks directly to the internet, increasing the attack surface and the risk of unauthorized access.\n\nFor Ansible tasks using the `community.aws.ecs_service` or `ecs_service` modules, the `network_configuration.assign_public_ip` property must be defined and set to `false`. Tasks with `assign_public_ip: true` are flagged. If services require outbound internet access, use private subnets with a NAT Gateway or expose services via a load balancer instead of assigning public IPs.\n\nSecure configuration example:\n\n```yaml\n- name: Create ECS service with no public IP\n community.aws.ecs_service:\n name: my-service\n cluster: my-cluster\n task_definition: my-task:1\n network_configuration:\n subnets:\n - subnet-0123456789abcdef0\n security_groups:\n - sg-0123456789abcdef0\n assign_public_ip: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ecs_services_assigned_with_public_ip_address", "platform": "Ansible", "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/ecs_service_module.html", diff --git a/assets/queries/ansible/aws/ecs_services_assigned_with_public_ip_address/test/positive_expected_result.json b/assets/queries/ansible/aws/ecs_services_assigned_with_public_ip_address/test/positive_expected_result.json index 14ccf071..82e5810c 100644 --- a/assets/queries/ansible/aws/ecs_services_assigned_with_public_ip_address/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ecs_services_assigned_with_public_ip_address/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "ECS Services assigned with public IP address", + "queryName": "ECS services should not be assigned public IP addresses", "severity": "MEDIUM", "line": 19 } diff --git a/assets/queries/ansible/aws/ecs_task_definition_network_mode_not_recommended/metadata.json b/assets/queries/ansible/aws/ecs_task_definition_network_mode_not_recommended/metadata.json index 177045a6..c4fe2ad2 100644 --- a/assets/queries/ansible/aws/ecs_task_definition_network_mode_not_recommended/metadata.json +++ b/assets/queries/ansible/aws/ecs_task_definition_network_mode_not_recommended/metadata.json @@ -1,9 +1,9 @@ { "id": "01aec7c2-3e4d-4274-ae47-2b8fea22fd1f", - "queryName": "ECS Task Definition Network Mode Not Recommended", + "queryName": "ECS task definition network mode not recommended", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Network_Mode should be 'awsvpc' in ecs_task_definition. AWS VPCs provides the controls to facilitate a formal process for approving and testing all network connections and changes to the firewall and router configurations", + "descriptionText": "ECS task definitions must use the `awsvpc` network mode so each task receives its own ENI and can be isolated with security groups and VPC controls. Without `awsvpc`, tasks may share the host network namespace or lack per-task security group enforcement, increasing exposure to lateral movement and unintended network access.\n\nThe `network_mode` property in Ansible `community.aws.ecs_taskdefinition` or `ecs_taskdefinition` resources must be set to `\"awsvpc\"`. Resources missing `network_mode` or with values such as `\"host\"`, `\"bridge\"`, or `\"none\"` are flagged. AWS Fargate requires `awsvpc`, and using legacy modes causes tasks to share host networking and bypass per-task security group rules.\n\nSecure configuration example:\n\n```yaml\n- name: Register ECS task definition with awsvpc\n community.aws.ecs_taskdefinition:\n family: my-task\n network_mode: awsvpc\n container_definitions: \"{{ lookup('file', 'containers.json') }}\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ecs_task_definition_network_mode_not_recommended", "platform": "Ansible", "descriptionID": "5424397d", diff --git a/assets/queries/ansible/aws/ecs_task_definition_network_mode_not_recommended/test/positive_expected_result.json b/assets/queries/ansible/aws/ecs_task_definition_network_mode_not_recommended/test/positive_expected_result.json index 84a0f173..ef76b94d 100644 --- a/assets/queries/ansible/aws/ecs_task_definition_network_mode_not_recommended/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ecs_task_definition_network_mode_not_recommended/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "ECS Task Definition Network Mode Not Recommended", + "queryName": "ECS task definition network mode not recommended", "severity": "MEDIUM", "line": 15 }, { - "queryName": "ECS Task Definition Network Mode Not Recommended", + "queryName": "ECS task definition network mode not recommended", "severity": "MEDIUM", "line": 31 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/efs_not_encrypted/metadata.json b/assets/queries/ansible/aws/efs_not_encrypted/metadata.json index 2f658da0..f2760179 100644 --- a/assets/queries/ansible/aws/efs_not_encrypted/metadata.json +++ b/assets/queries/ansible/aws/efs_not_encrypted/metadata.json @@ -1,9 +1,9 @@ { "id": "727c4fd4-d604-4df6-a179-7713d3c85e20", - "queryName": "EFS Not Encrypted", + "queryName": "EFS not encrypted", "severity": "HIGH", "category": "Encryption", - "descriptionText": "Elastic File System (EFS) must be encrypted", + "descriptionText": "EFS file systems must have encryption enabled to protect data at rest and prevent exposure of file system contents, snapshots, and backups if storage media is compromised. For Ansible tasks using the `community.aws.efs` or `efs` modules, the `encrypt` property must be defined and set to `true`. Resources that omit `encrypt` or have `encrypt: false` are flagged as misconfigured. \n\nSecure example:\n\n```yaml\n- name: Create encrypted EFS filesystem\n community.aws.efs:\n name: my-efs\n encrypt: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/efs_not_encrypted", "platform": "Ansible", "descriptionID": "f4c8801c", diff --git a/assets/queries/ansible/aws/efs_not_encrypted/test/positive_expected_result.json b/assets/queries/ansible/aws/efs_not_encrypted/test/positive_expected_result.json index 970263b7..be0566d3 100644 --- a/assets/queries/ansible/aws/efs_not_encrypted/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/efs_not_encrypted/test/positive_expected_result.json @@ -1,16 +1,16 @@ [ { - "queryName": "EFS Not Encrypted", + "queryName": "EFS not encrypted", "severity": "HIGH", "line": 6 }, { - "queryName": "EFS Not Encrypted", + "queryName": "EFS not encrypted", "severity": "HIGH", "line": 17 }, { - "queryName": "EFS Not Encrypted", + "queryName": "EFS not encrypted", "severity": "HIGH", "line": 25 } diff --git a/assets/queries/ansible/aws/efs_without_kms/metadata.json b/assets/queries/ansible/aws/efs_without_kms/metadata.json index e1e981b9..e07d381a 100644 --- a/assets/queries/ansible/aws/efs_without_kms/metadata.json +++ b/assets/queries/ansible/aws/efs_without_kms/metadata.json @@ -1,9 +1,9 @@ { "id": "bd77554e-f138-40c5-91b2-2a09f878608e", - "queryName": "EFS Without KMS", + "queryName": "EFS without KMS", "severity": "LOW", "category": "Encryption", - "descriptionText": "Amazon Elastic Filesystem should have filesystem encryption enabled using KMS CMK customer-managed keys instead of AWS managed-keys", + "descriptionText": "EFS filesystems should be encrypted with a customer-managed AWS KMS CMK to protect data at rest and maintain control over key rotation, access policies, and audit logging.\n\nIn Ansible, the `kms_key_id` option on the `community.aws.efs` (or legacy `efs`) module must be defined and set to a customer-managed key identifier (KMS key ID, key ARN, or alias) rather than relying on the AWS-managed key. Tasks that omit `kms_key_id` or leave it undefined default to an AWS-managed key and are flagged by this rule.\n\nSecure configuration example:\n\n```yaml\n- name: Create encrypted EFS filesystem\n community.aws.efs:\n name: my-efs\n performance_mode: generalPurpose\n kms_key_id: arn:aws:kms:us-east-1:123456789012:key/abcdef12-3456-7890-abcd-ef1234567890\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/efs_without_kms", "platform": "Ansible", "descriptionID": "a01870d5", diff --git a/assets/queries/ansible/aws/efs_without_kms/test/positive_expected_result.json b/assets/queries/ansible/aws/efs_without_kms/test/positive_expected_result.json index 8995f772..4117f083 100644 --- a/assets/queries/ansible/aws/efs_without_kms/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/efs_without_kms/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "EFS Without KMS", + "queryName": "EFS without KMS", "severity": "LOW", "line": 3 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/efs_without_tags/metadata.json b/assets/queries/ansible/aws/efs_without_tags/metadata.json index e381e2f3..ddc0639c 100644 --- a/assets/queries/ansible/aws/efs_without_tags/metadata.json +++ b/assets/queries/ansible/aws/efs_without_tags/metadata.json @@ -1,9 +1,9 @@ { "id": "b8a9852c-9943-4973-b8d5-77dae9352851", - "queryName": "EFS Without Tags", + "queryName": "EFS without tags", "severity": "LOW", "category": "Build Process", - "descriptionText": "Amazon Elastic Filesystem should have filesystem tags associated", + "descriptionText": "EFS filesystems must have tags defined to support asset identification, tag-based access control, cost allocation, and automated lifecycle or compliance policies. For Ansible tasks using the `community.aws.efs` or `efs` modules, the `tags` property must be present and contain at least one key/value pair. Tasks that omit the `tags` property or provide an empty mapping are flagged as missing required metadata.\n\nSecure example:\n\n```yaml\n- name: Create EFS filesystem\n community.aws.efs:\n state: present\n name: my-efs\n performance_mode: generalPurpose\n tags:\n Name: my-efs\n Environment: production\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/efs_without_tags", "platform": "Ansible", "descriptionID": "1e03284b", diff --git a/assets/queries/ansible/aws/efs_without_tags/test/positive_expected_result.json b/assets/queries/ansible/aws/efs_without_tags/test/positive_expected_result.json index 8594bf83..336a0020 100644 --- a/assets/queries/ansible/aws/efs_without_tags/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/efs_without_tags/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "EFS Without Tags", + "queryName": "EFS without tags", "severity": "LOW", "line": 2 } diff --git a/assets/queries/ansible/aws/elasticache_using_default_port/metadata.json b/assets/queries/ansible/aws/elasticache_using_default_port/metadata.json index e383c006..46e6bdef 100644 --- a/assets/queries/ansible/aws/elasticache_using_default_port/metadata.json +++ b/assets/queries/ansible/aws/elasticache_using_default_port/metadata.json @@ -1,9 +1,9 @@ { "id": "7cc6c791-5f68-4816-a564-b9b699f9d26e", - "queryName": "ElastiCache Using Default Port", + "queryName": "ElastiCache using default port", "severity": "LOW", "category": "Networking and Firewall", - "descriptionText": "ElastiCache should not use the default port (an attacker can easily guess the port). For engine set to Redis, the default port is 6379. The Memcached default port is 11211", + "descriptionText": "ElastiCache instances using engine default ports are easy for attackers and automated scanners to discover and target, increasing the risk of unauthorized access and automated exploitation.\n\nIn Ansible, tasks that use the `community.aws.elasticache` or `elasticache` module must not set the `cache_port` property to the engine defaults: `6379` when `engine: redis` and `11211` when `engine: memcached`. Resources with `cache_port` equal to these default values are flagged. Choose a non-standard port and enforce network access controls (security groups/subnets) to limit exposure.\n\nSecure example changing the default port:\n\n```yaml\n- name: Create Redis ElastiCache cluster with non-default port\n community.aws.elasticache:\n name: my-redis-cluster\n engine: redis\n cache_port: 6380\n # other required properties...\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/elasticache_using_default_port", "platform": "Ansible", "descriptionID": "be73fca3", diff --git a/assets/queries/ansible/aws/elasticache_using_default_port/test/positive_expected_result.json b/assets/queries/ansible/aws/elasticache_using_default_port/test/positive_expected_result.json index a729b0e9..09b02861 100644 --- a/assets/queries/ansible/aws/elasticache_using_default_port/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/elasticache_using_default_port/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "ElastiCache Using Default Port", + "queryName": "ElastiCache using default port", "severity": "LOW", "line": 9, "fileName": "positive1.yaml" }, { - "queryName": "ElastiCache Using Default Port", + "queryName": "ElastiCache using default port", "severity": "LOW", "line": 9, "fileName": "positive2.yaml" diff --git a/assets/queries/ansible/aws/elasticache_without_vpc/metadata.json b/assets/queries/ansible/aws/elasticache_without_vpc/metadata.json index aef07dc4..94bc4807 100644 --- a/assets/queries/ansible/aws/elasticache_without_vpc/metadata.json +++ b/assets/queries/ansible/aws/elasticache_without_vpc/metadata.json @@ -1,9 +1,9 @@ { "id": "5527dcfc-94f9-4bf6-b7d4-1b78850cf41f", - "queryName": "ElastiCache Without VPC", + "queryName": "ElastiCache without VPC", "severity": "LOW", "category": "Networking and Firewall", - "descriptionText": "ElastiCache should be launched in a Virtual Private Cloud (VPC)", + "descriptionText": "ElastiCache clusters must be launched in a VPC to provide network isolation and reduce the risk of unauthorized access to cached data or lateral movement within your environment.\n\nIn Ansible playbooks, tasks using the `community.aws.elasticache` or `elasticache` modules must set the `cache_subnet_group` property to the name of an existing ElastiCache subnet group. A task where `cache_subnet_group` is undefined or null is flagged because omission typically results in resources being created outside a VPC or without the intended subnet isolation.\n\nSecure Ansible example:\n\n```yaml\n- name: Create ElastiCache cluster in VPC\n community.aws.elasticache:\n name: my-cache\n engine: redis\n cache_subnet_group: my-cache-subnet-group\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/elasticache_without_vpc", "platform": "Ansible", "descriptionID": "c9bde487", diff --git a/assets/queries/ansible/aws/elasticache_without_vpc/test/positive_expected_result.json b/assets/queries/ansible/aws/elasticache_without_vpc/test/positive_expected_result.json index 6367ea5e..e6878499 100644 --- a/assets/queries/ansible/aws/elasticache_without_vpc/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/elasticache_without_vpc/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ - { - "queryName": "ElastiCache Without VPC", - "severity": "LOW", - "line": 2, - "fileName": "positive.yaml" - } + { + "queryName": "ElastiCache without VPC", + "severity": "LOW", + "line": 2, + "fileName": "positive.yaml" + } ] diff --git a/assets/queries/ansible/aws/elasticsearch_with_https_disabled/metadata.json b/assets/queries/ansible/aws/elasticsearch_with_https_disabled/metadata.json index d1e9c983..f0ec52ea 100644 --- a/assets/queries/ansible/aws/elasticsearch_with_https_disabled/metadata.json +++ b/assets/queries/ansible/aws/elasticsearch_with_https_disabled/metadata.json @@ -3,7 +3,7 @@ "queryName": "Elasticsearch with HTTPS disabled", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Amazon Elasticsearch does not have encryption for its domains enabled. To prevent such a scenario, update the attribute 'EnforceHTTPS' to true.", + "descriptionText": "OpenSearch domain endpoints must enforce HTTPS to ensure client connections use TLS and prevent interception or tampering of sensitive data such as queries and credentials. In Ansible tasks using the `community.aws.opensearch` or `opensearch` modules, the `domain_endpoint_options.enforce_https` property must be set to `true`. Tasks that omit `domain_endpoint_options` or `enforce_https`, or that set `enforce_https: false`, are flagged.\n\nSecure Ansible task example:\n\n```yaml\n- name: create opensearch domain with HTTPS enforced\n community.aws.opensearch:\n domain_name: my-domain\n domain_endpoint_options:\n enforce_https: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/elasticsearch_with_https_disabled", "platform": "Ansible", "descriptionID": "4beff10d", diff --git a/assets/queries/ansible/aws/elb_using_insecure_protocols/metadata.json b/assets/queries/ansible/aws/elb_using_insecure_protocols/metadata.json index 66df1470..903abed5 100644 --- a/assets/queries/ansible/aws/elb_using_insecure_protocols/metadata.json +++ b/assets/queries/ansible/aws/elb_using_insecure_protocols/metadata.json @@ -1,14 +1,14 @@ { "id": "730a5951-2760-407a-b032-dd629b55c23a", - "queryName": "ELB Using Insecure Protocols", + "queryName": "ELB using insecure protocols", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "ELB Predefined or Custom Security Policies must not use insecure protocols, to reduce the risk of the SSL connection between the client and the load balancer being exploited. That means the 'SslPolicy' of 'listeners' must not coincide with any of a predefined list of insecure protocols.", + "descriptionText": "Load balancer listeners must use secure TLS policies to prevent protocol downgrade and known cryptographic vulnerabilities that could allow interception or decryption of client traffic.\n\nFor Ansible ELB modules (`community.aws.elb_network_lb`, `elb_network_lb`, `amazon.aws.elb_application_lb`, `elb_application_lb`), the `listeners` property must be defined and each listener must include `SslPolicy` set to a modern, secure policy (not legacy SSL/TLS protocol policies).\n\nThis rule flags resources missing `listeners`, listeners missing `SslPolicy`, or any `SslPolicy` set to `Protocol-SSLv2`, `Protocol-SSLv3`, `Protocol-TLSv1`, or `Protocol-TLSv1.1`. \n\nSecure example (use a TLS 1.2+ policy):\n\n```yaml\n- name: create application load balancer\n amazon.aws.elb_application_lb:\n name: my-alb\n listeners:\n - Protocol: HTTPS\n Port: 443\n SslPolicy: ELBSecurityPolicy-TLS-1-2-2017-01\n CertificateArn: arn:aws:acm:us-east-1:123456789012:certificate/abcd-1234\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/elb_using_insecure_protocols", "platform": "Ansible", "descriptionID": "8a2e6f3b", "cloudProvider": "aws", "cwe": "284", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/elb_application_lb_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/elb_application_lb_module.html" } diff --git a/assets/queries/ansible/aws/elb_using_insecure_protocols/test/positive_expected_result.json b/assets/queries/ansible/aws/elb_using_insecure_protocols/test/positive_expected_result.json index 0c3e3ad8..8e4d917a 100644 --- a/assets/queries/ansible/aws/elb_using_insecure_protocols/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/elb_using_insecure_protocols/test/positive_expected_result.json @@ -1,32 +1,32 @@ [ { - "queryName": "ELB Using Insecure Protocols", + "queryName": "ELB using insecure protocols", "severity": "MEDIUM", "line": 3 }, { - "queryName": "ELB Using Insecure Protocols", + "queryName": "ELB using insecure protocols", "severity": "MEDIUM", "line": 21 }, { - "queryName": "ELB Using Insecure Protocols", + "queryName": "ELB using insecure protocols", "severity": "MEDIUM", "line": 40 }, { - "queryName": "ELB Using Insecure Protocols", + "queryName": "ELB using insecure protocols", "severity": "MEDIUM", "line": 52 }, { - "queryName": "ELB Using Insecure Protocols", + "queryName": "ELB using insecure protocols", "severity": "MEDIUM", "line": 70 }, { - "queryName": "ELB Using Insecure Protocols", + "queryName": "ELB using insecure protocols", "severity": "MEDIUM", "line": 89 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/elb_using_weak_ciphers/metadata.json b/assets/queries/ansible/aws/elb_using_weak_ciphers/metadata.json index 4b6e5785..d5b86b0e 100644 --- a/assets/queries/ansible/aws/elb_using_weak_ciphers/metadata.json +++ b/assets/queries/ansible/aws/elb_using_weak_ciphers/metadata.json @@ -1,13 +1,13 @@ { "id": "2034fb37-bc23-4ca0-8d95-2b9f15829ab5", - "queryName": "ELB Using Weak Ciphers", + "queryName": "ELB using weak ciphers", "severity": "HIGH", "category": "Encryption", - "descriptionText": "ELB Predefined or Custom Security Policies must not use weak ciphers, to reduce the risk of the SSL connection between the client and the load balancer being exploited. That means the 'SslPolicy' of 'listeners' must not coincide with any of a predefined list of weak ciphers.", + "descriptionText": "ELB listeners must specify a strong SSL/TLS policy because weak cipher suites can enable protocol downgrade, interception, or decryption of traffic between clients and the load balancer. For Ansible ELB Application and Network load balancer modules (`amazon.aws.elb_application_lb`, `elb_application_lb`, `community.aws.elb_network_lb`, `elb_network_lb`), the `listeners` list must be defined and each listener must include the `SslPolicy` property set to a non-weak policy.\n\nResources missing `listeners` or listener entries missing `SslPolicy` are flagged. Any `SslPolicy` that matches a known weak policy in your baseline should be replaced with an AWS-managed strong policy (for example, a TLS 1.2+ policy) or a custom policy that excludes weak ciphers.\n\nSecure configuration example:\n\n```yaml\n- name: Create ALB with strong TLS policy\n amazon.aws.elb_application_lb:\n name: my-alb\n listeners:\n - Protocol: HTTPS\n Port: 443\n SslPolicy: ELBSecurityPolicy-TLS-1-2-2017-01\n CertificateArn: arn:aws:acm:us-west-2:123456789012:certificate/abcd-1234\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/elb_using_weak_ciphers", "platform": "Ansible", "descriptionID": "ab5b4a0b", "cloudProvider": "aws", "cwe": "326", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/elb_application_lb_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/elb_application_lb_module.html" } diff --git a/assets/queries/ansible/aws/elb_using_weak_ciphers/test/positive_expected_result.json b/assets/queries/ansible/aws/elb_using_weak_ciphers/test/positive_expected_result.json index deda62d1..493e62cd 100644 --- a/assets/queries/ansible/aws/elb_using_weak_ciphers/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/elb_using_weak_ciphers/test/positive_expected_result.json @@ -1,32 +1,32 @@ [ - { - "queryName": "ELB Using Weak Ciphers", - "severity": "HIGH", - "line": 3 - }, - { - "queryName": "ELB Using Weak Ciphers", - "severity": "HIGH", - "line": 21 - }, - { - "queryName": "ELB Using Weak Ciphers", - "severity": "HIGH", - "line": 40 - }, - { - "queryName": "ELB Using Weak Ciphers", - "severity": "HIGH", - "line": 52 - }, - { - "queryName": "ELB Using Weak Ciphers", - "severity": "HIGH", - "line": 70 - }, - { - "queryName": "ELB Using Weak Ciphers", - "severity": "HIGH", - "line": 89 - } + { + "queryName": "ELB using weak ciphers", + "severity": "HIGH", + "line": 3 + }, + { + "queryName": "ELB using weak ciphers", + "severity": "HIGH", + "line": 21 + }, + { + "queryName": "ELB using weak ciphers", + "severity": "HIGH", + "line": 40 + }, + { + "queryName": "ELB using weak ciphers", + "severity": "HIGH", + "line": 52 + }, + { + "queryName": "ELB using weak ciphers", + "severity": "HIGH", + "line": 70 + }, + { + "queryName": "ELB using weak ciphers", + "severity": "HIGH", + "line": 89 + } ] diff --git a/assets/queries/ansible/aws/hardcoded_aws_access_key/metadata.json b/assets/queries/ansible/aws/hardcoded_aws_access_key/metadata.json index 1d54dd5a..849d2eb2 100644 --- a/assets/queries/ansible/aws/hardcoded_aws_access_key/metadata.json +++ b/assets/queries/ansible/aws/hardcoded_aws_access_key/metadata.json @@ -1,12 +1,12 @@ { "id": "c2f15af3-66a0-4176-a56e-e4711e502e5c", - "queryName": "Hardcoded AWS Access Key", + "queryName": "Hardcoded AWS access key", "severity": "HIGH", "category": "Secret Management", - "descriptionText": "AWS Access Key should not be hardcoded", + "descriptionText": "Embedding AWS access keys in EC2 user data exposes credentials to source control, logs, and anyone with access to the instance. Attackers can use these credentials to access or escalate privileges in your AWS account.\n\nThis rule checks Ansible tasks using the `amazon.aws.ec2_instance` or `ec2_instance` modules and flags the `user_data` property when it contains patterns matching AWS Access Key IDs (20 uppercase alphanumeric characters) or Secret Access Keys (40-character base64-like strings). Resources whose `user_data` contains sequences matching those key patterns are flagged.\n\nDo not hardcode credentials. Assign an IAM instance profile to the instance or retrieve secrets at runtime from AWS Secrets Manager or AWS Systems Manager Parameter Store and inject them securely.\n\nSecure example using an instance profile and avoiding embedded keys:\n\n```yaml\n- name: Launch EC2 without hardcoded keys\n amazon.aws.ec2_instance:\n name: my-instance\n image_id: ami-0123456789abcdef0\n instance_type: t3.micro\n instance_profile_name: my-iam-instance-profile\n user_data: |\n #!/bin/bash\n # No hardcoded AWS keys here; fetch secrets from SSM or Secrets Manager at runtime\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/hardcoded_aws_access_key", "platform": "Ansible", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/ec2_instance_module.html", + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html", "descriptionID": "d764256e", "cloudProvider": "aws", "cwe": "798", diff --git a/assets/queries/ansible/aws/hardcoded_aws_access_key/test/positive_expected_result.json b/assets/queries/ansible/aws/hardcoded_aws_access_key/test/positive_expected_result.json index 85ca16e0..b58b412f 100644 --- a/assets/queries/ansible/aws/hardcoded_aws_access_key/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/hardcoded_aws_access_key/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Hardcoded AWS Access Key", + "queryName": "Hardcoded AWS access key", "severity": "HIGH", "line": 7 } diff --git a/assets/queries/ansible/aws/hardcoded_aws_access_key_in_lambda/metadata.json b/assets/queries/ansible/aws/hardcoded_aws_access_key_in_lambda/metadata.json index 19b597cb..a8bf139b 100644 --- a/assets/queries/ansible/aws/hardcoded_aws_access_key_in_lambda/metadata.json +++ b/assets/queries/ansible/aws/hardcoded_aws_access_key_in_lambda/metadata.json @@ -1,12 +1,12 @@ { "id": "f34508b9-f574-4330-b42d-88c44cced645", - "queryName": "Hardcoded AWS Access Key In Lambda", + "queryName": "Hardcoded AWS access key in Lambda", "severity": "HIGH", "category": "Secret Management", - "descriptionText": "Lambda access/secret keys should not be hardcoded", + "descriptionText": "Hardcoding AWS secret access keys in Ansible Lambda tasks exposes credentials to source control, logs, and build artifacts. Attackers who obtain the key can impersonate the account and access AWS resources. This check targets Ansible tasks using the `amazon.aws.lambda` or `lambda` modules and flags tasks that include an `aws_access_key` property containing a 40-character plaintext secret (matched by regex `^[A-Za-z0-9/+=]{40}$`).\n\nDo not set `aws_access_key` or `aws_secret_key` inline. Instead, supply credentials via IAM instance/profile roles, shared AWS credential profiles, environment variables, or encrypted secrets (Ansible Vault or a secrets manager). You can also reference vaulted or lookup variables in the task. Tasks with a literal 40-character `aws_access_key` value are flagged. Omitting the properties to rely on role-based auth or referencing vaulted variables is acceptable.\n\nSecure examples:\n\n```yaml\n- name: Deploy Lambda using instance profile (no inline credentials)\n amazon.aws.lambda:\n name: my_function\n state: present\n region: us-east-1\n```\n\n```yaml\n- name: Deploy Lambda with credentials stored in Ansible Vault\n amazon.aws.lambda:\n name: my_function\n state: present\n region: us-east-1\n aws_access_key: \"{{ vault_aws_access_key }}\"\n aws_secret_key: \"{{ vault_aws_secret_key }}\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/hardcoded_aws_access_key_in_lambda", "platform": "Ansible", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/lambda_module.html", + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_module.html", "descriptionID": "fc78f6de", "cloudProvider": "aws", "cwe": "798", diff --git a/assets/queries/ansible/aws/hardcoded_aws_access_key_in_lambda/test/positive_expected_result.json b/assets/queries/ansible/aws/hardcoded_aws_access_key_in_lambda/test/positive_expected_result.json index 7f437562..17dc4863 100644 --- a/assets/queries/ansible/aws/hardcoded_aws_access_key_in_lambda/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/hardcoded_aws_access_key_in_lambda/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "Hardcoded AWS Access Key In Lambda", + "queryName": "Hardcoded AWS access key in Lambda", "severity": "HIGH", "line": 3 }, { - "queryName": "Hardcoded AWS Access Key In Lambda", + "queryName": "Hardcoded AWS access key in Lambda", "severity": "HIGH", "line": 32 } diff --git a/assets/queries/ansible/aws/http_port_open_to_internet/metadata.json b/assets/queries/ansible/aws/http_port_open_to_internet/metadata.json index 0b4cccff..cf569fb9 100644 --- a/assets/queries/ansible/aws/http_port_open_to_internet/metadata.json +++ b/assets/queries/ansible/aws/http_port_open_to_internet/metadata.json @@ -1,9 +1,9 @@ { "id": "a14ad534-acbe-4a8e-9404-2f7e1045646e", - "queryName": "HTTP Port Open To Internet", + "queryName": "HTTP port open to internet", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "The HTTP port is open to the internet in a Security Group", + "descriptionText": "Allowing HTTP (TCP port 80) from 0.0.0.0/0 in a Security Group exposes services to unauthenticated public access and subjects unencrypted traffic to eavesdropping and automated scanning. In Ansible tasks using `amazon.aws.ec2_group` or `ec2_group`, this rule flags `rules` entries where `cidr_ip` is `0.0.0.0/0` and the entry opens port 80.\n\nResources with such `rules` are flagged. To remediate, restrict `cidr_ip` to explicit trusted CIDR ranges or remove the public HTTP rule. Alternatively, serve traffic over HTTPS (port 443) terminated at a load balancer or proxy with appropriate access controls.\n\nSecure example showing HTTP restricted to a trusted CIDR:\n\n```yaml\n- name: create security group with restricted HTTP\n amazon.aws.ec2_group:\n name: my-sg\n description: \"example\"\n rules:\n - proto: tcp\n from_port: 80\n to_port: 80\n cidr_ip: 10.0.0.0/16\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/http_port_open_to_internet", "platform": "Ansible", "descriptionID": "8c6031b8", diff --git a/assets/queries/ansible/aws/http_port_open_to_internet/test/positive_expected_result.json b/assets/queries/ansible/aws/http_port_open_to_internet/test/positive_expected_result.json index 094f0bad..eb8e0ae0 100644 --- a/assets/queries/ansible/aws/http_port_open_to_internet/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/http_port_open_to_internet/test/positive_expected_result.json @@ -1,37 +1,37 @@ [ { - "queryName": "HTTP Port Open To Internet", + "queryName": "HTTP port open to internet", "severity": "MEDIUM", "line": 9 }, { - "queryName": "HTTP Port Open To Internet", + "queryName": "HTTP port open to internet", "severity": "MEDIUM", "line": 23 }, { - "queryName": "HTTP Port Open To Internet", + "queryName": "HTTP port open to internet", "severity": "MEDIUM", "line": 36 }, { - "queryName": "HTTP Port Open To Internet", + "queryName": "HTTP port open to internet", "severity": "MEDIUM", "line": 49 }, { - "queryName": "HTTP Port Open To Internet", + "queryName": "HTTP port open to internet", "severity": "MEDIUM", "line": 64 }, { - "queryName": "HTTP Port Open To Internet", + "queryName": "HTTP port open to internet", "severity": "MEDIUM", "line": 79 }, { - "queryName": "HTTP Port Open To Internet", + "queryName": "HTTP port open to internet", "severity": "MEDIUM", "line": 93 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/iam_access_key_is_exposed/metadata.json b/assets/queries/ansible/aws/iam_access_key_is_exposed/metadata.json index be18a19f..afe87558 100644 --- a/assets/queries/ansible/aws/iam_access_key_is_exposed/metadata.json +++ b/assets/queries/ansible/aws/iam_access_key_is_exposed/metadata.json @@ -1,13 +1,13 @@ { "id": "7f79f858-fbe8-4186-8a2c-dfd0d958a40f", - "queryName": "IAM Access Key Is Exposed", + "queryName": "IAM access key is exposed", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "Check if IAM Access Key is active for some user besides 'root'", + "descriptionText": "Active, long‑lived access keys for non‑root IAM users increase the risk of credential compromise and unauthorized API access because leaked keys can be used to impersonate users and perform privileged actions. This rule inspects Ansible tasks that use the `amazon.aws.iam_access_key` or `iam_access_key` modules and flags tasks where the `active` property is `true` (or absent, since `true` is the default) and the `state` is not `absent`, while the `user_name` property does not contain `root`.\n\nResources with active access keys for non‑root users are flagged. Remediate by removing or deactivating unused keys, rotating keys frequently, or replacing long‑lived keys with IAM roles and temporary credentials. The check is case‑insensitive and treats any username containing the substring `root` as the root account exception.", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/iam_access_key_is_exposed", "platform": "Ansible", "descriptionID": "d8bc01a5", "cloudProvider": "aws", "cwe": "522", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_access_key_module.html" } diff --git a/assets/queries/ansible/aws/iam_access_key_is_exposed/test/positive_expected_result.json b/assets/queries/ansible/aws/iam_access_key_is_exposed/test/positive_expected_result.json index a0fbc1f5..f0d285f5 100644 --- a/assets/queries/ansible/aws/iam_access_key_is_exposed/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/iam_access_key_is_exposed/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "IAM Access Key Is Exposed", + "queryName": "IAM access key is exposed", "severity": "MEDIUM", "line": 2 }, { - "queryName": "IAM Access Key Is Exposed", + "queryName": "IAM access key is exposed", "severity": "MEDIUM", "line": 6 } diff --git a/assets/queries/ansible/aws/iam_database_auth_not_enabled/metadata.json b/assets/queries/ansible/aws/iam_database_auth_not_enabled/metadata.json index 97ca7cc7..cb7ee1e5 100644 --- a/assets/queries/ansible/aws/iam_database_auth_not_enabled/metadata.json +++ b/assets/queries/ansible/aws/iam_database_auth_not_enabled/metadata.json @@ -1,14 +1,14 @@ { "id": "0ed012a4-9199-43d2-b9e4-9bd049a48aa4", - "queryName": "IAM Database Auth Not Enabled", + "queryName": "IAM database authentication is not enabled", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "IAM Database Auth Enabled should be configured to true when using compatible engine and version", + "descriptionText": "IAM database authentication should be enabled to avoid reliance on static database passwords and centralize access control. This reduces the risk of credential leakage and makes rotation and auditing easier.\n\nFor Ansible RDS resources using the `amazon.aws.rds_instance` or `rds_instance` modules, the `enable_iam_database_authentication` property must be defined and set to `true`. This check only applies to engines, engine versions, and instance types that support IAM authentication. The policy validates `engine`, `engine_version`, and `instance_type`. Resources where the property is missing or set to `false` are flagged.\n\nSecure Ansible example:\n\n```yaml\n- name: Create RDS instance with IAM auth enabled\n amazon.aws.rds_instance:\n db_instance_identifier: mydb\n engine: mysql\n engine_version: \"8.0\"\n instance_type: db.t3.medium\n enable_iam_database_authentication: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/iam_database_auth_not_enabled", "platform": "Ansible", "descriptionID": "952e08fc", "cloudProvider": "aws", "cwe": "311", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/rds_instance_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html" } diff --git a/assets/queries/ansible/aws/iam_database_auth_not_enabled/test/positive_expected_result.json b/assets/queries/ansible/aws/iam_database_auth_not_enabled/test/positive_expected_result.json index 3c78515b..1a21ccee 100644 --- a/assets/queries/ansible/aws/iam_database_auth_not_enabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/iam_database_auth_not_enabled/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "IAM Database Auth Not Enabled", + "queryName": "IAM database authentication is not enabled", "severity": "MEDIUM", "line": 9 }, { - "queryName": "IAM Database Auth Not Enabled", + "queryName": "IAM database authentication is not enabled", "severity": "MEDIUM", "line": 23 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/iam_group_without_users/metadata.json b/assets/queries/ansible/aws/iam_group_without_users/metadata.json index 570b65b4..4c8eb069 100644 --- a/assets/queries/ansible/aws/iam_group_without_users/metadata.json +++ b/assets/queries/ansible/aws/iam_group_without_users/metadata.json @@ -1,14 +1,14 @@ { "id": "f509931b-bbb0-443c-bd9b-10e92ecf2193", - "queryName": "IAM Group Without Users", + "queryName": "IAM group without users", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "IAM Group should have at least one user associated", + "descriptionText": "IAM groups should include at least one user to ensure group membership and any attached permissions are intentional, auditable, and not left orphaned.\n\nThis rule checks Ansible `amazon.aws.iam_group` and `iam_group` tasks and requires the `users` property to be defined and non-null (a list containing one or more usernames). Resources missing the `users` property or with `users: null` or an empty list are flagged. Either populate the list with the intended usernames or remove unused groups and associated policies.\n\nSecure configuration example:\n\n```\n- name: Create developers IAM group with users\n amazon.aws.iam_group:\n name: developers\n users:\n - alice\n - bob\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/iam_group_without_users", "platform": "Ansible", "descriptionID": "082a1e01", "cloudProvider": "aws", "cwe": "284", "oldSeverity": "LOW", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_group_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_group_module.html" } diff --git a/assets/queries/ansible/aws/iam_group_without_users/test/positive_expected_result.json b/assets/queries/ansible/aws/iam_group_without_users/test/positive_expected_result.json index 476d69ce..2a2f4415 100644 --- a/assets/queries/ansible/aws/iam_group_without_users/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/iam_group_without_users/test/positive_expected_result.json @@ -1,14 +1,14 @@ [ { - "queryName": "IAM Group Without Users", + "queryName": "IAM group without users", "severity": "MEDIUM", "line": 2, "fileName": "positive1.yaml" }, { - "queryName": "IAM Group Without Users", + "queryName": "IAM group without users", "severity": "MEDIUM", "line": 2, "fileName": "positive2.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/iam_password_without_minimum_length/metadata.json b/assets/queries/ansible/aws/iam_password_without_minimum_length/metadata.json index 953dee6e..ef825e24 100644 --- a/assets/queries/ansible/aws/iam_password_without_minimum_length/metadata.json +++ b/assets/queries/ansible/aws/iam_password_without_minimum_length/metadata.json @@ -1,14 +1,14 @@ { "id": "8bc2168c-1723-4eeb-a6f3-a1ba614b9a6d", - "queryName": "IAM Password Without Minimum Length", + "queryName": "IAM password without minimum length", "severity": "LOW", "category": "Best Practices", - "descriptionText": "IAM password should have the required minimum length", + "descriptionText": "IAM password policies must enforce a minimum length to reduce the risk of credential brute-force and credential-stuffing attacks and limit the effectiveness of weak passwords.\n\nThis rule checks Ansible tasks using `amazon.aws.iam_password_policy` or `iam_password_policy` and requires `min_pw_length` or `minimum_password_length` to be set to a numeric value of at least 8. Tasks missing both properties are flagged as MissingAttribute. Tasks where the configured value is less than 8 are flagged as IncorrectValue. Configure the property to 8 or higher.\n\nSecure example:\n\n```yaml\n- name: Enforce IAM password policy\n amazon.aws.iam_password_policy:\n min_pw_length: 12\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/iam_password_without_minimum_length", "platform": "Ansible", "descriptionID": "b1066765", "cloudProvider": "aws", "cwe": "710", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_password_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_password_policy_module.html" } diff --git a/assets/queries/ansible/aws/iam_password_without_minimum_length/test/positive_expected_result.json b/assets/queries/ansible/aws/iam_password_without_minimum_length/test/positive_expected_result.json index 11e6af83..e7fe9d00 100644 --- a/assets/queries/ansible/aws/iam_password_without_minimum_length/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/iam_password_without_minimum_length/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "IAM Password Without Minimum Length", + "queryName": "IAM password without minimum length", "severity": "LOW", "line": 2 }, { - "queryName": "IAM Password Without Minimum Length", + "queryName": "IAM password without minimum length", "severity": "LOW", "line": 16 }, { - "queryName": "IAM Password Without Minimum Length", + "queryName": "IAM password without minimum length", "severity": "LOW", "line": 27 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/iam_policies_attached_to_user/metadata.json b/assets/queries/ansible/aws/iam_policies_attached_to_user/metadata.json index 65c308bd..2d0c39f7 100644 --- a/assets/queries/ansible/aws/iam_policies_attached_to_user/metadata.json +++ b/assets/queries/ansible/aws/iam_policies_attached_to_user/metadata.json @@ -1,13 +1,13 @@ { "id": "eafe4bc3-1042-4f88-b988-1939e64bf060", - "queryName": "IAM Policies Attached To User", + "queryName": "IAM policies attached to user", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "IAM policies should be attached only to groups or roles", + "descriptionText": "Attaching IAM policies directly to individual IAM users increases the risk of privilege sprawl, makes permissions harder to audit and revoke, and magnifies impact if a user's credentials are compromised.\n\nFor Ansible `amazon.aws.iam_policy` or `iam_policy` tasks, the `iam_type` property must be set to `group` or `role` rather than `user`. Resources missing the `iam_type` property or with `iam_type` set to `user` are flagged. Attach policies to groups or roles to centralize permission management and enable role-based access patterns.\n\nSecure example (attach policy to a role):\n\n```yaml\n- name: Attach policy to role\n amazon.aws.iam_policy:\n name: my-policy\n policy_document: \"{{ lookup('file', 'my-policy.json') }}\"\n iam_type: role\n iam_name: my-role\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/iam_policies_attached_to_user", "platform": "Ansible", "descriptionID": "cd4d500d", "cloudProvider": "aws", "cwe": "284", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_policy_module.html" } diff --git a/assets/queries/ansible/aws/iam_policies_attached_to_user/test/positive_expected_result.json b/assets/queries/ansible/aws/iam_policies_attached_to_user/test/positive_expected_result.json index 3eb0b178..be832bfd 100644 --- a/assets/queries/ansible/aws/iam_policies_attached_to_user/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/iam_policies_attached_to_user/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ - { - "queryName": "IAM Policies Attached To User", - "severity": "MEDIUM", - "line": 3 - } + { + "queryName": "IAM policies attached to user", + "severity": "MEDIUM", + "line": 3 + } ] diff --git a/assets/queries/ansible/aws/iam_policies_with_full_privileges/metadata.json b/assets/queries/ansible/aws/iam_policies_with_full_privileges/metadata.json index c87ed812..5ec66016 100644 --- a/assets/queries/ansible/aws/iam_policies_with_full_privileges/metadata.json +++ b/assets/queries/ansible/aws/iam_policies_with_full_privileges/metadata.json @@ -1,14 +1,14 @@ { "id": "e401d614-8026-4f4b-9af9-75d1197461ba", - "queryName": "IAM Policies With Full Privileges", + "queryName": "IAM policies with full privileges", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "IAM policies shouldn't allow full administrative privileges (for all resources)", + "descriptionText": "IAM policies must not grant full administrative privileges (Allow for all actions on all resources). Such statements enable privilege escalation and allow any principal with the policy to access, modify, or delete resources account-wide. For Ansible managed policy resources (modules `amazon.aws.iam_managed_policy` and `iam_managed_policy`), inspect the `policy` document's `Statement` entries. Ensure no `Statement` has `Effect: Allow` where `Action` is `\"*\"` and `Resource` is `\"*\"`. Define explicit action lists and restrict `Resource` to specific ARNs, or use condition keys to enforce least privilege. If full admin rights are truly required, attach AWS-managed administrative policies only to trusted admin roles or groups. Statements matching `Effect` set to `Allow` with both `Action` set to `'*'` and `Resource` set to `'*'` are flagged.\n\nSecure example with explicit actions and narrowed resources:\n\n```yaml\n- name: Create limited S3 read policy\n amazon.aws.iam_managed_policy:\n name: ReadOnlyS3Policy\n policy:\n Version: '2012-10-17'\n Statement:\n - Effect: Allow\n Action:\n - s3:ListBucket\n - s3:GetObject\n Resource:\n - arn:aws:s3:::my-bucket\n - arn:aws:s3:::my-bucket/*\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/iam_policies_with_full_privileges", "platform": "Ansible", "descriptionID": "3827a620", "cloudProvider": "aws", "cwe": "284", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_managed_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_managed_policy_module.html" } diff --git a/assets/queries/ansible/aws/iam_policies_with_full_privileges/test/positive_expected_result.json b/assets/queries/ansible/aws/iam_policies_with_full_privileges/test/positive_expected_result.json index b7843098..03675a5c 100644 --- a/assets/queries/ansible/aws/iam_policies_with_full_privileges/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/iam_policies_with_full_privileges/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ { - "queryName": "IAM Policies With Full Privileges", + "queryName": "IAM policies with full privileges", "severity": "MEDIUM", "line": 5, "fileName": "positive.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services/metadata.json b/assets/queries/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services/metadata.json index 19bccd4f..f174b616 100644 --- a/assets/queries/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services/metadata.json +++ b/assets/queries/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services/metadata.json @@ -1,14 +1,14 @@ { "id": "12a7a7ce-39d6-49dd-923d-aeb4564eb66c", - "queryName": "IAM Policy Grants 'AssumeRole' Permission Across All Services", + "queryName": "IAM policy grants 'AssumeRole' permission across all services", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "IAM Policy should not grant 'AssumeRole' permission across all services.", + "descriptionText": "Policy statements that use a wildcard principal (`*`) with `Effect` set to `Allow` grant trust or permissions to any AWS principal. This can enable unauthorized accounts or external services to assume roles or perform actions, increasing the risk of privilege escalation and data exposure.\n\nIn Ansible resources `amazon.aws.iam_managed_policy` and `iam_managed_policy`, check the `policy.Statement[].Effect` and `policy.Statement[].Principal.AWS` properties. Statements must not have an `Allow` effect combined with `Principal.AWS` equal to or containing `\"*\"`. This rule flags managed policy resources where any statement authorizes `\"*\"` as a principal. Replace wildcards with explicit principals such as AWS account IDs, ARNs, or specific service principals to limit trust to known entities.", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services", "platform": "Ansible", "descriptionID": "860cc010", "cloudProvider": "aws", "cwe": "284", "oldSeverity": "LOW", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_managed_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_managed_policy_module.html" } diff --git a/assets/queries/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services/test/positive_expected_result.json b/assets/queries/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services/test/positive_expected_result.json index 623c2549..b0b48b59 100644 --- a/assets/queries/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ { - "queryName": "IAM Policy Grants 'AssumeRole' Permission Across All Services", + "queryName": "IAM policy grants 'AssumeRole' permission across all services", "severity": "MEDIUM", "line": 5, "fileName": "positive.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/iam_policy_grants_full_permissions/metadata.json b/assets/queries/ansible/aws/iam_policy_grants_full_permissions/metadata.json index 4af38b16..7f226a3e 100644 --- a/assets/queries/ansible/aws/iam_policy_grants_full_permissions/metadata.json +++ b/assets/queries/ansible/aws/iam_policy_grants_full_permissions/metadata.json @@ -1,13 +1,13 @@ { "id": "b5ed026d-a772-4f07-97f9-664ba0b116f8", - "queryName": "IAM Policy Grants Full Permissions", + "queryName": "IAM policy grants full permissions", "severity": "HIGH", "category": "Access Control", - "descriptionText": "IAM policy should not grant full permissions to resources from the get-go, instead of granting permissions gradually as necessary.", + "descriptionText": "IAM managed policies must not include statements that allow all actions on all resources. Wildcard Allow statements grant unrestricted privileges, greatly increase blast radius, and raise the risk of privilege escalation or data exposure.\n\nFor Ansible tasks using the `amazon.aws.iam_managed_policy` or `iam_managed_policy` modules, examine the policy document's `Statement` entries: any statement with `Effect: \"Allow\"` must not have both `Action` and `Resource` set to `\"*\"`. This rule flags tasks where `policy.Statement[].Action == \"*\"` and `policy.Statement[].Resource == \"*\"`. Instead, scope `Action` to specific API operations and `Resource` to concrete ARNs, or apply conditions to limit access.\n\nSecure example with scoped actions and resources:\n\n```yaml\n- name: Create IAM managed policy with scoped permissions\n amazon.aws.iam_managed_policy:\n name: ExampleReadOnlyPolicy\n policy:\n Version: \"2012-10-17\"\n Statement:\n - Effect: \"Allow\"\n Action:\n - \"s3:GetObject\"\n - \"s3:ListBucket\"\n Resource:\n - \"arn:aws:s3:::example-bucket\"\n - \"arn:aws:s3:::example-bucket/*\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/iam_policy_grants_full_permissions", "platform": "Ansible", "descriptionID": "97b2a82d", "cloudProvider": "aws", "cwe": "284", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_managed_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_managed_policy_module.html" } diff --git a/assets/queries/ansible/aws/iam_policy_grants_full_permissions/test/positive_expected_result.json b/assets/queries/ansible/aws/iam_policy_grants_full_permissions/test/positive_expected_result.json index 757e4968..840d5a60 100644 --- a/assets/queries/ansible/aws/iam_policy_grants_full_permissions/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/iam_policy_grants_full_permissions/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "IAM Policy Grants Full Permissions", + "queryName": "IAM policy grants full permissions", "severity": "HIGH", "line": 5, "fileName": "positive.yaml" diff --git a/assets/queries/ansible/aws/iam_role_allows_all_principals_to_assume/metadata.json b/assets/queries/ansible/aws/iam_role_allows_all_principals_to_assume/metadata.json index 4f4d2954..fd7baa22 100644 --- a/assets/queries/ansible/aws/iam_role_allows_all_principals_to_assume/metadata.json +++ b/assets/queries/ansible/aws/iam_role_allows_all_principals_to_assume/metadata.json @@ -1,14 +1,14 @@ { "id": "babdedcf-d859-43da-9a7b-6d72e661a8fd", - "queryName": "IAM Role Allows All Principals To Assume", + "queryName": "IAM role allows all principals to assume", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "IAM role allows all services or principals to assume it", + "descriptionText": "Specifying the account root or an entire AWS account as a principal (ARNs that end with `:root`) grants every identity in that account the ability to assume the role or act as that principal. This increases the risk of privilege escalation, lateral movement, and unauthorized access if any identity is compromised.\n\nThis rule checks Ansible tasks using the `amazon.aws.iam_managed_policy` or `iam_managed_policy` modules and flags policy statements where `policy.Statement[].Principal.AWS` contains `:root`. Principal values must be explicit and least-privileged — use specific IAM role or user ARNs or service principals instead of account-root ARNs (or wildcards). Resources with `Principal.AWS` containing `:root` are flagged. \n\nSecure example with an explicit principal:\n\n```json\n{\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::123456789012:role/SpecificRole\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ],\n \"Version\": \"2012-10-17\"\n}\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/iam_role_allows_all_principals_to_assume", "platform": "Ansible", "descriptionID": "58219ae0", "cloudProvider": "aws", "cwe": "284", "oldSeverity": "LOW", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_managed_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_managed_policy_module.html" } diff --git a/assets/queries/ansible/aws/iam_role_allows_all_principals_to_assume/test/positive_expected_result.json b/assets/queries/ansible/aws/iam_role_allows_all_principals_to_assume/test/positive_expected_result.json index d33e22bc..d051f966 100644 --- a/assets/queries/ansible/aws/iam_role_allows_all_principals_to_assume/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/iam_role_allows_all_principals_to_assume/test/positive_expected_result.json @@ -1,14 +1,14 @@ [ { - "queryName": "IAM Role Allows All Principals To Assume", + "queryName": "IAM role allows all principals to assume", "severity": "MEDIUM", "line": 5, "fileName": "positive.yaml" }, { - "queryName": "IAM Role Allows All Principals To Assume", + "queryName": "IAM role allows all principals to assume", "severity": "MEDIUM", "line": 19, "fileName": "positive.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/instance_uses_metadata_service_IMDSv1/metadata.json b/assets/queries/ansible/aws/instance_uses_metadata_service_IMDSv1/metadata.json index e1d08dff..b24309f7 100644 --- a/assets/queries/ansible/aws/instance_uses_metadata_service_IMDSv1/metadata.json +++ b/assets/queries/ansible/aws/instance_uses_metadata_service_IMDSv1/metadata.json @@ -1,9 +1,9 @@ { "id": "b9ef8c0e-1392-4df4-aa84-2e0f95681c75", - "queryName": "Instance Uses Metadata Service IMDSv1", + "queryName": "Instance uses metadata service IMDSv1", "severity": "LOW", "category": "Insecure Configurations", - "descriptionText": "Instance metadata can be accessed with both IMDSv1 or IMDSv2. Although, IMDSv2 service is a session-oriented service, granting additional protection against exposure of metadata information. That version should be used instead of IMDSv1 in order to mitigate those situations.", + "descriptionText": "The EC2 instance metadata service should require IMDSv2 session tokens to reduce the risk of metadata and credential exposure via SSRF or from compromised instances.\n\nFor Ansible-managed EC2 resources (`amazon.aws.ec2_instance`, `community.aws.autoscaling_launch_config`), the `metadata_options.http_tokens` property must be set to `required` to enforce IMDSv2. Resources missing `metadata_options`, missing `metadata_options.http_tokens`, or where `http_tokens` is not `required` are flagged as insecure.\n\nSecure configuration example:\n\n```yaml\n- name: Launch EC2 instance with IMDSv2 required\n amazon.aws.ec2_instance:\n name: my-instance\n image_id: ami-0123456789abcdef0\n instance_type: t3.micro\n metadata_options:\n http_tokens: required\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/instance_uses_metadata_service_imdsv1", "platform": "Ansible", "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html", diff --git a/assets/queries/ansible/aws/instance_uses_metadata_service_IMDSv1/test/positive_expected_result.json b/assets/queries/ansible/aws/instance_uses_metadata_service_IMDSv1/test/positive_expected_result.json index 2f59b457..e3595c52 100644 --- a/assets/queries/ansible/aws/instance_uses_metadata_service_IMDSv1/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/instance_uses_metadata_service_IMDSv1/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "Instance Uses Metadata Service IMDSv1", + "queryName": "Instance uses metadata service IMDSv1", "severity": "LOW", "line": 10 }, { - "queryName": "Instance Uses Metadata Service IMDSv1", + "queryName": "Instance uses metadata service IMDSv1", "severity": "LOW", "line": 21 } diff --git a/assets/queries/ansible/aws/instance_with_no_vpc/metadata.json b/assets/queries/ansible/aws/instance_with_no_vpc/metadata.json index 9f81d48a..521e7413 100644 --- a/assets/queries/ansible/aws/instance_with_no_vpc/metadata.json +++ b/assets/queries/ansible/aws/instance_with_no_vpc/metadata.json @@ -1,14 +1,14 @@ { "id": "61d1a2d0-4db8-405a-913d-5d2ce49dff6f", - "queryName": "Instance With No VPC", + "queryName": "Instance with no VPC", "severity": "LOW", "category": "Insecure Configurations", - "descriptionText": "EC2 Instances should be configured under a VPC network. AWS VPCs provide the controls to facilitate a formal process for approving and testing all network connections and changes to the firewall and router configurations.", + "descriptionText": "EC2 instances must be launched into a VPC subnet so they are subject to VPC network controls such as security groups, network ACLs, private addressing, and VPC flow logs. Without a subnet assignment, instances can lack network isolation and be exposed to the public network or miss critical network monitoring.\n\nFor Ansible EC2 modules (`amazon.aws.ec2_instance`, `ec2_instance`), the `vpc_subnet_id` property must be defined and set to a valid VPC subnet ID. Tasks with `state` equal to `absent` or `list` are ignored. Resources missing `vpc_subnet_id` or with it undefined are flagged.\n\nSecure example Ansible task:\n\n```yaml\n- name: Launch EC2 instance in VPC subnet\n amazon.aws.ec2_instance:\n name: my-instance\n image_id: ami-0123456789abcdef0\n instance_type: t3.micro\n vpc_subnet_id: subnet-0abc1234def567890\n security_groups:\n - sg-0a1b2c3d4e5f6g7h\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/instance_with_no_vpc", "platform": "Ansible", "descriptionID": "27754eca", "cloudProvider": "aws", "cwe": "287", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html" } diff --git a/assets/queries/ansible/aws/instance_with_no_vpc/test/positive_expected_result.json b/assets/queries/ansible/aws/instance_with_no_vpc/test/positive_expected_result.json index 911c547c..fcbc4d48 100644 --- a/assets/queries/ansible/aws/instance_with_no_vpc/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/instance_with_no_vpc/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Instance With No VPC", + "queryName": "Instance with no VPC", "severity": "LOW", "line": 2 } diff --git a/assets/queries/ansible/aws/kinesis_not_encrypted_with_kms/metadata.json b/assets/queries/ansible/aws/kinesis_not_encrypted_with_kms/metadata.json index ec57dc97..a43d5af5 100644 --- a/assets/queries/ansible/aws/kinesis_not_encrypted_with_kms/metadata.json +++ b/assets/queries/ansible/aws/kinesis_not_encrypted_with_kms/metadata.json @@ -1,9 +1,9 @@ { "id": "f2ea6481-1d31-4d40-946a-520dc6321dd7", - "queryName": "Kinesis Not Encrypted With KMS", + "queryName": "Kinesis not encrypted with KMS", "severity": "HIGH", "category": "Encryption", - "descriptionText": "AWS Kinesis Streams and metadata should be protected with KMS", + "descriptionText": "Kinesis Data Streams must have server-side encryption enabled to protect stream data and metadata at rest and reduce the risk of unauthorized access or data exposure.\n\nFor Ansible resources using the `community.aws.kinesis_stream` or `kinesis_stream` module, the `encryption_state` property must be set to `\"enabled\"` and the `encryption_type` property must be defined and not set to `\"NONE\"`. If `encryption_type` is `\"KMS\"`, a valid `key_id` (KMS key ARN or ID) must also be provided.\n\nResources missing these properties or with `encryption_state != \"enabled\"`, `encryption_type == \"NONE\"`, or `encryption_type == \"KMS\"` without `key_id` are flagged.\n\nSecure Ansible configuration example:\n\n```yaml\n- name: Create Kinesis stream with SSE-KMS\n community.aws.kinesis_stream:\n name: my-stream\n shard_count: 1\n encryption_state: enabled\n encryption_type: KMS\n key_id: arn:aws:kms:us-east-1:123456789012:key/abcd1234-ef56-7890-abcd-ef1234567890\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/kinesis_not_encrypted_with_kms", "platform": "Ansible", "descriptionID": "017319a7", diff --git a/assets/queries/ansible/aws/kinesis_not_encrypted_with_kms/test/positive_expected_result.json b/assets/queries/ansible/aws/kinesis_not_encrypted_with_kms/test/positive_expected_result.json index 579cb51f..fdf86aae 100644 --- a/assets/queries/ansible/aws/kinesis_not_encrypted_with_kms/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/kinesis_not_encrypted_with_kms/test/positive_expected_result.json @@ -1,26 +1,26 @@ [ { - "queryName": "Kinesis Not Encrypted With KMS", + "queryName": "Kinesis not encrypted with KMS", "severity": "HIGH", "line": 2 }, { - "queryName": "Kinesis Not Encrypted With KMS", + "queryName": "Kinesis not encrypted with KMS", "severity": "HIGH", "line": 16 }, { - "queryName": "Kinesis Not Encrypted With KMS", + "queryName": "Kinesis not encrypted with KMS", "severity": "HIGH", "line": 23 }, { - "queryName": "Kinesis Not Encrypted With KMS", + "queryName": "Kinesis not encrypted with KMS", "severity": "HIGH", "line": 38 }, { - "queryName": "Kinesis Not Encrypted With KMS", + "queryName": "Kinesis not encrypted with KMS", "severity": "HIGH", "line": 44 } diff --git a/assets/queries/ansible/aws/kms_key_with_full_permissions/metadata.json b/assets/queries/ansible/aws/kms_key_with_full_permissions/metadata.json index cacbfe12..6a9a5843 100644 --- a/assets/queries/ansible/aws/kms_key_with_full_permissions/metadata.json +++ b/assets/queries/ansible/aws/kms_key_with_full_permissions/metadata.json @@ -1,13 +1,13 @@ { "id": "5b9d237a-57d5-4177-be0e-71434b0fef47", - "queryName": "KMS Key With Vulnerable Policy", + "queryName": "KMS key with vulnerable policy", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "Checks if the policy is vulnerable and needs updating.", + "descriptionText": "KMS key policies that grant broad permissions—such as Allow statements containing `kms:*` or wildcard principals—or that lack conditions can permit unauthorized principals to use, manage, or delete keys. This increases the risk of data exposure or loss.\n\nFor Ansible tasks using the `amazon.aws.kms_key` or `aws_kms` modules, inspect the `policy` property. Either omit a custom `policy` so the key uses a safe default, or ensure any provided `policy` does not include `Effect: \"Allow\"` statements that lack a `Condition` and contain wildcard actions like `kms:*` or wildcard principals (such as `\"*\"` or account-wide ARNs).\n\nThis rule flags KMS resources where a custom `policy` contains an Allow statement without a `Condition` that includes wildcard `kms:*` in `Action` or a wildcard `Principal`. It also flags cases where a custom `policy` is supplied when your organization requires the property to be undefined.\n\nSecure examples — either omit the policy to use safer defaults or supply a restrictive policy that specifies explicit principals, limited actions, and Conditions:\n\n```yaml\n- name: Create KMS key using default policy\n amazon.aws.kms_key:\n alias: alias/my-key\n description: \"Encryption key for app\"\n state: present\n```\n\n```yaml\n- name: Create KMS key with restricted policy\n amazon.aws.kms_key:\n alias: alias/my-key\n policy:\n Version: \"2012-10-17\"\n Statement:\n - Sid: \"AllowSpecificUse\"\n Effect: \"Allow\"\n Principal:\n AWS: \"arn:aws:iam::123456789012:role/MyRole\"\n Action:\n - \"kms:Encrypt\"\n - \"kms:Decrypt\"\n Resource: \"*\"\n Condition:\n StringEquals:\n aws:CalledVia: \"my-allowed-service.amazonaws.com\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/kms_key_with_full_permissions", "platform": "Ansible", "descriptionID": "a1f47164", "cloudProvider": "aws", "cwe": "807", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_kms_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/kms_key_module.html" } diff --git a/assets/queries/ansible/aws/kms_key_with_full_permissions/test/positive_expected_result.json b/assets/queries/ansible/aws/kms_key_with_full_permissions/test/positive_expected_result.json index bfae2532..be3bbbfe 100644 --- a/assets/queries/ansible/aws/kms_key_with_full_permissions/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/kms_key_with_full_permissions/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "KMS Key With Vulnerable Policy", + "queryName": "KMS key with vulnerable policy", "severity": "HIGH", "line": 5, "fileName": "positive.yaml" }, { - "queryName": "KMS Key With Vulnerable Policy", + "queryName": "KMS key with vulnerable policy", "severity": "HIGH", "line": 3, "fileName": "positive2.yaml" diff --git a/assets/queries/ansible/aws/lambda_function_without_tags/metadata.json b/assets/queries/ansible/aws/lambda_function_without_tags/metadata.json index 18c3b111..fbeae4e2 100644 --- a/assets/queries/ansible/aws/lambda_function_without_tags/metadata.json +++ b/assets/queries/ansible/aws/lambda_function_without_tags/metadata.json @@ -1,14 +1,14 @@ { "id": "265d9725-2fb8-42a2-bc57-3279c5db82d5", - "queryName": "Lambda Function Without Tags", + "queryName": "Lambda function without tags", "severity": "LOW", "category": "Insecure Configurations", - "descriptionText": "AWS Lambda Functions must have associated tags.", + "descriptionText": "AWS Lambda functions should be tagged so resources can be reliably inventoried and assigned ownership. Tags also enable tag-based access controls and automated security or operational workflows.\n\nIn Ansible playbooks, tasks using the `amazon.aws.lambda` or legacy `lambda` module must define the `tags` property as a mapping/dictionary. Resources where `tags` is undefined are flagged. Ensure `tags` is present on the module invocation and contains at least the necessary keys for your organization (for example, `Owner`, `Environment`, or `Project`).\n\nSecure example:\n\n```yaml\n- name: create application lambda\n amazon.aws.lambda:\n name: my-function\n state: present\n runtime: python3.9\n role: arn:aws:iam::123456789012:role/lambda-exec\n handler: app.handler\n tags:\n Owner: team-foo\n Environment: production\n Project: billing\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/lambda_function_without_tags", "platform": "Ansible", "descriptionID": "45d5ac61", "cloudProvider": "aws", "cwe": "665", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/lambda_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_module.html" } diff --git a/assets/queries/ansible/aws/lambda_function_without_tags/test/positive_expected_result.json b/assets/queries/ansible/aws/lambda_function_without_tags/test/positive_expected_result.json index 39bea3ef..526eda1f 100644 --- a/assets/queries/ansible/aws/lambda_function_without_tags/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/lambda_function_without_tags/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "Lambda Function Without Tags", + "queryName": "Lambda function without tags", "severity": "LOW", "line": 2 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/lambda_functions_without_x-ray_tracing/metadata.json b/assets/queries/ansible/aws/lambda_functions_without_x-ray_tracing/metadata.json index 2b8b36c2..1ff06910 100644 --- a/assets/queries/ansible/aws/lambda_functions_without_x-ray_tracing/metadata.json +++ b/assets/queries/ansible/aws/lambda_functions_without_x-ray_tracing/metadata.json @@ -1,13 +1,13 @@ { "id": "71397b34-1d50-4ee1-97cb-c96c34676f74", - "queryName": "Lambda Functions Without X-Ray Tracing", + "queryName": "Lambda functions without X-Ray tracing", "severity": "LOW", "category": "Observability", - "descriptionText": "AWS Lambda functions should have TracingConfig enabled. For this, property 'tracing_mode' should have the value 'Active'", + "descriptionText": "Lambda functions should have active AWS X-Ray tracing enabled to provide end-to-end request visibility and help detect performance problems and security incidents. For Ansible `amazon.aws.lambda` or `lambda` module tasks, the `tracing_mode` property must be defined and set to `Active`. Tasks that omit `tracing_mode` or set it to any value other than `Active` are flagged.\n\nSecure Ansible example:\n\n```yaml\n- name: Create Lambda with active X-Ray tracing\n amazon.aws.lambda:\n name: my_lambda_function\n state: present\n runtime: python3.9\n handler: app.handler\n tracing_mode: Active\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/lambda_functions_without_x-ray_tracing", "platform": "Ansible", "descriptionID": "bff4deb9", "cloudProvider": "aws", "cwe": "778", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/lambda_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_module.html" } diff --git a/assets/queries/ansible/aws/lambda_functions_without_x-ray_tracing/test/positive_expected_result.json b/assets/queries/ansible/aws/lambda_functions_without_x-ray_tracing/test/positive_expected_result.json index 1b3f379b..b74172d6 100644 --- a/assets/queries/ansible/aws/lambda_functions_without_x-ray_tracing/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/lambda_functions_without_x-ray_tracing/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "Lambda Functions Without X-Ray Tracing", + "queryName": "Lambda functions without X-Ray tracing", "severity": "LOW", "line": 2 }, { - "queryName": "Lambda Functions Without X-Ray Tracing", + "queryName": "Lambda functions without X-Ray tracing", "severity": "LOW", "line": 37 } diff --git a/assets/queries/ansible/aws/lambda_permission_misconfigured/metadata.json b/assets/queries/ansible/aws/lambda_permission_misconfigured/metadata.json index 6c73fd62..b81f975b 100644 --- a/assets/queries/ansible/aws/lambda_permission_misconfigured/metadata.json +++ b/assets/queries/ansible/aws/lambda_permission_misconfigured/metadata.json @@ -1,13 +1,13 @@ { "id": "3ddf3417-424d-420d-8275-0724dc426520", - "queryName": "Lambda Permission Misconfigured", + "queryName": "Lambda permission misconfigured", "severity": "LOW", "category": "Best Practices", - "descriptionText": "Lambda permission may be misconfigured if the action field is not filled in by 'lambda:InvokeFunction'", + "descriptionText": "Lambda permission statements must set the action to `lambda:InvokeFunction` so callers are limited to invoking the function and cannot receive broader or unintended Lambda privileges.\n\nCheck Ansible tasks that use the `amazon.aws.lambda_policy` or `lambda_policy` modules. The `action` property must be defined and set to the exact string `lambda:InvokeFunction`. Tasks missing the `action` property or using any other value (for example `lambda:*`, a different Lambda action, or an empty value) are flagged because they can over-privilege callers or result in misconfigured access.\n\nSecure example with the action explicitly set:\n\n```yaml\n- name: Allow S3 to invoke my Lambda\n amazon.aws.lambda_policy:\n name: my_lambda_policy\n state: present\n principal: s3.amazonaws.com\n action: lambda:InvokeFunction\n function_name: my-function\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/lambda_permission_misconfigured", "platform": "Ansible", "descriptionID": "9f8d2402", "cloudProvider": "aws", "cwe": "710", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/lambda_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_policy_module.html" } diff --git a/assets/queries/ansible/aws/lambda_permission_misconfigured/test/positive_expected_result.json b/assets/queries/ansible/aws/lambda_permission_misconfigured/test/positive_expected_result.json index ac77dd2e..43e84e2e 100644 --- a/assets/queries/ansible/aws/lambda_permission_misconfigured/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/lambda_permission_misconfigured/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Lambda Permission Misconfigured", + "queryName": "Lambda permission misconfigured", "severity": "LOW", "line": 7 } diff --git a/assets/queries/ansible/aws/lambda_permission_principal_is_wildcard/metadata.json b/assets/queries/ansible/aws/lambda_permission_principal_is_wildcard/metadata.json index d0fa97fe..db04f360 100644 --- a/assets/queries/ansible/aws/lambda_permission_principal_is_wildcard/metadata.json +++ b/assets/queries/ansible/aws/lambda_permission_principal_is_wildcard/metadata.json @@ -1,13 +1,13 @@ { "id": "1d972c56-8ec2-48c1-a578-887adb09c57a", - "queryName": "Lambda Permission Principal Is Wildcard", + "queryName": "Lambda permission principal is wildcard", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "Lambda Permission Principal should not contain a wildcard.", + "descriptionText": "Lambda function permissions must not use wildcard principals (`*`). This effectively allows any AWS account or anonymous principal to invoke the function, increasing the risk of unauthorized invocations and data exposure.\n\nIn Ansible, check tasks using the `amazon.aws.lambda_policy` or `lambda_policy` modules and ensure the `principal` property does not contain `*` or other wildcard values. The `principal` must specify explicit principals such as an AWS account ARN, role ARN, or service principal (for example, `arn:aws:iam::123456789012:role/MyRole` or `events.amazonaws.com`). Tasks where `principal` includes `*` are flagged.\n\nSecure example using an explicit service principal:\n\n```yaml\n- name: Allow EventBridge to invoke Lambda\n amazon.aws.lambda_policy:\n state: present\n function_name: my-function\n principal: events.amazonaws.com\n action: lambda:InvokeFunction\n source_arn: arn:aws:events:us-east-1:123456789012:rule/MyRule\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/lambda_permission_principal_is_wildcard", "platform": "Ansible", "descriptionID": "1740275a", "cloudProvider": "aws", "cwe": "155", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/lambda_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_policy_module.html" } diff --git a/assets/queries/ansible/aws/lambda_permission_principal_is_wildcard/test/positive_expected_result.json b/assets/queries/ansible/aws/lambda_permission_principal_is_wildcard/test/positive_expected_result.json index 5e494d7d..58aa3f77 100644 --- a/assets/queries/ansible/aws/lambda_permission_principal_is_wildcard/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/lambda_permission_principal_is_wildcard/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { "line": 8, - "queryName": "Lambda Permission Principal Is Wildcard", + "queryName": "Lambda permission principal is wildcard", "severity": "MEDIUM" } ] diff --git a/assets/queries/ansible/aws/launch_configuration_is_not_encrypted/metadata.json b/assets/queries/ansible/aws/launch_configuration_is_not_encrypted/metadata.json index 65966b41..2ac30592 100644 --- a/assets/queries/ansible/aws/launch_configuration_is_not_encrypted/metadata.json +++ b/assets/queries/ansible/aws/launch_configuration_is_not_encrypted/metadata.json @@ -1,13 +1,13 @@ { "id": "66477506-6abb-49ed-803d-3fa174cd5f6a", - "queryName": "Launch Configuration Is Not Encrypted", + "queryName": "Launch configuration is not encrypted", "severity": "HIGH", "category": "Encryption", - "descriptionText": "Launch Configurations should have the data in the volumes encrypted. To encrypt the data, the 'encrypted' parameter should be set to true in each volume", + "descriptionText": "Block device volumes in EC2 launch configurations must be encrypted to protect data at rest and prevent exposure of snapshots or AMIs if storage media is compromised.\n\nFor Ansible tasks using the `community.aws.autoscaling_launch_config` or `autoscaling_launch_config` modules, ensure the `volumes` list is defined and each volume entry sets `encrypted: true` (Ansible `yes` is also acceptable) under `ec2_lc.volumes`. Ephemeral (instance-store) volumes do not support encryption and are excluded. This rule flags launch configurations missing the `volumes` property, any volume entries without an `encrypted` property, or volumes where `encrypted` is explicitly false.\n\nExample secure configuration for an Ansible `autoscaling_launch_config` task:\n\n```yaml\n- name: Create launch configuration with encrypted volumes\n community.aws.autoscaling_launch_config:\n name: my-launch-config\n image_id: ami-0123456789abcdef0\n instance_type: t3.medium\n volumes:\n - device_name: /dev/xvda\n volume_size: 50\n volume_type: gp2\n encrypted: true\n delete_on_termination: yes\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/launch_configuration_is_not_encrypted", "platform": "Ansible", "descriptionID": "57b9aee0", "cloudProvider": "aws", "cwe": "311", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/ec2_lc_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/autoscaling_launch_config_module.html" } diff --git a/assets/queries/ansible/aws/launch_configuration_is_not_encrypted/test/positive_expected_result.json b/assets/queries/ansible/aws/launch_configuration_is_not_encrypted/test/positive_expected_result.json index 5793a13e..099ef44f 100644 --- a/assets/queries/ansible/aws/launch_configuration_is_not_encrypted/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/launch_configuration_is_not_encrypted/test/positive_expected_result.json @@ -1,16 +1,16 @@ [ { - "queryName": "Launch Configuration Is Not Encrypted", + "queryName": "Launch configuration is not encrypted", "severity": "HIGH", "line": 8 }, { - "queryName": "Launch Configuration Is Not Encrypted", + "queryName": "Launch configuration is not encrypted", "severity": "HIGH", "line": 22 }, { - "queryName": "Launch Configuration Is Not Encrypted", + "queryName": "Launch configuration is not encrypted", "severity": "HIGH", "line": 29 } diff --git a/assets/queries/ansible/aws/misconfigured_password_policy_expiration/metadata.json b/assets/queries/ansible/aws/misconfigured_password_policy_expiration/metadata.json index 39dbe797..3b71db4d 100644 --- a/assets/queries/ansible/aws/misconfigured_password_policy_expiration/metadata.json +++ b/assets/queries/ansible/aws/misconfigured_password_policy_expiration/metadata.json @@ -1,14 +1,14 @@ { "id": "3f2cf811-88fa-4eda-be45-7a191a18aba9", - "queryName": "Misconfigured Password Policy Expiration", + "queryName": "Misconfigured password policy expiration", "severity": "LOW", "category": "Best Practices", - "descriptionText": "No password expiration policy", + "descriptionText": "IAM account password policies must enforce regular password expiration to limit exposure from compromised or leaked credentials and reduce the risk of long-lived unauthorized access. In Ansible, tasks using the `amazon.aws.iam_password_policy` or `iam_password_policy` modules must define `pw_max_age` or `password_max_age` with a value of 90 days or fewer. Resources that omit both properties or set either to a value greater than 90 are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Enforce IAM password expiration\n amazon.aws.iam_password_policy:\n password_max_age: 90\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/misconfigured_password_policy_expiration", "platform": "Ansible", "descriptionID": "80db60d5", "cloudProvider": "aws", "cwe": "521", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_password_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_password_policy_module.html" } diff --git a/assets/queries/ansible/aws/misconfigured_password_policy_expiration/test/positive_expected_result.json b/assets/queries/ansible/aws/misconfigured_password_policy_expiration/test/positive_expected_result.json index 16b5f7cb..1a00032d 100644 --- a/assets/queries/ansible/aws/misconfigured_password_policy_expiration/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/misconfigured_password_policy_expiration/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "Misconfigured Password Policy Expiration", + "queryName": "Misconfigured password policy expiration", "severity": "LOW", "line": 2 }, { - "queryName": "Misconfigured Password Policy Expiration", + "queryName": "Misconfigured password policy expiration", "severity": "LOW", "line": 21 }, { - "queryName": "Misconfigured Password Policy Expiration", + "queryName": "Misconfigured password policy expiration", "severity": "LOW", "line": 33 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/no_stack_policy/metadata.json b/assets/queries/ansible/aws/no_stack_policy/metadata.json index f97beb0b..dcd6bff5 100644 --- a/assets/queries/ansible/aws/no_stack_policy/metadata.json +++ b/assets/queries/ansible/aws/no_stack_policy/metadata.json @@ -1,9 +1,9 @@ { "id": "ffe0fd52-7a8b-4a5c-8fc7-49844418e6c9", - "queryName": "No Stack Policy", + "queryName": "No stack policy", "severity": "MEDIUM", "category": "Resource Management", - "descriptionText": "AWS CloudFormation Stack should have a stack policy in order to protect stack resources from update actions", + "descriptionText": "CloudFormation stacks should have a stack policy to prevent unintended or unauthorized updates to stack resources, protecting critical resources from accidental changes or deployment mistakes.\n\nFor Ansible tasks using the `amazon.aws.cloudformation` or `cloudformation` modules, the `stack_policy` property must be defined and set to a valid JSON policy that restricts update actions. Resources missing the `stack_policy` property or with it undefined are flagged. Provide a JSON policy string (or file content) that explicitly denies Update actions for any logical resource IDs you want to protect.\n\nSecure configuration example:\n\n```yaml\n- name: Create CloudFormation stack with stack policy\n amazon.aws.cloudformation:\n stack_name: my-stack\n state: present\n template: \"{{ lookup('file', 'template.yml') }}\"\n stack_policy: |\n {\n \"Statement\": [\n {\n \"Effect\": \"Deny\",\n \"Action\": \"Update:*\",\n \"Principal\": \"*\",\n \"Resource\": \"LogicalResourceId/MyCriticalResource\"\n }\n ]\n }\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/no_stack_policy", "platform": "Ansible", "descriptionID": "327969ac", diff --git a/assets/queries/ansible/aws/no_stack_policy/test/positive_expected_result.json b/assets/queries/ansible/aws/no_stack_policy/test/positive_expected_result.json index 9efdc650..905393a3 100644 --- a/assets/queries/ansible/aws/no_stack_policy/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/no_stack_policy/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "No Stack Policy", + "queryName": "No stack policy", "severity": "MEDIUM", "line": 2 } diff --git a/assets/queries/ansible/aws/password_without_reuse_prevention/metadata.json b/assets/queries/ansible/aws/password_without_reuse_prevention/metadata.json index 972e755d..ab1af3b4 100644 --- a/assets/queries/ansible/aws/password_without_reuse_prevention/metadata.json +++ b/assets/queries/ansible/aws/password_without_reuse_prevention/metadata.json @@ -1,14 +1,14 @@ { "id": "6f5f5444-1422-495f-81ef-24cefd61ed2c", - "queryName": "Password Without Reuse Prevention", + "queryName": "Password without reuse prevention", "severity": "LOW", "category": "Best Practices", - "descriptionText": "Password policy `password_reuse_prevention` doesn't exist or is equal to 0", + "descriptionText": "IAM password policies must prevent reuse of previous passwords to reduce the risk of account compromise from credential stuffing and replay of older credentials.\n\nFor Ansible tasks using the `amazon.aws.iam_password_policy` or `iam_password_policy` modules, define one of the reuse-prevention properties (`password_reuse_prevent`, `pw_reuse_prevent`, or `prevent_reuse`) and set it to a positive integer greater than 0. This specifies how many prior passwords are disallowed. This rule flags tasks where none of these properties are present or where the property is explicitly set to `0`.\n\nSecure example (prevents reuse of the last 5 passwords):\n\n```yaml\n- name: Enforce IAM password reuse prevention\n amazon.aws.iam_password_policy:\n password_reuse_prevent: 5\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/password_without_reuse_prevention", "platform": "Ansible", "descriptionID": "ad12d750", "cloudProvider": "aws", "cwe": "521", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_password_policy_module.html#parameter-pw_reuse_prevent" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_password_policy_module.html#parameter-pw_reuse_prevent" } diff --git a/assets/queries/ansible/aws/password_without_reuse_prevention/test/positive_expected_result.json b/assets/queries/ansible/aws/password_without_reuse_prevention/test/positive_expected_result.json index 1dacba2e..2c710e22 100644 --- a/assets/queries/ansible/aws/password_without_reuse_prevention/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/password_without_reuse_prevention/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "Password Without Reuse Prevention", + "queryName": "Password without reuse prevention", "severity": "LOW", "line": 3 }, { - "queryName": "Password Without Reuse Prevention", + "queryName": "Password without reuse prevention", "severity": "LOW", "line": 23 }, { - "queryName": "Password Without Reuse Prevention", + "queryName": "Password without reuse prevention", "severity": "LOW", "line": 26 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/public_lambda_via_api_gateway/metadata.json b/assets/queries/ansible/aws/public_lambda_via_api_gateway/metadata.json index 28a6521d..d472862f 100644 --- a/assets/queries/ansible/aws/public_lambda_via_api_gateway/metadata.json +++ b/assets/queries/ansible/aws/public_lambda_via_api_gateway/metadata.json @@ -3,11 +3,11 @@ "queryName": "Public Lambda via API Gateway", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "Allowing to run lambda function using public API Gateway", + "descriptionText": "Allowing API Gateway to invoke a Lambda using a wildcard source ARN like `/*/*` grants any API, stage, or method the ability to invoke the function. This can result in unintended public or cross-account invocation and increase the risk of unauthorized execution.\n\nIn Ansible tasks using the `amazon.aws.lambda_policy` or `lambda_policy` modules, the `source_arn` property must not be set to `/*/*`. Instead, specify the full execute-api ARN (for example `arn:aws:execute-api:::///`).\n\nThis rule flags policies where `action` is `lambda:InvokeFunction` or `lambda:*` and `principal` is `apigateway.amazonaws.com` or `*` while `source_arn` matches `/*/*`. Avoid using a wildcard principal and prefer the explicit `apigateway.amazonaws.com` principal with a narrowed `source_arn`. \n\nSecure configuration example:\n\n```yaml\n- name: Allow specific API Gateway to invoke Lambda\n amazon.aws.lambda_policy:\n function_name: my-function\n action: lambda:InvokeFunction\n principal: apigateway.amazonaws.com\n source_arn: arn:aws:execute-api:us-east-1:123456789012:abcd1234/prod/POST/myresource\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/public_lambda_via_api_gateway", "platform": "Ansible", "descriptionID": "50e0d3f5", "cloudProvider": "aws", "cwe": "285", - "providerUrl": "https://docs.ansible.com/ansible/2.4/lambda_policy_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_policy_module.html" } diff --git a/assets/queries/ansible/aws/public_port_wide/metadata.json b/assets/queries/ansible/aws/public_port_wide/metadata.json index a5ed27fd..71768505 100644 --- a/assets/queries/ansible/aws/public_port_wide/metadata.json +++ b/assets/queries/ansible/aws/public_port_wide/metadata.json @@ -1,9 +1,9 @@ { "id": "71ea648a-d31a-4b5a-a589-5674243f1c33", - "queryName": "Public Port Wide", + "queryName": "Public port with wide port range", "severity": "HIGH", "category": "Networking and Firewall", - "descriptionText": "AWS Security Group should not have public port wide", + "descriptionText": "Security groups must not allow a wide port range to the entire internet. Exposing multiple ports publicly increases attack surface and enables broad port scanning, automated exploitation, and easier lateral movement.\n\nFor Ansible `amazon.aws.ec2_group` or `ec2_group` resources, check `rules[].from_port` and `rules[].to_port` and ensure rules where `to_port - from_port > 0` are not paired with `cidr_ip` set to `0.0.0.0/0` or `cidr_ipv6` set to `::/0`. Rules that require external access should restrict CIDR ranges to trusted networks or use specific single-port entries. Any rule defining a port range with an entire-network CIDR is flagged. \n\nSecure example restricting access to a single port and a specific CIDR:\n\n```yaml\nmy_sg:\n name: my-security-group\n rules:\n - proto: tcp\n from_port: 22\n to_port: 22\n cidr_ip: 203.0.113.5/32\n - proto: tcp\n from_port: 443\n to_port: 443\n cidr_ip: 198.51.100.0/24\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/public_port_wide", "platform": "Ansible", "descriptionID": "be17d13e", diff --git a/assets/queries/ansible/aws/public_port_wide/test/positive_expected_result.json b/assets/queries/ansible/aws/public_port_wide/test/positive_expected_result.json index 697c1bac..ad239cef 100644 --- a/assets/queries/ansible/aws/public_port_wide/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/public_port_wide/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ - { - "queryName": "Public Port Wide", - "severity": "HIGH", - "line": 8 - }, - { - "queryName": "Public Port Wide", - "severity": "HIGH", - "line": 12 - } + { + "queryName": "Public port with wide port range", + "severity": "HIGH", + "line": 8 + }, + { + "queryName": "Public port with wide port range", + "severity": "HIGH", + "line": 12 + } ] diff --git a/assets/queries/ansible/aws/rds_associated_with_public_subnet/metadata.json b/assets/queries/ansible/aws/rds_associated_with_public_subnet/metadata.json index 6ff21f02..dc5b55b3 100644 --- a/assets/queries/ansible/aws/rds_associated_with_public_subnet/metadata.json +++ b/assets/queries/ansible/aws/rds_associated_with_public_subnet/metadata.json @@ -1,14 +1,14 @@ { "id": "16732649-4ff6-4cd2-8746-e72c13fae4b8", - "queryName": "RDS Associated with Public Subnet", + "queryName": "RDS instance associated with a public subnet", "severity": "CRITICAL", "category": "Networking and Firewall", - "descriptionText": "RDS should not run in public subnet", + "descriptionText": "RDS instances must not be placed in public subnets because an internet-routable subnet exposes the database endpoint to the internet, increasing the risk of unauthorized access and data exfiltration. This rule inspects Ansible tasks that create RDS instances (resource types `amazon.aws.rds_instance` or `rds_instance`) and requires the subnet group property (`db_subnet_group_name` or `subnet_group`) to reference a subnet group composed only of private subnets.\n\nIt verifies the referenced subnet group tasks (`amazon.aws.rds_subnet_group` or `rds_subnet_group`) and the subnet tasks (`amazon.aws.ec2_vpc_subnet` or `ec2_vpc_subnet`). Any subnet with `cidr` equal to `0.0.0.0/0` or `ipv6_cidr` equal to `::/0` is treated as public and triggers a finding.\n\nResources that are missing the subnet-group property or that include any public subnet in the subnet group are flagged. Ensure subnet groups list subnets using private CIDR ranges and that registered subnet task names match the entries in the subnet group.\n\nSecure example with private subnet CIDRs:\n\n```yaml\n- name: Create private subnet\n amazon.aws.ec2_vpc_subnet:\n vpc_id: vpc-123\n cidr: 10.0.1.0/24\n register: private_subnet_a\n\n- name: Create RDS subnet group using private subnets\n amazon.aws.rds_subnet_group:\n name: my-db-subnet-group\n subnets:\n - \"{{ private_subnet_a }}\"\n\n- name: Create RDS instance in private subnet group\n amazon.aws.rds_instance:\n db_subnet_group_name: my-db-subnet-group\n # other RDS properties...\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/rds_associated_with_public_subnet", "platform": "Ansible", "descriptionID": "89ed6e35", "cloudProvider": "aws", "cwe": "200", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/rds_instance_module.html#parameter-db_subnet_group_name" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-db_subnet_group_name" } diff --git a/assets/queries/ansible/aws/rds_associated_with_public_subnet/test/positive_expected_result.json b/assets/queries/ansible/aws/rds_associated_with_public_subnet/test/positive_expected_result.json index 4d438cbf..737bb5b6 100644 --- a/assets/queries/ansible/aws/rds_associated_with_public_subnet/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/rds_associated_with_public_subnet/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ { - "queryName": "RDS Associated with Public Subnet", + "queryName": "RDS instance associated with a public subnet", "severity": "CRITICAL", "line": 9, "fileName": "positive.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/rds_db_instance_publicly_accessible/metadata.json b/assets/queries/ansible/aws/rds_db_instance_publicly_accessible/metadata.json index acac4ff5..3b6eeeec 100644 --- a/assets/queries/ansible/aws/rds_db_instance_publicly_accessible/metadata.json +++ b/assets/queries/ansible/aws/rds_db_instance_publicly_accessible/metadata.json @@ -1,14 +1,14 @@ { "id": "c09e3ca5-f08a-4717-9c87-3919c5e6d209", - "queryName": "RDS DB Instance Publicly Accessible", + "queryName": "RDS DB instance is not publicly accessible", "severity": "CRITICAL", "category": "Insecure Configurations", - "descriptionText": "RDS must not be defined with public interface, which means the field 'publicly_accessible' should not be set to 'true' (default is 'false').", + "descriptionText": "RDS instances must not be configured as publicly accessible. Exposing a database to the public internet increases the risk of unauthorized access and enables brute-force or credential-stuffing attacks.\n\nIn Ansible RDS tasks using the `amazon.aws.rds_instance` or `rds_instance` modules, ensure the `publicly_accessible` property is set to `false`. Tasks with `publicly_accessible: true` are flagged. If the property is omitted, the modules default to `false`, but explicitly setting it to `false` and placing instances in private subnets with restrictive security groups provides defense-in-depth.\n\nSecure example:\n\n```yaml\n- name: Create RDS instance (private)\n amazon.aws.rds_instance:\n db_instance_identifier: mydb\n engine: postgres\n instance_class: db.t3.medium\n publicly_accessible: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/rds_db_instance_publicly_accessible", "platform": "Ansible", "descriptionID": "e1b53fb6", "cloudProvider": "aws", "cwe": "668", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/rds_instance_module.html#parameter-auto_minor_version_upgrade" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-auto_minor_version_upgrade" } diff --git a/assets/queries/ansible/aws/rds_db_instance_publicly_accessible/test/positive_expected_result.json b/assets/queries/ansible/aws/rds_db_instance_publicly_accessible/test/positive_expected_result.json index 564c380f..9fa72ffc 100644 --- a/assets/queries/ansible/aws/rds_db_instance_publicly_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/rds_db_instance_publicly_accessible/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "RDS DB Instance Publicly Accessible", + "queryName": "RDS DB instance is not publicly accessible", "severity": "CRITICAL", "line": 13 }, { - "queryName": "RDS DB Instance Publicly Accessible", + "queryName": "RDS DB instance is not publicly accessible", "severity": "CRITICAL", "line": 24 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/rds_using_default_port/metadata.json b/assets/queries/ansible/aws/rds_using_default_port/metadata.json index d2f1b31d..d03dfd8f 100644 --- a/assets/queries/ansible/aws/rds_using_default_port/metadata.json +++ b/assets/queries/ansible/aws/rds_using_default_port/metadata.json @@ -1,13 +1,13 @@ { "id": "2cb674f6-32f9-40be-97f2-62c0dc38f0d5", - "queryName": "RDS Using Default Port", + "queryName": "RDS instance uses a default port", "severity": "LOW", "category": "Networking and Firewall", - "descriptionText": "RDS should not use the default port (an attacker can easily guess the port). For engines related to Aurora, MariaDB or MySQL, the default port is 3306. PostgreSQL default port is 5432, Oracle default port is 1521 and SQL Server default port is 1433", + "descriptionText": "Using the database engine's default port makes instances easy for attackers to discover and target with automated scanning and exploit tooling, increasing the likelihood of brute-force, credential stuffing, or other network-based attacks. For Ansible RDS tasks using the `amazon.aws.rds_instance` or `rds_instance` modules, the `port` property must not be set to the engine default. Choose a non-default port and ensure access is restricted at the network level (security groups/ACLs).\n\nThis rule flags module tasks where `port` equals the engine default: MySQL/MariaDB/Aurora = 3306, PostgreSQL = 5432, Oracle = 1521, and SQL Server = 1433. This check flags explicit `port` settings that match defaults. If `port` is omitted, the engine may still use its default port, so also verify engine behavior and enforce least-privilege network access.\n\nSecure configuration example (MySQL using a non-default port):\n\n```yaml\n- name: Create RDS instance with non-default port\n amazon.aws.rds_instance:\n db_instance_identifier: my-db\n engine: mysql\n port: 3307\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/rds_using_default_port", "platform": "Ansible", "descriptionID": "4e928197", "cloudProvider": "aws", "cwe": "668", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/rds_instance_module.html#parameter-port" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-port" } diff --git a/assets/queries/ansible/aws/rds_using_default_port/test/positive_expected_result.json b/assets/queries/ansible/aws/rds_using_default_port/test/positive_expected_result.json index 0282b397..e77ac44e 100644 --- a/assets/queries/ansible/aws/rds_using_default_port/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/rds_using_default_port/test/positive_expected_result.json @@ -1,24 +1,24 @@ [ { - "queryName": "RDS Using Default Port", + "queryName": "RDS instance uses a default port", "severity": "LOW", "line": 10, "fileName": "positive1.yaml" }, { - "queryName": "RDS Using Default Port", + "queryName": "RDS instance uses a default port", "severity": "LOW", "line": 10, "fileName": "positive2.yaml" }, { - "queryName": "RDS Using Default Port", + "queryName": "RDS instance uses a default port", "severity": "LOW", "line": 10, "fileName": "positive3.yaml" }, { - "queryName": "RDS Using Default Port", + "queryName": "RDS instance uses a default port", "severity": "LOW", "line": 10, "fileName": "positive4.yaml" diff --git a/assets/queries/ansible/aws/rds_with_backup_disabled/metadata.json b/assets/queries/ansible/aws/rds_with_backup_disabled/metadata.json index 29912eeb..612c7df6 100644 --- a/assets/queries/ansible/aws/rds_with_backup_disabled/metadata.json +++ b/assets/queries/ansible/aws/rds_with_backup_disabled/metadata.json @@ -1,13 +1,13 @@ { "id": "e69890e6-fce5-461d-98ad-cb98318dfc96", - "queryName": "RDS With Backup Disabled", + "queryName": "RDS instance with backup disabled", "severity": "MEDIUM", "category": "Backup", - "descriptionText": "Make sure the AWS RDS configuration has automatic backup configured. If the retention period is equal to 0 there is no backup", + "descriptionText": "An RDS instance with automated backups disabled (`backup_retention_period` set to `0`) cannot perform point-in-time recovery and is at increased risk of permanent data loss and regulatory non‑compliance.\n\nFor Ansible resources using `amazon.aws.rds_instance` or `rds_instance`, the `backup_retention_period` property must be defined and set to an integer greater than `0` (value is in days). Resources missing this property or with `backup_retention_period: 0` are flagged. Set it to at least `1` (commonly 7 or more) based on your recovery objectives.\n\nSecure configuration example for Ansible:\n\n```yaml\n- name: Create RDS instance with automated backups\n amazon.aws.rds_instance:\n db_instance_identifier: mydb\n engine: postgres\n instance_class: db.t3.medium\n allocated_storage: 20\n backup_retention_period: 7\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/rds_with_backup_disabled", "platform": "Ansible", "descriptionID": "51f94eee", "cloudProvider": "aws", "cwe": "754", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/rds_instance_module.html#parameter-backup_retention_period" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-backup_retention_period" } diff --git a/assets/queries/ansible/aws/rds_with_backup_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/rds_with_backup_disabled/test/positive_expected_result.json index 61f421b9..e7e11977 100644 --- a/assets/queries/ansible/aws/rds_with_backup_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/rds_with_backup_disabled/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "RDS With Backup Disabled", + "queryName": "RDS instance with backup disabled", "severity": "MEDIUM", "line": 10 } diff --git a/assets/queries/ansible/aws/redis_not_compliant/metadata.json b/assets/queries/ansible/aws/redis_not_compliant/metadata.json index 7feaa56c..961686ee 100644 --- a/assets/queries/ansible/aws/redis_not_compliant/metadata.json +++ b/assets/queries/ansible/aws/redis_not_compliant/metadata.json @@ -1,9 +1,9 @@ { "id": "9f34885e-c08f-4d13-a7d1-cf190c5bd268", - "queryName": "Redis Not Compliant", + "queryName": "Redis not compliant", "severity": "HIGH", "category": "Encryption", - "descriptionText": "Check if the redis version is compliant with the necessary AWS PCI DSS requirements", + "descriptionText": "ElastiCache Redis engine versions must meet the AWS PCI DSS baseline. Running outdated Redis releases can expose known vulnerabilities and lead to non-compliance. In Ansible, tasks using the `community.aws.elasticache` or `elasticache` modules must define `cache_engine_version` and set it to a version equal to or newer than `4.0.10`. Resources missing `cache_engine_version` or specifying a lower version are flagged as non-compliant. Update to a maintained Redis release that satisfies PCI DSS requirements.\n\nSecure example for Ansible:\n\n```yaml\n- name: Create ElastiCache Redis cluster\n community.aws.elasticache:\n name: my-redis-cluster\n engine: redis\n cache_engine_version: \"4.0.10\"\n node_type: cache.t3.small\n num_cache_nodes: 1\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/redis_not_compliant", "platform": "Ansible", "descriptionID": "99b5ec71", diff --git a/assets/queries/ansible/aws/redis_not_compliant/test/positive_expected_result.json b/assets/queries/ansible/aws/redis_not_compliant/test/positive_expected_result.json index b9ec5be9..4649ad77 100644 --- a/assets/queries/ansible/aws/redis_not_compliant/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/redis_not_compliant/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Redis Not Compliant", + "queryName": "Redis not compliant", "severity": "HIGH", "line": 6 } diff --git a/assets/queries/ansible/aws/redshift_not_encrypted/metadata.json b/assets/queries/ansible/aws/redshift_not_encrypted/metadata.json index 0e7455b0..8aa02eb2 100644 --- a/assets/queries/ansible/aws/redshift_not_encrypted/metadata.json +++ b/assets/queries/ansible/aws/redshift_not_encrypted/metadata.json @@ -1,9 +1,9 @@ { "id": "6a647814-def5-4b85-88f5-897c19f509cd", - "queryName": "Redshift Not Encrypted", + "queryName": "Redshift cluster is not encrypted", "severity": "HIGH", "category": "Encryption", - "descriptionText": "AWS Redshift Cluster should be encrypted. Check if 'encrypted' field is false or undefined (default is false)", + "descriptionText": "AWS Redshift clusters must have storage encryption enabled to protect sensitive data at rest, including data on cluster disks, automated snapshots, and backups. Without encryption, data can be exposed if storage media or snapshots are compromised. For Ansible, tasks using the `redshift` or `community.aws.redshift` modules that create or modify clusters must set the `encrypted` parameter to `true`. Resources where `encrypted` is omitted or explicitly set to `false` are flagged because the modules default to unencrypted when the property is not provided. Optionally specify a customer-managed KMS key with `kms_key_id` when `encrypted: true` is required.\n\nSecure example:\n\n```yaml\n- name: Create encrypted Redshift cluster\n community.aws.redshift:\n command: create\n cluster_identifier: my-cluster\n node_type: dc2.large\n number_of_nodes: 2\n encrypted: true\n kms_key_id: arn:aws:kms:us-east-1:123456789012:key/abcd-ef01-2345-6789\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/redshift_not_encrypted", "platform": "Ansible", "descriptionID": "85cddbf3", diff --git a/assets/queries/ansible/aws/redshift_not_encrypted/test/positive_expected_result.json b/assets/queries/ansible/aws/redshift_not_encrypted/test/positive_expected_result.json index e51ed569..dda963da 100644 --- a/assets/queries/ansible/aws/redshift_not_encrypted/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/redshift_not_encrypted/test/positive_expected_result.json @@ -1,16 +1,16 @@ [ { - "queryName": "Redshift Not Encrypted", + "queryName": "Redshift cluster is not encrypted", "severity": "HIGH", "line": 2 }, { - "queryName": "Redshift Not Encrypted", + "queryName": "Redshift cluster is not encrypted", "severity": "HIGH", "line": 19 }, { - "queryName": "Redshift Not Encrypted", + "queryName": "Redshift cluster is not encrypted", "severity": "HIGH", "line": 29 } diff --git a/assets/queries/ansible/aws/redshift_publicly_accessible/metadata.json b/assets/queries/ansible/aws/redshift_publicly_accessible/metadata.json index 675cfce2..139a12f5 100644 --- a/assets/queries/ansible/aws/redshift_publicly_accessible/metadata.json +++ b/assets/queries/ansible/aws/redshift_publicly_accessible/metadata.json @@ -1,9 +1,9 @@ { "id": "5c6b727b-1382-4629-8ba9-abd1365e5610", - "queryName": "Redshift Publicly Accessible", + "queryName": "Redshift publicly accessible", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "AWS Redshift Clusters must not be publicly accessible. Check if 'publicly_accessible' field is true (default is false)", + "descriptionText": "Redshift clusters must not be publicly accessible. Exposing cluster endpoints to the internet increases the risk of unauthorized access, data exfiltration, and brute-force attacks. For Ansible, check tasks using the `redshift` or `community.aws.redshift` modules: the `publicly_accessible` parameter must be set to `false`. This rule flags any task where `publicly_accessible` is `true`. Explicitly set `publicly_accessible: false` in your task to ensure the cluster is not reachable from the public internet. Relying on implicit defaults may be ambiguous across versions.\n\nSecure configuration example:\n\n```yaml\n- name: Create Redshift cluster (not publicly accessible)\n community.aws.redshift:\n cluster_identifier: my-cluster\n node_type: dc2.large\n number_of_nodes: 2\n publicly_accessible: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/redshift_publicly_accessible", "platform": "Ansible", "descriptionID": "ffdc02cc", diff --git a/assets/queries/ansible/aws/redshift_publicly_accessible/test/positive_expected_result.json b/assets/queries/ansible/aws/redshift_publicly_accessible/test/positive_expected_result.json index 183c583e..a33c00aa 100644 --- a/assets/queries/ansible/aws/redshift_publicly_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/redshift_publicly_accessible/test/positive_expected_result.json @@ -1,16 +1,16 @@ [ { - "queryName": "Redshift Publicly Accessible", + "queryName": "Redshift publicly accessible", "severity": "HIGH", "line": 9 }, { - "queryName": "Redshift Publicly Accessible", + "queryName": "Redshift publicly accessible", "severity": "HIGH", "line": 17 }, { - "queryName": "Redshift Publicly Accessible", + "queryName": "Redshift publicly accessible", "severity": "HIGH", "line": 25 } diff --git a/assets/queries/ansible/aws/redshift_using_default_port/metadata.json b/assets/queries/ansible/aws/redshift_using_default_port/metadata.json index af3fe93b..6a231317 100644 --- a/assets/queries/ansible/aws/redshift_using_default_port/metadata.json +++ b/assets/queries/ansible/aws/redshift_using_default_port/metadata.json @@ -1,9 +1,9 @@ { "id": "e01de151-a7bd-4db4-b49b-3c4775a5e881", - "queryName": "Redshift Using Default Port", + "queryName": "Redshift using default port", "severity": "LOW", "category": "Networking and Firewall", - "descriptionText": "Redshift should not use the default port (5439) because an attacker can easily guess the port", + "descriptionText": "Using the default Amazon Redshift port (5439) increases exposure because well-known ports are easy to discover and target with automated scanning and brute-force attempts.\n\nIn Ansible playbooks that use the `redshift` or `community.aws.redshift` modules, the `port` property must not be set to `5439`. Tasks with `port: 5439` are flagged. Choose a non-default port and restrict access using VPC private subnets and security group rules to limit which IPs or subnets can reach the cluster.\n\nSecure example with a non-default port:\n\n```yaml\n- name: Create Redshift cluster with non-default port\n community.aws.redshift:\n cluster_identifier: my-redshift-cluster\n node_type: dc2.large\n master_username: masteruser\n master_user_password: secretpassword\n db_name: mydb\n port: 15432\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/redshift_using_default_port", "platform": "Ansible", "descriptionID": "64fe28a7", diff --git a/assets/queries/ansible/aws/redshift_using_default_port/test/positive_expected_result.json b/assets/queries/ansible/aws/redshift_using_default_port/test/positive_expected_result.json index 8241a368..068893c3 100644 --- a/assets/queries/ansible/aws/redshift_using_default_port/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/redshift_using_default_port/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Redshift Using Default Port", + "queryName": "Redshift using default port", "severity": "LOW", "line": 8, "fileName": "positive.yaml" diff --git a/assets/queries/ansible/aws/remote_desktop_port_open/metadata.json b/assets/queries/ansible/aws/remote_desktop_port_open/metadata.json index 9e48f7cb..8cb87410 100644 --- a/assets/queries/ansible/aws/remote_desktop_port_open/metadata.json +++ b/assets/queries/ansible/aws/remote_desktop_port_open/metadata.json @@ -1,9 +1,9 @@ { "id": "eda7301d-1f3e-47cf-8d4e-976debc64341", - "queryName": "Remote Desktop Port Open To Internet", + "queryName": "Remote desktop port open to internet", "severity": "HIGH", "category": "Networking and Firewall", - "descriptionText": "The Remote Desktop port is open to the internet in a Security Group", + "descriptionText": "Security groups that allow Remote Desktop (RDP, TCP port 3389) from 0.0.0.0/0 expose Windows hosts to the public internet, increasing the likelihood of brute-force compromise, unauthorized access, and ransomware or lateral movement.\n\nAnsible EC2 security group resources using the `amazon.aws.ec2_group` or `ec2_group` module must not include a rule where `cidr_ip` is `\"0.0.0.0/0\"` that permits port 3389 (that is, a rule with `proto: tcp`, `from_port: 3389`, `to_port: 3389`). Tasks with such a rule are flagged. Restrict RDP to specific trusted CIDR ranges, require bastion hosts or VPN access, or remove the rule entirely. \n\nSecure example restricting RDP to a trusted network:\n\n```yaml\n- name: Create security group with restricted RDP\n amazon.aws.ec2_group:\n name: my-sg\n description: SG with RDP restricted\n rules:\n - proto: tcp\n from_port: 3389\n to_port: 3389\n cidr_ip: 203.0.113.0/24\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/remote_desktop_port_open", "platform": "Ansible", "descriptionID": "d644276b", diff --git a/assets/queries/ansible/aws/remote_desktop_port_open/test/positive_expected_result.json b/assets/queries/ansible/aws/remote_desktop_port_open/test/positive_expected_result.json index 65612015..c512c295 100644 --- a/assets/queries/ansible/aws/remote_desktop_port_open/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/remote_desktop_port_open/test/positive_expected_result.json @@ -1,36 +1,36 @@ [ { - "queryName": "Remote Desktop Port Open To Internet", + "queryName": "Remote desktop port open to internet", "severity": "HIGH", "line": 9 }, { - "queryName": "Remote Desktop Port Open To Internet", + "queryName": "Remote desktop port open to internet", "severity": "HIGH", "line": 23 }, { - "queryName": "Remote Desktop Port Open To Internet", + "queryName": "Remote desktop port open to internet", "severity": "HIGH", "line": 36 }, { - "queryName": "Remote Desktop Port Open To Internet", + "queryName": "Remote desktop port open to internet", "severity": "HIGH", "line": 49 }, { - "queryName": "Remote Desktop Port Open To Internet", + "queryName": "Remote desktop port open to internet", "severity": "HIGH", "line": 64 }, { - "queryName": "Remote Desktop Port Open To Internet", + "queryName": "Remote desktop port open to internet", "severity": "HIGH", "line": 79 }, { - "queryName": "Remote Desktop Port Open To Internet", + "queryName": "Remote desktop port open to internet", "severity": "HIGH", "line": 93 } diff --git a/assets/queries/ansible/aws/root_account_has_active_access_keys/metadata.json b/assets/queries/ansible/aws/root_account_has_active_access_keys/metadata.json index 368df6e6..0df959b7 100644 --- a/assets/queries/ansible/aws/root_account_has_active_access_keys/metadata.json +++ b/assets/queries/ansible/aws/root_account_has_active_access_keys/metadata.json @@ -1,13 +1,13 @@ { "id": "e71d0bc7-d9e8-4e6e-ae90-0a4206db6f40", - "queryName": "Root Account Has Active Access Keys", + "queryName": "Root account has active access keys", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "The AWS Root Account must not have active access keys associated, which means if there are access keys associated to the Root Account, they must be inactive.", + "descriptionText": "Active root access keys grant full, account-wide privileges. A leaked key could lead to immediate and complete compromise of the environment. This rule inspects Ansible tasks using the `amazon.aws.iam_access_key` or `iam_access_key` modules and flags entries where `user_name` contains \"root\", the `active` property is `true` (or absent, since `true` is the default), and `state` is not `absent`.\n\nThe `active` property must not be `true` for root account entries. Resources should either omit root access keys or set `active` to `false`. Any task with an active root access key is flagged. Remove or deactivate root access keys and use IAM users or roles with least privilege for automation and service access.", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/root_account_has_active_access_keys", "platform": "Ansible", "descriptionID": "6cd5514d", "cloudProvider": "aws", "cwe": "710", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/iam_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_access_key_module.html" } diff --git a/assets/queries/ansible/aws/root_account_has_active_access_keys/test/positive_expected_result.json b/assets/queries/ansible/aws/root_account_has_active_access_keys/test/positive_expected_result.json index 25b4a191..f7ca5fc8 100644 --- a/assets/queries/ansible/aws/root_account_has_active_access_keys/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/root_account_has_active_access_keys/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Root Account Has Active Access Keys", + "queryName": "Root account has active access keys", "severity": "HIGH", "line": 3 } diff --git a/assets/queries/ansible/aws/route53_record_undefined/metadata.json b/assets/queries/ansible/aws/route53_record_undefined/metadata.json index 5ae2056f..e06adb6e 100644 --- a/assets/queries/ansible/aws/route53_record_undefined/metadata.json +++ b/assets/queries/ansible/aws/route53_record_undefined/metadata.json @@ -1,13 +1,13 @@ { "id": "445dce51-7e53-4e50-80ef-7f94f14169e4", - "queryName": "Route53 Record Undefined", + "queryName": "Route 53 record undefined", "severity": "HIGH", "category": "Networking and Firewall", - "descriptionText": "Route53 Record should have a list of records", + "descriptionText": "Route 53 record resources must include one or more record values so DNS entries are created and resolve correctly. Missing values can lead to service disruption, broken name resolution, or unintended traffic routing. For Ansible tasks using the `amazon.aws.route53` or `route53` modules, the `value` parameter must be present and non-null, typically as a list of one or more string values. Tasks missing the `value` parameter, with `value: null`, or with an empty list are flagged.\n\nSecure example Ansible task:\n\n```yaml\n- name: Create A record for app.example.com\n amazon.aws.route53:\n zone: example.com\n record: app\n type: A\n ttl: 300\n value:\n - \"203.0.113.10\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/route53_record_undefined", "platform": "Ansible", "descriptionID": "2b699de7", "cloudProvider": "aws", "cwe": "778", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/route53_module.html#parameter-value" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/route53_module.html#parameter-value" } diff --git a/assets/queries/ansible/aws/route53_record_undefined/test/positive_expected_result.json b/assets/queries/ansible/aws/route53_record_undefined/test/positive_expected_result.json index f2880c7d..e0ea6ef1 100644 --- a/assets/queries/ansible/aws/route53_record_undefined/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/route53_record_undefined/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ - { - "queryName": "Route53 Record Undefined", - "severity": "HIGH", - "line": 3 - }, - { - "queryName": "Route53 Record Undefined", - "severity": "HIGH", - "line": 14 - } + { + "queryName": "Route 53 record undefined", + "severity": "HIGH", + "line": 3 + }, + { + "queryName": "Route 53 record undefined", + "severity": "HIGH", + "line": 14 + } ] diff --git a/assets/queries/ansible/aws/s3_bucket_access_to_any_principal/metadata.json b/assets/queries/ansible/aws/s3_bucket_access_to_any_principal/metadata.json index 2bbc84cf..d21c8a66 100644 --- a/assets/queries/ansible/aws/s3_bucket_access_to_any_principal/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_access_to_any_principal/metadata.json @@ -1,9 +1,9 @@ { "id": "3ab1f27d-52cc-4943-af1d-43c1939e739a", - "queryName": "S3 Bucket Access to Any Principal", + "queryName": "S3 bucket access to any principal", "severity": "CRITICAL", "category": "Access Control", - "descriptionText": "Checks if the S3 bucket is accessible for all users", + "descriptionText": "S3 bucket policies must not grant the wildcard principal (`\"*\"`) `Allow` access. This effectively makes the bucket accessible to any AWS account or anonymous user and can expose sensitive objects or lead to data leakage. This rule checks Ansible tasks using the `amazon.aws.s3_bucket` or `s3_bucket` modules and inspects the `policy` document to ensure no `Statement` has `Effect: \"Allow\"` with `Principal: \"*\"`.\n\nResources with a policy Statement where `Principal` is `*` and the effect is `Allow` are flagged. Instead, specify explicit principals (account IDs or IAM ARNs) or restrict access using conditions (for example `aws:SourceAccount` or `aws:PrincipalOrgID`) or S3 Block Public Access.\n\nSecure example with an explicit principal:\n\n```yaml\n- name: Create S3 bucket with restricted policy\n amazon.aws.s3_bucket:\n name: my-bucket\n policy: |\n {\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": { \"AWS\": \"arn:aws:iam::123456789012:root\" },\n \"Action\": \"s3:GetObject\",\n \"Resource\": \"arn:aws:s3:::my-bucket/*\"\n }\n ]\n }\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_access_to_any_principal", "platform": "Ansible", "descriptionID": "25111d64", diff --git a/assets/queries/ansible/aws/s3_bucket_access_to_any_principal/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_access_to_any_principal/test/positive_expected_result.json index a0c9e642..fad9f31b 100644 --- a/assets/queries/ansible/aws/s3_bucket_access_to_any_principal/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_access_to_any_principal/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ { - "queryName": "S3 Bucket Access to Any Principal", + "queryName": "S3 bucket access to any principal", "severity": "CRITICAL", "line": 4, "fileName": "positive.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_all_users/metadata.json b/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_all_users/metadata.json index b8eeab59..b38aebb7 100644 --- a/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_all_users/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_all_users/metadata.json @@ -1,13 +1,13 @@ { "id": "a1ef9d2e-4163-40cb-bd92-04f0d602a15d", - "queryName": "S3 Bucket ACL Allows Read to All Users", + "queryName": "S3 bucket ACL allows read access to all users", "severity": "HIGH", "category": "Access Control", - "descriptionText": "S3 Buckets should not be readable to all users", + "descriptionText": "S3 buckets must not be configured to allow read access to all users. Public-read ACLs make objects and metadata accessible to anyone on the internet, risking data exposure and compliance violations.\n\nFor Ansible tasks using the `amazon.aws.s3_object` or `s3_object` modules, the `permission` parameter must not be set to values that start with `public-read` (for example `public-read` or `public-read-write`). Tasks with `permission` omitted or set to restrictive values such as `private`, or that rely on explicit bucket policies to grant scoped access, are acceptable. Resources with `permission` starting with `public-read` are flagged. Secure configuration example:\n\n```yaml\n- name: Create S3 bucket with private ACL\n amazon.aws.s3_object:\n bucket: my-bucket\n permission: private\n mode: create\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_acl_allows_read_to_all_users", "platform": "Ansible", "descriptionID": "446af0d8", "cloudProvider": "aws", "cwe": "732", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/aws_s3_module.html#parameter-permission" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_object_module.html#parameter-permission" } diff --git a/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_all_users/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_all_users/test/positive_expected_result.json index 51216684..7ad1770c 100644 --- a/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_all_users/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_all_users/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "S3 Bucket ACL Allows Read to All Users", + "queryName": "S3 bucket ACL allows read access to all users", "severity": "HIGH", "line": 7 }, { - "queryName": "S3 Bucket ACL Allows Read to All Users", + "queryName": "S3 bucket ACL allows read access to all users", "severity": "HIGH", "line": 13 } diff --git a/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user/metadata.json b/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user/metadata.json index 1ebba32e..70017a83 100644 --- a/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user/metadata.json @@ -1,13 +1,13 @@ { "id": "75480b31-f349-4b9a-861f-bce19588e674", - "queryName": "S3 Bucket ACL Allows Read to Any Authenticated User", + "queryName": "S3 bucket ACL allows read access to any authenticated user", "severity": "HIGH", "category": "Access Control", - "descriptionText": "S3 Buckets should not be readable to any authenticated user", + "descriptionText": "S3 objects or buckets configured with the `authenticated-read` ACL allow any AWS authenticated user to read your data. This exposes content beyond your account boundary and increases the risk of unauthorized data access or leakage.\n\nIn Ansible, tasks using the `amazon.aws.s3_object` or `s3_object` modules must not set the `permission` parameter to `authenticated-read`. Prefer `permission: private` or enforce access via explicit bucket policies or IAM roles. This rule flags Ansible tasks where `permission` is exactly `authenticated-read`.\n\nSecure example:\n\n```yaml\n- name: Upload file to S3 with private ACL\n amazon.aws.s3_object:\n bucket: my-bucket\n object: path/file.txt\n src: /local/file.txt\n permission: private\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user", "platform": "Ansible", "descriptionID": "e9e4ca47", "cloudProvider": "aws", "cwe": "732", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/aws_s3_module.html#parameter-permission" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_object_module.html#parameter-permission" } diff --git a/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user/test/positive_expected_result.json index 251cf576..e93282d1 100644 --- a/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "S3 Bucket ACL Allows Read to Any Authenticated User", + "queryName": "S3 bucket ACL allows read access to any authenticated user", "severity": "HIGH", "line": 7 } diff --git a/assets/queries/ansible/aws/s3_bucket_allows_delete_action_from_all_principals/metadata.json b/assets/queries/ansible/aws/s3_bucket_allows_delete_action_from_all_principals/metadata.json index 51c94e19..b501e3f4 100644 --- a/assets/queries/ansible/aws/s3_bucket_allows_delete_action_from_all_principals/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_allows_delete_action_from_all_principals/metadata.json @@ -1,9 +1,9 @@ { "id": "6fa44721-ef21-41c6-8665-330d59461163", - "queryName": "S3 Bucket Allows Delete Action From All Principals", + "queryName": "S3 bucket allows delete action from all principals", "severity": "CRITICAL", "category": "Access Control", - "descriptionText": "S3 Buckets must not allow Delete Action From All Principals, as to prevent leaking private information to the entire internet or allow unauthorized data tampering / deletion. This means the 'Effect' must not be 'Allow' when the 'Action' is Delete, for all Principals.", + "descriptionText": "S3 bucket policies must not grant delete permissions to all principals (`*`). Public delete rights can enable unauthorized data tampering or complete data loss by allowing anyone on the internet to remove objects or buckets.\n\nFor Ansible S3 resources (`amazon.aws.s3_bucket` or `s3_bucket`), ensure the `policy` document contains no Statement with `Effect: \"Allow\"`, `Principal: \"*\"`, and an `Action` that includes delete operations (for example `s3:DeleteObject` or `s3:DeleteBucket`).\n\nThis rule flags bucket resources whose `policy` includes an Allow statement granting delete-related actions to the wildcard principal. Instead, restrict delete permissions to specific AWS account IDs, IAM roles/ARNs, or remove delete actions for public principals.\n\nSecure example restricting delete to a specific AWS account:\n\n```yaml\n- name: Create S3 bucket with restricted delete permissions\n amazon.aws.s3_bucket:\n name: my-bucket\n policy: |\n {\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AllowSpecificAccountDelete\",\n \"Effect\": \"Allow\",\n \"Principal\": {\"AWS\": \"arn:aws:iam::123456789012:root\"},\n \"Action\": [\"s3:DeleteObject\", \"s3:DeleteBucket\"],\n \"Resource\": [\"arn:aws:s3:::my-bucket\", \"arn:aws:s3:::my-bucket/*\"]\n }\n ]\n }\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_allows_delete_action_from_all_principals", "platform": "Ansible", "descriptionID": "7c11444e", diff --git a/assets/queries/ansible/aws/s3_bucket_allows_delete_action_from_all_principals/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_allows_delete_action_from_all_principals/test/positive_expected_result.json index 06c2ef92..e7d609f9 100644 --- a/assets/queries/ansible/aws/s3_bucket_allows_delete_action_from_all_principals/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_allows_delete_action_from_all_principals/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ { - "queryName": "S3 Bucket Allows Delete Action From All Principals", + "queryName": "S3 bucket allows delete action from all principals", "severity": "CRITICAL", "line": 6, "fileName": "positive.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/s3_bucket_allows_get_action_from_all_principals/metadata.json b/assets/queries/ansible/aws/s3_bucket_allows_get_action_from_all_principals/metadata.json index e5567642..3a78f250 100644 --- a/assets/queries/ansible/aws/s3_bucket_allows_get_action_from_all_principals/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_allows_get_action_from_all_principals/metadata.json @@ -1,9 +1,9 @@ { "id": "53bce6a8-5492-4b1b-81cf-664385f0c4bf", - "queryName": "S3 Bucket Allows Get Action From All Principals", + "queryName": "S3 bucket allows GET action from all principals", "severity": "HIGH", "category": "Access Control", - "descriptionText": "S3 Buckets must not allow Get Action From All Principals, as to prevent leaking private information to the entire internet or allow unauthorized data tampering / deletion. This means the 'Effect' must not be 'Allow' when the 'Action' is Get, for all Principals.", + "descriptionText": "S3 bucket policies must not grant Get actions to all principals (\"*\"). Allowing public read access exposes bucket objects to unauthorized disclosure and accidental data leaks. For Ansible S3 bucket resources (modules `amazon.aws.s3_bucket` and `s3_bucket`), inspect the `policy` property for any Statement with `Effect: \"Allow\"`, `Principal: \"*\"`, and an `Action` that includes Get operations (for example, `s3:GetObject` or any action name containing \"Get\"). Such statements are flagged.\n\nRestrict access by specifying explicit principals (AWS account IDs, roles, or ARNs), narrowing the allowed actions, or adding conditions (IP/VPC, MFA, or other constraints). If public access is required, use presigned URLs or a controlled distribution layer rather than a public bucket policy.\n\nSecure example with an explicit principal:\n\n```yaml\n- name: Create S3 bucket with restricted policy\n amazon.aws.s3_bucket:\n name: my-bucket\n policy:\n Version: \"2012-10-17\"\n Statement:\n - Sid: \"AllowSpecificAccountGet\"\n Effect: \"Allow\"\n Principal:\n AWS: \"arn:aws:iam::123456789012:role/ReadOnlyRole\"\n Action:\n - \"s3:GetObject\"\n Resource: \"arn:aws:s3:::my-bucket/*\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_allows_get_action_from_all_principals", "platform": "Ansible", "descriptionID": "de0687eb", diff --git a/assets/queries/ansible/aws/s3_bucket_allows_get_action_from_all_principals/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_allows_get_action_from_all_principals/test/positive_expected_result.json index 8f1b3fbc..2c7514ba 100644 --- a/assets/queries/ansible/aws/s3_bucket_allows_get_action_from_all_principals/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_allows_get_action_from_all_principals/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "S3 Bucket Allows Get Action From All Principals", + "queryName": "S3 bucket allows GET action from all principals", "severity": "HIGH", "line": 6, "fileName": "positive.yaml" diff --git a/assets/queries/ansible/aws/s3_bucket_allows_list_action_from_all_principals/metadata.json b/assets/queries/ansible/aws/s3_bucket_allows_list_action_from_all_principals/metadata.json index fdc26f1f..c5d4ad07 100644 --- a/assets/queries/ansible/aws/s3_bucket_allows_list_action_from_all_principals/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_allows_list_action_from_all_principals/metadata.json @@ -1,9 +1,9 @@ { "id": "d395a950-12ce-4314-a742-ac5a785ab44e", - "queryName": "S3 Bucket Allows List Action From All Principals", + "queryName": "S3 bucket allows list action from all principals", "severity": "HIGH", "category": "Access Control", - "descriptionText": "S3 Buckets must not allow List Action From All Principals, as to prevent leaking private information to the entire internet or allow unauthorized data tampering / deletion. This means the 'Effect' must not be 'Allow' when the 'Action' is List, for all Principals.", + "descriptionText": "S3 bucket policies must not allow list actions to all principals ('*'). Exposing bucket listings to everyone reveals object inventories and metadata, enabling data discovery and potential unauthorized access or exfiltration.\n\nFor Ansible resources using `amazon.aws.s3_bucket` or `s3_bucket`, inspect the bucket `policy` document. Ensure there are no policy statements with `Effect` set to `Allow`, `Principal` set to `\"*\"`, and `Action` that includes list operations such as `s3:ListBucket`.\n\nResources with a statement that combines `Effect: Allow`, `Principal: \"*\"`, and a list action are flagged. Instead, restrict access to explicit principals (account IDs, role or service ARNs), apply IAM policies, or use S3 Public Access Block settings to prevent public listing.\n\nSecure example policy that grants List only to a specific principal:\n\n```json\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AllowListToSpecificPrincipal\",\n \"Effect\": \"Allow\",\n \"Principal\": { \"AWS\": \"arn:aws:iam::123456789012:role/AllowedRole\" },\n \"Action\": \"s3:ListBucket\",\n \"Resource\": \"arn:aws:s3:::my-bucket\"\n }\n ]\n}\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_allows_list_action_from_all_principals", "platform": "Ansible", "descriptionID": "8232deb2", diff --git a/assets/queries/ansible/aws/s3_bucket_allows_list_action_from_all_principals/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_allows_list_action_from_all_principals/test/positive_expected_result.json index 29ade8a4..4b8144b2 100644 --- a/assets/queries/ansible/aws/s3_bucket_allows_list_action_from_all_principals/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_allows_list_action_from_all_principals/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "S3 Bucket Allows List Action From All Principals", + "queryName": "S3 bucket allows list action from all principals", "severity": "HIGH", "line": 6, "fileName": "positive.yaml" diff --git a/assets/queries/ansible/aws/s3_bucket_allows_put_action_from_all_principals/metadata.json b/assets/queries/ansible/aws/s3_bucket_allows_put_action_from_all_principals/metadata.json index c94292e9..c6624f25 100644 --- a/assets/queries/ansible/aws/s3_bucket_allows_put_action_from_all_principals/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_allows_put_action_from_all_principals/metadata.json @@ -1,9 +1,9 @@ { "id": "a0f1bfe0-741e-473f-b3b2-13e66f856fab", - "queryName": "S3 Bucket Allows Put Action From All Principals", + "queryName": "S3 bucket allows put action from all principals", "severity": "CRITICAL", "category": "Access Control", - "descriptionText": "S3 Buckets must not allow Put Action From All Principals, as to prevent leaking private information to the entire internet or allow unauthorized data tampering / deletion. This means the 'Effect' must not be 'Allow' when the 'Action' is Put, for all Principals.", + "descriptionText": "S3 bucket policy statements that allow put actions to all principals (`Principal='*'` and `Effect='Allow'`) let anyone upload or overwrite objects, risking data tampering, malware injection, and unauthorized exposure of sensitive data.\n\nThis rule inspects Ansible `amazon.aws.s3_bucket` and `s3_bucket` resources' `policy` statements and flags any statement where `Effect` is `\"Allow\"`, `Principal` is `\"*\"`, and `Action` includes Put operations (for example `s3:PutObject` or any action name containing \"Put\").\n\nRemediate by restricting Put permissions to explicit principals, such as AWS account ARNs, IAM role ARNs, or service principals. Apply least-privilege permissions and conditions, or remove public Put permissions entirely.\n\nSecure example with a restricted principal:\n\n```yaml\n- name: Create S3 bucket with restricted Put permissions\n amazon.aws.s3_bucket:\n name: my-bucket\n policy: |\n {\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AllowPutForSpecificAccount\",\n \"Effect\": \"Allow\",\n \"Principal\": { \"AWS\": \"arn:aws:iam::123456789012:root\" },\n \"Action\": [ \"s3:PutObject\" ],\n \"Resource\": \"arn:aws:s3:::my-bucket/*\"\n }\n ]\n }\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_allows_put_action_from_all_principals", "platform": "Ansible", "descriptionID": "772b17ca", diff --git a/assets/queries/ansible/aws/s3_bucket_allows_put_action_from_all_principals/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_allows_put_action_from_all_principals/test/positive_expected_result.json index c876ed36..cb71d366 100644 --- a/assets/queries/ansible/aws/s3_bucket_allows_put_action_from_all_principals/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_allows_put_action_from_all_principals/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ { - "queryName": "S3 Bucket Allows Put Action From All Principals", + "queryName": "S3 bucket allows put action from all principals", "severity": "CRITICAL", "line": 6, "fileName": "positive.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/s3_bucket_logging_disabled/metadata.json b/assets/queries/ansible/aws/s3_bucket_logging_disabled/metadata.json index ac43afcb..b14a1b45 100644 --- a/assets/queries/ansible/aws/s3_bucket_logging_disabled/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_logging_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "c3b9f7b0-f5a0-49ec-9cbc-f1e346b7274d", - "queryName": "S3 Bucket Logging Disabled", + "queryName": "S3 bucket logging disabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Server Access Logging should be enabled on S3 Buckets so that all changes are logged and trackable", + "descriptionText": "Enabling botocore endpoint debug logs for S3 operations captures detailed client request and response traces useful for detecting suspicious activity and supporting incident investigation. For Ansible tasks using the `amazon.aws.s3_bucket` or `s3_bucket` modules, the `debug_botocore_endpoint_logs` property must be defined and set to `true`. Tasks where this property is missing or set to `false` are flagged.\n\nDebug logs can contain sensitive request data. Ensure they are collected, transmitted, and stored securely with appropriate access controls and retention policies.\n\nSecure configuration example:\n\n```yaml\n- name: Create S3 bucket with botocore endpoint debug logs enabled\n amazon.aws.s3_bucket:\n name: my-bucket\n state: present\n debug_botocore_endpoint_logs: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_logging_disabled", "platform": "Ansible", "descriptionID": "2b508aee", diff --git a/assets/queries/ansible/aws/s3_bucket_logging_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_logging_disabled/test/positive_expected_result.json index 312ed116..9e7e6f62 100644 --- a/assets/queries/ansible/aws/s3_bucket_logging_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_logging_disabled/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "S3 Bucket Logging Disabled", + "queryName": "S3 bucket logging disabled", "severity": "MEDIUM", "line": 6 } diff --git a/assets/queries/ansible/aws/s3_bucket_with_all_permissions/metadata.json b/assets/queries/ansible/aws/s3_bucket_with_all_permissions/metadata.json index c565f6a4..d1bce5de 100644 --- a/assets/queries/ansible/aws/s3_bucket_with_all_permissions/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_with_all_permissions/metadata.json @@ -1,9 +1,9 @@ { "id": "6a6d7e56-c913-4549-b5c5-5221e624d2ec", - "queryName": "S3 Bucket With All Permissions", + "queryName": "S3 bucket with all permissions", "severity": "CRITICAL", "category": "Access Control", - "descriptionText": "S3 Buckets should not have all permissions, as to prevent leaking private information to the entire internet or allow unauthorized data tampering / deletion. This means the 'Effect' must not be 'Allow' when the 'Action' is '*', for all Principals.", + "descriptionText": "S3 bucket policies must not grant all actions to all principals. A statement that sets `Effect`=`Allow` with both `Action`=`*` and `Principal`=`*` effectively makes the bucket publicly accessible and can enable data exfiltration or unauthorized modification/deletion.\n\nFor Ansible resources using the `amazon.aws.s3_bucket` or `s3_bucket` modules, inspect the resource `policy` document's `Statement` entries. Any statement where `Effect` is `Allow` and both `Action` and `Principal` contain the wildcard `*` (including arrays that include `*`) is flagged.\n\nRestrict `Principal` to explicit ARNs, account IDs, or service principals and scope `Action` to the minimum required permissions following least privilege.\n\nSecure example policy statement:\n\n```json\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": { \"AWS\": \"arn:aws:iam::123456789012:root\" },\n \"Action\": [ \"s3:GetObject\" ],\n \"Resource\": \"arn:aws:s3:::example-bucket/*\"\n }\n ]\n}\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_with_all_permissions", "platform": "Ansible", "descriptionID": "21fc95f2", diff --git a/assets/queries/ansible/aws/s3_bucket_with_all_permissions/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_with_all_permissions/test/positive_expected_result.json index fafb06cc..c05402b5 100644 --- a/assets/queries/ansible/aws/s3_bucket_with_all_permissions/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_with_all_permissions/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "S3 Bucket With All Permissions", + "queryName": "S3 bucket with all permissions", "severity": "CRITICAL", "line": 5 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/s3_bucket_with_public_access/metadata.json b/assets/queries/ansible/aws/s3_bucket_with_public_access/metadata.json index 1142fefc..ca2cc57d 100644 --- a/assets/queries/ansible/aws/s3_bucket_with_public_access/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_with_public_access/metadata.json @@ -1,14 +1,14 @@ { "id": "c3e073c1-f65e-4d18-bd67-4a8f20ad1ab9", - "queryName": "S3 Bucket With Public Access", + "queryName": "S3 bucket with public access", "severity": "CRITICAL", "category": "Access Control", - "descriptionText": "S3 Bucket allows public access", + "descriptionText": "Ansible tasks that set S3 `permission` to `public` create publicly accessible buckets or objects, risking data exposure and regulatory non‑compliance. For the `amazon.aws.s3_object` and `s3_object` modules, the `permission` property must be defined and must not contain the value `public`. Use `private` or other restricted values (for example, `authenticated-read`) as appropriate.\n\nThis rule flags tasks where `permission` contains `public`. Tasks missing an explicit `permission` should be reviewed and set to a non‑public value.\n\nSecure example:\n\n```yaml\n- name: Create private S3 bucket\n amazon.aws.s3_object:\n bucket: my-bucket\n permission: private\n mode: create\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_with_public_access", "platform": "Ansible", "descriptionID": "d7a19b7e", "cloudProvider": "aws", "cwe": "284", "oldSeverity": "MEDIUM", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/aws_s3_module.html#parameter-permission" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_object_module.html#parameter-permission" } diff --git a/assets/queries/ansible/aws/s3_bucket_with_public_access/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_with_public_access/test/positive_expected_result.json index 890c8177..36f57b0c 100644 --- a/assets/queries/ansible/aws/s3_bucket_with_public_access/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_with_public_access/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "S3 Bucket With Public Access", + "queryName": "S3 bucket with public access", "severity": "CRITICAL", "line": 7 }, { - "queryName": "S3 Bucket With Public Access", + "queryName": "S3 bucket with public access", "severity": "CRITICAL", "line": 13 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/s3_bucket_with_unsecured_cors_rule/metadata.json b/assets/queries/ansible/aws/s3_bucket_with_unsecured_cors_rule/metadata.json index 9f3477b9..9e021139 100644 --- a/assets/queries/ansible/aws/s3_bucket_with_unsecured_cors_rule/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_with_unsecured_cors_rule/metadata.json @@ -1,14 +1,14 @@ { "id": "3505094c-f77c-4ba0-95da-f83db712f86c", - "queryName": "S3 Bucket with Unsecured CORS Rule", + "queryName": "S3 bucket with unsecured CORS rule", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "If the CORS (Cross-Origin Resource Sharing) rule is defined in an S3 bucket, it should be secure", + "descriptionText": "S3 CORS rules must restrict allowed origins, methods, and headers to prevent unintended cross-origin access and data exfiltration. Overly permissive CORS (wildcard origins, all methods, or all headers) can allow arbitrary web pages to interact with or read bucket resources.\n\nFor Ansible resources `community.aws.s3_cors` and `s3_cors`, inspect each `rules` entry. `allowed_origins` should specify trusted origins (avoid `\"*\"` or unnecessarily broad lists). `allowed_methods` must not be `[\"*\"]` and should include only the HTTP verbs required by your application. `allowed_headers` must not be `[\"*\"]` and should be limited to the headers actually needed.\n\nRules with wildcard `allowed_methods` or `allowed_headers`, or with wildcard or overly broad origins are flagged. Prefer a single explicit origin or a narrowly-scoped set and the minimal set of methods and headers.\n\nSecure example:\n\n```yaml\n- name: Configure S3 CORS\n community.aws.s3_cors:\n name: my-bucket\n rules:\n - allowed_origins:\n - https://app.example.com\n allowed_methods:\n - GET\n - HEAD\n allowed_headers:\n - Authorization\n - Content-Type\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_with_unsecured_cors_rule", "platform": "Ansible", "descriptionID": "c700f52b", "cloudProvider": "aws", "cwe": "710", "oldSeverity": "HIGH", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_s3_cors_module.html#parameter-rules" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/s3_cors_module.html#parameter-rules" } diff --git a/assets/queries/ansible/aws/s3_bucket_with_unsecured_cors_rule/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_with_unsecured_cors_rule/test/positive_expected_result.json index 92201a3d..5e70702d 100644 --- a/assets/queries/ansible/aws/s3_bucket_with_unsecured_cors_rule/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_with_unsecured_cors_rule/test/positive_expected_result.json @@ -1,14 +1,14 @@ [ { - "queryName": "S3 Bucket with Unsecured CORS Rule", + "queryName": "S3 bucket with unsecured CORS rule", "severity": "MEDIUM", "line": 5, "fileName": "positive1.yaml" }, { - "queryName": "S3 Bucket with Unsecured CORS Rule", + "queryName": "S3 bucket with unsecured CORS rule", "severity": "MEDIUM", "line": 5, "fileName": "positive2.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/s3_bucket_without_server-side_encryption/metadata.json b/assets/queries/ansible/aws/s3_bucket_without_server-side_encryption/metadata.json index 299fdd97..69e84e4d 100644 --- a/assets/queries/ansible/aws/s3_bucket_without_server-side_encryption/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_without_server-side_encryption/metadata.json @@ -1,9 +1,9 @@ { "id": "594f54e7-f744-45ab-93e4-c6dbaf6cd571", - "queryName": "S3 Bucket Without Server-side-encryption", + "queryName": "S3 bucket without server-side encryption", "severity": "HIGH", "category": "Encryption", - "descriptionText": "AWS S3 Storage should be protected with SSE (Server-Side Encryption)", + "descriptionText": "S3 buckets should have server-side encryption (SSE) enabled to protect data at rest and prevent exposure of sensitive objects if a bucket is misconfigured or storage media is accessed.\n\nFor Ansible tasks using the amazon.aws.s3_bucket or s3_bucket modules, the `encryption` property must not be set to `'none'` and should be configured to a valid SSE algorithm such as `'AES256'` or `'aws:kms'`. Resources that omit the `encryption` property or explicitly set `encryption: 'none'` are flagged.\n\nWhen using `'aws:kms'`, also specify and manage a KMS key (for example via `kms_key_id`) to retain control over encryption keys and meet organizational access requirements.\n\nSecure example using KMS-managed keys:\n\n```yaml\n- name: Create S3 bucket with KMS encryption\n amazon.aws.s3_bucket:\n name: my-secure-bucket\n encryption: aws:kms\n kms_key_id: arn:aws:kms:us-east-1:123456789012:key/abcd-ef01-2345-6789\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_without_server-side_encryption", "platform": "Ansible", "descriptionID": "c0dc5aae", diff --git a/assets/queries/ansible/aws/s3_bucket_without_server-side_encryption/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_without_server-side_encryption/test/positive_expected_result.json index 64fca39c..7658b46c 100644 --- a/assets/queries/ansible/aws/s3_bucket_without_server-side_encryption/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_without_server-side_encryption/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "S3 Bucket Without Server-side-encryption", + "queryName": "S3 bucket without server-side encryption", "severity": "HIGH", "line": 5 } diff --git a/assets/queries/ansible/aws/s3_bucket_without_versioning/metadata.json b/assets/queries/ansible/aws/s3_bucket_without_versioning/metadata.json index 58e3ca29..5480f3cb 100644 --- a/assets/queries/ansible/aws/s3_bucket_without_versioning/metadata.json +++ b/assets/queries/ansible/aws/s3_bucket_without_versioning/metadata.json @@ -1,9 +1,9 @@ { "id": "9232306a-f839-40aa-b3ef-b352001da9a5", - "queryName": "S3 Bucket Without Versioning", + "queryName": "S3 bucket without versioning", "severity": "MEDIUM", "category": "Backup", - "descriptionText": "S3 bucket should have versioning enabled", + "descriptionText": "S3 buckets must have versioning enabled to protect objects from accidental or malicious deletion and retain prior versions for recovery, forensics, and compliance. For Ansible tasks using the `amazon.aws.s3_bucket` or `s3_bucket` modules, the `versioning` property must be defined and set to `true`. When omitted, the module defaults to versioning disabled. This rule flags tasks where the `versioning` key is missing or explicitly set to `false`.\n\nSecure configuration example:\n\n```yaml\n- name: Ensure S3 bucket with versioning enabled\n amazon.aws.s3_bucket:\n name: my-bucket\n versioning: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/s3_bucket_without_versioning", "platform": "Ansible", "descriptionID": "622e4c8e", diff --git a/assets/queries/ansible/aws/s3_bucket_without_versioning/test/positive_expected_result.json b/assets/queries/ansible/aws/s3_bucket_without_versioning/test/positive_expected_result.json index b92ee4c0..d4880dc1 100644 --- a/assets/queries/ansible/aws/s3_bucket_without_versioning/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/s3_bucket_without_versioning/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "S3 Bucket Without Versioning", + "queryName": "S3 bucket without versioning", "severity": "MEDIUM", "line": 3 }, { - "queryName": "S3 Bucket Without Versioning", + "queryName": "S3 bucket without versioning", "severity": "MEDIUM", "line": 15 } diff --git a/assets/queries/ansible/aws/secure_ciphers_disabled/metadata.json b/assets/queries/ansible/aws/secure_ciphers_disabled/metadata.json index a2f3f26b..5b66718d 100644 --- a/assets/queries/ansible/aws/secure_ciphers_disabled/metadata.json +++ b/assets/queries/ansible/aws/secure_ciphers_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "218413a0-c716-4b94-9e08-0bb70d854709", - "queryName": "Secure Ciphers Disabled", + "queryName": "Secure ciphers disabled", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "Check if secure ciphers aren't used in CloudFront", + "descriptionText": "CloudFront distributions that do not enforce a modern minimum TLS protocol version can allow legacy TLS/SSL versions, increasing the risk of downgrade attacks and interception of data in transit.\n\nFor Ansible CloudFront resources (modules `community.aws.cloudfront_distribution` and `cloudfront_distribution`), the `viewer_certificate.minimum_protocol_version` property must be defined and set to `TLSv1.1` or `TLSv1.2` (preferably `TLSv1.2`) when using a custom certificate (`viewer_certificate.cloudfront_default_certificate` set to `false`). Resources that omit `minimum_protocol_version` or specify any other value are flagged. \n\nSecure configuration example for Ansible:\n\n```yaml\n- name: Create CloudFront distribution with secure TLS\n community.aws.cloudfront_distribution:\n name: my-distribution\n viewer_certificate:\n cloudfront_default_certificate: false\n acm_certificate_arn: arn:aws:acm:region:acct:certificate/your-cert-id\n minimum_protocol_version: TLSv1.2\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/secure_ciphers_disabled", "platform": "Ansible", "descriptionID": "bc106b2e", diff --git a/assets/queries/ansible/aws/secure_ciphers_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/secure_ciphers_disabled/test/positive_expected_result.json index 0c32f95e..74fba33b 100644 --- a/assets/queries/ansible/aws/secure_ciphers_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/secure_ciphers_disabled/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "Secure Ciphers Disabled", + "queryName": "Secure ciphers disabled", "severity": "MEDIUM", "line": 14 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/security_group_ingress_not_restricted/metadata.json b/assets/queries/ansible/aws/security_group_ingress_not_restricted/metadata.json index d541c400..70dbbbc9 100644 --- a/assets/queries/ansible/aws/security_group_ingress_not_restricted/metadata.json +++ b/assets/queries/ansible/aws/security_group_ingress_not_restricted/metadata.json @@ -1,9 +1,9 @@ { "id": "ea6bc7a6-d696-4dcf-a788-17fa03c17c81", - "queryName": "Security Group Ingress Not Restricted", + "queryName": "Security group ingress not restricted", "severity": "HIGH", "category": "Networking and Firewall", - "descriptionText": "AWS Security Group should restrict ingress access", + "descriptionText": "Security groups must not allow unrestricted ingress from the public internet to all protocols and ports. Such rules expose instances to network scanning, exploitation, and unauthorized access.\n\nIn Ansible `amazon.aws.ec2_group` and `ec2_group` resources, each `rules` entry must not combine `from_port: 0` and `to_port: 0` with a non-explicit `proto` and an entire-network CIDR such as `cidr_ip: 0.0.0.0/0` or `cidr_ipv6: ::/0`.\n\nThe `proto` property must be an explicit protocol such as `tcp`, `udp`, `icmp`, `icmpv6`, or numeric values `1`, `6`, `17`, `58`. Rules where `proto` is missing or set to a catch-all (`-1`/`all`) with ports `0-0` and an entire-network CIDR are flagged.\n\nTo fix this, restrict the CIDR to trusted IP ranges or specify the exact protocol and port range required for the service.\n\nSecure configuration example:\n\n```yaml\n- name: secure security group\n amazon.aws.ec2_group:\n name: my_sg\n description: \"Allow SSH from admin network and HTTPS from anywhere\"\n rules:\n - proto: tcp\n from_port: 22\n to_port: 22\n cidr_ip: 203.0.113.0/24\n - proto: tcp\n from_port: 443\n to_port: 443\n cidr_ip: 0.0.0.0/0\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/security_group_ingress_not_restricted", "platform": "Ansible", "descriptionID": "7e47368a", diff --git a/assets/queries/ansible/aws/security_group_ingress_not_restricted/test/positive_expected_result.json b/assets/queries/ansible/aws/security_group_ingress_not_restricted/test/positive_expected_result.json index 6f227d16..da9212aa 100644 --- a/assets/queries/ansible/aws/security_group_ingress_not_restricted/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/security_group_ingress_not_restricted/test/positive_expected_result.json @@ -1,31 +1,31 @@ [ { - "queryName": "Security Group Ingress Not Restricted", + "queryName": "Security group ingress not restricted", "severity": "HIGH", "line": 8 }, { - "queryName": "Security Group Ingress Not Restricted", + "queryName": "Security group ingress not restricted", "severity": "HIGH", "line": 12 }, { - "queryName": "Security Group Ingress Not Restricted", + "queryName": "Security group ingress not restricted", "severity": "HIGH", "line": 16 }, { - "queryName": "Security Group Ingress Not Restricted", + "queryName": "Security group ingress not restricted", "severity": "HIGH", "line": 27 }, { - "queryName": "Security Group Ingress Not Restricted", + "queryName": "Security group ingress not restricted", "severity": "HIGH", "line": 31 }, { - "queryName": "Security Group Ingress Not Restricted", + "queryName": "Security group ingress not restricted", "severity": "HIGH", "line": 35 } diff --git a/assets/queries/ansible/aws/security_group_with_unrestricted_access_to_ssh/metadata.json b/assets/queries/ansible/aws/security_group_with_unrestricted_access_to_ssh/metadata.json index 88e1658d..87fd11e5 100644 --- a/assets/queries/ansible/aws/security_group_with_unrestricted_access_to_ssh/metadata.json +++ b/assets/queries/ansible/aws/security_group_with_unrestricted_access_to_ssh/metadata.json @@ -1,9 +1,9 @@ { "id": "57ced4b9-6ba4-487b-8843-b65562b90c77", - "queryName": "Security Group With Unrestricted Access To SSH", + "queryName": "Security group with unrestricted access to SSH", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "'SSH' (TCP:22) should not be public in AWS Security Group", + "descriptionText": "SSH (TCP port 22) must not be exposed to public CIDR ranges because it enables unauthorized remote access and increases the risk of brute-force or credential-stuffing attacks and lateral movement.\n\nThis check inspects Ansible tasks using `amazon.aws.ec2_group` or `ec2_group` and flags entries in the `rules` list where `from_port`/`to_port` cover port 22 (or are both `-1` indicating all ports) and `cidr_ip` or `cidr_ipv6` specify public CIDRs such as `0.0.0.0/0` or `::/0`. Limit `cidr_ip`/`cidr_ipv6` to specific trusted IP ranges, or remove SSH from the security group and enforce access through a bastion host or VPN. Any rule that leaves SSH open to public CIDRs is flagged. \n\nSecure example restricting SSH to a single trusted address:\n\n```yaml\n- name: my-secure-sg\n amazon.aws.ec2_group:\n name: my-secure-sg\n rules:\n - proto: tcp\n from_port: 22\n to_port: 22\n cidr_ip: 203.0.113.4/32\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/security_group_with_unrestricted_access_to_ssh", "platform": "Ansible", "descriptionID": "ea2f2c57", diff --git a/assets/queries/ansible/aws/security_group_with_unrestricted_access_to_ssh/test/positive_expected_result.json b/assets/queries/ansible/aws/security_group_with_unrestricted_access_to_ssh/test/positive_expected_result.json index 6976dfda..246e4502 100644 --- a/assets/queries/ansible/aws/security_group_with_unrestricted_access_to_ssh/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/security_group_with_unrestricted_access_to_ssh/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "Security Group With Unrestricted Access To SSH", + "queryName": "Security group with unrestricted access to SSH", "severity": "MEDIUM", "line": 8 }, { - "queryName": "Security Group With Unrestricted Access To SSH", + "queryName": "Security group with unrestricted access to SSH", "severity": "MEDIUM", "line": 12 }, { - "queryName": "Security Group With Unrestricted Access To SSH", + "queryName": "Security group with unrestricted access to SSH", "severity": "MEDIUM", "line": 16 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/ses_policy_with_allowed_iam_actions/metadata.json b/assets/queries/ansible/aws/ses_policy_with_allowed_iam_actions/metadata.json index efa272a5..8ef2d281 100644 --- a/assets/queries/ansible/aws/ses_policy_with_allowed_iam_actions/metadata.json +++ b/assets/queries/ansible/aws/ses_policy_with_allowed_iam_actions/metadata.json @@ -1,13 +1,13 @@ { "id": "8ed0bfce-f780-46d4-b086-21c3628f09ad", - "queryName": "SES Policy With Allowed IAM Actions", + "queryName": "SES policy with allowed IAM actions", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "SES policy should not allow IAM actions to all principals", + "descriptionText": "SES identity policies must not grant Allow permissions for all actions to all principals. A wildcard Action (`*`) combined with a wildcard Principal (`*`) lets any actor perform any API operation on the identity, enabling email spoofing, unauthorized sending, and potential privilege escalation.\n\nThis rule checks Ansible resources of type `community.aws.ses_identity_policy` and `aws.aws_ses_identity_policy`. The `policy` document must not contain statements with `\"Effect\": \"Allow\"` where `Action` is `\"*\"` (or contains `\"*\"`) and `Principal` is a wildcard (for example `\"*\"` or `{\"AWS\":\"*\"}`). Resources with such statements are flagged.\n\nSpecify explicit principals (AWS account ARNs or service principals) and restrict `Action` to the minimum required SES API operations. Secure example showing a restricted policy:\n\n```yaml\n- name: Attach SES identity policy\n community.aws.ses_identity_policy:\n identity: \"example.com\"\n policy: |\n {\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": { \"AWS\": \"arn:aws:iam::123456789012:root\" },\n \"Action\": [ \"ses:SendEmail\", \"ses:SendRawEmail\" ],\n \"Resource\": \"arn:aws:ses:us-east-1:123456789012:identity/example.com\"\n }\n ]\n }\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/ses_policy_with_allowed_iam_actions", "platform": "Ansible", "descriptionID": "89d6e6fd", "cloudProvider": "aws", "cwe": "284", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/aws_ses_identity_policy_module.html#parameter-policy" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/ses_identity_policy_module.html#parameter-policy" } diff --git a/assets/queries/ansible/aws/ses_policy_with_allowed_iam_actions/test/positive_expected_result.json b/assets/queries/ansible/aws/ses_policy_with_allowed_iam_actions/test/positive_expected_result.json index 7bffe912..e765e1f8 100644 --- a/assets/queries/ansible/aws/ses_policy_with_allowed_iam_actions/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/ses_policy_with_allowed_iam_actions/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "SES Policy With Allowed IAM Actions", + "queryName": "SES policy with allowed IAM actions", "severity": "MEDIUM", "line": 5, "fileName": "positive.yaml" diff --git a/assets/queries/ansible/aws/sns_topic_is_publicly_accessible/metadata.json b/assets/queries/ansible/aws/sns_topic_is_publicly_accessible/metadata.json index 27d004e9..de48f672 100644 --- a/assets/queries/ansible/aws/sns_topic_is_publicly_accessible/metadata.json +++ b/assets/queries/ansible/aws/sns_topic_is_publicly_accessible/metadata.json @@ -1,9 +1,9 @@ { "id": "905f4741-f965-45c1-98db-f7a00a0e5c73", - "queryName": "SNS Topic is Publicly Accessible", + "queryName": "SNS topic is publicly accessible", "severity": "HIGH", "category": "Access Control", - "descriptionText": "SNS Topic Policy should not allow any principal to access", + "descriptionText": "SNS topic policies must not allow any principal (`*`). Making a topic public permits unauthorized publishing or subscription, which can lead to message injection, data exfiltration, or unintended triggering of downstream systems.\n\nIn Ansible tasks using the `community.aws.sns_topic` or `sns_topic` modules, check the `policy` property and flag any `Statement` with `\"Effect\": \"Allow\"` where `Principal` is the wildcard (`\"*\"`) or contains `\"AWS\": \"*\"`. Policy statements must instead specify explicit principals such as AWS account IDs, ARNs, or service principals. Statements that use a wildcard principal or are not limited to a specific account ID are flagged.\n\nSecure configuration example for an Ansible task (explicit principal):\n\n```yaml\n- name: create sns topic with restricted policy\n community.aws.sns_topic:\n name: my-topic\n policy:\n Version: \"2012-10-17\"\n Statement:\n - Sid: AllowSpecificAccount\n Effect: Allow\n Principal:\n AWS: \"arn:aws:iam::123456789012:root\"\n Action: \"SNS:Publish\"\n Resource: \"arn:aws:sns:us-east-1:123456789012:my-topic\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/sns_topic_is_publicly_accessible", "platform": "Ansible", "descriptionID": "956322cf", diff --git a/assets/queries/ansible/aws/sns_topic_is_publicly_accessible/test/positive_expected_result.json b/assets/queries/ansible/aws/sns_topic_is_publicly_accessible/test/positive_expected_result.json index 73ce9cee..4e23c67a 100644 --- a/assets/queries/ansible/aws/sns_topic_is_publicly_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/sns_topic_is_publicly_accessible/test/positive_expected_result.json @@ -1,14 +1,14 @@ [ - { - "queryName": "SNS Topic is Publicly Accessible", - "severity": "HIGH", - "line": 23, - "fileName": "positive.yaml" - }, - { - "queryName": "SNS Topic is Publicly Accessible", - "severity": "HIGH", - "line": 50, - "fileName": "positive.yaml" - } + { + "queryName": "SNS topic is publicly accessible", + "severity": "HIGH", + "line": 23, + "fileName": "positive.yaml" + }, + { + "queryName": "SNS topic is publicly accessible", + "severity": "HIGH", + "line": 50, + "fileName": "positive.yaml" + } ] diff --git a/assets/queries/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible/metadata.json b/assets/queries/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible/metadata.json index 7efe30a2..a3a6a471 100644 --- a/assets/queries/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible/metadata.json +++ b/assets/queries/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible/metadata.json @@ -1,9 +1,9 @@ { "id": "7af1c447-c014-4f05-bd8b-ebe3a15734ac", - "queryName": "SQL Analysis Services Port 2383 (TCP) Is Publicly Accessible", + "queryName": "SQL Analysis Services port 2383 (TCP) is publicly accessible", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Check if port 2383 on TCP is publicly accessible by checking the CIDR block range that can access it.", + "descriptionText": "Allowing TCP port 2383 (SQL Server Analysis Services) from the public internet (CIDR `0.0.0.0/0`) exposes the analysis service to unauthorized connections, increasing the risk of data exposure, unauthorized queries, and lateral movement into your environment.\n\nFor Ansible tasks using the `amazon.aws.ec2_group` or `ec2_group` module, this rule flags any `rules` entry where `cidr_ip` is `0.0.0.0/0`, `proto` is `tcp`, and the rule includes port 2383. Restrict access by specifying a limited CIDR range or referencing internal security groups instead of `0.0.0.0/0`, or remove the rule if public access is not required.\n\nSecure configuration example:\n\n```yaml\nmy_security_group:\n amazon.aws.ec2_group:\n name: my-sg\n rules:\n - proto: tcp\n from_port: 2383\n to_port: 2383\n cidr_ip: 10.0.0.0/24\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible", "platform": "Ansible", "descriptionID": "69176b78", diff --git a/assets/queries/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible/test/positive_expected_result.json b/assets/queries/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible/test/positive_expected_result.json index a3eb82d8..a5c62321 100644 --- a/assets/queries/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible/test/positive_expected_result.json @@ -1,26 +1,26 @@ [ { - "queryName": "SQL Analysis Services Port 2383 (TCP) Is Publicly Accessible", + "queryName": "SQL Analysis Services port 2383 (TCP) is publicly accessible", "severity": "MEDIUM", "line": 9 }, { - "queryName": "SQL Analysis Services Port 2383 (TCP) Is Publicly Accessible", + "queryName": "SQL Analysis Services port 2383 (TCP) is publicly accessible", "severity": "MEDIUM", "line": 23 }, { - "queryName": "SQL Analysis Services Port 2383 (TCP) Is Publicly Accessible", + "queryName": "SQL Analysis Services port 2383 (TCP) is publicly accessible", "severity": "MEDIUM", "line": 37 }, { - "queryName": "SQL Analysis Services Port 2383 (TCP) Is Publicly Accessible", + "queryName": "SQL Analysis Services port 2383 (TCP) is publicly accessible", "severity": "MEDIUM", "line": 51 }, { - "queryName": "SQL Analysis Services Port 2383 (TCP) Is Publicly Accessible", + "queryName": "SQL Analysis Services port 2383 (TCP) is publicly accessible", "severity": "MEDIUM", "line": 65 } diff --git a/assets/queries/ansible/aws/sqs_policy_allows_all_actions/metadata.json b/assets/queries/ansible/aws/sqs_policy_allows_all_actions/metadata.json index e6ffd9d8..16bb0eac 100644 --- a/assets/queries/ansible/aws/sqs_policy_allows_all_actions/metadata.json +++ b/assets/queries/ansible/aws/sqs_policy_allows_all_actions/metadata.json @@ -1,9 +1,9 @@ { "id": "ed9b3beb-92cf-44d9-a9d2-171eeba569d4", - "queryName": "SQS Policy Allows All Actions", + "queryName": "SQS policy allows all actions", "severity": "HIGH", "category": "Access Control", - "descriptionText": "SQS policy allows ALL (*) actions", + "descriptionText": "SQS queue policies must not grant wildcard (`*`) actions. Allowing all actions on a queue enables unauthorized access, message retrieval or deletion, and queue modification, which can lead to data exposure or service disruption.\n\nFor Ansible SQS resources (`community.aws.sqs_queue` and `sqs_queue`), inspect the `policy` document and ensure no `Statement` with `Effect: \"Allow\"` has `Action` set to `*` or contains `*`. Resources with `Action` set to `*` or `Action` arrays that include `*` are flagged. Instead, specify explicit SQS actions (for example, `sqs:SendMessage`, `sqs:ReceiveMessage`, `sqs:DeleteMessage`) and restrict principals to the minimum required.\n\nSecure example with explicit actions and principal:\n\n```yaml\n- name: Create SQS queue with restricted policy\n community.aws.sqs_queue:\n name: my-queue\n policy: |\n {\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": { \"AWS\": \"arn:aws:iam::123456789012:role/MyRole\" },\n \"Action\": [\"sqs:SendMessage\", \"sqs:ReceiveMessage\", \"sqs:DeleteMessage\"],\n \"Resource\": \"arn:aws:sqs:us-east-1:123456789012:my-queue\"\n }\n ]\n }\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/sqs_policy_allows_all_actions", "platform": "Ansible", "descriptionID": "7e78a2e9", diff --git a/assets/queries/ansible/aws/sqs_policy_allows_all_actions/test/positive_expected_result.json b/assets/queries/ansible/aws/sqs_policy_allows_all_actions/test/positive_expected_result.json index c16a93df..bbffbd79 100644 --- a/assets/queries/ansible/aws/sqs_policy_allows_all_actions/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/sqs_policy_allows_all_actions/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ { - "queryName": "SQS Policy Allows All Actions", + "queryName": "SQS policy allows all actions", "severity": "HIGH", "line": 10, "fileName": "positive.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/sqs_policy_with_public_access/metadata.json b/assets/queries/ansible/aws/sqs_policy_with_public_access/metadata.json index b8cc6f06..4861a416 100644 --- a/assets/queries/ansible/aws/sqs_policy_with_public_access/metadata.json +++ b/assets/queries/ansible/aws/sqs_policy_with_public_access/metadata.json @@ -1,9 +1,9 @@ { "id": "d994585f-defb-4b51-b6d2-c70f020ceb10", - "queryName": "SQS Policy With Public Access", + "queryName": "SQS policy with public access", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "Checks for dangerous permissions in Action statements in an SQS Queue Policy. This is deemed a potential security risk as it would allow various attacks to the queue", + "descriptionText": "SQS queue policies must not grant Allow permissions to a wildcard principal (`*`) combined with wildcard actions, as this gives any principal unrestricted ability to send, receive, delete, or otherwise manipulate queue messages, risking data exposure, message loss, or unauthorized message injection. In Ansible tasks using the `community.aws.sqs_queue` or `sqs_queue` module, inspect the `policy` property for policy statements where `Effect` is `\"Allow\"`, `Principal` is `\"*\"` (either `Principal == \"*\"` or `Principal.AWS` contains `\"*\"`), and `Action` contains `\"*\"`. Such statements are flagged.\n\nDefine explicit principals (AWS account ARNs, IAM role/user ARNs, or service principals) and restrict `Action` to the minimal SQS actions required (for example, `sqs:SendMessage`, `sqs:ReceiveMessage`). You can optionally add conditions (source ARN/IP, VPC) to further limit access.\n\nSecure configuration example:\n\n```yaml\n- name: Create SQS queue with restricted policy\n community.aws.sqs_queue:\n name: my-queue\n policy:\n Version: \"2012-10-17\"\n Statement:\n - Sid: AllowSpecificAccount\n Effect: Allow\n Principal:\n AWS: \"arn:aws:iam::123456789012:root\"\n Action:\n - \"sqs:SendMessage\"\n - \"sqs:ReceiveMessage\"\n Resource: \"arn:aws:sqs:us-east-1:123456789012:my-queue\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/sqs_policy_with_public_access", "platform": "Ansible", "descriptionID": "dd40b568", diff --git a/assets/queries/ansible/aws/sqs_policy_with_public_access/test/positive_expected_result.json b/assets/queries/ansible/aws/sqs_policy_with_public_access/test/positive_expected_result.json index 0e7522dd..1bf1493a 100644 --- a/assets/queries/ansible/aws/sqs_policy_with_public_access/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/sqs_policy_with_public_access/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "SQS Policy With Public Access", + "queryName": "SQS policy with public access", "severity": "MEDIUM", "line": 10, "fileName": "positive.yaml" }, { - "queryName": "SQS Policy With Public Access", + "queryName": "SQS policy with public access", "severity": "MEDIUM", "line": 28, "fileName": "positive.yaml" diff --git a/assets/queries/ansible/aws/sqs_queue_exposed/metadata.json b/assets/queries/ansible/aws/sqs_queue_exposed/metadata.json index 01b27d54..0de53494 100644 --- a/assets/queries/ansible/aws/sqs_queue_exposed/metadata.json +++ b/assets/queries/ansible/aws/sqs_queue_exposed/metadata.json @@ -1,9 +1,9 @@ { "id": "86b0efa7-4901-4edd-a37a-c034bec6645a", - "queryName": "SQS Queue Exposed", + "queryName": "SQS queue exposed", "severity": "HIGH", "category": "Access Control", - "descriptionText": "Checks if the SQS Queue is exposed", + "descriptionText": "Granting the wildcard principal (`*`) `Allow` access in an SQS queue policy makes the queue publicly accessible. Unauthorized users or principals can send, receive, or modify messages, increasing the risk of data exposure and message injection.\n\nFor Ansible SQS tasks (modules `community.aws.sqs_queue` or `sqs_queue`), inspect the `policy` property and ensure no policy Statement has `\"Effect\": \"Allow\"` with `\"Principal\": \"*\"`. Statements must specify explicit principals (for example AWS account ARNs) or include restrictive conditions.\n\nResources with policy statements where `Principal == \"*\" ` and `Effect == \"Allow\"` are flagged. Replace wildcard principals with explicit ARNs or add conditions such as `aws:SourceAccount` or `aws:SourceVpce` to restrict access.\n\nSecure example (Ansible task with explicit principal):\n\n```yaml\n- name: Create SQS queue with restricted policy\n community.aws.sqs_queue:\n name: my-queue\n policy: |\n {\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"AllowSpecificAccount\",\n \"Effect\": \"Allow\",\n \"Principal\": { \"AWS\": \"arn:aws:iam::123456789012:root\" },\n \"Action\": [\"SQS:SendMessage\", \"SQS:ReceiveMessage\"],\n \"Resource\": \"arn:aws:sqs:us-east-1:123456789012:my-queue\"\n }\n ]\n }\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/sqs_queue_exposed", "platform": "Ansible", "descriptionID": "a835b707", diff --git a/assets/queries/ansible/aws/sqs_queue_exposed/test/positive_expected_result.json b/assets/queries/ansible/aws/sqs_queue_exposed/test/positive_expected_result.json index f2422275..479b0005 100644 --- a/assets/queries/ansible/aws/sqs_queue_exposed/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/sqs_queue_exposed/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "SQS Queue Exposed", + "queryName": "SQS queue exposed", "severity": "HIGH", "line": 10, "fileName": "positive.yaml" }, { - "queryName": "SQS Queue Exposed", + "queryName": "SQS queue exposed", "severity": "HIGH", "line": 31, "fileName": "positive.yaml" diff --git a/assets/queries/ansible/aws/sqs_with_sse_disabled/metadata.json b/assets/queries/ansible/aws/sqs_with_sse_disabled/metadata.json index 08a4c888..701370a6 100644 --- a/assets/queries/ansible/aws/sqs_with_sse_disabled/metadata.json +++ b/assets/queries/ansible/aws/sqs_with_sse_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "e1e7b278-2a8b-49bd-a26e-66a7f70b17eb", - "queryName": "SQS With SSE Disabled", + "queryName": "SQS queue with SSE disabled", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "Amazon Simple Queue Service (SQS) queue should protect the contents of their messages using Server-Side Encryption (SSE)", + "descriptionText": "SQS queues must have server-side encryption (SSE) enabled to protect message contents at rest and in backups. This reduces the risk of exposing sensitive data if someone accesses the underlying storage or compromises credentials.\n\nIn Ansible, tasks using the `community.aws.sqs_queue` or `sqs_queue` modules must define the `kms_master_key_id` property and set it to a valid KMS key identifier (for example, a KMS ARN, key ID, or alias) to enable KMS-backed SSE. Resources missing this property or with it undefined/empty are flagged. Using a customer-managed KMS key (ARN or key ID) is recommended for granular access control and auditability, though the AWS-managed alias (`alias/aws/sqs`) can be used if customer-managed keys are not required.\n\nSecure configuration example:\n\n```yaml\n- name: Create encrypted SQS queue\n community.aws.sqs_queue:\n name: my-queue\n kms_master_key_id: arn:aws:kms:us-east-1:123456789012:key/abcd1234-56ef-78gh-90ij-klmnopqrstuv\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/sqs_with_sse_disabled", "platform": "Ansible", "descriptionID": "7825cf30", diff --git a/assets/queries/ansible/aws/sqs_with_sse_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/sqs_with_sse_disabled/test/positive_expected_result.json index 9508951f..11603fdc 100644 --- a/assets/queries/ansible/aws/sqs_with_sse_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/sqs_with_sse_disabled/test/positive_expected_result.json @@ -1,21 +1,21 @@ [ { - "queryName": "SQS With SSE Disabled", + "queryName": "SQS queue with SSE disabled", "severity": "MEDIUM", "line": 2 }, { - "queryName": "SQS With SSE Disabled", + "queryName": "SQS queue with SSE disabled", "severity": "MEDIUM", "line": 16 }, { - "queryName": "SQS With SSE Disabled", + "queryName": "SQS queue with SSE disabled", "severity": "MEDIUM", "line": 22 }, { - "queryName": "SQS With SSE Disabled", + "queryName": "SQS queue with SSE disabled", "severity": "MEDIUM", "line": 29 } diff --git a/assets/queries/ansible/aws/stack_notifications_disabled/metadata.json b/assets/queries/ansible/aws/stack_notifications_disabled/metadata.json index a657b2f0..25a1d671 100644 --- a/assets/queries/ansible/aws/stack_notifications_disabled/metadata.json +++ b/assets/queries/ansible/aws/stack_notifications_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "d39761d7-94ab-45b0-ab5e-27c44e381d58", - "queryName": "Stack Notifications Disabled", + "queryName": "Stack notifications disabled", "severity": "LOW", "category": "Observability", - "descriptionText": "AWS CloudFormation should have stack notifications enabled to be notified when an event occurs", + "descriptionText": "CloudFormation stacks should publish notifications so operators are alerted to important stack events, such as failed deployments or unexpected stack changes. Without notifications, security incidents or configuration drift can go undetected and response times increase. In Ansible, tasks using the `amazon.aws.cloudformation` or legacy `cloudformation` module must define the `notification_arns` parameter and set it to one or more SNS topic ARNs. Resources missing `notification_arns` are flagged for remediation.\n\nSecure example:\n\n```yaml\n- name: Create or update CloudFormation stack with notifications\n amazon.aws.cloudformation:\n stack_name: my-stack\n state: present\n template_body: \"{{ lookup('file', 'template.yaml') }}\"\n notification_arns:\n - arn:aws:sns:us-east-1:123456789012:stack-notifications\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/stack_notifications_disabled", "platform": "Ansible", "descriptionID": "59f8905d", diff --git a/assets/queries/ansible/aws/stack_notifications_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/stack_notifications_disabled/test/positive_expected_result.json index ef4b887b..6c5c3956 100644 --- a/assets/queries/ansible/aws/stack_notifications_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/stack_notifications_disabled/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "Stack Notifications Disabled", + "queryName": "Stack notifications disabled", "severity": "LOW", "line": 2 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/stack_retention_disabled/metadata.json b/assets/queries/ansible/aws/stack_retention_disabled/metadata.json index b86d748c..58f8d24b 100644 --- a/assets/queries/ansible/aws/stack_retention_disabled/metadata.json +++ b/assets/queries/ansible/aws/stack_retention_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "17d5ba1d-7667-4729-b1a6-b11fde3db7f7", - "queryName": "Stack Retention Disabled", + "queryName": "Stack retention disabled", "severity": "MEDIUM", "category": "Backup", - "descriptionText": "Make sure that retain_stack is enabled to keep the Stack and it's associated resources during resource destruction", + "descriptionText": "CloudFormation StackSet deletions must not purge stacks and their associated resources. Purging can irreversibly delete resources, causing data loss or service interruption. For Ansible tasks using the `community.aws.cloudformation_stack_set` module, the `purge_stacks` property must be explicitly set to the boolean value `false`. Resources missing `purge_stacks` or with `purge_stacks: true` are flagged.\n\n```yaml\n- name: Create or update StackSet without purging stacks on deletion\n community.aws.cloudformation_stack_set:\n name: my-stack-set\n template: /path/to/template.yaml\n parameters:\n Param1: value\n purge_stacks: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/stack_retention_disabled", "platform": "Ansible", "descriptionID": "78d78b74", diff --git a/assets/queries/ansible/aws/stack_retention_disabled/test/positive_expected_result.json b/assets/queries/ansible/aws/stack_retention_disabled/test/positive_expected_result.json index 26d12a6c..f8c57a4f 100644 --- a/assets/queries/ansible/aws/stack_retention_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/stack_retention_disabled/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "Stack Retention Disabled", + "queryName": "Stack retention disabled", "severity": "MEDIUM", "line": 2 }, { - "queryName": "Stack Retention Disabled", + "queryName": "Stack retention disabled", "severity": "MEDIUM", "line": 23 } diff --git a/assets/queries/ansible/aws/stack_without_template/metadata.json b/assets/queries/ansible/aws/stack_without_template/metadata.json index 4d560d51..84771b4e 100644 --- a/assets/queries/ansible/aws/stack_without_template/metadata.json +++ b/assets/queries/ansible/aws/stack_without_template/metadata.json @@ -1,9 +1,9 @@ { "id": "32d31f1f-0f83-4721-b7ec-1e6948c60145", - "queryName": "Stack Without Template", + "queryName": "Stack without template", "severity": "LOW", "category": "Build Process", - "descriptionText": "AWS CloudFormation should have a template defined through the attribute template, template_url or attribute template_body", + "descriptionText": "CloudFormation stack tasks must specify exactly one template source. Missing or ambiguous templates can cause failed deployments or unintended resource changes that increase security and availability risks.\n\nFor Ansible modules `amazon.aws.cloudformation`, `cloudformation`, `community.aws.cloudformation_stack_set`, and `cloudformation_stack_set`, one of the properties `template`, `template_body`, or `template_url` must be present and non-empty. Resources that omit all three properties are flagged as missing a template. Resources that set more than one are flagged because multiple template sources are ambiguous and can lead to unexpected template selection.\n\nSecure examples (valid configurations):\n\n```yaml\n- name: Create CloudFormation stack from local template\n amazon.aws.cloudformation:\n stack_name: my-stack\n template: /path/to/template.yaml\n\n- name: Create CloudFormation stack from S3 URL\n amazon.aws.cloudformation:\n stack_name: my-stack\n template_url: https://s3.amazonaws.com/bucket/my-template.yaml\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/stack_without_template", "platform": "Ansible", "descriptionID": "de8347bd", diff --git a/assets/queries/ansible/aws/stack_without_template/test/positive_expected_result.json b/assets/queries/ansible/aws/stack_without_template/test/positive_expected_result.json index 45c13d3d..b74b623b 100644 --- a/assets/queries/ansible/aws/stack_without_template/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/stack_without_template/test/positive_expected_result.json @@ -1,22 +1,22 @@ [ { - "queryName": "Stack Without Template", + "queryName": "Stack without template", "severity": "LOW", "line": 2 }, { - "queryName": "Stack Without Template", + "queryName": "Stack without template", "severity": "LOW", "line": 15 }, { - "queryName": "Stack Without Template", + "queryName": "Stack without template", "severity": "LOW", "line": 30 }, { - "queryName": "Stack Without Template", + "queryName": "Stack without template", "severity": "LOW", "line": 40 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/unknown_port_exposed_to_internet/metadata.json b/assets/queries/ansible/aws/unknown_port_exposed_to_internet/metadata.json index a8b1441e..75bc931d 100644 --- a/assets/queries/ansible/aws/unknown_port_exposed_to_internet/metadata.json +++ b/assets/queries/ansible/aws/unknown_port_exposed_to_internet/metadata.json @@ -1,9 +1,9 @@ { "id": "722b0f24-5a64-4cca-aa96-cfc26b7e3a5b", - "queryName": "Unknown Port Exposed To Internet", + "queryName": "Unknown port exposed to internet", "severity": "HIGH", "category": "Networking and Firewall", - "descriptionText": "AWS Security Group should not have an unknown port exposed to the entire Internet", + "descriptionText": "Security groups must not expose unknown or undocumented TCP ports to the entire Internet. Exposing unexpected ports increases attack surface and makes it easier for attackers to discover and exploit unintended services.\n\nThis rule inspects Ansible tasks using the `amazon.aws.ec2_group` and `ec2_group` modules. It checks each `rules` entry and flags rules where any port in the range from `from_port` to `to_port` is not found in the recognized TCP ports map and where `cidr_ip` equals `0.0.0.0/0` or `cidr_ipv6` equals `::/0` (entire network).\n\nTo remediate, restrict ingress to only known, required ports and limit CIDR ranges to trusted networks or reference other security groups. Review and document any non-standard ports before allowing public access. \n\nSecure example for Ansible `ec2_group` with a single, known port limited to a specific IPv4 range:\n\n```yaml\n- name: Create security group with restricted HTTPS access\n amazon.aws.ec2_group:\n name: example-sg\n rules:\n - proto: tcp\n from_port: 443\n to_port: 443\n cidr_ip: 203.0.113.0/24\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/unknown_port_exposed_to_internet", "platform": "Ansible", "descriptionID": "f6437a28", diff --git a/assets/queries/ansible/aws/unknown_port_exposed_to_internet/test/positive_expected_result.json b/assets/queries/ansible/aws/unknown_port_exposed_to_internet/test/positive_expected_result.json index 05f6d2f3..d841d2a0 100644 --- a/assets/queries/ansible/aws/unknown_port_exposed_to_internet/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/unknown_port_exposed_to_internet/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ - { - "queryName": "Unknown Port Exposed To Internet", - "severity": "HIGH", - "line": 9 - }, - { - "queryName": "Unknown Port Exposed To Internet", - "severity": "HIGH", - "line": 13 - } + { + "queryName": "Unknown port exposed to internet", + "severity": "HIGH", + "line": 9 + }, + { + "queryName": "Unknown port exposed to internet", + "severity": "HIGH", + "line": 13 + } ] diff --git a/assets/queries/ansible/aws/unrestricted_security_group_ingress/metadata.json b/assets/queries/ansible/aws/unrestricted_security_group_ingress/metadata.json index acb05d41..f3e3c466 100644 --- a/assets/queries/ansible/aws/unrestricted_security_group_ingress/metadata.json +++ b/assets/queries/ansible/aws/unrestricted_security_group_ingress/metadata.json @@ -1,9 +1,9 @@ { "id": "83c5fa4c-e098-48fc-84ee-0a537287ddd2", - "queryName": "Unrestricted Security Group Ingress", + "queryName": "Unrestricted security group ingress", "severity": "HIGH", "category": "Networking and Firewall", - "descriptionText": "Security groups allow ingress from 0.0.0.0/0", + "descriptionText": "Security group ingress rules must not allow traffic from the entire Internet (IPv4 `0.0.0.0/0` or IPv6 `::/0`) to specific ports. This exposes services to unauthorized access and automated attacks such as brute force and port scanning.\n\nThis rule inspects Ansible `amazon.aws.ec2_group` and `ec2_group` tasks and flags `rules` entries that define ports (via `from_port`/`to_port` or `ports`) where `cidr_ip` is `0.0.0.0/0` or `cidr_ipv6` is `::/0`. It also detects these values when CIDRs are provided as lists.\n\nTo remediate, restrict ingress to specific trusted CIDR ranges, use security group-to-security group references or VPN/bastion hosts, and remove or replace `0.0.0.0/0` and `::/0` from rules that open ports. \n\nSecure configuration example (restrict SSH to a trusted IPv4 range and allow HTTPS from a specific IPv6 range):\n\n```yaml\n- name: Create restricted SG\n amazon.aws.ec2_group:\n name: my-sg\n description: \"Restrict SSH and HTTPS to trusted networks\"\n rules:\n - proto: tcp\n from_port: 22\n to_port: 22\n cidr_ip: 10.0.0.0/24\n - proto: tcp\n from_port: 443\n to_port: 443\n cidr_ipv6: \"2001:db8::/32\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/unrestricted_security_group_ingress", "platform": "Ansible", "descriptionID": "015995bb", diff --git a/assets/queries/ansible/aws/unrestricted_security_group_ingress/test/positive_expected_result.json b/assets/queries/ansible/aws/unrestricted_security_group_ingress/test/positive_expected_result.json index e600c057..0713c5a7 100644 --- a/assets/queries/ansible/aws/unrestricted_security_group_ingress/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/unrestricted_security_group_ingress/test/positive_expected_result.json @@ -1,21 +1,21 @@ [ { - "queryName": "Unrestricted Security Group Ingress", + "queryName": "Unrestricted security group ingress", "severity": "HIGH", "line": 14 }, { - "queryName": "Unrestricted Security Group Ingress", + "queryName": "Unrestricted security group ingress", "severity": "HIGH", "line": 28 }, { - "queryName": "Unrestricted Security Group Ingress", + "queryName": "Unrestricted security group ingress", "severity": "HIGH", "line": 41 }, { - "queryName": "Unrestricted Security Group Ingress", + "queryName": "Unrestricted security group ingress", "severity": "HIGH", "line": 55 } diff --git a/assets/queries/ansible/aws/user_data_contains_encoded_private_key/metadata.json b/assets/queries/ansible/aws/user_data_contains_encoded_private_key/metadata.json index 2f332446..ca6b6cdb 100644 --- a/assets/queries/ansible/aws/user_data_contains_encoded_private_key/metadata.json +++ b/assets/queries/ansible/aws/user_data_contains_encoded_private_key/metadata.json @@ -1,13 +1,13 @@ { "id": "c09f4d3e-27d2-4d46-9453-abbe9687a64e", - "queryName": "User Data Contains Encoded Private Key", + "queryName": "User data contains encoded private key", "severity": "HIGH", "category": "Encryption", - "descriptionText": "User Data should not contain a base64 encoded private key. If so, anyone can decode the private key easily", + "descriptionText": "Embedding base64-encoded private keys in EC2 launch configuration user data exposes sensitive credentials that can be decoded and used to impersonate instances or access private services, resulting in credential compromise and lateral movement.\n\nThis rule inspects Ansible tasks using the `community.aws.autoscaling_launch_config` or `autoscaling_launch_config` modules and flags the `user_data` property when it contains the base64 prefix `LS0tLS1CR`, which corresponds to the start of an RSA private key header (`-----BEGIN R...`).\n\nRemove any private keys from `user_data` and instead store secrets in a secure secrets manager or fetch them at runtime using instance IAM roles. Tasks embedding keys are flagged.", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/user_data_contains_encoded_private_key", "platform": "Ansible", "descriptionID": "45cb51c3", "cloudProvider": "aws", "cwe": "326", - "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/ec2_lc_module.html" + "providerUrl": "https://docs.ansible.com/ansible/latest/collections/community/aws/autoscaling_launch_config_module.html" } diff --git a/assets/queries/ansible/aws/user_data_contains_encoded_private_key/test/positive_expected_result.json b/assets/queries/ansible/aws/user_data_contains_encoded_private_key/test/positive_expected_result.json index 772c99ba..a8893e1d 100644 --- a/assets/queries/ansible/aws/user_data_contains_encoded_private_key/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/user_data_contains_encoded_private_key/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ - { - "queryName": "User Data Contains Encoded Private Key", - "severity": "HIGH", - "line": 9 - } + { + "queryName": "User data contains encoded private key", + "severity": "HIGH", + "line": 9 + } ] diff --git a/assets/queries/ansible/aws/viewer_protocol_policy_allows_http/metadata.json b/assets/queries/ansible/aws/viewer_protocol_policy_allows_http/metadata.json index 574b8d36..6d9c8fad 100644 --- a/assets/queries/ansible/aws/viewer_protocol_policy_allows_http/metadata.json +++ b/assets/queries/ansible/aws/viewer_protocol_policy_allows_http/metadata.json @@ -1,9 +1,9 @@ { "id": "a6d27cf7-61dc-4bde-ae08-3b353b609f76", - "queryName": "Cloudfront Viewer Protocol Policy Allows HTTP", + "queryName": "CloudFront viewer protocol policy allows HTTP", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "Checks if the connection between CloudFront and the viewer is encrypted", + "descriptionText": "CloudFront distributions must enforce HTTPS for viewer connections to prevent sensitive data from being transmitted in plaintext and reduce the risk of downgrade or man-in-the-middle attacks.\n\nFor Ansible CloudFront resources (modules `community.aws.cloudfront_distribution` or `cloudfront_distribution`), the `viewer_protocol_policy` property in `default_cache_behavior` and in each `cache_behaviors` entry must be set to `https-only` or `redirect-to-https`. Tasks with `viewer_protocol_policy` set to `allow-all` or without an explicit secure setting are flagged. Ensure every cache behavior explicitly specifies a secure policy.\n\nSecure configuration example:\n\n```yaml\n- name: Create CloudFront distribution\n community.aws.cloudfront_distribution:\n origin:\n - id: origin1\n domain_name: example.com\n default_cache_behavior:\n viewer_protocol_policy: https-only\n cache_behaviors:\n - path_pattern: /images/*\n viewer_protocol_policy: redirect-to-https\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/viewer_protocol_policy_allows_http", "platform": "Ansible", "descriptionID": "5dd1ca8b", diff --git a/assets/queries/ansible/aws/viewer_protocol_policy_allows_http/test/positive_expected_result.json b/assets/queries/ansible/aws/viewer_protocol_policy_allows_http/test/positive_expected_result.json index 7a3f75db..711943be 100644 --- a/assets/queries/ansible/aws/viewer_protocol_policy_allows_http/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/viewer_protocol_policy_allows_http/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Cloudfront Viewer Protocol Policy Allows HTTP", + "queryName": "CloudFront viewer protocol policy allows HTTP", "severity": "MEDIUM", "line": 20 }, { - "queryName": "Cloudfront Viewer Protocol Policy Allows HTTP", + "queryName": "CloudFront viewer protocol policy allows HTTP", "severity": "MEDIUM", "line": 50 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/aws/vulnerable_default_ssl_certificate/metadata.json b/assets/queries/ansible/aws/vulnerable_default_ssl_certificate/metadata.json index 552f53d9..058254e5 100644 --- a/assets/queries/ansible/aws/vulnerable_default_ssl_certificate/metadata.json +++ b/assets/queries/ansible/aws/vulnerable_default_ssl_certificate/metadata.json @@ -1,9 +1,9 @@ { "id": "fb8f8929-afeb-4c46-99f0-a6cf410f7df4", - "queryName": "Vulnerable Default SSL Certificate", + "queryName": "Vulnerable default SSL certificate", "severity": "MEDIUM", "category": "Insecure Defaults", - "descriptionText": "CloudFront web distributions should use custom (and not default) SSL certificates. Custom SSL certificates allow only defined users to access content by using an alternate domain name instead of the default one.", + "descriptionText": "CloudFront distributions should use custom SSL certificates rather than the default CloudFront certificate. Custom certificates enable serving content on custom domain names and enforce strong, managed TLS settings for data in transit.\n\nFor Ansible tasks using `community.aws.cloudfront_distribution` or `cloudfront_distribution`, the `viewer_certificate.cloudfront_default_certificate` property must be `false` or not defined. If `viewer_certificate.acm_certificate_arn` or `viewer_certificate.iam_certificate_id` is provided, then `viewer_certificate.ssl_support_method` and `viewer_certificate.minimum_protocol_version` must also be defined.\n\nResources with `cloudfront_default_certificate` set to `true`, or with a custom certificate but missing `ssl_support_method` or `minimum_protocol_version`, are flagged. Use a secure `viewer_certificate` block that references a custom ACM or IAM certificate and explicitly sets the SSL support method and a modern minimum protocol version.\n\nSecure example for an Ansible CloudFront distribution:\n\n```yaml\n- name: Create CloudFront distribution with custom certificate\n community.aws.cloudfront_distribution:\n name: my-distribution\n viewer_certificate:\n acm_certificate_arn: arn:aws:acm:us-east-1:123456789012:certificate/abcd-ef01-2345\n ssl_support_method: sni-only\n minimum_protocol_version: TLSv1.2_2019\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/aws/vulnerable_default_ssl_certificate", "platform": "Ansible", "descriptionID": "324e63d7", diff --git a/assets/queries/ansible/aws/vulnerable_default_ssl_certificate/test/positive_expected_result.json b/assets/queries/ansible/aws/vulnerable_default_ssl_certificate/test/positive_expected_result.json index 39fcc86f..33d4b05d 100644 --- a/assets/queries/ansible/aws/vulnerable_default_ssl_certificate/test/positive_expected_result.json +++ b/assets/queries/ansible/aws/vulnerable_default_ssl_certificate/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "Vulnerable Default SSL Certificate", + "queryName": "Vulnerable default SSL certificate", "severity": "MEDIUM", "line": 7 }, { - "queryName": "Vulnerable Default SSL Certificate", + "queryName": "Vulnerable default SSL certificate", "severity": "MEDIUM", "line": 17 }, { - "queryName": "Vulnerable Default SSL Certificate", + "queryName": "Vulnerable default SSL certificate", "severity": "MEDIUM", "line": 17 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/metadata.json b/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/metadata.json index 49777bf9..96e2cfb1 100644 --- a/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/metadata.json +++ b/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/metadata.json @@ -1,9 +1,9 @@ { "id": "b176e927-bbe2-44a6-a9c3-041417137e5f", - "queryName": "AD Admin Not Configured For SQL Server", + "queryName": "AD admin not configured for SQL server", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "The Active Directory Administrator is not configured for a SQL server", + "descriptionText": "SQL servers should have an Active Directory administrator configured to enforce centralized identity, stronger authentication, and auditable access controls. Relying solely on SQL authentication increases the attack surface and makes access management and auditing more difficult. For Ansible, tasks using the `azure.azcollection.azure_rm_sqlserver` or `azure_rm_sqlserver` module must define the `ad_user` property and set it to a valid Azure AD principal (for example, a user UPN or objectId). Resources missing `ad_user` or with it empty or undefined are flagged.\n\nSecure example:\n\n```\n- name: Create Azure SQL Server with AD admin\n azure.azcollection.azure_rm_sqlserver:\n name: my-sql-server\n resource_group: my-rg\n location: eastus\n ad_user: \"adminuser@contoso.com\"\n admin_password: \"secure-password\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/ad_admin_not_configured_for_sql_server", "platform": "Ansible", "descriptionID": "afa96f09", diff --git a/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/test/positive_expected_result.json b/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/test/positive_expected_result.json index 85e34b71..570b37c8 100644 --- a/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/ad_admin_not_configured_for_sql_server/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "AD Admin Not Configured For SQL Server", + "queryName": "AD admin not configured for SQL server", "severity": "MEDIUM", "line": 3 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/admin_user_enabled_for_container_registry/metadata.json b/assets/queries/ansible/azure/admin_user_enabled_for_container_registry/metadata.json index ac1ea6fb..d0df6e7d 100644 --- a/assets/queries/ansible/azure/admin_user_enabled_for_container_registry/metadata.json +++ b/assets/queries/ansible/azure/admin_user_enabled_for_container_registry/metadata.json @@ -1,9 +1,9 @@ { "id": "29f35127-98e6-43af-8ec1-201b79f99604", - "queryName": "Admin User Enabled For Container Registry", + "queryName": "Admin user enabled for container registry", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "Admin user is enabled for Container Registry", + "descriptionText": "Enabling the admin user on an Azure Container Registry creates a shared username/password credential that can be leaked or abused to push or pull images, increasing the risk of unauthorized access and lateral movement.\n\nFor Ansible resources using `azure_rm_containerregistry` or `azure.azcollection.azure_rm_containerregistry`, the `admin_user_enabled` property must be set to `false` or omitted (it defaults to `false`). Tasks with `admin_user_enabled: true` are flagged. Use Azure AD RBAC with scoped service principals or managed identities for registry access instead. \n\nSecure example (explicitly disabling the admin user):\n\n```yaml\n- name: Create secure Azure Container Registry\n azure.azcollection.azure_rm_containerregistry:\n name: myRegistry\n resource_group: myResourceGroup\n sku: Basic\n admin_user_enabled: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/admin_user_enabled_for_container_registry", "platform": "Ansible", "descriptionID": "d8ca5381", diff --git a/assets/queries/ansible/azure/admin_user_enabled_for_container_registry/test/positive_expected_result.json b/assets/queries/ansible/azure/admin_user_enabled_for_container_registry/test/positive_expected_result.json index 8db54668..ef2a4802 100644 --- a/assets/queries/ansible/azure/admin_user_enabled_for_container_registry/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/admin_user_enabled_for_container_registry/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Admin User Enabled For Container Registry", + "queryName": "Admin user enabled for container registry", "severity": "MEDIUM", "line": 7 }, { - "queryName": "Admin User Enabled For Container Registry", + "queryName": "Admin user enabled for container registry", "severity": "MEDIUM", "line": 17 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/aks_monitoring_logging_disabled/metadata.json b/assets/queries/ansible/azure/aks_monitoring_logging_disabled/metadata.json index 10063a60..ad2a418a 100644 --- a/assets/queries/ansible/azure/aks_monitoring_logging_disabled/metadata.json +++ b/assets/queries/ansible/azure/aks_monitoring_logging_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "d5e83b32-56dd-4247-8c2e-074f43b38a5e", - "queryName": "AKS Monitoring Logging Disabled", + "queryName": "AKS monitoring logging disabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Azure Container Service (AKS) instance should have logging enabled to Azure Monitoring", + "descriptionText": "AKS clusters must have the monitoring addon enabled and configured to send logs and metrics to an Azure Log Analytics workspace. This ensures that cluster activity, security events, and configuration changes are visible for detection, alerting, and incident investigation.\n\nFor Ansible tasks using `azure_rm_aks` or `azure.azcollection.azure_rm_aks`, the `addon.monitoring` block must be present with `enabled` set to an Ansible-`true` value and `log_analytics_workspace_resource_id` set to the workspace resource ID. Tasks missing the `addon` or `addon.monitoring` blocks, missing `enabled` or the workspace ID, or with `enabled` not set to an Ansible-`true` value (for example `yes`, `true`, `on`, or `1`) are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create AKS cluster with monitoring enabled\n azure_rm_aks:\n name: myAKS\n resource_group: myRg\n addon:\n monitoring:\n enabled: yes\n log_analytics_workspace_resource_id: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.OperationalInsights/workspaces/myWorkspace\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/aks_monitoring_logging_disabled", "platform": "Ansible", "descriptionID": "6d8d362e", diff --git a/assets/queries/ansible/azure/aks_monitoring_logging_disabled/test/positive_expected_result.json b/assets/queries/ansible/azure/aks_monitoring_logging_disabled/test/positive_expected_result.json index 222abd32..3f10d39c 100644 --- a/assets/queries/ansible/azure/aks_monitoring_logging_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/aks_monitoring_logging_disabled/test/positive_expected_result.json @@ -1,21 +1,21 @@ [ { - "queryName": "AKS Monitoring Logging Disabled", + "queryName": "AKS monitoring logging disabled", "severity": "MEDIUM", "line": 2 }, { - "queryName": "AKS Monitoring Logging Disabled", + "queryName": "AKS monitoring logging disabled", "severity": "MEDIUM", "line": 43 }, { - "queryName": "AKS Monitoring Logging Disabled", + "queryName": "AKS monitoring logging disabled", "severity": "MEDIUM", "line": 68 }, { - "queryName": "AKS Monitoring Logging Disabled", + "queryName": "AKS monitoring logging disabled", "severity": "MEDIUM", "line": 94 } diff --git a/assets/queries/ansible/azure/aks_network_policy_misconfigured/metadata.json b/assets/queries/ansible/azure/aks_network_policy_misconfigured/metadata.json index 2a396225..6b7315bd 100644 --- a/assets/queries/ansible/azure/aks_network_policy_misconfigured/metadata.json +++ b/assets/queries/ansible/azure/aks_network_policy_misconfigured/metadata.json @@ -1,9 +1,9 @@ { "id": "8c3bedf1-c570-4c3b-b414-d068cd39a00c", - "queryName": "AKS Network Policy Misconfigured", + "queryName": "AKS network policy misconfigured", "severity": "LOW", "category": "Insecure Configurations", - "descriptionText": "Azure Kubernetes Service should have the proper network policy configuration to ensure the principle of least privileges, which means that 'network_profile.network_policy' should be defined", + "descriptionText": "AKS clusters must have a network policy configured to enforce pod-to-pod network isolation and the principle of least privilege. Without a network policy, pods can communicate freely, increasing the risk of lateral movement and unintended access to services.\n\nFor Ansible resources using `azure.azcollection.azure_rm_aks` or `azure_rm_aks`, the `network_profile.network_policy` property must be defined and set to either `calico` or `azure`. Tasks that omit `network_profile` or `network_profile.network_policy`, or that set the property to any value other than `calico` or `azure`, are flagged.\n\nSecure example Ansible task:\n\n```yaml\n- name: Create AKS cluster with network policy\n azure.azcollection.azure_rm_aks:\n name: my-aks-cluster\n resource_group: my-rg\n dns_prefix: myaks\n network_profile:\n network_policy: calico\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/aks_network_policy_misconfigured", "platform": "Ansible", "descriptionID": "75bbf826", diff --git a/assets/queries/ansible/azure/aks_network_policy_misconfigured/test/positive_expected_result.json b/assets/queries/ansible/azure/aks_network_policy_misconfigured/test/positive_expected_result.json index a9f6033d..00092d30 100644 --- a/assets/queries/ansible/azure/aks_network_policy_misconfigured/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/aks_network_policy_misconfigured/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "AKS Network Policy Misconfigured", + "queryName": "AKS network policy misconfigured", "severity": "LOW", "line": 10 }, { - "queryName": "AKS Network Policy Misconfigured", + "queryName": "AKS network policy misconfigured", "severity": "LOW", "line": 24 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/aks_rbac_disabled/metadata.json b/assets/queries/ansible/azure/aks_rbac_disabled/metadata.json index 693879b8..0fda3820 100644 --- a/assets/queries/ansible/azure/aks_rbac_disabled/metadata.json +++ b/assets/queries/ansible/azure/aks_rbac_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "149fa56c-4404-4f90-9e25-d34b676d5b39", - "queryName": "AKS RBAC Disabled", + "queryName": "AKS RBAC disabled", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "Azure Container Service (AKS) instance should have role-based access control (RBAC) enabled", + "descriptionText": "AKS clusters must have role-based access control (RBAC) enabled to restrict Kubernetes API operations to authorized principals and prevent privilege escalation or unauthorized cluster modifications.\n\nIn Ansible playbooks, tasks using the `azure.azcollection.azure_rm_aks` or `azure_rm_aks` modules must define the `enable_rbac` property and set it to a truthy value (for example `yes`/`true` or YAML true). Resources with `enable_rbac` missing or not set to a truthy value are flagged as insecure.\n\nSecure Ansible example:\n\n```yaml\n- name: Create AKS cluster with RBAC enabled\n azure.azcollection.azure_rm_aks:\n name: myAKS\n resource_group: myRG\n enable_rbac: yes\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/aks_rbac_disabled", "platform": "Ansible", "descriptionID": "16276251", diff --git a/assets/queries/ansible/azure/aks_rbac_disabled/test/positive_expected_result.json b/assets/queries/ansible/azure/aks_rbac_disabled/test/positive_expected_result.json index 8d15a886..198e6297 100644 --- a/assets/queries/ansible/azure/aks_rbac_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/aks_rbac_disabled/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "AKS RBAC Disabled", + "queryName": "AKS RBAC disabled", "severity": "MEDIUM", "line": 21 }, { - "queryName": "AKS RBAC Disabled", + "queryName": "AKS RBAC disabled", "severity": "MEDIUM", "line": 23 } diff --git a/assets/queries/ansible/azure/azure_container_registry_with_no_locks/metadata.json b/assets/queries/ansible/azure/azure_container_registry_with_no_locks/metadata.json index 79a67f45..f7719e8b 100644 --- a/assets/queries/ansible/azure/azure_container_registry_with_no_locks/metadata.json +++ b/assets/queries/ansible/azure/azure_container_registry_with_no_locks/metadata.json @@ -1,9 +1,9 @@ { "id": "581dae78-307d-45d5-aae4-fe2b0db267a5", - "queryName": "Azure Container Registry With No Locks", + "queryName": "Azure Container Registry with no locks", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "Azurerm Container Registry should contain associated locks, which means 'azure_rm_lock.managed_resource_id' or 'azure_rm_lock.resource_group' association should be defined", + "descriptionText": "Azure Container Registries must be protected by Azure resource locks to prevent accidental or unauthorized deletion or modification of container images and registry configuration.\n\nIn Ansible playbooks, tasks that create or manage ACRs using the `azure.azcollection.azure_rm_containerregistry` or `azure_rm_containerregistry` modules must be accompanied by a lock task using `azure.azcollection.azure_rm_lock` or `azure_rm_lock`. The lock should either target the specific registry—by having `managed_resource_id` contain the registry's `.id`—or be scoped to the same `resource_group` as the registry (lock `resource_group` equals registry `resource_group`). Tasks without a corresponding lock task, or with locks that do not reference the registry by `managed_resource_id` nor share the same `resource_group`, are flagged.", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/azure_container_registry_with_no_locks", "platform": "Ansible", "descriptionID": "7489a85f", diff --git a/assets/queries/ansible/azure/azure_container_registry_with_no_locks/test/positive_expected_result.json b/assets/queries/ansible/azure/azure_container_registry_with_no_locks/test/positive_expected_result.json index 1129e0d6..ddedb986 100644 --- a/assets/queries/ansible/azure/azure_container_registry_with_no_locks/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/azure_container_registry_with_no_locks/test/positive_expected_result.json @@ -1,18 +1,18 @@ [ { - "queryName": "Azure Container Registry With No Locks", + "queryName": "Azure Container Registry with no locks", "severity": "HIGH", "line": 2, "fileName": "positive1.yaml" }, { - "queryName": "Azure Container Registry With No Locks", + "queryName": "Azure Container Registry with no locks", "severity": "HIGH", "line": 17, "fileName": "positive1.yaml" }, { - "queryName": "Azure Container Registry With No Locks", + "queryName": "Azure Container Registry with no locks", "severity": "HIGH", "line": 2, "fileName": "positive2.yaml" diff --git a/assets/queries/ansible/azure/azure_instance_using_basic_authentication/metadata.json b/assets/queries/ansible/azure/azure_instance_using_basic_authentication/metadata.json index a974e99f..f7079899 100644 --- a/assets/queries/ansible/azure/azure_instance_using_basic_authentication/metadata.json +++ b/assets/queries/ansible/azure/azure_instance_using_basic_authentication/metadata.json @@ -1,9 +1,9 @@ { "id": "e2d834b7-8b25-4935-af53-4a60668dcbe0", - "queryName": "Azure Instance Using Basic Authentication", + "queryName": "Azure instance using basic authentication", "severity": "MEDIUM", "category": "Best Practices", - "descriptionText": "Azure Instances should use SSH Key instead of basic authentication", + "descriptionText": "Linux virtual machines must require SSH key authentication instead of username/password. Password-based login is susceptible to brute-force attacks and credential compromise, which can lead to unauthorized access and lateral movement.\n\nFor Ansible `azure_rm_virtualmachine` resources, ensure `ssh_password_enabled` is set to `false` and `linux_config.disable_password_authentication` is set to `true` so only SSH key authentication is allowed. This rule applies to resources intended to be Linux VMs (where `os_type` is `\"linux\"` or unspecified). Resources missing these properties or that allow password authentication are flagged.\n\nSecure example configuration:\n\n```yaml\n- name: Create Linux VM with SSH keys only\n azure_rm_virtualmachine:\n name: my-linux-vm\n resource_group: my-rg\n os_type: Linux\n ssh_password_enabled: false\n linux_config:\n disable_password_authentication: true\n ssh_public_keys:\n - path: /home/azureuser/.ssh/authorized_keys\n key_data: \"{{ lookup('file','~/.ssh/id_rsa.pub') }}\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/azure_instance_using_basic_authentication", "platform": "Ansible", "descriptionID": "e2d834b7", diff --git a/assets/queries/ansible/azure/azure_instance_using_basic_authentication/test/positive_expected_result.json b/assets/queries/ansible/azure/azure_instance_using_basic_authentication/test/positive_expected_result.json index 25b6b19d..767ed959 100644 --- a/assets/queries/ansible/azure/azure_instance_using_basic_authentication/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/azure_instance_using_basic_authentication/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ { - "queryName": "Azure Instance Using Basic Authentication", + "queryName": "Azure instance using basic authentication", "severity": "MEDIUM", "line": 3, "fileName": "positive.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/cosmosdb_account_ip_range_filter_not_set/metadata.json b/assets/queries/ansible/azure/cosmosdb_account_ip_range_filter_not_set/metadata.json index 1b95399e..44b893f7 100644 --- a/assets/queries/ansible/azure/cosmosdb_account_ip_range_filter_not_set/metadata.json +++ b/assets/queries/ansible/azure/cosmosdb_account_ip_range_filter_not_set/metadata.json @@ -1,9 +1,9 @@ { "id": "e8c80448-31d8-4755-85fc-6dbab69c2717", - "queryName": "CosmosDB Account IP Range Filter Not Set", + "queryName": "CosmosDB account IP range filter not set", "severity": "CRITICAL", "category": "Networking and Firewall", - "descriptionText": "The IP range filter should be defined to secure the data stored", + "descriptionText": "Cosmos DB accounts should have an IP range filter configured to restrict which client IP addresses can connect. Without one, the account may accept connections from unintended networks, increasing the risk of unauthorized data access.\n\nIn Ansible, the `azure.azcollection.azure_rm_cosmosdbaccount` (and legacy `azure_rm_cosmosdbaccount`) resource must include the `ip_range_filter` property set to the allowed IP addresses or CIDR ranges. Resources missing `ip_range_filter` or with it empty are flagged, as they indicate no network-level IP restrictions. Provide a comma-separated list of IPs/CIDRs to enforce access control.\n\nSecure example with IP restrictions:\n\n```yaml\n- name: Create Cosmos DB account with IP restrictions\n azure.azcollection.azure_rm_cosmosdbaccount:\n resource_group: my-rg\n name: my-cosmosdb\n location: eastus\n offer_type: Standard\n ip_range_filter: \"10.0.0.0/24,203.0.113.5\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/cosmosdb_account_ip_range_filter_not_set", "platform": "Ansible", "descriptionID": "7cb8bdbe", diff --git a/assets/queries/ansible/azure/cosmosdb_account_ip_range_filter_not_set/test/positive_expected_result.json b/assets/queries/ansible/azure/cosmosdb_account_ip_range_filter_not_set/test/positive_expected_result.json index 0d8570fd..c3c7e895 100644 --- a/assets/queries/ansible/azure/cosmosdb_account_ip_range_filter_not_set/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/cosmosdb_account_ip_range_filter_not_set/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "CosmosDB Account IP Range Filter Not Set", + "queryName": "CosmosDB account IP range filter not set", "severity": "CRITICAL", "line": 2 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/cosmosdb_account_without_tags/metadata.json b/assets/queries/ansible/azure/cosmosdb_account_without_tags/metadata.json index 008e38d9..35de8542 100644 --- a/assets/queries/ansible/azure/cosmosdb_account_without_tags/metadata.json +++ b/assets/queries/ansible/azure/cosmosdb_account_without_tags/metadata.json @@ -1,9 +1,9 @@ { "id": "23a4dc83-4959-4d99-8056-8e051a82bc1e", - "queryName": "Cosmos DB Account Without Tags", + "queryName": "Cosmos DB account without tags", "severity": "LOW", "category": "Build Process", - "descriptionText": "Cosmos DB Account must have a mapping of tags.", + "descriptionText": "Cosmos DB account resources must include tags to support asset identification, ownership, and automated security or incident response processes. Without tags, inventory, cost allocation, and security triage become more difficult.\n\nFor Ansible, tasks using the `azure.azcollection.azure_rm_cosmosdbaccount` or `azure_rm_cosmosdbaccount` modules must define the `tags` property as a mapping of key-value pairs. Resources missing the `tags` property or with it undefined are flagged. Include keys such as Owner and Environment to enable governance and automation.\n\nSecure example:\n\n```yaml\n- name: create cosmosdb account\n azure.azcollection.azure_rm_cosmosdbaccount:\n name: my-cosmosdb\n resource_group: my-rg\n location: eastus\n kind: GlobalDocumentDB\n offer_type: Standard\n tags:\n Owner: team-abc\n Environment: production\n Project: billing-service\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/cosmosdb_account_without_tags", "platform": "Ansible", "descriptionID": "8469d3ac", diff --git a/assets/queries/ansible/azure/cosmosdb_account_without_tags/test/positive_expected_result.json b/assets/queries/ansible/azure/cosmosdb_account_without_tags/test/positive_expected_result.json index a1ef5db4..b68206c2 100644 --- a/assets/queries/ansible/azure/cosmosdb_account_without_tags/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/cosmosdb_account_without_tags/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "Cosmos DB Account Without Tags", + "queryName": "Cosmos DB account without tags", "severity": "LOW", "line": 3 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/default_azure_storage_account_network_access_is_too_permissive/metadata.json b/assets/queries/ansible/azure/default_azure_storage_account_network_access_is_too_permissive/metadata.json index 762f52f2..f93c59fb 100644 --- a/assets/queries/ansible/azure/default_azure_storage_account_network_access_is_too_permissive/metadata.json +++ b/assets/queries/ansible/azure/default_azure_storage_account_network_access_is_too_permissive/metadata.json @@ -1,9 +1,9 @@ { "id": "ca4df748-613a-4fbf-9c76-f02cbd580307", - "queryName": "Default Azure Storage Account Network Access Is Too Permissive", + "queryName": "Default Azure storage account network access is too permissive", "severity": "HIGH", "category": "Access Control", - "descriptionText": "Make sure that your Azure Storage Account access is limited to those who require it.", + "descriptionText": "Storage accounts must not permit broad public access or use a permissive default ACL. Public network access or a default-allow policy can expose blobs, queues, and file storage to unauthorized users, increasing the risk of data exfiltration.\n\nFor Ansible resources using `azure.azcollection.azure_rm_storageaccount` or `azure_rm_storageaccount`, explicitly set `public_network_access` to `Disabled` and set `network_acls.default_action` to `Deny`. Resources that omit `public_network_access` (the default is `Enabled`), that set `public_network_access: Enabled`, or that set `network_acls.default_action: Allow` are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create secure Azure Storage Account\n azure_rm_storageaccount:\n resource_group: my-rg\n name: mystorageacct\n location: eastus\n public_network_access: Disabled\n network_acls:\n default_action: Deny\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/default_azure_storage_account_network_access_is_too_permissive", "platform": "Ansible", "descriptionID": "ca4df748", diff --git a/assets/queries/ansible/azure/default_azure_storage_account_network_access_is_too_permissive/test/positive_expected_result.json b/assets/queries/ansible/azure/default_azure_storage_account_network_access_is_too_permissive/test/positive_expected_result.json index 0a1c0773..d97ebc73 100644 --- a/assets/queries/ansible/azure/default_azure_storage_account_network_access_is_too_permissive/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/default_azure_storage_account_network_access_is_too_permissive/test/positive_expected_result.json @@ -1,20 +1,20 @@ [ { - "queryName": "Default Azure Storage Account Network Access Is Too Permissive", + "queryName": "Default Azure storage account network access is too permissive", "severity": "HIGH", "line": 3, "fileName": "positive1.yaml" }, { - "queryName": "Default Azure Storage Account Network Access Is Too Permissive", + "queryName": "Default Azure storage account network access is too permissive", "severity": "HIGH", "line": 3, "fileName": "positive2.yaml" }, { - "queryName": "Default Azure Storage Account Network Access Is Too Permissive", + "queryName": "Default Azure storage account network access is too permissive", "severity": "HIGH", "line": 3, "fileName": "positive3.yaml" } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache/metadata.json b/assets/queries/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache/metadata.json index d78503f4..84184616 100644 --- a/assets/queries/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache/metadata.json +++ b/assets/queries/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache/metadata.json @@ -1,9 +1,9 @@ { "id": "69f72007-502e-457b-bd2d-5012e31ac049", - "queryName": "Firewall Rule Allows Too Many Hosts To Access Redis Cache", + "queryName": "Firewall rule allows too many hosts to access Redis Cache", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Check if any firewall rule allows too many hosts to access Redis Cache.", + "descriptionText": "Redis Cache firewall rules should restrict the IP address range to minimize attack surface and prevent broad network access that could allow unauthorized access or lateral movement.\n\nIn Ansible, tasks using `azure.azcollection.azure_rm_rediscachefirewallrule` or `azure_rm_rediscachefirewallrule` must set `start_ip_address` and `end_ip_address` so the numeric range covers at most 255 hosts. Any rule where the computed range (`abs(end - start)`) is greater than 255 is flagged.\n\nResources missing these properties or defining overly large ranges should be tightened to a single IP or a narrow range. Alternatively, replace them with network-level controls such as private endpoints or service endpoints to limit access.\n\nSecure example with a small allowed range:\n\n```yaml\n- name: Allow small Redis access range\n azure.azcollection.azure_rm_rediscachefirewallrule:\n resource_group: my-rg\n name: my-redis\n start_ip_address: 10.0.0.10\n end_ip_address: 10.0.0.20\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache", "platform": "Ansible", "descriptionID": "99f14985", diff --git a/assets/queries/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache/test/positive_expected_result.json b/assets/queries/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache/test/positive_expected_result.json index 29b80de7..210b84f1 100644 --- a/assets/queries/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Firewall Rule Allows Too Many Hosts To Access Redis Cache", + "queryName": "Firewall rule allows too many hosts to access Redis Cache", "severity": "MEDIUM", "line": 6 } diff --git a/assets/queries/ansible/azure/key_vault_soft_delete_is_disabled/metadata.json b/assets/queries/ansible/azure/key_vault_soft_delete_is_disabled/metadata.json index 9729fd01..0108f584 100644 --- a/assets/queries/ansible/azure/key_vault_soft_delete_is_disabled/metadata.json +++ b/assets/queries/ansible/azure/key_vault_soft_delete_is_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "881696a8-68c5-4073-85bc-7c38a3deb854", - "queryName": "Key Vault Soft Delete Is Disabled", + "queryName": "Key Vault soft delete is disabled", "severity": "MEDIUM", "category": "Backup", - "descriptionText": "Make sure Soft Delete is enabled for Key Vault", + "descriptionText": "Key Vaults must have soft delete enabled to prevent permanent loss of keys, secrets, and certificates. This ensures deleted items can be recovered after accidental or malicious deletion.\n\nThis rule checks Ansible tasks using the `azure.azcollection.azure_rm_keyvault` or `azure_rm_keyvault` modules and requires the `enable_soft_delete` property to be defined and set to `true`. Resources missing `enable_soft_delete` or with `enable_soft_delete: false` are flagged as insecure. Consider enabling purge protection for additional safeguards against permanent deletion.\n\nSecure configuration example:\n\n```yaml\n- name: Create Key Vault with soft delete enabled\n azure.azcollection.azure_rm_keyvault:\n name: myKeyVault\n resource_group: myResourceGroup\n location: eastus\n sku: standard\n enable_soft_delete: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/key_vault_soft_delete_is_disabled", "platform": "Ansible", "descriptionID": "ca1a9cde", diff --git a/assets/queries/ansible/azure/key_vault_soft_delete_is_disabled/test/positive_expected_result.json b/assets/queries/ansible/azure/key_vault_soft_delete_is_disabled/test/positive_expected_result.json index 188baf9b..b47b2b13 100644 --- a/assets/queries/ansible/azure/key_vault_soft_delete_is_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/key_vault_soft_delete_is_disabled/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "Key Vault Soft Delete Is Disabled", + "queryName": "Key Vault soft delete is disabled", "severity": "MEDIUM", "line": 7 }, { - "queryName": "Key Vault Soft Delete Is Disabled", + "queryName": "Key Vault soft delete is disabled", "severity": "MEDIUM", "line": 18 } diff --git a/assets/queries/ansible/azure/log_retention_is_not_set/metadata.json b/assets/queries/ansible/azure/log_retention_is_not_set/metadata.json index c97365dc..ab2298ec 100644 --- a/assets/queries/ansible/azure/log_retention_is_not_set/metadata.json +++ b/assets/queries/ansible/azure/log_retention_is_not_set/metadata.json @@ -1,9 +1,9 @@ { "id": "0461b4fd-21ef-4687-929e-484ee4796785", - "queryName": "Log Retention Is Not Set", + "queryName": "Log retention is not set", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Make sure that for PostgreSQL Database, server parameter 'log_retention' is set to 'ON'", + "descriptionText": "PostgreSQL servers must retain logs to support security incident investigation and satisfy audit and compliance requirements. Without log retention, attackers or misconfigurations may go undetected and forensic analysis is impeded.\n\nIn Ansible playbooks using the `azure.azcollection.azure_rm_postgresqlconfiguration` or `azure_rm_postgresqlconfiguration` modules, the configuration entry with `name: log_retention` must have `value: on` (case-insensitive). Tasks missing the `log_retention` configuration or with `value` not equal to `on` are flagged as insecure.\n\nSecure Ansible example:\n\n```yaml\n- name: Ensure PostgreSQL log_retention is enabled\n azure.azcollection.azure_rm_postgresqlconfiguration:\n resource_group: my-resource-group\n server_name: my-postgres-server\n name: log_retention\n value: on\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/log_retention_is_not_set", "platform": "Ansible", "descriptionID": "bf371036", diff --git a/assets/queries/ansible/azure/log_retention_is_not_set/test/positive_expected_result.json b/assets/queries/ansible/azure/log_retention_is_not_set/test/positive_expected_result.json index 6295e41f..a0d69e3d 100644 --- a/assets/queries/ansible/azure/log_retention_is_not_set/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/log_retention_is_not_set/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Log Retention Is Not Set", + "queryName": "Log retention is not set", "severity": "MEDIUM", "line": 7 } diff --git a/assets/queries/ansible/azure/monitoring_log_profile_without_all_activities/metadata.json b/assets/queries/ansible/azure/monitoring_log_profile_without_all_activities/metadata.json index b7027169..85a05289 100644 --- a/assets/queries/ansible/azure/monitoring_log_profile_without_all_activities/metadata.json +++ b/assets/queries/ansible/azure/monitoring_log_profile_without_all_activities/metadata.json @@ -1,9 +1,9 @@ { "id": "89f84a1e-75f8-47c5-83b5-bee8e2de4168", - "queryName": "Monitoring Log Profile Without All Activities", + "queryName": "Monitoring log profile without all activities", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Monitoring log profile captures all the activities (Action, Write, Delete)", + "descriptionText": "Monitor log profiles must include the Write, Action, and Delete categories so Azure records operations, configuration changes, and deletions. These records support detection, auditing, and forensic investigations.\n\nIn Ansible tasks using `azure.azcollection.azure_rm_monitorlogprofile` (or `azure_rm_monitorlogprofile`), the `categories` property must be defined as a list and include the values `Write`, `Action`, and `Delete` (case-insensitive). Tasks missing the `categories` property or omitting any of these categories are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create monitor log profile\n azure_rm_monitorlogprofile:\n name: myLogProfile\n categories:\n - Write\n - Action\n - Delete\n locations:\n - eastus\n retention_policy:\n enabled: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/monitoring_log_profile_without_all_activities", "platform": "Ansible", "descriptionID": "cb93f630", diff --git a/assets/queries/ansible/azure/monitoring_log_profile_without_all_activities/test/positive_expected_result.json b/assets/queries/ansible/azure/monitoring_log_profile_without_all_activities/test/positive_expected_result.json index 43927abc..0e84df0c 100644 --- a/assets/queries/ansible/azure/monitoring_log_profile_without_all_activities/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/monitoring_log_profile_without_all_activities/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "Monitoring Log Profile Without All Activities", + "queryName": "Monitoring log profile without all activities", "severity": "MEDIUM", "line": 9 }, { - "queryName": "Monitoring Log Profile Without All Activities", + "queryName": "Monitoring log profile without all activities", "severity": "MEDIUM", "line": 21 } diff --git a/assets/queries/ansible/azure/mysql_ssl_connection_disabled/metadata.json b/assets/queries/ansible/azure/mysql_ssl_connection_disabled/metadata.json index 45149434..03efb3e9 100644 --- a/assets/queries/ansible/azure/mysql_ssl_connection_disabled/metadata.json +++ b/assets/queries/ansible/azure/mysql_ssl_connection_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "2a901825-0f3b-4655-a0fe-e0470e50f8e6", - "queryName": "MySQL SSL Connection Disabled", + "queryName": "MySQL SSL connection disabled", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "Make sure that for MySQL Database Server, 'Enforce SSL connection' is enabled", + "descriptionText": "MySQL servers must enforce SSL/TLS connections to protect data in transit and prevent interception or man-in-the-middle attacks. For Ansible tasks using the `azure.azcollection.azure_rm_mysqlserver` or `azure_rm_mysqlserver` modules, the `enforce_ssl` property must be defined and set to `true` so the server requires TLS for client connections.\n\nResources missing this property or with `enforce_ssl: false` (the default) are flagged. Use Ansible boolean values such as `true` or `yes` to enable this setting. The rule treats Ansible truthy values as valid.\n\n```yaml\n- name: Create Azure MySQL server with SSL enforced\n azure.azcollection.azure_rm_mysqlserver:\n name: my-mysql-server\n resource_group: my-rg\n location: eastus\n sku: B_Gen5_1\n version: \"5.7\"\n administrator_login: adminuser\n administrator_login_password: \"{{ mysql_password }}\"\n enforce_ssl: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/mysql_ssl_connection_disabled", "platform": "Ansible", "descriptionID": "9709164b", diff --git a/assets/queries/ansible/azure/mysql_ssl_connection_disabled/test/positive_expected_result.json b/assets/queries/ansible/azure/mysql_ssl_connection_disabled/test/positive_expected_result.json index a9e5e06c..9d3b96eb 100644 --- a/assets/queries/ansible/azure/mysql_ssl_connection_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/mysql_ssl_connection_disabled/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "MySQL SSL Connection Disabled", + "queryName": "MySQL SSL connection disabled", "severity": "MEDIUM", "line": 3 }, { - "queryName": "MySQL SSL Connection Disabled", + "queryName": "MySQL SSL connection disabled", "severity": "MEDIUM", "line": 23 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/postgresql_log_checkpoints_disabled/metadata.json b/assets/queries/ansible/azure/postgresql_log_checkpoints_disabled/metadata.json index 4f402617..3a9200cc 100644 --- a/assets/queries/ansible/azure/postgresql_log_checkpoints_disabled/metadata.json +++ b/assets/queries/ansible/azure/postgresql_log_checkpoints_disabled/metadata.json @@ -1,13 +1,13 @@ { "id": "7ab33ac0-e4a3-418f-a673-50da4e34df21", - "queryName": "PostgreSQL Log Checkpoints Disabled", + "queryName": "PostgreSQL log checkpoints disabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Make sure that for Postgre SQL Database Server, parameter 'log_checkpoints' is set to 'ON'", + "descriptionText": "PostgreSQL's `log_checkpoints` should be enabled to record checkpoint activity. This improves visibility into I/O behavior and aids detection and troubleshooting of performance or recovery issues.\n\nIn Ansible Azure PostgreSQL configuration resources (`azure.azcollection.azure_rm_postgresqlconfiguration` or `azure_rm_postgresqlconfiguration`), when the `name` property is `log_checkpoints`, the `value` property must be set to `ON` (case-insensitive). Resources missing this setting or with `value` not equal to `ON` are flagged as misconfigured.\n\nSecure configuration example:\n\n```yaml\n- name: Ensure log_checkpoints is enabled\n azure.azcollection.azure_rm_postgresqlconfiguration:\n resource_group: my-rg\n server_name: my-pg-server\n name: log_checkpoints\n value: \"ON\"\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/postgresql_log_checkpoints_disabled", "platform": "Ansible", "descriptionID": "ddcfea46", "cloudProvider": "azure", "cwe": "", "providerUrl": "https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlconfiguration_module.html" -} \ No newline at end of file +} diff --git a/assets/queries/ansible/azure/postgresql_log_checkpoints_disabled/test/positive_expected_result.json b/assets/queries/ansible/azure/postgresql_log_checkpoints_disabled/test/positive_expected_result.json index c9faef24..baab7c1a 100644 --- a/assets/queries/ansible/azure/postgresql_log_checkpoints_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/postgresql_log_checkpoints_disabled/test/positive_expected_result.json @@ -1,31 +1,31 @@ [ { - "queryName": "PostgreSQL Log Checkpoints Disabled", + "queryName": "PostgreSQL log checkpoints disabled", "severity": "MEDIUM", "line": 7 }, { - "queryName": "PostgreSQL Log Checkpoints Disabled", + "queryName": "PostgreSQL log checkpoints disabled", "severity": "MEDIUM", "line": 13 }, { - "queryName": "PostgreSQL Log Checkpoints Disabled", + "queryName": "PostgreSQL log checkpoints disabled", "severity": "MEDIUM", "line": 19 }, { - "queryName": "PostgreSQL Log Checkpoints Disabled", + "queryName": "PostgreSQL log checkpoints disabled", "severity": "MEDIUM", "line": 25 }, { - "queryName": "PostgreSQL Log Checkpoints Disabled", + "queryName": "PostgreSQL log checkpoints disabled", "severity": "MEDIUM", "line": 31 }, { - "queryName": "PostgreSQL Log Checkpoints Disabled", + "queryName": "PostgreSQL log checkpoints disabled", "severity": "MEDIUM", "line": 37 } diff --git a/assets/queries/ansible/azure/postgresql_log_connections_not_set/metadata.json b/assets/queries/ansible/azure/postgresql_log_connections_not_set/metadata.json index b1e60906..d86b03ab 100644 --- a/assets/queries/ansible/azure/postgresql_log_connections_not_set/metadata.json +++ b/assets/queries/ansible/azure/postgresql_log_connections_not_set/metadata.json @@ -1,13 +1,13 @@ { "id": "7b47138f-ec0e-47dc-8516-e7728fe3cc17", - "queryName": "PostgreSQL Log Connections Not Set", + "queryName": "PostgreSQL log connections not set", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Make sure that for PostgreSQL Database, server parameter 'log_connections' is set to 'ON'", + "descriptionText": "PostgreSQL servers must have the server parameter `log_connections` set to `ON` so connection events are recorded for auditing and intrusion detection. Without this logging, connection attempts and session activity can go unnoticed, hampering incident investigation and compliance.\n\nIn Ansible, tasks using the `azure.azcollection.azure_rm_postgresqlconfiguration` or `azure_rm_postgresqlconfiguration` modules must set the `name` property to `log_connections` and the `value` property to `ON`. This rule flags tasks where `name` equals `log_connections` (case-insensitive) and `value` is missing or not `ON` (case-insensitive). Secure configuration example:\n\n```yaml\n- name: Enable PostgreSQL connection logging\n azure.azcollection.azure_rm_postgresqlconfiguration:\n resource_group: my-rg\n server_name: my-pg-server\n name: log_connections\n value: \"ON\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/postgresql_log_connections_not_set", "platform": "Ansible", "descriptionID": "774a65e4", "cloudProvider": "azure", "cwe": "", "providerUrl": "https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlconfiguration_module.html" -} \ No newline at end of file +} diff --git a/assets/queries/ansible/azure/postgresql_log_connections_not_set/test/positive_expected_result.json b/assets/queries/ansible/azure/postgresql_log_connections_not_set/test/positive_expected_result.json index dfe5d144..2b9a8d9b 100644 --- a/assets/queries/ansible/azure/postgresql_log_connections_not_set/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/postgresql_log_connections_not_set/test/positive_expected_result.json @@ -1,31 +1,31 @@ [ { - "queryName": "PostgreSQL Log Connections Not Set", + "queryName": "PostgreSQL log connections not set", "severity": "MEDIUM", "line": 7 }, { - "queryName": "PostgreSQL Log Connections Not Set", + "queryName": "PostgreSQL log connections not set", "severity": "MEDIUM", "line": 13 }, { - "queryName": "PostgreSQL Log Connections Not Set", + "queryName": "PostgreSQL log connections not set", "severity": "MEDIUM", "line": 19 }, { - "queryName": "PostgreSQL Log Connections Not Set", + "queryName": "PostgreSQL log connections not set", "severity": "MEDIUM", "line": 25 }, { - "queryName": "PostgreSQL Log Connections Not Set", + "queryName": "PostgreSQL log connections not set", "severity": "MEDIUM", "line": 31 }, { - "queryName": "PostgreSQL Log Connections Not Set", + "queryName": "PostgreSQL log connections not set", "severity": "MEDIUM", "line": 37 } diff --git a/assets/queries/ansible/azure/postgresql_log_disconnections_not_set/metadata.json b/assets/queries/ansible/azure/postgresql_log_disconnections_not_set/metadata.json index bc50c84f..2db47c30 100644 --- a/assets/queries/ansible/azure/postgresql_log_disconnections_not_set/metadata.json +++ b/assets/queries/ansible/azure/postgresql_log_disconnections_not_set/metadata.json @@ -1,9 +1,9 @@ { "id": "054d07b5-941b-4c28-8eef-18989dc62323", - "queryName": "PostgreSQL Log Disconnections Not Set", + "queryName": "PostgreSQL log disconnections not set", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Make sure that for PostgreSQL Database, server parameter 'log_disconnections' is set to 'ON'", + "descriptionText": "Enabling the PostgreSQL server parameter `log_disconnections` ensures the server records client connection termination events. This is important for detecting abnormal connection patterns, troubleshooting connectivity issues, and supporting forensic investigations.\n\nFor Ansible, the `azure.azcollection.azure_rm_postgresqlconfiguration` (or legacy `azure_rm_postgresqlconfiguration`) resource must have `name: log_disconnections` and `value: ON` (value compared case-insensitively). Resources where `name` is `log_disconnections` but `value` is missing, not a string, or not set to `ON` are flagged as insecure.\n\nSecure Ansible configuration example:\n\n```yaml\n- name: Enable PostgreSQL log_disconnections\n azure.azcollection.azure_rm_postgresqlconfiguration:\n resource_group: my-rg\n server_name: my-pg-server\n name: log_disconnections\n value: ON\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/postgresql_log_disconnections_not_set", "platform": "Ansible", "descriptionID": "8d159e5e", diff --git a/assets/queries/ansible/azure/postgresql_log_disconnections_not_set/test/positive_expected_result.json b/assets/queries/ansible/azure/postgresql_log_disconnections_not_set/test/positive_expected_result.json index d3675e68..f1dd6aee 100644 --- a/assets/queries/ansible/azure/postgresql_log_disconnections_not_set/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/postgresql_log_disconnections_not_set/test/positive_expected_result.json @@ -1,31 +1,31 @@ [ { - "queryName": "PostgreSQL Log Disconnections Not Set", + "queryName": "PostgreSQL log disconnections not set", "severity": "MEDIUM", "line": 7 }, { - "queryName": "PostgreSQL Log Disconnections Not Set", + "queryName": "PostgreSQL log disconnections not set", "severity": "MEDIUM", "line": 13 }, { - "queryName": "PostgreSQL Log Disconnections Not Set", + "queryName": "PostgreSQL log disconnections not set", "severity": "MEDIUM", "line": 19 }, { - "queryName": "PostgreSQL Log Disconnections Not Set", + "queryName": "PostgreSQL log disconnections not set", "severity": "MEDIUM", "line": 25 }, { - "queryName": "PostgreSQL Log Disconnections Not Set", + "queryName": "PostgreSQL log disconnections not set", "severity": "MEDIUM", "line": 31 }, { - "queryName": "PostgreSQL Log Disconnections Not Set", + "queryName": "PostgreSQL log disconnections not set", "severity": "MEDIUM", "line": 37 } diff --git a/assets/queries/ansible/azure/postgresql_log_duration_not_set/metadata.json b/assets/queries/ansible/azure/postgresql_log_duration_not_set/metadata.json index fe88e643..dd0ac73b 100644 --- a/assets/queries/ansible/azure/postgresql_log_duration_not_set/metadata.json +++ b/assets/queries/ansible/azure/postgresql_log_duration_not_set/metadata.json @@ -1,13 +1,13 @@ { "id": "729ebb15-8060-40f7-9017-cb72676a5487", - "queryName": "PostgreSQL Log Duration Not Set", + "queryName": "PostgreSQL log duration not set", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Make sure that for PostgreSQL Database, server parameter 'log_duration' is set to 'ON'", + "descriptionText": "Enable the PostgreSQL server parameter `log_duration` to record statement execution durations. Without duration logging, slow queries and malicious long-running activity can go undetected, hindering timely detection and forensic investigation.\n\nIn Ansible tasks using the `azure.azcollection.azure_rm_postgresqlconfiguration` or `azure_rm_postgresqlconfiguration` module, the parameter entry with `name: log_duration` must have `value: 'ON'`. Tasks missing the `value` property or with a value other than `ON` (case-insensitive) are flagged.\n\nSecure Ansible task example:\n\n```yaml\n- name: Enable log_duration for PostgreSQL server\n azure.azcollection.azure_rm_postgresqlconfiguration:\n resource_group: myResourceGroup\n server_name: myPostgresServer\n name: log_duration\n value: \"ON\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/postgresql_log_duration_not_set", "platform": "Ansible", "descriptionID": "a30f009d", "cloudProvider": "azure", "cwe": "", "providerUrl": "https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlconfiguration_module.html" -} \ No newline at end of file +} diff --git a/assets/queries/ansible/azure/postgresql_log_duration_not_set/test/positive_expected_result.json b/assets/queries/ansible/azure/postgresql_log_duration_not_set/test/positive_expected_result.json index 86f29653..91396b47 100644 --- a/assets/queries/ansible/azure/postgresql_log_duration_not_set/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/postgresql_log_duration_not_set/test/positive_expected_result.json @@ -1,31 +1,31 @@ [ { - "queryName": "PostgreSQL Log Duration Not Set", + "queryName": "PostgreSQL log duration not set", "severity": "MEDIUM", "line": 6 }, { - "queryName": "PostgreSQL Log Duration Not Set", + "queryName": "PostgreSQL log duration not set", "severity": "MEDIUM", "line": 12 }, { - "queryName": "PostgreSQL Log Duration Not Set", + "queryName": "PostgreSQL log duration not set", "severity": "MEDIUM", "line": 18 }, { - "queryName": "PostgreSQL Log Duration Not Set", + "queryName": "PostgreSQL log duration not set", "severity": "MEDIUM", "line": 24 }, { - "queryName": "PostgreSQL Log Duration Not Set", + "queryName": "PostgreSQL log duration not set", "severity": "MEDIUM", "line": 30 }, { - "queryName": "PostgreSQL Log Duration Not Set", + "queryName": "PostgreSQL log duration not set", "severity": "MEDIUM", "line": 36 } diff --git a/assets/queries/ansible/azure/postgresql_server_without_connection_throttling/metadata.json b/assets/queries/ansible/azure/postgresql_server_without_connection_throttling/metadata.json index adb6168d..457210d6 100644 --- a/assets/queries/ansible/azure/postgresql_server_without_connection_throttling/metadata.json +++ b/assets/queries/ansible/azure/postgresql_server_without_connection_throttling/metadata.json @@ -1,13 +1,13 @@ { "id": "a9becca7-892a-4af7-b9e1-44bf20a4cd9a", - "queryName": "PostgreSQL Server Without Connection Throttling", + "queryName": "PostgreSQL server without connection throttling", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Ensure that Connection Throttling is set for the PostgreSQL server", + "descriptionText": "Connection throttling must be enabled on PostgreSQL servers to limit concurrent connection attempts and prevent resource exhaustion or availability degradation from runaway clients or connection storms.\n\nThis rule checks Ansible tasks using the `azure.azcollection.azure_rm_postgresqlconfiguration` or `azure_rm_postgresqlconfiguration` module where `name` equals `connection_throttling`. The `value` property must be set to `ON` (case-insensitive). Resources missing this setting or with `value` set to `OFF` (or any value other than `ON`) are flagged as an incorrect configuration.\n\nSecure Ansible task example:\n\n```yaml\n- name: Enable connection throttling on PostgreSQL server\n azure.azcollection.azure_rm_postgresqlconfiguration:\n resource_group: myResourceGroup\n server_name: myPostgresServer\n name: connection_throttling\n value: ON\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/postgresql_server_without_connection_throttling", "platform": "Ansible", "descriptionID": "47504c54", "cloudProvider": "azure", "cwe": "", "providerUrl": "https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlconfiguration_module.html" -} \ No newline at end of file +} diff --git a/assets/queries/ansible/azure/postgresql_server_without_connection_throttling/test/positive_expected_result.json b/assets/queries/ansible/azure/postgresql_server_without_connection_throttling/test/positive_expected_result.json index ded293c1..72f678c3 100644 --- a/assets/queries/ansible/azure/postgresql_server_without_connection_throttling/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/postgresql_server_without_connection_throttling/test/positive_expected_result.json @@ -1,31 +1,31 @@ [ { - "queryName": "PostgreSQL Server Without Connection Throttling", + "queryName": "PostgreSQL server without connection throttling", "severity": "MEDIUM", "line": 7 }, { - "queryName": "PostgreSQL Server Without Connection Throttling", + "queryName": "PostgreSQL server without connection throttling", "severity": "MEDIUM", "line": 13 }, { - "queryName": "PostgreSQL Server Without Connection Throttling", + "queryName": "PostgreSQL server without connection throttling", "severity": "MEDIUM", "line": 19 }, { - "queryName": "PostgreSQL Server Without Connection Throttling", + "queryName": "PostgreSQL server without connection throttling", "severity": "MEDIUM", "line": 25 }, { - "queryName": "PostgreSQL Server Without Connection Throttling", + "queryName": "PostgreSQL server without connection throttling", "severity": "MEDIUM", "line": 31 }, { - "queryName": "PostgreSQL Server Without Connection Throttling", + "queryName": "PostgreSQL server without connection throttling", "severity": "MEDIUM", "line": 37 } diff --git a/assets/queries/ansible/azure/public_storage_account/metadata.json b/assets/queries/ansible/azure/public_storage_account/metadata.json index 2bc55a65..99b35c54 100644 --- a/assets/queries/ansible/azure/public_storage_account/metadata.json +++ b/assets/queries/ansible/azure/public_storage_account/metadata.json @@ -1,9 +1,9 @@ { "id": "35e2f133-a395-40de-a79d-b260d973d1bd", - "queryName": "Public Storage Account", + "queryName": "Public storage account", "severity": "HIGH", "category": "Access Control", - "descriptionText": "Storage Account should not be public to grant the principle of least privileges", + "descriptionText": "Storage accounts must not allow public network access. Broad network access or open IP ranges expose account endpoints and data to unauthorized access and exfiltration.\n\nFor Ansible `azure_rm_storageaccount` and `azure.azcollection.azure_rm_storageaccount` tasks, ensure `network_acls.default_action` is not set to `\"Allow\"` (use `\"Deny\"`). When `default_action` is `\"Deny\"`, the `network_acls.ip_rules` list must not contain the catch-all `\"0.0.0.0/0\"`. Resources missing these properties, with `default_action='Allow'`, or with `ip_rules` containing `0.0.0.0/0` are flagged. \n\nSecure example for an Ansible task:\n\n```yaml\n- name: Create storage account with restricted network access\n azure.azcollection.azure_rm_storageaccount:\n resource_group: my-rg\n name: mystorageacct\n location: eastus\n network_acls:\n default_action: Deny\n ip_rules:\n - value: 203.0.113.5/32\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/public_storage_account", "platform": "Ansible", "descriptionID": "78d2c5b3", diff --git a/assets/queries/ansible/azure/public_storage_account/test/positive_expected_result.json b/assets/queries/ansible/azure/public_storage_account/test/positive_expected_result.json index 95adb340..62cf407d 100644 --- a/assets/queries/ansible/azure/public_storage_account/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/public_storage_account/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ - { - "queryName": "Public Storage Account", - "severity": "HIGH", - "line": 9 - }, - { - "queryName": "Public Storage Account", - "severity": "HIGH", - "line": 19 - } + { + "queryName": "Public storage account", + "severity": "HIGH", + "line": 9 + }, + { + "queryName": "Public storage account", + "severity": "HIGH", + "line": 19 + } ] diff --git a/assets/queries/ansible/azure/redis_cache_allows_non_ssl_connections/metadata.json b/assets/queries/ansible/azure/redis_cache_allows_non_ssl_connections/metadata.json index 40c1bcc2..17097ccf 100644 --- a/assets/queries/ansible/azure/redis_cache_allows_non_ssl_connections/metadata.json +++ b/assets/queries/ansible/azure/redis_cache_allows_non_ssl_connections/metadata.json @@ -1,9 +1,9 @@ { "id": "869e7fb4-30f0-4bdb-b360-ad548f337f2f", - "queryName": "Redis Cache Allows Non SSL Connections", + "queryName": "Redis cache allows non-SSL connections", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Redis Cache resources should not allow non-SSL connections", + "descriptionText": "Allowing non-SSL (plaintext) connections to Azure Cache for Redis exposes data in transit to interception and tampering. This can leak credentials and sensitive cached data or enable man-in-the-middle attacks.\n\nFor Ansible tasks using the `azure.azcollection.azure_rm_rediscache` or `azure_rm_rediscache` modules, the `enable_non_ssl_port` property must be set to `false` or omitted so only SSL/TLS connections are permitted. Resources with `enable_non_ssl_port: true` are flagged. Ensure clients connect over the TLS/SSL port (typically 6380) and validate certificates. \n\nSecure Ansible configuration example:\n\n```yaml\n- name: Create Redis Cache with TLS-only access\n azure.azcollection.azure_rm_rediscache:\n resource_group: my-rg\n name: my-redis\n location: eastus\n sku: name=Standard\n enable_non_ssl_port: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/redis_cache_allows_non_ssl_connections", "platform": "Ansible", "descriptionID": "31e56819", diff --git a/assets/queries/ansible/azure/redis_cache_allows_non_ssl_connections/test/positive_expected_result.json b/assets/queries/ansible/azure/redis_cache_allows_non_ssl_connections/test/positive_expected_result.json index 34ebe756..de3779d7 100644 --- a/assets/queries/ansible/azure/redis_cache_allows_non_ssl_connections/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/redis_cache_allows_non_ssl_connections/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Redis Cache Allows Non SSL Connections", + "queryName": "Redis cache allows non-SSL connections", "severity": "MEDIUM", "line": 5 } diff --git a/assets/queries/ansible/azure/redis_entirely_accessible/metadata.json b/assets/queries/ansible/azure/redis_entirely_accessible/metadata.json index 3871db94..dd55e35e 100644 --- a/assets/queries/ansible/azure/redis_entirely_accessible/metadata.json +++ b/assets/queries/ansible/azure/redis_entirely_accessible/metadata.json @@ -1,9 +1,9 @@ { "id": "0d0c12b9-edce-4510-9065-13f6a758750c", - "queryName": "Redis Entirely Accessible", + "queryName": "Redis entirely accessible", "severity": "CRITICAL", "category": "Networking and Firewall", - "descriptionText": "Firewall rule allowing unrestricted access to Redis from the Internet", + "descriptionText": "Allowing a Redis cache firewall rule to use `0.0.0.0` for both start and end addresses grants unrestricted internet access to the cache, exposing it to unauthorized access, data exposure, and potential remote exploitation.\n\nFor Ansible tasks using `azure.azcollection.azure_rm_rediscachefirewallrule` or `azure_rm_rediscachefirewallrule`, the `start_ip_address` and `end_ip_address` properties must be defined and must not be set to `\"0.0.0.0\"`. Specify a limited IP range or a single trusted IP address (set both start and end to the same IP for a single host). Resources where both `start_ip_address` and `end_ip_address` equal `\"0.0.0.0\"` are flagged. Restrict access to known management IPs, use VNet integration, or Azure service endpoints to avoid exposing Redis to the public internet.\n\nSecure example limiting access to a single admin IP:\n\n```yaml\n- name: Allow Redis access from admin IP\n azure.azcollection.azure_rm_rediscachefirewallrule:\n resource_group: my-resource-group\n name: my-redis-cache\n start_ip_address: 203.0.113.5\n end_ip_address: 203.0.113.5\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/redis_entirely_accessible", "platform": "Ansible", "descriptionID": "30a0bdeb", diff --git a/assets/queries/ansible/azure/redis_entirely_accessible/test/positive_expected_result.json b/assets/queries/ansible/azure/redis_entirely_accessible/test/positive_expected_result.json index f04cd803..58546814 100644 --- a/assets/queries/ansible/azure/redis_entirely_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/redis_entirely_accessible/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "Redis Entirely Accessible", + "queryName": "Redis entirely accessible", "severity": "CRITICAL", "line": 7 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/redis_publicly_accessible/metadata.json b/assets/queries/ansible/azure/redis_publicly_accessible/metadata.json index fc192132..8b3d98cb 100644 --- a/assets/queries/ansible/azure/redis_publicly_accessible/metadata.json +++ b/assets/queries/ansible/azure/redis_publicly_accessible/metadata.json @@ -1,9 +1,9 @@ { "id": "0632d0db-9190-450a-8bb3-c283bffea445", - "queryName": "Redis Publicly Accessible", + "queryName": "Redis publicly accessible", "severity": "CRITICAL", "category": "Networking and Firewall", - "descriptionText": "Firewall rule allowing unrestricted access to Redis from other Azure sources", + "descriptionText": "Allowing public IP ranges in Azure Redis Cache firewall rules exposes the cache to unauthorized internet access, increasing the risk of data exfiltration and lateral movement.\n\nThe Ansible modules `azure.azcollection.azure_rm_rediscachefirewallrule` and `azure_rm_rediscachefirewallrule` must set `start_ip_address` and `end_ip_address` to private IP ranges (RFC1918). Tasks missing these properties or specifying non-private or public IPs are flagged.\n\nIf access should be limited to Azure resources, prefer virtual network rules or service endpoints instead of broad IP ranges, and ensure any IP range only includes trusted internal addresses.\n\nSecure configuration example:\n\n```yaml\n- name: allow internal subnet to access redis\n azure.azcollection.azure_rm_rediscachefirewallrule:\n name: allow-internal\n resource_group: my-rg\n redis_name: my-redis\n start_ip_address: 10.0.0.1\n end_ip_address: 10.0.0.255\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/redis_publicly_accessible", "platform": "Ansible", "descriptionID": "140392b3", diff --git a/assets/queries/ansible/azure/redis_publicly_accessible/test/positive_expected_result.json b/assets/queries/ansible/azure/redis_publicly_accessible/test/positive_expected_result.json index bc5829b3..b7acb7e8 100644 --- a/assets/queries/ansible/azure/redis_publicly_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/redis_publicly_accessible/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "Redis Publicly Accessible", + "queryName": "Redis publicly accessible", "severity": "CRITICAL", "line": 7 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/role_definition_allows_custom_role_creation/metadata.json b/assets/queries/ansible/azure/role_definition_allows_custom_role_creation/metadata.json index 92215589..0b60e57d 100644 --- a/assets/queries/ansible/azure/role_definition_allows_custom_role_creation/metadata.json +++ b/assets/queries/ansible/azure/role_definition_allows_custom_role_creation/metadata.json @@ -1,9 +1,9 @@ { "id": "5c80db8e-03f5-43a2-b4af-1f3f87018157", - "queryName": "Role Definition Allows Custom Role Creation", + "queryName": "Role definition allows custom role creation", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "Role Definition should not allow custom role creation (Microsoft.Authorization/roleDefinitions/write)", + "descriptionText": "Role definitions must not grant the ability to create or modify other role definitions (`Microsoft.Authorization/roleDefinitions/write`). This capability enables privilege escalation and persistent unauthorized access by allowing creation of custom roles with elevated permissions.\n\nIn Ansible playbooks using the `azure.azcollection.azure_rm_roledefinition` or `azure_rm_roledefinition` modules, the `permissions[].actions` array must not include the literal action `Microsoft.Authorization/roleDefinitions/write` and must not be a wildcard (`*`). This rule flags tasks where `permissions.actions` is `[\"*\"]` or contains `Microsoft.Authorization/roleDefinitions/write`. Ensure the actions list contains only the specific, least-privilege actions required for the role.\n\nSecure example with no role-definition write permission:\n\n```yaml\n- name: example role\n azure.azcollection.azure_rm_roledefinition:\n name: customReadOnlyRole\n scope: /subscriptions/00000000-0000-0000-0000-000000000000\n permissions:\n - actions:\n - \"Microsoft.Storage/storageAccounts/read\"\n - \"Microsoft.Compute/virtualMachines/read\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/role_definition_allows_custom_role_creation", "platform": "Ansible", "descriptionID": "6296166a", diff --git a/assets/queries/ansible/azure/role_definition_allows_custom_role_creation/test/positive_expected_result.json b/assets/queries/ansible/azure/role_definition_allows_custom_role_creation/test/positive_expected_result.json index ac9202cc..0fd20811 100644 --- a/assets/queries/ansible/azure/role_definition_allows_custom_role_creation/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/role_definition_allows_custom_role_creation/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Role Definition Allows Custom Role Creation", + "queryName": "Role definition allows custom role creation", "severity": "MEDIUM", "line": 7, "fileName": "positive1.yaml" }, { - "queryName": "Role Definition Allows Custom Role Creation", + "queryName": "Role definition allows custom role creation", "severity": "MEDIUM", "line": 7, "fileName": "positive2.yaml" diff --git a/assets/queries/ansible/azure/security_group_is_not_configured/metadata.json b/assets/queries/ansible/azure/security_group_is_not_configured/metadata.json index 9022c332..ec87633d 100644 --- a/assets/queries/ansible/azure/security_group_is_not_configured/metadata.json +++ b/assets/queries/ansible/azure/security_group_is_not_configured/metadata.json @@ -1,9 +1,9 @@ { "id": "da4f2739-174f-4cdd-b9ef-dc3f14b5931f", - "queryName": "Security Group is Not Configured", + "queryName": "Security group is not configured", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "Azure Virtual Network subnet must be configured with a Network Security Group, which means the attribute 'security_group' must be defined and not empty", + "descriptionText": "A subnet without an associated Network Security Group (NSG) lacks network-level access controls, increasing exposure to unauthorized access and enabling lateral movement between resources.\n\nFor Ansible `azure_rm_subnet` resources (modules `azure.azcollection.azure_rm_subnet` and `azure_rm_subnet`), the `security_group` or `security_group_name` property must be defined and set to a non-empty value. Resources that omit these properties or set them to null/empty strings are flagged. Ensure the value references the appropriate NSG (name or ID) for your environment.\n\nSecure configuration example:\n\n```yaml\n- name: Create subnet with NSG\n azure.azcollection.azure_rm_subnet:\n resource_group: my-rg\n virtual_network: my-vnet\n name: my-subnet\n address_prefix: 10.0.1.0/24\n security_group: my-nsg\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/security_group_is_not_configured", "platform": "Ansible", "descriptionID": "381f57a0", diff --git a/assets/queries/ansible/azure/security_group_is_not_configured/test/positive_expected_result.json b/assets/queries/ansible/azure/security_group_is_not_configured/test/positive_expected_result.json index d0ea6a50..deb10ff5 100644 --- a/assets/queries/ansible/azure/security_group_is_not_configured/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/security_group_is_not_configured/test/positive_expected_result.json @@ -1,27 +1,27 @@ [ { - "queryName": "Security Group is Not Configured", + "queryName": "Security group is not configured", "severity": "HIGH", "line": 3 }, { - "queryName": "Security Group is Not Configured", + "queryName": "Security group is not configured", "severity": "HIGH", "line": 9 }, { - "queryName": "Security Group is Not Configured", + "queryName": "Security group is not configured", "severity": "HIGH", "line": 16 }, { - "queryName": "Security Group is Not Configured", + "queryName": "Security group is not configured", "severity": "HIGH", "line": 28 }, { - "queryName": "Security Group is Not Configured", + "queryName": "Security group is not configured", "severity": "HIGH", "line": 35 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/sensitive_port_is_exposed_to_entire_network/metadata.json b/assets/queries/ansible/azure/sensitive_port_is_exposed_to_entire_network/metadata.json index d8e70974..4e546975 100644 --- a/assets/queries/ansible/azure/sensitive_port_is_exposed_to_entire_network/metadata.json +++ b/assets/queries/ansible/azure/sensitive_port_is_exposed_to_entire_network/metadata.json @@ -1,9 +1,9 @@ { "id": "0ac9abbc-6d7a-41cf-af23-2e57ddb3dbfc", - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "category": "Networking and Firewall", - "descriptionText": "A sensitive port, such as port 23 or port 110, is open for the whole network in either TCP or UDP protocol", + "descriptionText": "Inbound network security group rules that allow TCP or UDP access to sensitive service ports from anywhere (for example, 0.0.0.0/0 or ::/0) expose services such as Telnet or POP3 to the public internet, increasing the risk of unauthorized access and exploitation.\n\nIn Ansible tasks using `azure.azcollection.azure_rm_securitygroup` or `azure_rm_securitygroup`, inspect each entry in `rules[]`. A rule is flagged when `access` is `\"Allow\"`, `direction` is `\"Inbound\"` (or absent), `source_address_prefix` ends with `\"/0\"`, `protocol` is TCP/UDP (or `\"*\"`, which expands to include TCP/UDP), and `destination_port_range` contains a sensitive TCP port.\n\nThe check handles `destination_port_range` as either a string or an array and supports single ports, comma-separated lists, and ranges. Resources missing the `direction` property are treated as inbound and are evaluated.\n\nRemediate by restricting `source_address_prefix` to specific CIDR ranges or internal/service endpoints, or by removing or denying public Allow rules for those ports. For example, allow only from a trusted management CIDR:\n\n```yaml\n- name: Create NSG with restricted rule\n azure_rm_securitygroup:\n name: myNSG\n resource_group: myRG\n rules:\n - name: AllowSSHFromMgmt\n protocol: Tcp\n destination_port_range: 22\n source_address_prefix: 10.0.0.0/24\n access: Allow\n direction: Inbound\n priority: 1000\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/sensitive_port_is_exposed_to_entire_network", "platform": "Ansible", "descriptionID": "33745204", diff --git a/assets/queries/ansible/azure/sensitive_port_is_exposed_to_entire_network/test/positive_expected_result.json b/assets/queries/ansible/azure/sensitive_port_is_exposed_to_entire_network/test/positive_expected_result.json index 14b47334..9f97ab5b 100644 --- a/assets/queries/ansible/azure/sensitive_port_is_exposed_to_entire_network/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/sensitive_port_is_exposed_to_entire_network/test/positive_expected_result.json @@ -1,221 +1,221 @@ [ { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 13 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 27 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 27 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 41 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 41 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 41 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 41 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 41 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 41 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 55 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 55 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 69 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 85 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 99 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 99 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 99 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 99 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 113 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 130 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 130 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 }, { - "queryName": "Sensitive Port Is Exposed To Entire Network", + "queryName": "Sensitive port is exposed to entire network", "severity": "HIGH", "line": 142 } diff --git a/assets/queries/ansible/azure/small_activity_log_retention_period/metadata.json b/assets/queries/ansible/azure/small_activity_log_retention_period/metadata.json index 4f26d3e7..006b4e0d 100644 --- a/assets/queries/ansible/azure/small_activity_log_retention_period/metadata.json +++ b/assets/queries/ansible/azure/small_activity_log_retention_period/metadata.json @@ -1,9 +1,9 @@ { "id": "37fafbea-dedb-4e0d-852e-d16ee0589326", - "queryName": "Small Activity Log Retention Period", + "queryName": "Small activity log retention period", "severity": "LOW", "category": "Observability", - "descriptionText": "Ensure that Activity Log Retention is set 365 days or greater", + "descriptionText": "Activity Log retention must be configured to retain logs for at least 365 days (or indefinitely). Short retention windows hinder incident response, forensic investigations, and regulatory compliance.\n\nFor Ansible `azure.azcollection.azure_rm_monitorlogprofile` / `azure_rm_monitorlogprofile` resources, the `retention_policy.enabled` property must be `true` and `retention_policy.days` must be set to `365` or greater, or to `0` to retain logs indefinitely. Tasks that omit `retention_policy`, set `retention_policy.enabled` to `false` (or `no`), or set `retention_policy.days` to a value between 1 and 364 are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Configure Activity Log retention\n azure.azcollection.azure_rm_monitorlogprofile:\n name: my-log-profile\n locations:\n - global\n categories:\n - Write\n - Delete\n - Action\n retention_policy:\n enabled: yes\n days: 365\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/small_activity_log_retention_period", "platform": "Ansible", "descriptionID": "5bad8aed", diff --git a/assets/queries/ansible/azure/small_activity_log_retention_period/test/positive_expected_result.json b/assets/queries/ansible/azure/small_activity_log_retention_period/test/positive_expected_result.json index 5282ba8c..890735a3 100644 --- a/assets/queries/ansible/azure/small_activity_log_retention_period/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/small_activity_log_retention_period/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "Small Activity Log Retention Period", + "queryName": "Small activity log retention period", "severity": "LOW", "line": 13 }, { - "queryName": "Small Activity Log Retention Period", + "queryName": "Small activity log retention period", "severity": "LOW", "line": 20 }, { - "queryName": "Small Activity Log Retention Period", + "queryName": "Small activity log retention period", "severity": "LOW", "line": 46 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/sql_server_ingress_from_any_ip/metadata.json b/assets/queries/ansible/azure/sql_server_ingress_from_any_ip/metadata.json index acd59b66..3d6c3e10 100644 --- a/assets/queries/ansible/azure/sql_server_ingress_from_any_ip/metadata.json +++ b/assets/queries/ansible/azure/sql_server_ingress_from_any_ip/metadata.json @@ -1,9 +1,9 @@ { "id": "f4e9ff70-0f3b-4c50-a713-26cbe7ec4039", - "queryName": "SQLServer Ingress From Any IP", + "queryName": "SQLServer ingress from any IP", "severity": "CRITICAL", "category": "Networking and Firewall", - "descriptionText": "Check if all IPs are allowed, check from start 0.0.0.0 to end 255.255.255.255.", + "descriptionText": "Allowing an Azure SQL firewall rule to accept connections from the entire internet (`start_ip_address` set to `0.0.0.0` and `end_ip_address` set to `255.255.255.255`) exposes database servers to unauthorized access and credential brute-force attacks.\n\nThis rule checks Ansible resources using the `azure.azcollection.azure_rm_sqlfirewallrule` (or `azure_rm_sqlfirewallrule`) module. Resources with `start_ip_address` set to `0.0.0.0` and `end_ip_address` set to `255.255.255.255` are flagged. Restrict firewall rules to specific client IPs or CIDR ranges, or use virtual network-based rules to limit access. \n\nSecure example with a single allowed IP:\n\n```yaml\n- name: Add SQL firewall rule for a specific IP\n azure.azcollection.azure_rm_sqlfirewallrule:\n resource_group: myResourceGroup\n server_name: my-sql-server\n name: allow-office-ip\n start_ip_address: 203.0.113.5\n end_ip_address: 203.0.113.5\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/sql_server_ingress_from_any_ip", "platform": "Ansible", "descriptionID": "ea086cca", diff --git a/assets/queries/ansible/azure/sql_server_ingress_from_any_ip/test/positive_expected_result.json b/assets/queries/ansible/azure/sql_server_ingress_from_any_ip/test/positive_expected_result.json index 31a8c77c..591ca722 100644 --- a/assets/queries/ansible/azure/sql_server_ingress_from_any_ip/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/sql_server_ingress_from_any_ip/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "SQLServer Ingress From Any IP", + "queryName": "SQLServer ingress from any IP", "severity": "CRITICAL", "line": 8 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/sql_server_predictable_active_directory_admin_account_name/metadata.json b/assets/queries/ansible/azure/sql_server_predictable_active_directory_admin_account_name/metadata.json index cfc28167..c92bd8be 100644 --- a/assets/queries/ansible/azure/sql_server_predictable_active_directory_admin_account_name/metadata.json +++ b/assets/queries/ansible/azure/sql_server_predictable_active_directory_admin_account_name/metadata.json @@ -1,9 +1,9 @@ { "id": "530e8291-2f22-4bab-b7ea-306f1bc2a308", - "queryName": "SQL Server Predictable Active Directory Account Name", + "queryName": "SQL Server predictable Active Directory account name", "severity": "LOW", "category": "Best Practices", - "descriptionText": "Azure SQL Server must avoid using predictable Active Directory Administrator Account names, like 'Admin', which means the attribute 'ad_user' must be set to a name that is not easy to predict", + "descriptionText": "Active Directory administrator accounts for Azure SQL Server must not use predictable or common names such as \"admin\" or \"administrator.\" Predictable account names make privileged accounts easy to discover and enable targeted brute-force and credential-stuffing attacks.\n\nIn Ansible, verify the `azure.azcollection.azure_rm_adserviceprincipal` (or `azure_rm_adserviceprincipal`) task's `ad_user` property is defined, non-empty, and set to a non-predictable, unique name. This rule flags tasks where `ad_user` is missing or `null`, or where the value matches common predictable names (case-insensitive) such as `admin`, `administrator`, `sqladmin`, `root`, `user`, `azure_admin`, `azure_administrator`, or `guest`. Use a clear, non-guessable name for `ad_user`. For example:\n\n```yaml\n- name: Create AD service principal for Azure SQL admin\n azure.azcollection.azure_rm_adserviceprincipal:\n ad_user: \"sqlsvc-prod-01\"\n password: \"{{ lookup('password', '/dev/null length=32') }}\"\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/sql_server_predictable_active_directory_admin_account_name", "platform": "Ansible", "descriptionID": "f9f40edb", diff --git a/assets/queries/ansible/azure/sql_server_predictable_active_directory_admin_account_name/test/positive_expected_result.json b/assets/queries/ansible/azure/sql_server_predictable_active_directory_admin_account_name/test/positive_expected_result.json index 86374d0d..143238e6 100644 --- a/assets/queries/ansible/azure/sql_server_predictable_active_directory_admin_account_name/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/sql_server_predictable_active_directory_admin_account_name/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "SQL Server Predictable Active Directory Account Name", + "queryName": "SQL Server predictable Active Directory account name", "severity": "LOW", "line": 8 }, { - "queryName": "SQL Server Predictable Active Directory Account Name", + "queryName": "SQL Server predictable Active Directory account name", "severity": "LOW", "line": 15 }, { - "queryName": "SQL Server Predictable Active Directory Account Name", + "queryName": "SQL Server predictable Active Directory account name", "severity": "LOW", "line": 22 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/metadata.json b/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/metadata.json index 49cb52b9..14a6dd69 100644 --- a/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/metadata.json +++ b/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/metadata.json @@ -1,9 +1,9 @@ { "id": "663062e9-473d-4e87-99bc-6f3684b3df40", - "queryName": "SQL Server Predictable Admin Account Name", + "queryName": "SQL Server predictable admin account name", "severity": "LOW", "category": "Best Practices", - "descriptionText": "Azure SQL Server's Admin account login must avoid using names like 'Admin', that are too predictable, which means the attribute 'admin_username' must be set to a name that is not easy to predict", + "descriptionText": "Admin usernames for Azure SQL Server must not be empty or use predictable names. Predictable account names (for example, \"admin\" or \"administrator\") make it significantly easier for attackers to perform brute-force, credential-stuffing, and targeted authentication attacks.\n\nFor Ansible resources using `azure.azcollection.azure_rm_sqlserver` or `azure_rm_sqlserver`, the `admin_username` property must be defined as a non-empty string. It must not be one of the following predictable names: `admin`, `administrator`, `root`, `user`, `azure_admin`, `azure_administrator`, or `guest`.\n\nTasks that omit `admin_username`, set it to an empty value, or use any of the predictable names (checked case-insensitively) are flagged as insecure. \n\nSecure example:\n\n```yaml\n- name: Create Azure SQL Server\n azure.azcollection.azure_rm_sqlserver:\n name: my-sql-server\n resource_group: my-rg\n location: eastus\n admin_username: dbadmin01\n admin_password: \"{{ sql_admin_password }}\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/sql_server_predictable_admin_account_name", "platform": "Ansible", "descriptionID": "8526646e", diff --git a/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/test/positive_expected_result.json b/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/test/positive_expected_result.json index ce810593..93d324a9 100644 --- a/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/sql_server_predictable_admin_account_name/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "SQL Server Predictable Admin Account Name", + "queryName": "SQL Server predictable admin account name", "severity": "LOW", "line": 7 }, { - "queryName": "SQL Server Predictable Admin Account Name", + "queryName": "SQL Server predictable admin account name", "severity": "LOW", "line": 14 }, { - "queryName": "SQL Server Predictable Admin Account Name", + "queryName": "SQL Server predictable admin account name", "severity": "LOW", "line": 21 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/ssl_enforce_is_disabled/metadata.json b/assets/queries/ansible/azure/ssl_enforce_is_disabled/metadata.json index ef00735f..85f88ae7 100644 --- a/assets/queries/ansible/azure/ssl_enforce_is_disabled/metadata.json +++ b/assets/queries/ansible/azure/ssl_enforce_is_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "961ce567-a16d-4d7d-9027-f0ec2628a555", - "queryName": "SSL Enforce Disabled", + "queryName": "SSL enforce disabled", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "Make sure that for PosgreSQL, the 'Enforce SSL connection' is set to 'ENABLED'", + "descriptionText": "PostgreSQL servers must enforce SSL connections to ensure client‑server traffic is encrypted and prevent credential exposure in transit. For Ansible playbooks using the `azure.azcollection.azure_rm_postgresqlserver` or `azure_rm_postgresqlserver` modules, the `enforce_ssl` parameter must be set to `true` (Ansible `yes`/true). Tasks that omit `enforce_ssl` (it defaults to `false`) or set it to `false` are flagged as insecure.\n\nSecure configuration example:\n\n```yaml\n- name: Create PostgreSQL server with SSL enforced\n azure.azcollection.azure_rm_postgresqlserver:\n name: mypgserver\n resource_group: my-rg\n location: eastus\n enforce_ssl: yes\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/ssl_enforce_is_disabled", "platform": "Ansible", "descriptionID": "b4bcb2d1", diff --git a/assets/queries/ansible/azure/ssl_enforce_is_disabled/test/positive_expected_result.json b/assets/queries/ansible/azure/ssl_enforce_is_disabled/test/positive_expected_result.json index a0427e72..d5a04c87 100644 --- a/assets/queries/ansible/azure/ssl_enforce_is_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/ssl_enforce_is_disabled/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "SSL Enforce Disabled", + "queryName": "SSL enforce disabled", "severity": "MEDIUM", "line": 2 }, { - "queryName": "SSL Enforce Disabled", + "queryName": "SSL enforce disabled", "severity": "MEDIUM", "line": 21 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/storage_account_not_forcing_https/metadata.json b/assets/queries/ansible/azure/storage_account_not_forcing_https/metadata.json index bf023726..2937a42e 100644 --- a/assets/queries/ansible/azure/storage_account_not_forcing_https/metadata.json +++ b/assets/queries/ansible/azure/storage_account_not_forcing_https/metadata.json @@ -1,9 +1,9 @@ { "id": "2c99a474-2a3c-4c17-8294-53ffa5ed0522", - "queryName": "Storage Account Not Forcing HTTPS", + "queryName": "Storage account not forcing HTTPS", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "Storage Accounts should enforce the use of HTTPS", + "descriptionText": "Storage Accounts must enforce HTTPS-only connections to prevent sensitive data from being transmitted in cleartext and reduce the risk of man-in-the-middle interception. For Ansible tasks using `azure.azcollection.azure_rm_storageaccount` or `azure_rm_storageaccount`, the `https_only` property must be set to `true`. Resources where `https_only` is missing (it defaults to `false`) or explicitly set to `false` are flagged. \n\nSecure example:\n\n```yaml\n- name: Create storage account with HTTPS enforced\n azure.azcollection.azure_rm_storageaccount:\n name: myStorageAccount\n resource_group: myResourceGroup\n location: eastus\n account_type: Standard_LRS\n https_only: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/storage_account_not_forcing_https", "platform": "Ansible", "descriptionID": "bc830876", diff --git a/assets/queries/ansible/azure/storage_account_not_forcing_https/test/positive_expected_result.json b/assets/queries/ansible/azure/storage_account_not_forcing_https/test/positive_expected_result.json index 571578e1..6e9baa47 100644 --- a/assets/queries/ansible/azure/storage_account_not_forcing_https/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/storage_account_not_forcing_https/test/positive_expected_result.json @@ -1,47 +1,47 @@ [ { - "queryName": "Storage Account Not Forcing HTTPS", + "queryName": "Storage account not forcing HTTPS", "severity": "MEDIUM", "line": 3 }, { - "queryName": "Storage Account Not Forcing HTTPS", + "queryName": "Storage account not forcing HTTPS", "severity": "MEDIUM", "line": 15 }, { - "queryName": "Storage Account Not Forcing HTTPS", + "queryName": "Storage account not forcing HTTPS", "severity": "MEDIUM", "line": 24 }, { - "queryName": "Storage Account Not Forcing HTTPS", + "queryName": "Storage account not forcing HTTPS", "severity": "MEDIUM", "line": 33 }, { - "queryName": "Storage Account Not Forcing HTTPS", + "queryName": "Storage account not forcing HTTPS", "severity": "MEDIUM", "line": 42 }, { - "queryName": "Storage Account Not Forcing HTTPS", + "queryName": "Storage account not forcing HTTPS", "severity": "MEDIUM", "line": 51 }, { - "queryName": "Storage Account Not Forcing HTTPS", + "queryName": "Storage account not forcing HTTPS", "severity": "MEDIUM", "line": 60 }, { - "queryName": "Storage Account Not Forcing HTTPS", + "queryName": "Storage account not forcing HTTPS", "severity": "MEDIUM", "line": 69 }, { - "queryName": "Storage Account Not Forcing HTTPS", + "queryName": "Storage account not forcing HTTPS", "severity": "MEDIUM", "line": 78 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/storage_account_not_using_latest_tls_encryption_version/metadata.json b/assets/queries/ansible/azure/storage_account_not_using_latest_tls_encryption_version/metadata.json index 48f635f0..918a4268 100644 --- a/assets/queries/ansible/azure/storage_account_not_using_latest_tls_encryption_version/metadata.json +++ b/assets/queries/ansible/azure/storage_account_not_using_latest_tls_encryption_version/metadata.json @@ -1,9 +1,9 @@ { "id": "c62746cf-92d5-4649-9acf-7d48d086f2ee", - "queryName": "Storage Account Not Using Latest TLS Encryption Version", + "queryName": "Storage account not using latest TLS encryption version", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "Ensure Storage Account is using the latest version of TLS encryption", + "descriptionText": "Storage accounts must enforce TLS 1.2 to protect data in transit and prevent downgrade attacks using older, vulnerable TLS versions. For Ansible, the `azure_rm_storageaccount` or `azure.azcollection.azure_rm_storageaccount` resource must include the `minimum_tls_version` property set to `\"TLS1_2\"`. Resources missing `minimum_tls_version` or configured with any value other than `\"TLS1_2\"` (for example `\"TLS1_0\"` or `\"TLS1_1\"`) are flagged.", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/storage_account_not_using_latest_tls_encryption_version", "platform": "Ansible", "descriptionID": "f050a4f3", diff --git a/assets/queries/ansible/azure/storage_account_not_using_latest_tls_encryption_version/test/positive_expected_result.json b/assets/queries/ansible/azure/storage_account_not_using_latest_tls_encryption_version/test/positive_expected_result.json index b46384e7..9ba81628 100644 --- a/assets/queries/ansible/azure/storage_account_not_using_latest_tls_encryption_version/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/storage_account_not_using_latest_tls_encryption_version/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ - { - "queryName": "Storage Account Not Using Latest TLS Encryption Version", - "severity": "MEDIUM", - "line": 8 - }, - { - "queryName": "Storage Account Not Using Latest TLS Encryption Version", - "severity": "MEDIUM", - "line": 12 - } + { + "queryName": "Storage account not using latest TLS encryption version", + "severity": "MEDIUM", + "line": 8 + }, + { + "queryName": "Storage account not using latest TLS encryption version", + "severity": "MEDIUM", + "line": 12 + } ] diff --git a/assets/queries/ansible/azure/storage_container_is_publicly_accessible/metadata.json b/assets/queries/ansible/azure/storage_container_is_publicly_accessible/metadata.json index 94066b49..cdfb4f49 100644 --- a/assets/queries/ansible/azure/storage_container_is_publicly_accessible/metadata.json +++ b/assets/queries/ansible/azure/storage_container_is_publicly_accessible/metadata.json @@ -1,9 +1,9 @@ { "id": "4d3817db-dd35-4de4-a80d-3867157e7f7f", - "queryName": "Storage Container Is Publicly Accessible", + "queryName": "Storage container is publicly accessible", "severity": "HIGH", "category": "Access Control", - "descriptionText": "Anonymous, public read access to a container and its blobs are enabled in Azure Blob Storage", + "descriptionText": "Allowing anonymous public read access to Azure Blob Storage containers or their blobs exposes stored data to anyone on the internet, increasing the risk of data exfiltration and compliance violations. In Ansible tasks using `azure.azcollection.azure_rm_storageblob` or `azure_rm_storageblob`, the `public_access` property must not be set to `\"blob\"` or `\"container\"`.\n\nThe rule flags tasks where `public_access` (case-insensitive) equals `blob` or `container`. Setting it to `blob` permits anonymous read of individual blobs, while `container` also allows listing container contents. To remediate, omit the `public_access` property or set it to `private`. Use SAS tokens, Azure RBAC, private endpoints, or signed URLs for controlled sharing.\n\nSecure example:\n\n```yaml\n- name: Create storage blob container (private)\n azure.azcollection.azure_rm_storageblob:\n resource_group: my-rg\n account_name: my-storage-account\n container: my-container\n public_access: private\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/storage_container_is_publicly_accessible", "platform": "Ansible", "descriptionID": "30144827", diff --git a/assets/queries/ansible/azure/storage_container_is_publicly_accessible/test/positive_expected_result.json b/assets/queries/ansible/azure/storage_container_is_publicly_accessible/test/positive_expected_result.json index b27a1167..024973fb 100644 --- a/assets/queries/ansible/azure/storage_container_is_publicly_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/storage_container_is_publicly_accessible/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "Storage Container Is Publicly Accessible", + "queryName": "Storage container is publicly accessible", "severity": "HIGH", "line": 9 }, { - "queryName": "Storage Container Is Publicly Accessible", + "queryName": "Storage container is publicly accessible", "severity": "HIGH", "line": 17 } diff --git a/assets/queries/ansible/azure/trusted_microsoft_services_not_enabled/metadata.json b/assets/queries/ansible/azure/trusted_microsoft_services_not_enabled/metadata.json index dc8739c5..6a10ccb8 100644 --- a/assets/queries/ansible/azure/trusted_microsoft_services_not_enabled/metadata.json +++ b/assets/queries/ansible/azure/trusted_microsoft_services_not_enabled/metadata.json @@ -1,9 +1,9 @@ { "id": "1bc398a8-d274-47de-a4c8-6ac867b353de", - "queryName": "Trusted Microsoft Services Not Enabled", + "queryName": "Trusted Microsoft services not enabled", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Trusted Microsoft Services should be enabled for Storage Account access", + "descriptionText": "When a Storage Account's network access is restricted (`network_acls.default_action` set to `Deny`), Trusted Microsoft Services must be allowed to bypass the network rules. This ensures platform features such as Azure Backup, diagnostics/monitoring, and replication can access the account. Without this bypass, backups, telemetry, and other managed operations can fail, impacting data protection and operational visibility.\n\nIn Ansible `azure_rm_storageaccount` or `azure.azcollection.azure_rm_storageaccount` resources, ensure the `network_acls.bypass` property includes the value `AzureServices` (it may be a comma-separated list, for example, `AzureServices,Logging`) whenever `network_acls.default_action` is `Deny`. Resources that omit `network_acls.bypass` or whose `bypass` value does not contain `AzureServices` are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create storage account with AzureServices bypass\n azure_rm_storageaccount:\n resource_group: my-rg\n name: mystorageacct\n location: eastus\n account_type: Standard_LRS\n network_acls:\n default_action: Deny\n bypass: AzureServices\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/trusted_microsoft_services_not_enabled", "platform": "Ansible", "descriptionID": "e86db9c1", diff --git a/assets/queries/ansible/azure/trusted_microsoft_services_not_enabled/test/positive_expected_result.json b/assets/queries/ansible/azure/trusted_microsoft_services_not_enabled/test/positive_expected_result.json index 3d9d3360..b4fd1ce0 100644 --- a/assets/queries/ansible/azure/trusted_microsoft_services_not_enabled/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/trusted_microsoft_services_not_enabled/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "Trusted Microsoft Services Not Enabled", + "queryName": "Trusted Microsoft services not enabled", "severity": "MEDIUM", "line": 7 }, { - "queryName": "Trusted Microsoft Services Not Enabled", + "queryName": "Trusted Microsoft services not enabled", "severity": "MEDIUM", "line": 24 }, { - "queryName": "Trusted Microsoft Services Not Enabled", + "queryName": "Trusted Microsoft services not enabled", "severity": "MEDIUM", "line": 40 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/unrestricted_sql_server_acess/metadata.json b/assets/queries/ansible/azure/unrestricted_sql_server_acess/metadata.json index 59f14c7d..e475998c 100644 --- a/assets/queries/ansible/azure/unrestricted_sql_server_acess/metadata.json +++ b/assets/queries/ansible/azure/unrestricted_sql_server_acess/metadata.json @@ -1,9 +1,9 @@ { "id": "3f23c96c-f9f5-488d-9b17-605b8da5842f", - "queryName": "Unrestricted SQL Server Access", + "queryName": "Unrestricted SQL Server access", "severity": "CRITICAL", "category": "Networking and Firewall", - "descriptionText": "Azure SQL Server Accessibility should be set to a minimal address range to grant the principle of least privileges, which means the difference between the values of the 'end_ip_address' and 'start_ip_address' should be less than 256. Additionally, both ips should be different from '0.0.0.0'", + "descriptionText": "Allowing large IP ranges in Azure SQL firewall rules broadens the database attack surface and increases the risk of unauthorized access, brute-force attempts, and data exposure. Firewall rules should grant the minimal address range required.\n\nFor Ansible tasks using `azure_rm_sqlfirewallrule` or `azure.azcollection.azure_rm_sqlfirewallrule`, ensure the `start_ip_address` and `end_ip_address` properties are defined and that the numeric difference between them is less than 256 (that is, a single IP or up to 255 addresses). Tasks that omit these properties, set either address to `0.0.0.0`, or specify a range with difference >= 256 are flagged as insecure.\n\nSecure configuration example:\n\n```yaml\n- name: Allow single client IP to Azure SQL firewall\n azure.azcollection.azure_rm_sqlfirewallrule:\n resource_group: my-rg\n server_name: my-sql-server\n name: allow-client\n start_ip_address: 203.0.113.45\n end_ip_address: 203.0.113.45\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/unrestricted_sql_server_acess", "platform": "Ansible", "descriptionID": "03235d5d", diff --git a/assets/queries/ansible/azure/unrestricted_sql_server_acess/test/positive_expected_result.json b/assets/queries/ansible/azure/unrestricted_sql_server_acess/test/positive_expected_result.json index 44cefe27..6cfd606f 100644 --- a/assets/queries/ansible/azure/unrestricted_sql_server_acess/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/unrestricted_sql_server_acess/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Unrestricted SQL Server Access", + "queryName": "Unrestricted SQL Server access", "severity": "CRITICAL", "line": 3 }, { - "queryName": "Unrestricted SQL Server Access", + "queryName": "Unrestricted SQL Server access", "severity": "CRITICAL", "line": 10 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/vm_not_attached_to_network/metadata.json b/assets/queries/ansible/azure/vm_not_attached_to_network/metadata.json index bcba7163..c6ee8593 100644 --- a/assets/queries/ansible/azure/vm_not_attached_to_network/metadata.json +++ b/assets/queries/ansible/azure/vm_not_attached_to_network/metadata.json @@ -1,9 +1,9 @@ { "id": "1e5f5307-3e01-438d-8da6-985307ed25ce", - "queryName": "VM Not Attached To Network", + "queryName": "VM not attached to network", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "No Network Security Group is attached to the Virtual Machine", + "descriptionText": "Virtual machines should reference explicit network interfaces so network security controls (for example, Network Security Groups) can be applied and network exposure is predictable. Without explicit NIC configuration, instances may be created without NSGs or with default networking that exposes them to unintended access.\n\nFor Ansible VM tasks using `azure.azcollection.azure_rm_virtualmachine` or `azure_rm_virtualmachine`, either the `network_interface_names` property (a list of existing NIC names) or the `network_interfaces` property (a list of interface definitions) must be defined. Tasks missing both `network_interface_names` and `network_interfaces` are flagged. This rule verifies the presence of NIC references only and does not validate whether the referenced NICs themselves have NSGs attached.\n\nSecure configuration examples:\n\n```yaml\n- name: Create VM with NIC name\n azure.azcollection.azure_rm_virtualmachine:\n name: myVM\n resource_group: myRG\n network_interface_names:\n - myNic\n\n- name: Create VM with inline NIC definition\n azure.azcollection.azure_rm_virtualmachine:\n name: myVM2\n resource_group: myRG\n network_interfaces:\n - name: myNic2\n primary: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/vm_not_attached_to_network", "platform": "Ansible", "descriptionID": "33a5e5ec", diff --git a/assets/queries/ansible/azure/vm_not_attached_to_network/test/positive_expected_result.json b/assets/queries/ansible/azure/vm_not_attached_to_network/test/positive_expected_result.json index 0c333d3f..31edbba9 100644 --- a/assets/queries/ansible/azure/vm_not_attached_to_network/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/vm_not_attached_to_network/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "VM Not Attached To Network", + "queryName": "VM not attached to network", "severity": "MEDIUM", "line": 3 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/azure/waf_is_disabled_for_azure_application_gateway/metadata.json b/assets/queries/ansible/azure/waf_is_disabled_for_azure_application_gateway/metadata.json index bfdc6695..36115a71 100644 --- a/assets/queries/ansible/azure/waf_is_disabled_for_azure_application_gateway/metadata.json +++ b/assets/queries/ansible/azure/waf_is_disabled_for_azure_application_gateway/metadata.json @@ -1,9 +1,9 @@ { "id": "2fc5ab5a-c5eb-4ae4-b687-0f16fe77c255", - "queryName": "WAF Is Disabled For Azure Application Gateway", + "queryName": "WAF is disabled for Azure Application Gateway", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Check if Web Application Firewall is disabled or not configured for Azure's Application Gateway.", + "descriptionText": "Application Gateway instances must have the Web Application Firewall (WAF) SKU enabled to protect web traffic from application-layer threats like SQL injection, cross-site scripting, and automated attacks.\n\nFor Ansible resources using `azure.azcollection.azure_rm_appgateway` or `azure_rm_appgateway`, the `sku.tier` property must be set to `WAF` or `WAF_v2` (case-insensitive) to enable WAF capabilities. Resources missing `sku.tier` or configured with non-WAF tiers (for example `Standard` or `Standard_v2`) are flagged as insecure.\n\nSecure configuration example:\n\n```yaml\n- name: Create Application Gateway with WAF_v2\n azure.azcollection.azure_rm_appgateway:\n resource_group: myResourceGroup\n name: myAppGateway\n sku:\n tier: WAF_v2\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/waf_is_disabled_for_azure_application_gateway", "platform": "Ansible", "descriptionID": "eda7b816", diff --git a/assets/queries/ansible/azure/waf_is_disabled_for_azure_application_gateway/test/positive_expected_result.json b/assets/queries/ansible/azure/waf_is_disabled_for_azure_application_gateway/test/positive_expected_result.json index 22b5f2f5..4eaa6f09 100644 --- a/assets/queries/ansible/azure/waf_is_disabled_for_azure_application_gateway/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/waf_is_disabled_for_azure_application_gateway/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "WAF Is Disabled For Azure Application Gateway", + "queryName": "WAF is disabled for Azure Application Gateway", "severity": "MEDIUM", "line": 7 } diff --git a/assets/queries/ansible/azure/web_app_accepting_traffic_other_than_https/metadata.json b/assets/queries/ansible/azure/web_app_accepting_traffic_other_than_https/metadata.json index 25449d75..c17335c6 100644 --- a/assets/queries/ansible/azure/web_app_accepting_traffic_other_than_https/metadata.json +++ b/assets/queries/ansible/azure/web_app_accepting_traffic_other_than_https/metadata.json @@ -1,9 +1,9 @@ { "id": "eb8c2560-8bee-4248-9d0d-e80c8641dd91", - "queryName": "Web App Accepting Traffic Other Than HTTPS", + "queryName": "Web app accepting traffic other than HTTPS", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Web app should only accept HTTPS traffic in Azure Web App Service.", + "descriptionText": "Azure Web Apps must accept only HTTPS traffic to protect data in transit from interception, tampering, and credential or session-token exposure. For Ansible deployments using the `azure_rm_webapp` or `azure.azcollection.azure_rm_webapp` module, the `https_only` property must be defined and set to `true` (or `yes`). Tasks that omit `https_only` or set it to a `false` value are flagged. \n\nSecure configuration example:\n\n```yaml\n- name: Create web app with HTTPS only\n azure.azcollection.azure_rm_webapp:\n name: my-webapp\n resource_group: my-rg\n plan: my-plan\n https_only: yes\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/azure/web_app_accepting_traffic_other_than_https", "platform": "Ansible", "descriptionID": "c518b2f2", diff --git a/assets/queries/ansible/azure/web_app_accepting_traffic_other_than_https/test/positive_expected_result.json b/assets/queries/ansible/azure/web_app_accepting_traffic_other_than_https/test/positive_expected_result.json index fe2ba763..4deff903 100644 --- a/assets/queries/ansible/azure/web_app_accepting_traffic_other_than_https/test/positive_expected_result.json +++ b/assets/queries/ansible/azure/web_app_accepting_traffic_other_than_https/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Web App Accepting Traffic Other Than HTTPS", + "queryName": "Web app accepting traffic other than HTTPS", "severity": "MEDIUM", "line": 5 }, { - "queryName": "Web App Accepting Traffic Other Than HTTPS", + "queryName": "Web app accepting traffic other than HTTPS", "severity": "MEDIUM", "line": 12 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/config/allow_unsafe_lookups_enabled_in_defaults/metadata.json b/assets/queries/ansible/config/allow_unsafe_lookups_enabled_in_defaults/metadata.json index 1a2f7416..fdea132f 100644 --- a/assets/queries/ansible/config/allow_unsafe_lookups_enabled_in_defaults/metadata.json +++ b/assets/queries/ansible/config/allow_unsafe_lookups_enabled_in_defaults/metadata.json @@ -1,13 +1,13 @@ { "id": "86b97bb4-85c9-462d-8635-cbc057c5c8c5", - "queryName": "Allow Unsafe Lookups Enabled In Defaults", + "queryName": "Allow unsafe lookups enabled in defaults", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "When enabled, this option allows lookup plugins to return data that is not marked 'unsafe'.", + "descriptionText": "The Ansible `allow_unsafe_lookups` option must be disabled. When enabled, lookup plugins can return values that bypass safety markers, which can expose sensitive data or cause playbooks to process untrusted input. Check the `defaults.allow_unsafe_lookups` property in your Ansible configuration and ensure it is defined and set to `False`. Configurations with this property set to `True` are flagged. Set this option in your `ansible.cfg` under the `[defaults]` section as shown in the following example:\n\n```ini\n[defaults]\nallow_unsafe_lookups = False\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/config/allow_unsafe_lookups_enabled_in_defaults", "platform": "Ansible", "descriptionID": "0d491461", "cloudProvider": "common", "cwe": "", "providerUrl": "https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-allow-unsafe-lookups" -} \ No newline at end of file +} diff --git a/assets/queries/ansible/config/allow_unsafe_lookups_enabled_in_defaults/test/positive_expected_result.json b/assets/queries/ansible/config/allow_unsafe_lookups_enabled_in_defaults/test/positive_expected_result.json index ab2ceef5..1a31cbd8 100644 --- a/assets/queries/ansible/config/allow_unsafe_lookups_enabled_in_defaults/test/positive_expected_result.json +++ b/assets/queries/ansible/config/allow_unsafe_lookups_enabled_in_defaults/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ - { - "queryName": "Allow Unsafe Lookups Enabled In Defaults", - "severity": "HIGH", - "line": 19 - } + { + "queryName": "Allow unsafe lookups enabled in defaults", + "severity": "HIGH", + "line": 19 + } ] diff --git a/assets/queries/ansible/config/communication_over_http_in_defaults/metadata.json b/assets/queries/ansible/config/communication_over_http_in_defaults/metadata.json index 34b3766d..59a5ae6c 100644 --- a/assets/queries/ansible/config/communication_over_http_in_defaults/metadata.json +++ b/assets/queries/ansible/config/communication_over_http_in_defaults/metadata.json @@ -1,13 +1,13 @@ { "id": "d7dc9350-74bc-485b-8c85-fed22d276c43", - "queryName": "Communication Over HTTP In Defaults", + "queryName": "Communication over HTTP in defaults", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Using HTTP URLs (without encryption) could lead to security vulnerabilities and risks", + "descriptionText": "Galaxy `server` URLs must use HTTPS to protect the confidentiality and integrity of downloaded roles and any credentials exchanged. Using plain HTTP exposes downloads and authentication data to interception or tampering.\n\nIn Ansible configuration documents, this is the `groups.galaxy.server` property, which must begin with `https://` instead of `http://`. Resources with a missing `server` property or a value that starts with `http://` are flagged. Ensure the HTTPS endpoint presents a valid TLS certificate and do not disable certificate verification.\n\nSecure configuration example:\n\n```yaml\ngroups:\n galaxy:\n server: \"https://galaxy.example.com\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/config/communication_over_http_in_defaults", "platform": "Ansible", "descriptionID": "8c03b0c0", "cloudProvider": "common", "cwe": "", "providerUrl": "https://docs.ansible.com/ansible/latest/plugins/httpapi.html" -} \ No newline at end of file +} diff --git a/assets/queries/ansible/config/communication_over_http_in_defaults/test/positive_expected_result.json b/assets/queries/ansible/config/communication_over_http_in_defaults/test/positive_expected_result.json index af3bb39b..e052d754 100644 --- a/assets/queries/ansible/config/communication_over_http_in_defaults/test/positive_expected_result.json +++ b/assets/queries/ansible/config/communication_over_http_in_defaults/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ - { - "queryName": "Communication Over HTTP In Defaults", - "severity": "MEDIUM", - "line": 5 - } + { + "queryName": "Communication over HTTP in defaults", + "severity": "MEDIUM", + "line": 5 + } ] diff --git a/assets/queries/ansible/config/logging_of_sensitive_data_in_defaults/metadata.json b/assets/queries/ansible/config/logging_of_sensitive_data_in_defaults/metadata.json index 786d0612..8b5e3552 100644 --- a/assets/queries/ansible/config/logging_of_sensitive_data_in_defaults/metadata.json +++ b/assets/queries/ansible/config/logging_of_sensitive_data_in_defaults/metadata.json @@ -1,13 +1,13 @@ { "id": "c6473dae-8477-4119-88b7-b909b435ce7b", - "queryName": "Logging of Sensitive Data In Defaults", + "queryName": "Logging of sensitive data in defaults", "severity": "LOW", "category": "Best Practices", - "descriptionText": "To keep sensitive values out of logs, tasks that expose them need to be marked defining 'no_log' and setting to True", + "descriptionText": "The Ansible `no_log` setting must be enabled to prevent sensitive data such as passwords, tokens, or PII from being written to logs. Exposed log data can be accessed by unauthorized users or retained in build artifacts. This rule applies to resources of type `ansible_config` in the `defaults` group. The `no_log` property must be defined and set to boolean `true`. Resources missing the `no_log` property or with `no_log` set to `false` are flagged as insecure. \n\nSecure configuration example for ansible.cfg:\n\n```ini\n[defaults]\nno_log = True\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/config/logging_of_sensitive_data_in_defaults", "platform": "Ansible", "descriptionID": "0eca35f3", "cloudProvider": "common", "cwe": "", "providerUrl": "https://docs.ansible.com/ansible/latest/reference_appendices/logging.html#protecting-sensitive-data-with-no-log" -} \ No newline at end of file +} diff --git a/assets/queries/ansible/config/logging_of_sensitive_data_in_defaults/test/positive_expected_result.json b/assets/queries/ansible/config/logging_of_sensitive_data_in_defaults/test/positive_expected_result.json index 42361286..a3f6871b 100644 --- a/assets/queries/ansible/config/logging_of_sensitive_data_in_defaults/test/positive_expected_result.json +++ b/assets/queries/ansible/config/logging_of_sensitive_data_in_defaults/test/positive_expected_result.json @@ -1,14 +1,14 @@ [ - { - "queryName": "Logging of Sensitive Data In Defaults", - "severity": "LOW", - "filename": "positive1.cfg", - "line": 1 - }, - { - "queryName": "Logging of Sensitive Data In Defaults", - "severity": "LOW", - "filename": "positive2.cfg", - "line": 39 - } + { + "queryName": "Logging of sensitive data in defaults", + "severity": "LOW", + "filename": "positive1.cfg", + "line": 1 + }, + { + "queryName": "Logging of sensitive data in defaults", + "severity": "LOW", + "filename": "positive2.cfg", + "line": 39 + } ] diff --git a/assets/queries/ansible/config/privilege_escalation_using_become_plugin_in_defaults/metadata.json b/assets/queries/ansible/config/privilege_escalation_using_become_plugin_in_defaults/metadata.json index d001bd76..de2ca642 100644 --- a/assets/queries/ansible/config/privilege_escalation_using_become_plugin_in_defaults/metadata.json +++ b/assets/queries/ansible/config/privilege_escalation_using_become_plugin_in_defaults/metadata.json @@ -1,13 +1,13 @@ { "id": "404908b6-4954-4611-98f0-e8ceacdabcb1", - "queryName": "Privilege Escalation Using Become Plugin In Defaults", + "queryName": "Privilege escalation using become plugin in defaults", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "In order to perform an action as a different user with the become_user, 'become' must be defined and set to 'true'", + "descriptionText": "Specifying a `become_user` without enabling privilege escalation prevents Ansible from elevating privileges. Tasks intended to run as that user will execute as the invoking user instead, which can cause configuration changes to be applied with incorrect permissions or fail entirely, leading to insecure or inconsistent system state. In the Ansible defaults group, when `defaults.become_user` is defined, the `defaults.become` property must be present and set to `true`. This rule flags defaults entries where `become_user` exists but `become` is missing or set to `false`.\n\nSecure configuration example:\n\n```yaml\ndefaults:\n become: true\n become_user: root\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/config/privilege_escalation_using_become_plugin_in_defaults", "platform": "Ansible", "descriptionID": "00396668", "cloudProvider": "common", "cwe": "", "providerUrl": "https://docs.ansible.com/ansible/latest/plugins/become.html" -} \ No newline at end of file +} diff --git a/assets/queries/ansible/config/privilege_escalation_using_become_plugin_in_defaults/test/positive_expected_result.json b/assets/queries/ansible/config/privilege_escalation_using_become_plugin_in_defaults/test/positive_expected_result.json index edcbda36..825b1e54 100644 --- a/assets/queries/ansible/config/privilege_escalation_using_become_plugin_in_defaults/test/positive_expected_result.json +++ b/assets/queries/ansible/config/privilege_escalation_using_become_plugin_in_defaults/test/positive_expected_result.json @@ -1,14 +1,14 @@ [ - { - "queryName": "Privilege Escalation Using Become Plugin In Defaults", - "severity": "MEDIUM", - "filename": "positive1.cfg", - "line": 10 - }, - { - "queryName": "Privilege Escalation Using Become Plugin In Defaults", - "severity": "MEDIUM", - "filename": "positive2.cfg", - "line": 12 - } + { + "queryName": "Privilege escalation using become plugin in defaults", + "severity": "MEDIUM", + "filename": "positive1.cfg", + "line": 10 + }, + { + "queryName": "Privilege escalation using become plugin in defaults", + "severity": "MEDIUM", + "filename": "positive2.cfg", + "line": 12 + } ] diff --git a/assets/queries/ansible/gcp/bigquery_dataset_is_public/metadata.json b/assets/queries/ansible/gcp/bigquery_dataset_is_public/metadata.json index eac0ae90..49514718 100644 --- a/assets/queries/ansible/gcp/bigquery_dataset_is_public/metadata.json +++ b/assets/queries/ansible/gcp/bigquery_dataset_is_public/metadata.json @@ -1,9 +1,9 @@ { "id": "2263b286-2fe9-4747-a0ae-8b4768a2bbd2", - "queryName": "BigQuery Dataset Is Public", + "queryName": "BigQuery dataset is public", "severity": "HIGH", "category": "Access Control", - "descriptionText": "BigQuery dataset is anonymously or publicly accessible", + "descriptionText": "BigQuery datasets must not grant access to the special group `allAuthenticatedUsers`. This allows any Google account to access the dataset, increasing the risk of sensitive data exposure and regulatory non-compliance.\n\nFor Ansible tasks using the `google.cloud.gcp_bigquery_dataset` (or `gcp_bigquery_dataset`) module, validate the `access` entries and ensure no entry has `special_group` set to `\"allAuthenticatedUsers\"` (checked case-insensitively). Resources with `access` entries where `special_group` equals `allAuthenticatedUsers` are flagged. Restrict dataset access to specific users, groups, domains, or predefined roles instead.\n\nSecure Ansible task example (do not include `special_group: allAuthenticatedUsers`):\n\n```yaml\n- name: Create BigQuery dataset with restricted access\n google.cloud.gcp_bigquery_dataset:\n dataset_id: my_dataset\n access:\n - role: READER\n userByEmail: alice@example.com\n - role: OWNER\n groupByEmail: admins@example.com\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/bigquery_dataset_is_public", "platform": "Ansible", "descriptionID": "72ba0b75", diff --git a/assets/queries/ansible/gcp/bigquery_dataset_is_public/test/positive_expected_result.json b/assets/queries/ansible/gcp/bigquery_dataset_is_public/test/positive_expected_result.json index 6896c822..a26e0b1f 100644 --- a/assets/queries/ansible/gcp/bigquery_dataset_is_public/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/bigquery_dataset_is_public/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ - { - "queryName": "BigQuery Dataset Is Public", - "severity": "HIGH", - "line": 6 - } + { + "queryName": "BigQuery dataset is public", + "severity": "HIGH", + "line": 6 + } ] diff --git a/assets/queries/ansible/gcp/client_certificate_disabled/metadata.json b/assets/queries/ansible/gcp/client_certificate_disabled/metadata.json index 97901041..b3494357 100644 --- a/assets/queries/ansible/gcp/client_certificate_disabled/metadata.json +++ b/assets/queries/ansible/gcp/client_certificate_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "20180133-a0d0-4745-bfe0-94049fbb12a9", - "queryName": "Client Certificate Disabled", + "queryName": "Client certificate disabled", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "Kubernetes Clusters must be created with Client Certificate enabled, which means 'master_auth' must have 'client_certificate_config' with the attribute 'issue_client_certificate' equal to true", + "descriptionText": "Client certificate authentication for the Kubernetes control plane ensures administrators authenticate with strong cryptographic credentials, reducing reliance on weaker or shared credentials that can lead to unauthorized control-plane access.\n\nFor Ansible GCP Container Cluster resources (`google.cloud.gcp_container_cluster` and `gcp_container_cluster`), the `master_auth` object must include `client_certificate_config` with `issue_client_certificate: true`. Resources that omit `master_auth`, omit `client_certificate_config`, or set `issue_client_certificate` to `false` are flagged. \n\nSecure configuration example for an Ansible task:\n\n```yaml\n- name: Create GKE cluster with client certificate enabled\n google.cloud.gcp_container_cluster:\n name: my-cluster\n master_auth:\n client_certificate_config:\n issue_client_certificate: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/client_certificate_disabled", "platform": "Ansible", "descriptionID": "53a8ab26", diff --git a/assets/queries/ansible/gcp/client_certificate_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/client_certificate_disabled/test/positive_expected_result.json index 36503065..1494b4e2 100644 --- a/assets/queries/ansible/gcp/client_certificate_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/client_certificate_disabled/test/positive_expected_result.json @@ -1,16 +1,16 @@ [ { - "queryName": "Client Certificate Disabled", + "queryName": "Client certificate disabled", "severity": "HIGH", "line": 3 }, { - "queryName": "Client Certificate Disabled", + "queryName": "Client certificate disabled", "severity": "HIGH", "line": 18 }, { - "queryName": "Client Certificate Disabled", + "queryName": "Client certificate disabled", "severity": "HIGH", "line": 37 } diff --git a/assets/queries/ansible/gcp/cloud_dns_without_dnnsec/metadata.json b/assets/queries/ansible/gcp/cloud_dns_without_dnnsec/metadata.json index 25e8a485..4ef39777 100644 --- a/assets/queries/ansible/gcp/cloud_dns_without_dnnsec/metadata.json +++ b/assets/queries/ansible/gcp/cloud_dns_without_dnnsec/metadata.json @@ -1,9 +1,9 @@ { "id": "80b15fb1-6207-40f4-a803-6915ae619a03", - "queryName": "Cloud DNS Without DNSSEC", + "queryName": "Cloud DNS without DNSSEC", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "DNSSEC must be enabled for Cloud DNS", + "descriptionText": "DNS zones must have DNSSEC enabled to protect DNS responses from tampering, spoofing, and cache poisoning and to ensure the authenticity and integrity of name resolution.\n\nFor Ansible-managed Google Cloud DNS zones using `google.cloud.gcp_dns_managed_zone` or `gcp_dns_managed_zone`, the `dnssec_config.state` property must be defined and set to `\"on\"`. Resources missing `dnssec_config`, missing `dnssec_config.state`, or with `dnssec_config.state` not equal to `\"on\"` are flagged. \n\nSecure configuration example:\n\n```yaml\n- name: Create DNS managed zone with DNSSEC enabled\n google.cloud.gcp_dns_managed_zone:\n name: my-managed-zone\n dns_name: example.com.\n dnssec_config:\n state: \"on\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/cloud_dns_without_dnnsec", "platform": "Ansible", "descriptionID": "1797efc5", diff --git a/assets/queries/ansible/gcp/cloud_dns_without_dnnsec/test/positive_expected_result.json b/assets/queries/ansible/gcp/cloud_dns_without_dnnsec/test/positive_expected_result.json index 9379e54d..ac2f9950 100644 --- a/assets/queries/ansible/gcp/cloud_dns_without_dnnsec/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/cloud_dns_without_dnnsec/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ - { - "queryName": "Cloud DNS Without DNSSEC", - "severity": "MEDIUM", - "line": 3 - }, - { - "queryName": "Cloud DNS Without DNSSEC", - "severity": "MEDIUM", - "line": 20 - }, - { - "queryName": "Cloud DNS Without DNSSEC", - "severity": "MEDIUM", - "line": 33 - } + { + "queryName": "Cloud DNS without DNSSEC", + "severity": "MEDIUM", + "line": 3 + }, + { + "queryName": "Cloud DNS without DNSSEC", + "severity": "MEDIUM", + "line": 20 + }, + { + "queryName": "Cloud DNS without DNSSEC", + "severity": "MEDIUM", + "line": 33 + } ] diff --git a/assets/queries/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on/metadata.json b/assets/queries/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on/metadata.json index 8e90bf6f..47fa60c3 100644 --- a/assets/queries/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on/metadata.json +++ b/assets/queries/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on/metadata.json @@ -1,9 +1,9 @@ { "id": "6d34aff3-fdd2-460c-8190-756a3b4969e8", - "queryName": "Cloud SQL Instance With Contained Database Authentication On", + "queryName": "Cloud SQL instance with contained database authentication on", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "SQL Instance should not have Contained Database Authentication On", + "descriptionText": "Cloud SQL for SQL Server instances must have Contained Database Authentication disabled. Contained database users authenticate at the database level, bypassing server-level authentication and centralized IAM controls. This increases the risk of unauthorized access and unmanaged credentials.\n\nFor Ansible `google.cloud.gcp_sql_instance` or `gcp_sql_instance` resources, ensure `settings.database_flags` includes an entry with `name: \"contained database authentication\"` and `value: \"off\"`. Resources that omit this flag or set it to any value other than `\"off\"` are flagged. The check evaluates the `settings.database_flags` entries.\n\nSecure configuration example:\n\n- name: Create Cloud SQL SQL Server instance\n google.cloud.gcp_sql_instance:\n name: my-sqlserver-instance\n database_version: SQLSERVER_2019_STANDARD\n settings:\n database_flags:\n - name: contained database authentication\n value: \"off\"", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on", "platform": "Ansible", "descriptionID": "b1a5b8f0", diff --git a/assets/queries/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on/test/positive_expected_result.json b/assets/queries/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on/test/positive_expected_result.json index 0d9b8e90..f758c008 100644 --- a/assets/queries/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Cloud SQL Instance With Contained Database Authentication On", + "queryName": "Cloud SQL instance with contained database authentication on", "severity": "HIGH", "line": 10 } diff --git a/assets/queries/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on/metadata.json b/assets/queries/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on/metadata.json index db1c1670..96ff91f3 100644 --- a/assets/queries/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on/metadata.json +++ b/assets/queries/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on/metadata.json @@ -1,9 +1,9 @@ { "id": "9e0c33ed-97f3-4ed6-8be9-bcbf3f65439f", - "queryName": "Cloud SQL Instance With Cross DB Ownership Chaining On", + "queryName": "Cloud SQL instance with cross DB ownership chaining on", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "GCP SQL Instance should not have Cross DB Ownership Chaining On", + "descriptionText": "SQL Server instances must have Cross DB Ownership Chaining disabled to prevent cross-database privilege escalation and lateral access between databases.\n\nFor Ansible-managed Google Cloud SQL resources (`google.cloud.gcp_sql_instance` or `gcp_sql_instance`), ensure the `settings.database_flags` entry with name `cross db ownership chaining` is present and its `value` is set to `off`. This check applies only when `database_version` indicates SQL Server. Instances missing the flag or with a value other than `off` are flagged.\n\nSecure Ansible configuration example:\n\n```yaml\n- name: Create secure Cloud SQL SQLServer instance\n google.cloud.gcp_sql_instance:\n name: my-sqlserver-instance\n database_version: SQLSERVER_2019_STANDARD\n settings:\n tier: db-custom-1-3840\n database_flags:\n - name: \"cross db ownership chaining\"\n value: \"off\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on", "platform": "Ansible", "descriptionID": "2a2f1164", diff --git a/assets/queries/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on/test/positive_expected_result.json b/assets/queries/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on/test/positive_expected_result.json index 195a8587..4fffc9b3 100644 --- a/assets/queries/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Cloud SQL Instance With Cross DB Ownership Chaining On", + "queryName": "Cloud SQL instance with cross DB ownership chaining on", "severity": "HIGH", "line": 10 } diff --git a/assets/queries/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible/metadata.json b/assets/queries/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible/metadata.json index 4f56cc8d..ccdefec1 100644 --- a/assets/queries/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible/metadata.json +++ b/assets/queries/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible/metadata.json @@ -1,9 +1,9 @@ { "id": "086031e1-9d4a-4249-acb3-5bfe4c363db2", - "queryName": "Cloud Storage Anonymous or Publicly Accessible", + "queryName": "Cloud storage anonymous or publicly accessible", "severity": "CRITICAL", "category": "Access Control", - "descriptionText": "Cloud Storage Buckets must not be anonymously or publicly accessible, which means the attribute 'entity' must not be 'allUsers' or 'allAuthenticatedUsers'", + "descriptionText": "Cloud Storage buckets must not be anonymously or publicly accessible. Setting an ACL entity to `allUsers` or `allAuthenticatedUsers` grants broad read or write access to anyone on the internet or to any authenticated Google account, risking data exposure or unauthorized modification.\n\nFor Ansible `gcp_storage_bucket` resources (modules `google.cloud.gcp_storage_bucket` and `gcp_storage_bucket`), ensure neither the `acl.entity` nor the `default_object_acl.entity` property is set to `allUsers` or `allAuthenticatedUsers`. If a bucket does not define `acl`, `default_object_acl` must be explicitly defined and must not contain those public entities. Tasks missing `default_object_acl` or with either entity set to `allUsers`/`allAuthenticatedUsers` are flagged.", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible", "platform": "Ansible", "descriptionID": "bdebc5b5", diff --git a/assets/queries/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible/test/positive_expected_result.json b/assets/queries/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible/test/positive_expected_result.json index e0ee2d2a..f667222d 100644 --- a/assets/queries/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "Cloud Storage Anonymous or Publicly Accessible", + "queryName": "Cloud storage anonymous or publicly accessible", "severity": "CRITICAL", "line": 11 }, { - "queryName": "Cloud Storage Anonymous or Publicly Accessible", + "queryName": "Cloud storage anonymous or publicly accessible", "severity": "CRITICAL", "line": 22 }, { - "queryName": "Cloud Storage Anonymous or Publicly Accessible", + "queryName": "Cloud storage anonymous or publicly accessible", "severity": "CRITICAL", "line": 28 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/cloud_storage_bucket_logging_not_enabled/metadata.json b/assets/queries/ansible/gcp/cloud_storage_bucket_logging_not_enabled/metadata.json index 5701a3d8..e8671517 100644 --- a/assets/queries/ansible/gcp/cloud_storage_bucket_logging_not_enabled/metadata.json +++ b/assets/queries/ansible/gcp/cloud_storage_bucket_logging_not_enabled/metadata.json @@ -1,9 +1,9 @@ { "id": "507df964-ad97-4035-ab14-94a82eabdfdd", - "queryName": "Cloud Storage Bucket Logging Not Enabled", + "queryName": "Cloud storage bucket logging not enabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Cloud storage bucket should have logging enabled", + "descriptionText": "Cloud Storage buckets must have access logging enabled to provide audit trails for object access and modifications. This is critical for detecting and investigating unauthorized access, data exfiltration, and operational incidents.\n\nFor Ansible tasks using the `google.cloud.gcp_storage_bucket` or `gcp_storage_bucket` modules, the `logging` property must be defined. It should specify a `logBucket` (the destination bucket for logs) and may include `logObjectPrefix` to organize log objects.\n\nResources missing the `logging` property are flagged. Ensure the designated log bucket exists and has the necessary IAM permissions so logs can be written and retained according to your retention and compliance requirements.\n\nSecure example (Ansible task):\n\n```yaml\n- name: Create GCS bucket with access logging enabled\n google.cloud.gcp_storage_bucket:\n name: my-data-bucket\n logging:\n logBucket: my-logs-bucket\n logObjectPrefix: access-logs/\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/cloud_storage_bucket_logging_not_enabled", "platform": "Ansible", "descriptionID": "ba5af65f", diff --git a/assets/queries/ansible/gcp/cloud_storage_bucket_logging_not_enabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/cloud_storage_bucket_logging_not_enabled/test/positive_expected_result.json index 1d61cc30..c23e886b 100644 --- a/assets/queries/ansible/gcp/cloud_storage_bucket_logging_not_enabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/cloud_storage_bucket_logging_not_enabled/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "Cloud Storage Bucket Logging Not Enabled", + "queryName": "Cloud storage bucket logging not enabled", "severity": "MEDIUM", "line": 3 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/cloud_storage_bucket_versioning_disabled/metadata.json b/assets/queries/ansible/gcp/cloud_storage_bucket_versioning_disabled/metadata.json index 9099362f..be47c2ad 100644 --- a/assets/queries/ansible/gcp/cloud_storage_bucket_versioning_disabled/metadata.json +++ b/assets/queries/ansible/gcp/cloud_storage_bucket_versioning_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "7814ddda-e758-4a56-8be3-289a81ded929", - "queryName": "Cloud Storage Bucket Versioning Disabled", + "queryName": "Cloud storage bucket versioning disabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Cloud Storage Bucket should have versioning enabled", + "descriptionText": "Cloud Storage buckets should have object versioning enabled to protect against accidental or malicious deletion and allow recovery of prior object states. In Ansible, tasks using the `google.cloud.gcp_storage_bucket` or `gcp_storage_bucket` modules must define the `versioning` parameter and set `versioning.enabled` to `true`. Resources missing the `versioning` parameter or with `versioning.enabled` set to `false` are flagged. \n\nSecure configuration example:\n\n```yaml\n- name: Create GCS bucket with versioning\n google.cloud.gcp_storage_bucket:\n name: my-bucket\n versioning:\n enabled: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/cloud_storage_bucket_versioning_disabled", "platform": "Ansible", "descriptionID": "2d791672", diff --git a/assets/queries/ansible/gcp/cloud_storage_bucket_versioning_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/cloud_storage_bucket_versioning_disabled/test/positive_expected_result.json index e2023f74..967f0afc 100644 --- a/assets/queries/ansible/gcp/cloud_storage_bucket_versioning_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/cloud_storage_bucket_versioning_disabled/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Cloud Storage Bucket Versioning Disabled", + "queryName": "Cloud storage bucket versioning disabled", "severity": "MEDIUM", "line": 3 }, { - "queryName": "Cloud Storage Bucket Versioning Disabled", + "queryName": "Cloud storage bucket versioning disabled", "severity": "MEDIUM", "line": 17 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/cluster_labels_disabled/metadata.json b/assets/queries/ansible/gcp/cluster_labels_disabled/metadata.json index ebacd03b..98e9e727 100644 --- a/assets/queries/ansible/gcp/cluster_labels_disabled/metadata.json +++ b/assets/queries/ansible/gcp/cluster_labels_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "fbe9b2d0-a2b7-47a1-a534-03775f3013f7", - "queryName": "Cluster Labels Disabled", + "queryName": "Cluster labels disabled", "severity": "LOW", "category": "Insecure Configurations", - "descriptionText": "Kubernetes Clusters must be configured with labels, which means the attribute 'resource_labels' must be defined", + "descriptionText": "Kubernetes clusters should include resource labels to ensure assets are identifiable and support inventory, policy targeting, and incident response. For Ansible-managed GKE clusters using the `google.cloud.gcp_container_cluster` or `gcp_container_cluster` modules, the `resource_labels` property must be defined and contain at least one key/value pair. Tasks missing the `resource_labels` property or with it set to an empty value (for example, an empty string) are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create GKE cluster with labels\n google.cloud.gcp_container_cluster:\n name: my-cluster\n resource_labels:\n env: prod\n owner: team-a\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/cluster_labels_disabled", "platform": "Ansible", "descriptionID": "dc1b3288", diff --git a/assets/queries/ansible/gcp/cluster_labels_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/cluster_labels_disabled/test/positive_expected_result.json index 8c1a8dc6..f690ae9e 100644 --- a/assets/queries/ansible/gcp/cluster_labels_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/cluster_labels_disabled/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "Cluster Labels Disabled", + "queryName": "Cluster labels disabled", "severity": "LOW", "line": 2 }, { - "queryName": "Cluster Labels Disabled", + "queryName": "Cluster labels disabled", "severity": "LOW", "line": 17 }, { - "queryName": "Cluster Labels Disabled", + "queryName": "Cluster labels disabled", "severity": "LOW", "line": 47 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/cluster_master_authentication_disabled/metadata.json b/assets/queries/ansible/gcp/cluster_master_authentication_disabled/metadata.json index ab09399f..b53c4b0f 100644 --- a/assets/queries/ansible/gcp/cluster_master_authentication_disabled/metadata.json +++ b/assets/queries/ansible/gcp/cluster_master_authentication_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "9df7f78f-ebe3-432e-ac3b-b67189c15518", - "queryName": "Cluster Master Authentication Disabled", + "queryName": "Cluster master authentication disabled", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Kubernetes Engine Clusters must have Master Authentication set to enabled, which means the attribute 'master_auth' must have the subattributes 'username' and 'password' defined and not empty", + "descriptionText": "Kubernetes Engine clusters must have master authentication credentials defined so control plane access is not left unauthenticated or ambiguous. This ensures administrative access is explicit and auditable.\n\nFor Ansible GKE cluster resources using the `google.cloud.gcp_container_cluster` or `gcp_container_cluster` modules, the `master_auth` property must be present and its `username` and `password` subproperties must be defined and non-empty strings. This rule flags resources where `master_auth` is undefined or null, or where `master_auth.username` or `master_auth.password` are undefined, null, or empty. If you use alternative authentication mechanisms, ensure they are explicitly configured. Otherwise, provide non-empty credentials so the cluster admin account is not left unspecified.\n\nSecure configuration example for an Ansible task:\n\n```yaml\n- name: Create GKE cluster with master auth\n google.cloud.gcp_container_cluster:\n name: my-cluster\n zone: us-central1\n master_auth:\n username: admin\n password: \"{{ gke_admin_password }}\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/cluster_master_authentication_disabled", "platform": "Ansible", "descriptionID": "6452ea1e", diff --git a/assets/queries/ansible/gcp/cluster_master_authentication_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/cluster_master_authentication_disabled/test/positive_expected_result.json index 06b3b7a8..07fecc68 100644 --- a/assets/queries/ansible/gcp/cluster_master_authentication_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/cluster_master_authentication_disabled/test/positive_expected_result.json @@ -1,27 +1,27 @@ [ { - "queryName": "Cluster Master Authentication Disabled", + "queryName": "Cluster master authentication disabled", "severity": "MEDIUM", "line": 3 }, { - "queryName": "Cluster Master Authentication Disabled", + "queryName": "Cluster master authentication disabled", "severity": "MEDIUM", "line": 18 }, { - "queryName": "Cluster Master Authentication Disabled", + "queryName": "Cluster master authentication disabled", "severity": "MEDIUM", "line": 32 }, { - "queryName": "Cluster Master Authentication Disabled", + "queryName": "Cluster master authentication disabled", "severity": "MEDIUM", "line": 46 }, { - "queryName": "Cluster Master Authentication Disabled", + "queryName": "Cluster master authentication disabled", "severity": "MEDIUM", "line": 61 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/compute_instance_is_publicly_accessible/metadata.json b/assets/queries/ansible/gcp/compute_instance_is_publicly_accessible/metadata.json index 2014d287..4538e506 100644 --- a/assets/queries/ansible/gcp/compute_instance_is_publicly_accessible/metadata.json +++ b/assets/queries/ansible/gcp/compute_instance_is_publicly_accessible/metadata.json @@ -1,9 +1,9 @@ { "id": "829f1c60-2bab-44c6-8a21-5cd9d39a2c82", - "queryName": "Compute Instance Is Publicly Accessible", + "queryName": "Compute instance is publicly accessible", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Compute instances shouldn't be accessible from the Internet.", + "descriptionText": "Compute instances must not be assigned external (public) IP addresses. Public IPs expose instances directly to the internet, increasing the risk of unauthorized access, brute-force attacks, and data exfiltration.\n\nFor Ansible Google Cloud compute instance resources (modules `google.cloud.gcp_compute_instance` and `gcp_compute_instance`), ensure the `network_interfaces[].access_configs` property is not defined. Any `network_interfaces` entry containing `access_configs` indicates an external IP is being assigned and is flagged. Remove `access_configs` to prevent automatic external IP allocation and use Cloud NAT, internal load balancers, or bastion hosts for controlled outbound/inbound access instead.\n\nSecure configuration example (no external IP):\n\n```yaml\n- name: Create instance without external IP\n google.cloud.gcp_compute_instance:\n name: my-instance\n machine_type: e2-medium\n zone: us-central1-a\n network_interfaces:\n - network: default\n subnetwork: default\n # no access_configs defined -> no external IP assigned\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/compute_instance_is_publicly_accessible", "platform": "Ansible", "descriptionID": "ef20d5fb", diff --git a/assets/queries/ansible/gcp/compute_instance_is_publicly_accessible/test/positive_expected_result.json b/assets/queries/ansible/gcp/compute_instance_is_publicly_accessible/test/positive_expected_result.json index 77e4bef3..85a7d62f 100644 --- a/assets/queries/ansible/gcp/compute_instance_is_publicly_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/compute_instance_is_publicly_accessible/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "Compute Instance Is Publicly Accessible", + "queryName": "Compute instance is publicly accessible", "severity": "MEDIUM", "line": 6 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/cos_node_image_not_used/metadata.json b/assets/queries/ansible/gcp/cos_node_image_not_used/metadata.json index 06e4cbb4..bb158e27 100644 --- a/assets/queries/ansible/gcp/cos_node_image_not_used/metadata.json +++ b/assets/queries/ansible/gcp/cos_node_image_not_used/metadata.json @@ -1,9 +1,9 @@ { "id": "be41f891-96b1-4b9d-b74f-b922a918c778", - "queryName": "COS Node Image Not Used", + "queryName": "COS node image not used", "severity": "LOW", "category": "Insecure Configurations", - "descriptionText": "The node image should be Container-Optimized OS(COS)", + "descriptionText": "GKE node pools should use Container-Optimized OS (COS) images. COS is a Google-managed, hardened OS with automatic security updates and tighter integration with GKE, reducing exposure to unpatched vulnerabilities and kernel-level attack surface.\n\nIn Ansible, check tasks using the `google.cloud.gcp_container_node_pool` or `gcp_container_node_pool` modules and ensure the `config.image_type` property is defined and starts with `COS` (case-insensitive). Tasks missing `config.image_type` or with values that do not start with `COS` are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create GKE node pool with COS image\n google.cloud.gcp_container_node_pool:\n name: my-node-pool\n initial_node_count: 3\n config:\n machine_type: e2-medium\n image_type: COS_CONTAINERD\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/cos_node_image_not_used", "platform": "Ansible", "descriptionID": "1bcc90d0", diff --git a/assets/queries/ansible/gcp/cos_node_image_not_used/test/positive_expected_result.json b/assets/queries/ansible/gcp/cos_node_image_not_used/test/positive_expected_result.json index 7749e1bc..7b0bb980 100644 --- a/assets/queries/ansible/gcp/cos_node_image_not_used/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/cos_node_image_not_used/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "COS Node Image Not Used", + "queryName": "COS node image not used", "severity": "LOW", "line": 13 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/disk_encryption_disabled/metadata.json b/assets/queries/ansible/gcp/disk_encryption_disabled/metadata.json index 35fb3f59..e677f3a2 100644 --- a/assets/queries/ansible/gcp/disk_encryption_disabled/metadata.json +++ b/assets/queries/ansible/gcp/disk_encryption_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "092bae86-6105-4802-99d2-99cd7e7431f3", - "queryName": "Disk Encryption Disabled", + "queryName": "Disk encryption disabled", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "VM disks for critical VMs must be encrypted with Customer Supplied Encryption Keys (CSEK) or with Customer-managed encryption keys (CMEK), which means the attribute 'disk_encryption_key' must be defined and its sub attributes 'raw_key' or 'kms_key_self_link' must also be defined", + "descriptionText": "VM disks must be encrypted using customer-supplied (CSEK) or customer-managed (CMEK) keys. This ensures you retain control over key lifecycle and reduces the risk of cloud-managed keys being used to decrypt sensitive data without your authorization.\n\nFor Ansible resources using `google.cloud.gcp_compute_disk` (or `gcp_compute_disk`), the `disk_encryption_key` property must be defined and contain either a non-empty `kms_key_name` (CMEK) or a non-empty `raw_key` (CSEK). This rule flags disks where `disk_encryption_key` is missing or `null`, where both `raw_key` and `kms_key_name` are absent, or where either subproperty is an empty string.\n\nPrefer using `kms_key_name` (a full KMS crypto key resource name, for example, `projects/.../locations/.../keyRings/.../cryptoKeys/...`) and avoid hardcoding `raw_key` in source code—store secrets in a secure secret manager.\n\nSecure configuration examples:\n\n```yaml\n- name: create disk with CMEK\n google.cloud.gcp_compute_disk:\n name: my-disk\n zone: us-central1-a\n size_gb: 100\n disk_encryption_key:\n kms_key_name: projects/my-project/locations/global/keyRings/my-kr/cryptoKeys/my-key\n```\n\n```yaml\n- name: create disk with CSEK (raw key stored securely, not in plaintext)\n google.cloud.gcp_compute_disk:\n name: my-disk\n zone: us-central1-a\n size_gb: 100\n disk_encryption_key:\n raw_key: REDACTED_BASE64_KEY\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/disk_encryption_disabled", "platform": "Ansible", "descriptionID": "1272593c", diff --git a/assets/queries/ansible/gcp/disk_encryption_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/disk_encryption_disabled/test/positive_expected_result.json index ed85273a..af7ac280 100644 --- a/assets/queries/ansible/gcp/disk_encryption_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/disk_encryption_disabled/test/positive_expected_result.json @@ -1,30 +1,30 @@ [ { - "queryName": "Disk Encryption Disabled", + "queryName": "Disk encryption disabled", "severity": "MEDIUM", "line": 3, "filename": "positive1.yaml" }, { - "queryName": "Disk Encryption Disabled", + "queryName": "Disk encryption disabled", "severity": "MEDIUM", "line": 15, "filename": "positive1.yaml" }, { - "queryName": "Disk Encryption Disabled", + "queryName": "Disk encryption disabled", "severity": "MEDIUM", "line": 27, "filename": "positive1.yaml" }, { - "queryName": "Disk Encryption Disabled", + "queryName": "Disk encryption disabled", "severity": "MEDIUM", "line": 5, "filename": "positive2.yaml" }, { - "queryName": "Disk Encryption Disabled", + "queryName": "Disk encryption disabled", "severity": "MEDIUM", "line": 17, "filename": "positive2.yaml" diff --git a/assets/queries/ansible/gcp/dnssec_using_rsasha1/metadata.json b/assets/queries/ansible/gcp/dnssec_using_rsasha1/metadata.json index ec05dc3a..abe9abaf 100644 --- a/assets/queries/ansible/gcp/dnssec_using_rsasha1/metadata.json +++ b/assets/queries/ansible/gcp/dnssec_using_rsasha1/metadata.json @@ -1,9 +1,9 @@ { "id": "6cf4c3a7-ceb0-4475-8892-3745b84be24a", - "queryName": "DNSSEC Using RSASHA1", + "queryName": "DNSSEC using RSASHA1", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "DNSSEC should not use the RSASHA1 algorithm", + "descriptionText": "Using the RSASHA1 algorithm for DNSSEC weakens DNS integrity because SHA-1 is deprecated and vulnerable to collision attacks, increasing the risk of forged or tampered DNS responses.\n\nFor Ansible-managed Google Cloud DNS zones (modules `google.cloud.gcp_dns_managed_zone` and `gcp_dns_managed_zone`), the `dnssec_config.defaultKeySpecs.algorithm` property must not be set to `rsasha1` (checked case-insensitively). Resources with `dnssec_config.defaultKeySpecs.algorithm` set to `rsasha1` are flagged. Update the property to a stronger algorithm such as `RSASHA256`, `RSASHA512`, or an ECDSA option like `ECDSAP256SHA256`. \n\nSecure configuration example:\n\n```yaml\n- name: Create managed zone with secure DNSSEC algorithm\n google.cloud.gcp_dns_managed_zone:\n name: my-zone\n dnssec_config:\n defaultKeySpecs:\n - algorithm: RSASHA256\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/dnssec_using_rsasha1", "platform": "Ansible", "descriptionID": "e6a4b99b", diff --git a/assets/queries/ansible/gcp/dnssec_using_rsasha1/test/positive_expected_result.json b/assets/queries/ansible/gcp/dnssec_using_rsasha1/test/positive_expected_result.json index e0b836b4..887995fe 100644 --- a/assets/queries/ansible/gcp/dnssec_using_rsasha1/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/dnssec_using_rsasha1/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "DNSSEC Using RSASHA1", + "queryName": "DNSSEC using RSASHA1", "severity": "MEDIUM", "line": 13 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/gke_basic_authentication_enabled/metadata.json b/assets/queries/ansible/gcp/gke_basic_authentication_enabled/metadata.json index 14306439..3c659b4b 100644 --- a/assets/queries/ansible/gcp/gke_basic_authentication_enabled/metadata.json +++ b/assets/queries/ansible/gcp/gke_basic_authentication_enabled/metadata.json @@ -1,9 +1,9 @@ { "id": "344bf8ab-9308-462b-a6b2-697432e40ba1", - "queryName": "GKE Basic Authentication Enabled", + "queryName": "GKE basic authentication enabled", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "GCP - Google Kubernetes Engine (GKE) Basic Authentication must be disabled, which means the username and password provided in the master_auth block must be empty", + "descriptionText": "Disabling GKE basic authentication is required because an embedded cluster username and password can be leaked or abused to gain direct admin access to the Kubernetes API, bypassing IAM and RBAC protections.\n\nThe Ansible GKE resources `google.cloud.gcp_container_cluster` and `gcp_container_cluster` must include a `master_auth` block with both `username` and `password` set to empty strings to indicate basic auth is disabled. Resources that omit `master_auth`, omit either `username` or `password`, or provide non-empty values are flagged. \n\nSecure configuration example:\n\n```yaml\n- name: Create GKE cluster with basic auth disabled\n google.cloud.gcp_container_cluster:\n name: my-cluster\n master_auth:\n username: \"\"\n password: \"\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/gke_basic_authentication_enabled", "platform": "Ansible", "descriptionID": "d49df828", diff --git a/assets/queries/ansible/gcp/gke_basic_authentication_enabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/gke_basic_authentication_enabled/test/positive_expected_result.json index 7d0d7d53..535d2eaa 100644 --- a/assets/queries/ansible/gcp/gke_basic_authentication_enabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/gke_basic_authentication_enabled/test/positive_expected_result.json @@ -1,27 +1,27 @@ [ { - "queryName": "GKE Basic Authentication Enabled", + "queryName": "GKE basic authentication enabled", "severity": "MEDIUM", "line": 3 }, { - "queryName": "GKE Basic Authentication Enabled", + "queryName": "GKE basic authentication enabled", "severity": "MEDIUM", "line": 18 }, { - "queryName": "GKE Basic Authentication Enabled", + "queryName": "GKE basic authentication enabled", "severity": "MEDIUM", "line": 32 }, { - "queryName": "GKE Basic Authentication Enabled", + "queryName": "GKE basic authentication enabled", "severity": "MEDIUM", "line": 47 }, { - "queryName": "GKE Basic Authentication Enabled", + "queryName": "GKE basic authentication enabled", "severity": "MEDIUM", "line": 63 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/gke_legacy_authorization_enabled/metadata.json b/assets/queries/ansible/gcp/gke_legacy_authorization_enabled/metadata.json index 743a02e2..3dbc5ea9 100644 --- a/assets/queries/ansible/gcp/gke_legacy_authorization_enabled/metadata.json +++ b/assets/queries/ansible/gcp/gke_legacy_authorization_enabled/metadata.json @@ -1,9 +1,9 @@ { "id": "300a9964-b086-41f7-9378-b6de3ba1c32b", - "queryName": "GKE Legacy Authorization Enabled", + "queryName": "GKE legacy authorization enabled", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "Kubernetes Engine Clusters must have Legacy Authorization set to disabled, which means the attribute 'legacy_abac.enabled' must be false.", + "descriptionText": "Legacy Authorization (ABAC) must be disabled on Kubernetes Engine (GKE) clusters. ABAC grants access based on user attributes instead of fine-grained RBAC rules, which can result in overly broad permissions and increase the risk of unauthorized access or privilege escalation.\n\nFor Ansible-managed clusters using the `google.cloud.gcp_container_cluster` or `gcp_container_cluster` modules, the `legacy_abac.enabled` property must be set to `false`. This rule flags cluster resources where `legacy_abac.enabled` is `true`. Ensure the property is explicitly defined as `false` in your cluster declaration to enforce RBAC.\n\nSecure configuration example:\n\n```yaml\n- name: Create GKE cluster with legacy ABAC disabled\n google.cloud.gcp_container_cluster:\n name: my-cluster\n location: us-central1\n legacy_abac:\n enabled: false\n initial_node_count: 3\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/gke_legacy_authorization_enabled", "platform": "Ansible", "descriptionID": "f26d0d51", diff --git a/assets/queries/ansible/gcp/gke_legacy_authorization_enabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/gke_legacy_authorization_enabled/test/positive_expected_result.json index 7a792dec..be65e070 100644 --- a/assets/queries/ansible/gcp/gke_legacy_authorization_enabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/gke_legacy_authorization_enabled/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "GKE Legacy Authorization Enabled", + "queryName": "GKE legacy authorization enabled", "severity": "HIGH", "line": 18 } diff --git a/assets/queries/ansible/gcp/gke_master_authorized_networks_disabled/metadata.json b/assets/queries/ansible/gcp/gke_master_authorized_networks_disabled/metadata.json index 311d6df1..495fbea8 100644 --- a/assets/queries/ansible/gcp/gke_master_authorized_networks_disabled/metadata.json +++ b/assets/queries/ansible/gcp/gke_master_authorized_networks_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "d43366c5-80b0-45de-bbe8-2338f4ab0a83", - "queryName": "GKE Master Authorized Networks Disabled", + "queryName": "GKE master authorized networks disabled", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Master authorized networks must be enabled in GKE clusters", + "descriptionText": "GKE clusters must enable master authorized networks to restrict access to the Kubernetes control plane to trusted network ranges. Without this restriction, unauthorized or network-based access could lead to cluster compromise.\n\nFor Ansible resources using `google.cloud.gcp_container_cluster` or `gcp_container_cluster`, the `master_authorized_networks_config` property must be defined and its `enabled` field set to `true`. Resources missing `master_authorized_networks_config` or with `master_authorized_networks_config.enabled` set to `false` are flagged as insecure. Optionally, include CIDR entries to specify allowed client networks via `master_authorized_networks_config.cidr_blocks`.\n\nSecure Ansible example:\n\n```yaml\n- name: Create secure GKE cluster with master authorized networks\n google.cloud.gcp_container_cluster:\n name: my-cluster\n location: us-central1\n master_authorized_networks_config:\n enabled: true\n cidr_blocks:\n - cidr_block: 203.0.113.0/24\n display_name: office-network\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/gke_master_authorized_networks_disabled", "platform": "Ansible", "descriptionID": "67294baa", diff --git a/assets/queries/ansible/gcp/gke_master_authorized_networks_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/gke_master_authorized_networks_disabled/test/positive_expected_result.json index 5535f921..0d85f3aa 100644 --- a/assets/queries/ansible/gcp/gke_master_authorized_networks_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/gke_master_authorized_networks_disabled/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "GKE Master Authorized Networks Disabled", + "queryName": "GKE master authorized networks disabled", "severity": "MEDIUM", "line": 10 }, { - "queryName": "GKE Master Authorized Networks Disabled", + "queryName": "GKE master authorized networks disabled", "severity": "MEDIUM", "line": 17 }, { - "queryName": "GKE Master Authorized Networks Disabled", + "queryName": "GKE master authorized networks disabled", "severity": "MEDIUM", "line": 22 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/gke_using_default_service_account/metadata.json b/assets/queries/ansible/gcp/gke_using_default_service_account/metadata.json index 4239ab1f..cde9686b 100644 --- a/assets/queries/ansible/gcp/gke_using_default_service_account/metadata.json +++ b/assets/queries/ansible/gcp/gke_using_default_service_account/metadata.json @@ -1,9 +1,9 @@ { "id": "dc126833-125a-40fb-905a-ce5f2afde240", - "queryName": "GKE Using Default Service Account", + "queryName": "GKE using default service account", "severity": "MEDIUM", "category": "Insecure Defaults", - "descriptionText": "Kubernetes Engine Clusters should not be configured to use the default service account", + "descriptionText": "Kubernetes Engine clusters should not use the default node service account. The default account typically has broad permissions, increasing the blast radius if a node is compromised.\n\nFor Ansible resources using `google.cloud.gcp_container_cluster` or `gcp_container_cluster`, the `node_config.service_account` property must be defined and set to a dedicated, least-privilege IAM service account (full email address). Resources missing `node_config.service_account` or with a value containing `\"default\"` are flagged. Use a distinct service account with narrowly scoped IAM roles, for example, `my-sa@PROJECT_ID.iam.gserviceaccount.com`.\n\nSecure configuration example:\n\n```yaml\n- name: Create GKE cluster with custom node service account\n google.cloud.gcp_container_cluster:\n name: my-cluster\n location: us-central1\n node_config:\n service_account: my-sa@my-project.iam.gserviceaccount.com\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/gke_using_default_service_account", "platform": "Ansible", "descriptionID": "d6e87258", diff --git a/assets/queries/ansible/gcp/gke_using_default_service_account/test/positive_expected_result.json b/assets/queries/ansible/gcp/gke_using_default_service_account/test/positive_expected_result.json index c1d0cdbb..b8df6d46 100644 --- a/assets/queries/ansible/gcp/gke_using_default_service_account/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/gke_using_default_service_account/test/positive_expected_result.json @@ -1,14 +1,14 @@ [ - { - "queryName": "GKE Using Default Service Account", - "severity": "MEDIUM", - "line": 8, - "fileName": "positive1.yaml" - }, - { - "queryName": "GKE Using Default Service Account", - "severity": "MEDIUM", - "line": 11, - "fileName": "positive2.yaml" - } + { + "queryName": "GKE using default service account", + "severity": "MEDIUM", + "line": 8, + "fileName": "positive1.yaml" + }, + { + "queryName": "GKE using default service account", + "severity": "MEDIUM", + "line": 11, + "fileName": "positive2.yaml" + } ] diff --git a/assets/queries/ansible/gcp/google_compute_network_using_default_firewall_rule/metadata.json b/assets/queries/ansible/gcp/google_compute_network_using_default_firewall_rule/metadata.json index 77dcb220..e5d38aa1 100644 --- a/assets/queries/ansible/gcp/google_compute_network_using_default_firewall_rule/metadata.json +++ b/assets/queries/ansible/gcp/google_compute_network_using_default_firewall_rule/metadata.json @@ -1,9 +1,9 @@ { "id": "29b8224a-60e9-4011-8ac2-7916a659841f", - "queryName": "Google Compute Network Using Default Firewall Rule", + "queryName": "Google Compute network using default firewall rule", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Google Compute Network should not use default firewall rule", + "descriptionText": "Using a default firewall rule named \"default\" can expose a Compute Network to overly permissive ingress or egress, violating least-privilege network segmentation and increasing the risk of unauthorized access and lateral movement.\n\nThis rule flags Ansible tasks using the `google.cloud.gcp_compute_firewall` or `gcp_compute_firewall` module where the firewall `name` contains \"default\" and the `network` property attaches to a network created or registered by a prior `google.cloud.gcp_compute_network` or `gcp_compute_network` task. Specifically, firewall tasks with `name` including \"default\" and `network` set to the registered network value (for example, `network: \"{{ }}\"`) are flagged.\n\nReplace default rules with explicit, least-privilege firewall rules that specify precise allowed ports and source ranges, or reference the intended network and rule names explicitly rather than reusing the default.", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/google_compute_network_using_default_firewall_rule", "platform": "Ansible", "descriptionID": "1c9178bb", diff --git a/assets/queries/ansible/gcp/google_compute_network_using_default_firewall_rule/test/positive_expected_result.json b/assets/queries/ansible/gcp/google_compute_network_using_default_firewall_rule/test/positive_expected_result.json index ed1b5a81..0a954dc7 100644 --- a/assets/queries/ansible/gcp/google_compute_network_using_default_firewall_rule/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/google_compute_network_using_default_firewall_rule/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Google Compute Network Using Default Firewall Rule", + "queryName": "Google Compute network using default firewall rule", "severity": "MEDIUM", "line": 11, "fileName": "positive.yaml" diff --git a/assets/queries/ansible/gcp/google_compute_network_using_firewall_allows_port_range/metadata.json b/assets/queries/ansible/gcp/google_compute_network_using_firewall_allows_port_range/metadata.json index 2d801aa0..3c5520d6 100644 --- a/assets/queries/ansible/gcp/google_compute_network_using_firewall_allows_port_range/metadata.json +++ b/assets/queries/ansible/gcp/google_compute_network_using_firewall_allows_port_range/metadata.json @@ -1,9 +1,9 @@ { "id": "7289eebd-a477-4064-8ad4-3c044bd70b00", - "queryName": "Google Compute Network Using Firewall Rule that Allows Port Range", + "queryName": "Google Compute network using firewall rule that allows port range", "severity": "LOW", "category": "Networking and Firewall", - "descriptionText": "Google Compute Network should not use a firewall rule that allows port range", + "descriptionText": "Compute network firewall rules must not permit ingress using broad port ranges because ranges increase attack surface, make it harder to apply least privilege, and can unintentionally expose multiple services.\n\nThis check inspects Ansible tasks using the `google.cloud.gcp_compute_firewall` or `gcp_compute_firewall` modules and flags ingress rules where `allowed[].ports[]` entries are numeric ranges matching the `start-end` pattern (for example, `\"8000-9000\"`). The rule does not match the literal `\"0-65535\"`.\n\nThe check applies when the firewall's `network` references a compute network task, meaning the firewall `network` equals the compute network's registered name. To resolve, specify explicit single ports or a minimal list of ports and scope ingress with specific source ranges or target tags.\n\nSecure example with explicit single ports:\n\n```yaml\n- name: Create restricted firewall rule\n google.cloud.gcp_compute_firewall:\n name: allow-ssh\n network: \"{{ my_network.registered_name }}\"\n direction: INGRESS\n allowed:\n - IPProtocol: tcp\n ports:\n - \"22\"\n sourceRanges:\n - \"203.0.113.0/24\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/google_compute_network_using_firewall_allows_port_range", "platform": "Ansible", "descriptionID": "2b7880b0", diff --git a/assets/queries/ansible/gcp/google_compute_network_using_firewall_allows_port_range/test/positive_expected_result.json b/assets/queries/ansible/gcp/google_compute_network_using_firewall_allows_port_range/test/positive_expected_result.json index 35ee2790..793d7e0c 100644 --- a/assets/queries/ansible/gcp/google_compute_network_using_firewall_allows_port_range/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/google_compute_network_using_firewall_allows_port_range/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Google Compute Network Using Firewall Rule that Allows Port Range", + "queryName": "Google Compute network using firewall rule that allows port range", "severity": "LOW", "line": 19, "fileName": "positive.yaml" diff --git a/assets/queries/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports/metadata.json b/assets/queries/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports/metadata.json index be5a9a15..fc934967 100644 --- a/assets/queries/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports/metadata.json +++ b/assets/queries/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports/metadata.json @@ -1,9 +1,9 @@ { "id": "3602d273-3290-47b2-80fa-720162b1a8af", - "queryName": "Google Compute Network Using Firewall Rule that Allows All Ports", + "queryName": "Google Compute network using firewall rule that allows all ports", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Google Compute Network should not use a firewall rule that allows all ports", + "descriptionText": "Allowing ingress on all ports (0-65535) greatly increases attack surface by exposing every service port to network scanning and exploitation. This can lead to unauthorized access, lateral movement, and easier compromise of instances.\n\nThis rule flags Ansible tasks using the `google.cloud.gcp_compute_firewall` or `gcp_compute_firewall` module where the rule is ingress and the `allowed` entry contains `ports: [\"0-65535\"]` for a firewall associated with a compute network referenced by a preceding `google.cloud.gcp_compute_network`/`gcp_compute_network` task.\n\nThe `allowed.ports` property must not include `\"0-65535\"`. Instead, specify explicit ports or narrow ranges (for example `\"80\"`, `\"443\"`, or `\"1024-2048\"`) and restrict access with appropriate `sourceRanges` or other selectors. \n\nSecure example (allow only HTTP/HTTPS from a limited source range):\n\n```yaml\n- name: Allow HTTP and HTTPS from internal range\n google.cloud.gcp_compute_firewall:\n name: allow-web\n network: \"{{ my_network }}\"\n direction: INGRESS\n allowed:\n - IPProtocol: tcp\n ports: [\"80\", \"443\"]\n sourceRanges: [\"10.0.0.0/8\"]\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports", "platform": "Ansible", "descriptionID": "da7c0346", diff --git a/assets/queries/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports/test/positive_expected_result.json b/assets/queries/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports/test/positive_expected_result.json index c07d1c52..2868186b 100644 --- a/assets/queries/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "Google Compute Network Using Firewall Rule that Allows All Ports", + "queryName": "Google Compute network using firewall rule that allows all ports", "severity": "MEDIUM", "line": 19, "fileName": "positive.yaml" diff --git a/assets/queries/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use/metadata.json b/assets/queries/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use/metadata.json index 3b69353b..2aeddeba 100644 --- a/assets/queries/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use/metadata.json +++ b/assets/queries/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use/metadata.json @@ -1,9 +1,9 @@ { "id": "b28bcd2f-c309-490e-ab7c-35fc4023eb26", - "queryName": "Google Compute SSL Policy Weak Cipher In Use", + "queryName": "Google Compute SSL policy weak cipher in use", "severity": "MEDIUM", "category": "Encryption", - "descriptionText": "This query confirms if Google Compute SSL Policy Weak Chyper Suits is Enabled, to do so we need to check if TLS is TLS_1_2, because other version have Weak Chypers", + "descriptionText": "Compute SSL policies must enforce a minimum TLS version of `TLS_1_2` to prevent use of older, vulnerable protocol versions and weak cipher suites. The `min_tls_version` property on `google.cloud.gcp_compute_ssl_policy` (or `gcp_compute_ssl_policy`) resources must be defined and set to `TLS_1_2`. Resources that omit `min_tls_version` or set it to any other value are flagged.\n\n```yaml\n- name: Create SSL policy with TLS 1.2 minimum\n google.cloud.gcp_compute_ssl_policy:\n name: my-ssl-policy\n profile: MODERN\n min_tls_version: TLS_1_2\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use", "platform": "Ansible", "descriptionID": "bb785f44", diff --git a/assets/queries/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use/test/positive_expected_result.json b/assets/queries/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use/test/positive_expected_result.json index 9fab2239..b6cf818a 100644 --- a/assets/queries/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "Google Compute SSL Policy Weak Cipher In Use", + "queryName": "Google Compute SSL policy weak cipher in use", "severity": "MEDIUM", "line": 2 }, { - "queryName": "Google Compute SSL Policy Weak Cipher In Use", + "queryName": "Google Compute SSL policy weak cipher in use", "severity": "MEDIUM", "line": 16 } diff --git a/assets/queries/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled/metadata.json b/assets/queries/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled/metadata.json index d6bbdca1..48a66f23 100644 --- a/assets/queries/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled/metadata.json +++ b/assets/queries/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "6a4080ae-79bd-42f6-a924-8f534c1c018b", - "queryName": "Google Compute Subnetwork with Private Google Access Disabled", + "queryName": "Google Compute subnetwork with Private Google Access disabled", "severity": "LOW", "category": "Networking and Firewall", - "descriptionText": "Google Compute Subnetwork should have Private Google Access enabled, which means 'private_ip_google_access' should be set to yes", + "descriptionText": "Subnetworks must have Private Google Access enabled so VM instances with only internal IPs can reach Google APIs and services over Google's internal network. Without Private Google Access, operators may assign external IPs or route traffic over the public internet, increasing attack surface and the risk of data exposure or network-based attacks.\n\nFor Ansible resources using the google.cloud.gcp_compute_subnetwork or gcp_compute_subnetwork modules, the `private_ip_google_access` property must be defined and set to `yes`. Tasks missing this property or with `private_ip_google_access` not equal to `yes` are flagged.\n\nSecure Ansible example:\n\n```yaml\n- name: Create subnetwork with Private Google Access enabled\n google.cloud.gcp_compute_subnetwork:\n name: my-subnet\n region: us-central1\n ip_cidr_range: 10.0.0.0/24\n network: my-vpc\n private_ip_google_access: yes\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled", "platform": "Ansible", "descriptionID": "f5dece39", diff --git a/assets/queries/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled/test/positive_expected_result.json index 4b088e2c..e4e6476a 100644 --- a/assets/queries/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Google Compute Subnetwork with Private Google Access Disabled", + "queryName": "Google Compute subnetwork with Private Google Access disabled", "severity": "LOW", "line": 2, "fileName": "positive1.yaml" }, { - "queryName": "Google Compute Subnetwork with Private Google Access Disabled", + "queryName": "Google Compute subnetwork with Private Google Access disabled", "severity": "LOW", "line": 10, "fileName": "positive2.yaml" diff --git a/assets/queries/ansible/gcp/google_container_node_pool_auto_repair_disabled/metadata.json b/assets/queries/ansible/gcp/google_container_node_pool_auto_repair_disabled/metadata.json index 68a65ff5..c4bb4ad5 100644 --- a/assets/queries/ansible/gcp/google_container_node_pool_auto_repair_disabled/metadata.json +++ b/assets/queries/ansible/gcp/google_container_node_pool_auto_repair_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "d58c6f24-3763-4269-9f5b-86b2569a003b", - "queryName": "Google Container Node Pool Auto Repair Disabled", + "queryName": "Google container node pool auto repair disabled", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Google Container Node Pool Auto Repair should be enabled. This service periodically checks for failing nodes and repairs them to ensure a smooth running state.", + "descriptionText": "Node pools must have automatic node repair enabled so unhealthy or failing nodes are remediated automatically, reducing the risk of prolonged downtime and inconsistent cluster state.\n\nFor Ansible GKE node pool resources (modules `google.cloud.gcp_container_node_pool` and `gcp_container_node_pool`), the `management` block must be defined and its `auto_repair` property set to `true`. Tasks missing the `management` block or with `management.auto_repair` set to `false` are flagged. \n\nSecure configuration example:\n\n```yaml\n- name: Create GKE node pool with auto repair enabled\n google.cloud.gcp_container_node_pool:\n name: my-node-pool\n cluster: my-cluster\n location: us-central1\n initial_node_count: 3\n management:\n auto_repair: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/google_container_node_pool_auto_repair_disabled", "platform": "Ansible", "descriptionID": "14cf26ed", diff --git a/assets/queries/ansible/gcp/google_container_node_pool_auto_repair_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/google_container_node_pool_auto_repair_disabled/test/positive_expected_result.json index 6be1d2d4..4499b39e 100644 --- a/assets/queries/ansible/gcp/google_container_node_pool_auto_repair_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/google_container_node_pool_auto_repair_disabled/test/positive_expected_result.json @@ -1,16 +1,16 @@ [ { - "queryName": "Google Container Node Pool Auto Repair Disabled", + "queryName": "Google container node pool auto repair disabled", "severity": "MEDIUM", "line": 13 }, { - "queryName": "Google Container Node Pool Auto Repair Disabled", + "queryName": "Google container node pool auto repair disabled", "severity": "MEDIUM", "line": 26 }, { - "queryName": "Google Container Node Pool Auto Repair Disabled", + "queryName": "Google container node pool auto repair disabled", "severity": "MEDIUM", "line": 29 } diff --git a/assets/queries/ansible/gcp/high_google_kms_crypto_key_rotation_period/metadata.json b/assets/queries/ansible/gcp/high_google_kms_crypto_key_rotation_period/metadata.json index bbcb1577..a93b158a 100644 --- a/assets/queries/ansible/gcp/high_google_kms_crypto_key_rotation_period/metadata.json +++ b/assets/queries/ansible/gcp/high_google_kms_crypto_key_rotation_period/metadata.json @@ -1,9 +1,9 @@ { "id": "f9b7086b-deb8-4034-9330-d7fd38f1b8de", - "queryName": "High Google KMS Crypto Key Rotation Period", + "queryName": "High Google KMS crypto key rotation period", "severity": "MEDIUM", "category": "Secret Management", - "descriptionText": "KMS encryption keys should be rotated every 90 days or less. A short lifetime of encryption keys reduces the potential blast radius in case of compromise.", + "descriptionText": "KMS crypto keys must have a `rotation_period` of 90 days or less to limit key lifetime and reduce the blast radius if a key is compromised.\n\nFor Ansible resources using `google.cloud.gcp_kms_crypto_key` or `gcp_kms_crypto_key`, the `rotation_period` property must be a duration string in seconds ending with an `s`. The numeric value must be less than or equal to `7776000` (90 days). Resources missing `rotation_period`, lacking the `s` suffix, or with a value greater than `7776000` are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create KMS crypto key with 90-day rotation\n google.cloud.gcp_kms_crypto_key:\n name: my-key\n key_ring: projects/my-project/locations/global/keyRings/my-keyring\n purpose: ENCRYPT_DECRYPT\n rotation_period: \"7776000s\"\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/high_google_kms_crypto_key_rotation_period", "platform": "Ansible", "descriptionID": "9072f426", diff --git a/assets/queries/ansible/gcp/high_google_kms_crypto_key_rotation_period/test/positive_expected_result.json b/assets/queries/ansible/gcp/high_google_kms_crypto_key_rotation_period/test/positive_expected_result.json index 64749f94..94b55848 100644 --- a/assets/queries/ansible/gcp/high_google_kms_crypto_key_rotation_period/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/high_google_kms_crypto_key_rotation_period/test/positive_expected_result.json @@ -1,11 +1,11 @@ [ { - "queryName": "High Google KMS Crypto Key Rotation Period", + "queryName": "High Google KMS crypto key rotation period", "severity": "MEDIUM", "line": 18 }, { - "queryName": "High Google KMS Crypto Key Rotation Period", + "queryName": "High Google KMS crypto key rotation period", "severity": "MEDIUM", "line": 23 } diff --git a/assets/queries/ansible/gcp/ip_aliasing_disabled/metadata.json b/assets/queries/ansible/gcp/ip_aliasing_disabled/metadata.json index a42e36c0..934e62b0 100644 --- a/assets/queries/ansible/gcp/ip_aliasing_disabled/metadata.json +++ b/assets/queries/ansible/gcp/ip_aliasing_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "ed672a9f-fbf0-44d8-a47d-779501b0db05", - "queryName": "IP Aliasing Disabled", + "queryName": "IP aliasing disabled", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Kubernetes Clusters must be created with Alias IP ranges enabled, which means the attribute 'ip_allocation_policy' must be defined and the subattribute 'use_ip_aliases' must be set to 'yes'.", + "descriptionText": "Kubernetes clusters must enable Alias IP ranges so pods use VPC-native networking. This prevents pod IP address conflicts and enables VPC features such as network policy enforcement and private IP addressing.\n\nFor Ansible-managed GKE clusters using the `google.cloud.gcp_container_cluster` (or `gcp_container_cluster`) module, the `ip_allocation_policy` property must be defined and its `use_ip_aliases` subproperty must be set to `true` (Ansible: `yes`). Resources missing `ip_allocation_policy`, missing `use_ip_aliases`, or with `use_ip_aliases` set to `false` are flagged. Secure configuration example:\n\n```yaml\n- name: create gke cluster with alias IPs\n google.cloud.gcp_container_cluster:\n name: my-cluster\n location: us-central1\n ip_allocation_policy:\n use_ip_aliases: yes\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/ip_aliasing_disabled", "platform": "Ansible", "descriptionID": "a4ad3884", diff --git a/assets/queries/ansible/gcp/ip_aliasing_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/ip_aliasing_disabled/test/positive_expected_result.json index 515f5894..e97ac760 100644 --- a/assets/queries/ansible/gcp/ip_aliasing_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/ip_aliasing_disabled/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "IP Aliasing Disabled", + "queryName": "IP aliasing disabled", "severity": "MEDIUM", "line": 2 }, { - "queryName": "IP Aliasing Disabled", + "queryName": "IP aliasing disabled", "severity": "MEDIUM", "line": 31 }, { - "queryName": "IP Aliasing Disabled", + "queryName": "IP aliasing disabled", "severity": "MEDIUM", "line": 50 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/ip_forwarding_enabled/metadata.json b/assets/queries/ansible/gcp/ip_forwarding_enabled/metadata.json index c7e97597..555ea6e3 100644 --- a/assets/queries/ansible/gcp/ip_forwarding_enabled/metadata.json +++ b/assets/queries/ansible/gcp/ip_forwarding_enabled/metadata.json @@ -1,9 +1,9 @@ { "id": "11bd3554-cd56-4257-8e25-7aaf30cf8f5f", - "queryName": "IP Forwarding Enabled", + "queryName": "IP forwarding enabled", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Instances must not have IP forwarding enabled, which means the attribute 'can_ip_forward' must not be true", + "descriptionText": "Compute instances must not have IP forwarding enabled. Allowing an instance to forward packets can be used to intercept, relay, or spoof network traffic. This enables lateral movement or bypassing of network security controls. For Google Cloud Compute instances managed with the Ansible modules `google.cloud.gcp_compute_instance` or `gcp_compute_instance`, the `can_ip_forward` property must be defined and set to `false` (not true/yes).\n\nInstances with `can_ip_forward` set to `true` or where the property is omitted are flagged. Only enable IP forwarding when strictly necessary, and document justification and compensating controls such as restrictive firewall rules and isolated network segments.", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/ip_forwarding_enabled", "platform": "Ansible", "descriptionID": "bd405766", diff --git a/assets/queries/ansible/gcp/ip_forwarding_enabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/ip_forwarding_enabled/test/positive_expected_result.json index fd23b343..40944470 100644 --- a/assets/queries/ansible/gcp/ip_forwarding_enabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/ip_forwarding_enabled/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "IP Forwarding Enabled", + "queryName": "IP forwarding enabled", "severity": "MEDIUM", "line": 22 } diff --git a/assets/queries/ansible/gcp/mysql_instance_with_local_infile_on/metadata.json b/assets/queries/ansible/gcp/mysql_instance_with_local_infile_on/metadata.json index e20a439e..c0a345fe 100644 --- a/assets/queries/ansible/gcp/mysql_instance_with_local_infile_on/metadata.json +++ b/assets/queries/ansible/gcp/mysql_instance_with_local_infile_on/metadata.json @@ -1,9 +1,9 @@ { "id": "a7b520bb-2509-4fb0-be05-bc38f54c7a4c", - "queryName": "MySQL Instance With Local Infile On", + "queryName": "MySQL instance with local infile on", "severity": "HIGH", "category": "Insecure Configurations", - "descriptionText": "MySQL Instance should not have Local Infile On", + "descriptionText": "MySQL instances must have the `local_infile` flag disabled. Enabling `LOAD DATA LOCAL INFILE` can be abused to read or exfiltrate files via SQL operations or by malicious servers and clients, exposing sensitive data. For Ansible tasks using `google.cloud.gcp_sql_instance` or `gcp_sql_instance`, ensure the `settings.database_flags` list contains an entry with `name: local_infile` and `value: \"off\"` for instances whose `database_version` contains \"MYSQL\". Resources missing this flag or with `local_infile` set to any value other than `\"off\"` are flagged as insecure.\n\nSecure configuration example:\n\n```yaml\n- name: create MySQL Cloud SQL instance with local_infile disabled\n google.cloud.gcp_sql_instance:\n name: my-instance\n database_version: MYSQL_5_7\n settings:\n database_flags:\n - name: local_infile\n value: \"off\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/mysql_instance_with_local_infile_on", "platform": "Ansible", "descriptionID": "16bc53a4", diff --git a/assets/queries/ansible/gcp/mysql_instance_with_local_infile_on/test/positive_expected_result.json b/assets/queries/ansible/gcp/mysql_instance_with_local_infile_on/test/positive_expected_result.json index ca7bdf90..3b4d9552 100644 --- a/assets/queries/ansible/gcp/mysql_instance_with_local_infile_on/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/mysql_instance_with_local_infile_on/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "MySQL Instance With Local Infile On", + "queryName": "MySQL instance with local infile on", "severity": "HIGH", "line": 10 } diff --git a/assets/queries/ansible/gcp/network_policy_disabled/metadata.json b/assets/queries/ansible/gcp/network_policy_disabled/metadata.json index 5aac1f37..bc914731 100644 --- a/assets/queries/ansible/gcp/network_policy_disabled/metadata.json +++ b/assets/queries/ansible/gcp/network_policy_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "98e04ca0-34f5-4c74-8fec-d2e611ce2790", - "queryName": "Network Policy Disabled", + "queryName": "Network policy disabled", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Kubernetes Engine Clusters must have Network Policy enabled, meaning that the attribute 'network_policy.enabled' must be true and the attribute 'addons_config.network_policy_config.disabled' must be false", + "descriptionText": "Kubernetes Engine clusters must have network policy enabled to enforce pod-to-pod network segmentation and limit lateral movement. Without it, workloads can communicate unrestricted and a compromised pod could access other services or sensitive data.\n\nFor Ansible-managed GKE clusters using `google.cloud.gcp_container_cluster` or `gcp_container_cluster`, the `network_policy.enabled` property must be `true` and `addons_config.network_policy_config.disabled` must be `false`. Resources missing the `network_policy` or `addons_config.network_policy_config` blocks, or with `network_policy.enabled` set to `false` or `addons_config.network_policy_config.disabled` set to `true`, are flagged as misconfigured.\n\nSecure Ansible configuration example:\n\n```yaml\n- name: Create GKE cluster with Network Policy enabled\n google.cloud.gcp_container_cluster:\n name: my-cluster\n location: us-central1\n network_policy:\n enabled: true\n addons_config:\n network_policy_config:\n disabled: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/network_policy_disabled", "platform": "Ansible", "descriptionID": "6fc9b7a0", diff --git a/assets/queries/ansible/gcp/network_policy_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/network_policy_disabled/test/positive_expected_result.json index 422a0426..892468c5 100644 --- a/assets/queries/ansible/gcp/network_policy_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/network_policy_disabled/test/positive_expected_result.json @@ -1,27 +1,27 @@ [ { - "queryName": "Network Policy Disabled", + "queryName": "Network policy disabled", "severity": "MEDIUM", "line": 3 }, { - "queryName": "Network Policy Disabled", + "queryName": "Network policy disabled", "severity": "MEDIUM", "line": 21 }, { - "queryName": "Network Policy Disabled", + "queryName": "Network policy disabled", "severity": "MEDIUM", "line": 54 }, { - "queryName": "Network Policy Disabled", + "queryName": "Network policy disabled", "severity": "MEDIUM", "line": 73 }, { - "queryName": "Network Policy Disabled", + "queryName": "Network policy disabled", "severity": "MEDIUM", "line": 96 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/node_auto_upgrade_disabled/metadata.json b/assets/queries/ansible/gcp/node_auto_upgrade_disabled/metadata.json index 7f0c5714..8df942f9 100644 --- a/assets/queries/ansible/gcp/node_auto_upgrade_disabled/metadata.json +++ b/assets/queries/ansible/gcp/node_auto_upgrade_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "d6e10477-2e19-4bcd-b8a8-19c65b89ccdf", - "queryName": "Node Auto Upgrade Disabled", + "queryName": "Node auto-upgrade disabled", "severity": "MEDIUM", "category": "Resource Management", - "descriptionText": "Kubernetes nodes must have auto upgrades set to true, which means Node 'auto_upgrade' should be enabled for Kubernetes Clusters", + "descriptionText": "Kubernetes node pools must have automatic node upgrades enabled so nodes receive security patches and Kubernetes version updates promptly. This reduces exposure to known vulnerabilities and version drift.\n\nFor Ansible tasks using the `google.cloud.gcp_container_node_pool` or `gcp_container_node_pool` modules, the `management.auto_upgrade` property must be defined and set to `true`. Tasks missing the `management` block, missing `management.auto_upgrade`, or with `auto_upgrade` set to `false` are flagged as insecure. Secure configuration example:\n\n```yaml\n- name: Create GKE node pool with auto-upgrade\n google.cloud.gcp_container_node_pool:\n name: my-node-pool\n cluster: my-cluster\n zone: us-central1-a\n management:\n auto_upgrade: true\n initial_node_count: 3\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/node_auto_upgrade_disabled", "platform": "Ansible", "descriptionID": "4b200606", diff --git a/assets/queries/ansible/gcp/node_auto_upgrade_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/node_auto_upgrade_disabled/test/positive_expected_result.json index 1e8d4e0b..53228ad1 100644 --- a/assets/queries/ansible/gcp/node_auto_upgrade_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/node_auto_upgrade_disabled/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "Node Auto Upgrade Disabled", + "queryName": "Node auto-upgrade disabled", "severity": "MEDIUM", "line": 3 }, { - "queryName": "Node Auto Upgrade Disabled", + "queryName": "Node auto-upgrade disabled", "severity": "MEDIUM", "line": 22 }, { - "queryName": "Node Auto Upgrade Disabled", + "queryName": "Node auto-upgrade disabled", "severity": "MEDIUM", "line": 36 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/oslogin_is_disabled_for_vm_instance/metadata.json b/assets/queries/ansible/gcp/oslogin_is_disabled_for_vm_instance/metadata.json index b50aca66..bc1ee1f4 100644 --- a/assets/queries/ansible/gcp/oslogin_is_disabled_for_vm_instance/metadata.json +++ b/assets/queries/ansible/gcp/oslogin_is_disabled_for_vm_instance/metadata.json @@ -1,9 +1,9 @@ { "id": "66dae697-507b-4aef-be18-eec5bd707f33", - "queryName": "OSLogin Is Disabled In VM Instance", + "queryName": "OSLogin is disabled in VM instance", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "VM instance should have OSLogin enabled", + "descriptionText": "OS Login should be enabled on Google Compute VM instances to centralize SSH access control via IAM and avoid unmanaged, long-lived SSH keys on individual instances. For Ansible-managed instances using the `google.cloud.gcp_compute_instance` or `gcp_compute_instance` modules, set the `metadata.enable-oslogin` property to `true`. Resources missing the `enable-oslogin` metadata key or with a value that does not evaluate to Ansible true are flagged. \n\nSecure configuration example:\n\n```yaml\n- name: create instance with OS Login enabled\n google.cloud.gcp_compute_instance:\n name: my-instance\n zone: us-central1-a\n metadata:\n enable-oslogin: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/oslogin_is_disabled_for_vm_instance", "platform": "Ansible", "descriptionID": "2cc130e4", diff --git a/assets/queries/ansible/gcp/oslogin_is_disabled_for_vm_instance/test/positive_expected_result.json b/assets/queries/ansible/gcp/oslogin_is_disabled_for_vm_instance/test/positive_expected_result.json index e4f4e399..a5513d28 100644 --- a/assets/queries/ansible/gcp/oslogin_is_disabled_for_vm_instance/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/oslogin_is_disabled_for_vm_instance/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ - { - "queryName": "OSLogin Is Disabled In VM Instance", - "severity": "MEDIUM", - "line": 5 - } + { + "queryName": "OSLogin is disabled in VM instance", + "severity": "MEDIUM", + "line": 5 + } ] diff --git a/assets/queries/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on/metadata.json b/assets/queries/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on/metadata.json index f2f533d7..c068ba4a 100644 --- a/assets/queries/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on/metadata.json +++ b/assets/queries/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on/metadata.json @@ -1,9 +1,9 @@ { "id": "89afe3f0-4681-4ce3-89ed-896cebd4277c", - "queryName": "PostgreSQL log_checkpoints Flag Not Set To ON", + "queryName": "PostgreSQL log_checkpoints flag not set to on", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "PostgreSQL database instance should have a 'log_checkpoints' flag with its value set to 'on'", + "descriptionText": "PostgreSQL Cloud SQL instances must have the `log_checkpoints` flag enabled so checkpoint events are recorded. Without these logs, crash recovery and forensic analysis are hindered, making it harder to detect or investigate anomalous or destructive activity.\n\nFor Ansible tasks using `google.cloud.gcp_sql_instance` or `gcp_sql_instance`, the `settings.databaseFlags` list must include an entry with `name: log_checkpoints` and `value: on`. Tasks that omit the `settings` block, omit `databaseFlags`, or have `log_checkpoints` set to any value other than `on` are flagged.\n\nSecure example configuration in an Ansible task:\n\n```yaml\n- name: Create Cloud SQL PostgreSQL instance with checkpoint logging\n google.cloud.gcp_sql_instance:\n name: my-postgres-instance\n database_version: POSTGRES_13\n settings:\n databaseFlags:\n - name: log_checkpoints\n value: on\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on", "platform": "Ansible", "descriptionID": "4cdc64c3", diff --git a/assets/queries/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on/test/positive_expected_result.json b/assets/queries/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on/test/positive_expected_result.json index f8fa12d4..56acca22 100644 --- a/assets/queries/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ - { - "queryName": "PostgreSQL log_checkpoints Flag Not Set To ON", - "severity": "MEDIUM", - "line": 5 - }, - { - "queryName": "PostgreSQL log_checkpoints Flag Not Set To ON", - "severity": "MEDIUM", - "line": 16 - } + { + "queryName": "PostgreSQL log_checkpoints flag not set to on", + "severity": "MEDIUM", + "line": 5 + }, + { + "queryName": "PostgreSQL log_checkpoints flag not set to on", + "severity": "MEDIUM", + "line": 16 + } ] diff --git a/assets/queries/ansible/gcp/postgresql_log_connections_disabled/metadata.json b/assets/queries/ansible/gcp/postgresql_log_connections_disabled/metadata.json index b523a1b2..0b2636c2 100644 --- a/assets/queries/ansible/gcp/postgresql_log_connections_disabled/metadata.json +++ b/assets/queries/ansible/gcp/postgresql_log_connections_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "d7a5616f-0a3f-4d43-bc2b-29d1a183e317", - "queryName": "PostgreSQL Log Connections Disabled", + "queryName": "PostgreSQL log connections disabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "PostgreSQL database instance should have a 'log_connections' flag with its value set to 'on'", + "descriptionText": "PostgreSQL Cloud SQL instances must have the `log_connections` flag set to `on` so connection events are recorded for auditing and to help detect suspicious or unauthorized access. For Ansible resources using `google.cloud.gcp_sql_instance` or `gcp_sql_instance`, the `settings.databaseFlags` property must include an entry with `name: log_connections` and `value: on`. Resources missing `settings` or `settings.databaseFlags`, or where `log_connections` is absent or set to `off`, are flagged.\n\nSecure Ansible example:\n\n```yaml\n- name: Create PostgreSQL Cloud SQL instance with connection logging enabled\n google.cloud.gcp_sql_instance:\n name: my-postgres-instance\n database_version: POSTGRES_13\n settings:\n tier: db-custom-1-3840\n databaseFlags:\n - name: log_connections\n value: \"on\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/postgresql_log_connections_disabled", "platform": "Ansible", "descriptionID": "f22853f8", diff --git a/assets/queries/ansible/gcp/postgresql_log_connections_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/postgresql_log_connections_disabled/test/positive_expected_result.json index 6cd81a17..dba08e08 100644 --- a/assets/queries/ansible/gcp/postgresql_log_connections_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/postgresql_log_connections_disabled/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "PostgreSQL Log Connections Disabled", + "queryName": "PostgreSQL log connections disabled", "severity": "MEDIUM", "line": 5 }, { - "queryName": "PostgreSQL Log Connections Disabled", + "queryName": "PostgreSQL log connections disabled", "severity": "MEDIUM", "line": 16 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/postgresql_logging_of_temporary_files_disabled/metadata.json b/assets/queries/ansible/gcp/postgresql_logging_of_temporary_files_disabled/metadata.json index d52ad35d..82eace2a 100644 --- a/assets/queries/ansible/gcp/postgresql_logging_of_temporary_files_disabled/metadata.json +++ b/assets/queries/ansible/gcp/postgresql_logging_of_temporary_files_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "d6fae5b6-ada9-46c0-8b36-3108a2a2f77b", - "queryName": "PostgreSQL Logging Of Temporary Files Disabled", + "queryName": "PostgreSQL logging of temporary files disabled", "severity": "LOW", "category": "Observability", - "descriptionText": "PostgreSQL database 'log_temp_files' flag isn't set to '0'", + "descriptionText": "The PostgreSQL `log_temp_files` flag should be set to `0` so that all temporary file creation is logged. This provides visibility into queries that spill to disk and helps detect potential data exposure or performance issues.\n\nCheck Ansible Cloud SQL instance resources using the `google.cloud.gcp_sql_instance` or `gcp_sql_instance` modules. The `settings.database_flags` entry with `name: log_temp_files` must have `value: \"0\"`. Resources missing this flag or with a different value are flagged. In Ansible, `database_flags` is a list of name/value pairs, so specify the flag explicitly as shown below.\n\n```yaml\n- name: Create Cloud SQL instance\n google.cloud.gcp_sql_instance:\n name: my-postgres\n database_version: POSTGRES_13\n settings:\n database_flags:\n - name: log_temp_files\n value: \"0\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/postgresql_logging_of_temporary_files_disabled", "platform": "Ansible", "descriptionID": "764f0b06", diff --git a/assets/queries/ansible/gcp/postgresql_logging_of_temporary_files_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/postgresql_logging_of_temporary_files_disabled/test/positive_expected_result.json index 176293e6..d646435f 100644 --- a/assets/queries/ansible/gcp/postgresql_logging_of_temporary_files_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/postgresql_logging_of_temporary_files_disabled/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "PostgreSQL Logging Of Temporary Files Disabled", + "queryName": "PostgreSQL logging of temporary files disabled", "severity": "LOW", "line": 10 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/postgresql_misconfigured_log_messages_flag/metadata.json b/assets/queries/ansible/gcp/postgresql_misconfigured_log_messages_flag/metadata.json index ce69664b..9415a5d9 100644 --- a/assets/queries/ansible/gcp/postgresql_misconfigured_log_messages_flag/metadata.json +++ b/assets/queries/ansible/gcp/postgresql_misconfigured_log_messages_flag/metadata.json @@ -1,9 +1,9 @@ { "id": "28a757fc-3d8f-424a-90c0-4233363b2711", - "queryName": "PostgreSQL Misconfigured Log Messages Flag", + "queryName": "PostgreSQL misconfigured log messages flag", "severity": "LOW", "category": "Observability", - "descriptionText": "PostgreSQL database 'log_min_messages' flag isn't set to a valid value", + "descriptionText": "PostgreSQL instances must have the `log_min_messages` flag set to a valid verbosity level. This ensures critical database events are recorded for detection and forensic analysis, while avoiding overly verbose debug logs that can expose sensitive information.\n\nFor Ansible Google Cloud SQL resources using the `google.cloud.gcp_sql_instance` (or `gcp_sql_instance`) module, ensure `settings.database_flags` contains an entry with `name: \"log_min_messages\"` and `value` set to one of the following: `fatal`, `panic`, `log`, `error`, `warning`, `notice`, `info`, `debug1`, `debug2`, `debug3`, `debug4`, or `debug5`. Resources missing this entry or using a value outside the allowed set are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create Cloud SQL instance with secure logging\n google.cloud.gcp_sql_instance:\n name: my-sql-instance\n settings:\n database_flags:\n - name: log_min_messages\n value: warning\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/postgresql_misconfigured_log_messages_flag", "platform": "Ansible", "descriptionID": "339b0278", diff --git a/assets/queries/ansible/gcp/postgresql_misconfigured_log_messages_flag/test/positive_expected_result.json b/assets/queries/ansible/gcp/postgresql_misconfigured_log_messages_flag/test/positive_expected_result.json index 58a65f31..f4eed56b 100644 --- a/assets/queries/ansible/gcp/postgresql_misconfigured_log_messages_flag/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/postgresql_misconfigured_log_messages_flag/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "PostgreSQL Misconfigured Log Messages Flag", + "queryName": "PostgreSQL misconfigured log messages flag", "severity": "LOW", "line": 11 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/postgresql_misconfigured_logging_duration_flag/metadata.json b/assets/queries/ansible/gcp/postgresql_misconfigured_logging_duration_flag/metadata.json index a1ce0658..1cb6ea6a 100644 --- a/assets/queries/ansible/gcp/postgresql_misconfigured_logging_duration_flag/metadata.json +++ b/assets/queries/ansible/gcp/postgresql_misconfigured_logging_duration_flag/metadata.json @@ -1,9 +1,9 @@ { "id": "aed98a2a-e680-497a-8886-277cea0f4514", - "queryName": "PostgreSQL Misconfigured Logging Duration Flag", + "queryName": "PostgreSQL misconfigured logging duration flag", "severity": "LOW", "category": "Insecure Configurations", - "descriptionText": "PostgreSQL database 'log_min_duration_statement' flag isn't set to '-1'", + "descriptionText": "The PostgreSQL `log_min_duration_statement` flag controls whether SQL statements are recorded for slow queries. If it is not set to `-1`, statement text may be written to logs, increasing the risk of exposing sensitive data and creating additional compliance and log-management burden.\n\nFor Ansible-managed Cloud SQL PostgreSQL instances, ensure the `settings.database_flags` entry for `log_min_duration_statement` is present and set to `-1` in `google.cloud.gcp_sql_instance` or `gcp_sql_instance` tasks. Resources missing this flag or with a different value are flagged. Use `-1` (integer) to disable duration-based statement logging.\n\nSecure configuration example:\n\n```yaml\n- name: Create Cloud SQL PostgreSQL instance\n google.cloud.gcp_sql_instance:\n name: my-pg-instance\n database_version: POSTGRES_13\n region: us-central1\n settings:\n database_flags:\n - name: log_min_duration_statement\n value: -1\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/postgresql_misconfigured_logging_duration_flag", "platform": "Ansible", "descriptionID": "17fbbbd2", diff --git a/assets/queries/ansible/gcp/postgresql_misconfigured_logging_duration_flag/test/positive_expected_result.json b/assets/queries/ansible/gcp/postgresql_misconfigured_logging_duration_flag/test/positive_expected_result.json index 2032e1d2..7ef36fe6 100644 --- a/assets/queries/ansible/gcp/postgresql_misconfigured_logging_duration_flag/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/postgresql_misconfigured_logging_duration_flag/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "PostgreSQL Misconfigured Logging Duration Flag", + "queryName": "PostgreSQL misconfigured logging duration flag", "severity": "LOW", "line": 10 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/private_cluster_disabled/metadata.json b/assets/queries/ansible/gcp/private_cluster_disabled/metadata.json index 2c366262..00488946 100644 --- a/assets/queries/ansible/gcp/private_cluster_disabled/metadata.json +++ b/assets/queries/ansible/gcp/private_cluster_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "3b30e3d6-c99b-4318-b38f-b99db74578b5", - "queryName": "Private Cluster Disabled", + "queryName": "Private cluster disabled", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Kubernetes Clusters must be created with Private Clusters enabled, meaning the 'private_cluster_config' must be defined and the attributes 'enable_private_endpoint' and 'enable_private_nodes' must be true.", + "descriptionText": "GKE clusters must be configured as private to avoid exposing the control plane endpoint and worker nodes to the public internet. Public exposure increases the risk of unauthorized access and lateral movement.\n\nFor Ansible resources using `google.cloud.gcp_container_cluster` or `gcp_container_cluster`, the `private_cluster_config` property must be defined with `enable_private_endpoint` and `enable_private_nodes` set to `true`. Resources missing `private_cluster_config`, missing either attribute, or with either attribute set to `false` are flagged.\n\nSecure Ansible configuration example:\n\n```yaml\n- name: Create private GKE cluster\n google.cloud.gcp_container_cluster:\n name: my-cluster\n location: us-central1\n private_cluster_config:\n enable_private_nodes: true\n enable_private_endpoint: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/private_cluster_disabled", "platform": "Ansible", "descriptionID": "7b4c3b32", diff --git a/assets/queries/ansible/gcp/private_cluster_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/private_cluster_disabled/test/positive_expected_result.json index 689ed5ff..e300e93b 100644 --- a/assets/queries/ansible/gcp/private_cluster_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/private_cluster_disabled/test/positive_expected_result.json @@ -1,27 +1,27 @@ [ { - "queryName": "Private Cluster Disabled", + "queryName": "Private cluster disabled", "severity": "MEDIUM", "line": 2 }, { - "queryName": "Private Cluster Disabled", + "queryName": "Private cluster disabled", "severity": "MEDIUM", "line": 31 }, { - "queryName": "Private Cluster Disabled", + "queryName": "Private cluster disabled", "severity": "MEDIUM", "line": 48 }, { - "queryName": "Private Cluster Disabled", + "queryName": "Private cluster disabled", "severity": "MEDIUM", "line": 66 }, { - "queryName": "Private Cluster Disabled", + "queryName": "Private cluster disabled", "severity": "MEDIUM", "line": 85 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances/metadata.json b/assets/queries/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances/metadata.json index 50da33cc..73f6e306 100644 --- a/assets/queries/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances/metadata.json +++ b/assets/queries/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances/metadata.json @@ -1,9 +1,9 @@ { "id": "099b4411-d11e-4537-a0fc-146b19762a79", - "queryName": "Project-wide SSH Keys Are Enabled In VM Instances", + "queryName": "Project-wide SSH keys are enabled in VM instances", "severity": "MEDIUM", "category": "Secret Management", - "descriptionText": "VM Instance should block project-wide SSH keys", + "descriptionText": "VM instances should block project-wide SSH keys. This prevents SSH keys defined at the project level from granting access to individual instances, reducing the risk of unintended or persistent SSH access and lateral movement if project metadata or keys are compromised.\n\nFor Ansible resources using `google.cloud.gcp_compute_instance` or `gcp_compute_instance`, ensure the `metadata.block-project-ssh-keys` property is defined and set to `true`. Resources that omit the `metadata` map, omit the `block-project-ssh-keys` key, or set it to `false` are flagged. \n\nSecure configuration example for an Ansible task:\n\n```yaml\n- name: Create VM with project-wide SSH keys blocked\n google.cloud.gcp_compute_instance:\n name: my-instance\n machine_type: e2-medium\n metadata:\n block-project-ssh-keys: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances", "platform": "Ansible", "descriptionID": "bf6076f0", diff --git a/assets/queries/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances/test/positive_expected_result.json b/assets/queries/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances/test/positive_expected_result.json index d5c66eb8..fd346d5e 100644 --- a/assets/queries/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ - { - "queryName": "Project-wide SSH Keys Are Enabled In VM Instances", - "severity": "MEDIUM", - "line": 5 - }, - { - "queryName": "Project-wide SSH Keys Are Enabled In VM Instances", - "severity": "MEDIUM", - "line": 11 - }, - { - "queryName": "Project-wide SSH Keys Are Enabled In VM Instances", - "severity": "MEDIUM", - "line": 17 - } + { + "queryName": "Project-wide SSH keys are enabled in VM instances", + "severity": "MEDIUM", + "line": 5 + }, + { + "queryName": "Project-wide SSH keys are enabled in VM instances", + "severity": "MEDIUM", + "line": 11 + }, + { + "queryName": "Project-wide SSH keys are enabled in VM instances", + "severity": "MEDIUM", + "line": 17 + } ] diff --git a/assets/queries/ansible/gcp/rdp_access_is_not_restricted/metadata.json b/assets/queries/ansible/gcp/rdp_access_is_not_restricted/metadata.json index 0e0d62e3..835b8611 100644 --- a/assets/queries/ansible/gcp/rdp_access_is_not_restricted/metadata.json +++ b/assets/queries/ansible/gcp/rdp_access_is_not_restricted/metadata.json @@ -1,9 +1,9 @@ { "id": "75418eb9-39ec-465f-913c-6f2b6a80dc77", - "queryName": "RDP Access Is Not Restricted", + "queryName": "RDP access is not restricted", "severity": "HIGH", "category": "Networking and Firewall", - "descriptionText": "Check if the Google compute firewall allows unrestricted RDP access. Allowed ports should not contain RDP port 3389", + "descriptionText": "Allowing unrestricted RDP (TCP port 3389) ingress exposes hosts to automated brute-force attacks and unauthorized remote access. This rule inspects Ansible `google.cloud.gcp_compute_firewall` and `gcp_compute_firewall` tasks and flags ingress rules whose `source_ranges` include unrestricted CIDRs (for example `0.0.0.0/0` or `::/0`) and whose `allowed` entries include port `3389` (typically `ip_protocol: tcp`).\n\nThe `allowed` property must not include port `3389` for rules that permit unrestricted source ranges. Either remove or disable RDP on the firewall, or restrict `source_ranges` to trusted CIDRs. Consider using a bastion host, VPN, or identity-based access (IAP/SSM) instead of direct RDP. Resources where `direction` is ingress, `source_ranges` contains an unrestricted CIDR, and `allowed[].ports` contains `\"3389\"` are flagged.\n\nSecure example that restricts RDP to a corporate CIDR:\n\n```yaml\n- name: allow-rdp-from-corporate\n google.cloud.gcp_compute_firewall:\n name: allow-rdp-corp\n network: default\n direction: INGRESS\n source_ranges:\n - 10.0.0.0/8\n allowed:\n - ip_protocol: tcp\n ports:\n - \"3389\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/rdp_access_is_not_restricted", "platform": "Ansible", "descriptionID": "23f68cd6", diff --git a/assets/queries/ansible/gcp/rdp_access_is_not_restricted/test/positive_expected_result.json b/assets/queries/ansible/gcp/rdp_access_is_not_restricted/test/positive_expected_result.json index 54fdcecb..a9490791 100644 --- a/assets/queries/ansible/gcp/rdp_access_is_not_restricted/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/rdp_access_is_not_restricted/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "RDP Access Is Not Restricted", + "queryName": "RDP access is not restricted", "severity": "HIGH", "line": 8 }, { - "queryName": "RDP Access Is Not Restricted", + "queryName": "RDP access is not restricted", "severity": "HIGH", "line": 29 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/serial_ports_enabled_for_vm_instances/metadata.json b/assets/queries/ansible/gcp/serial_ports_enabled_for_vm_instances/metadata.json index bbf01f12..a4c376af 100644 --- a/assets/queries/ansible/gcp/serial_ports_enabled_for_vm_instances/metadata.json +++ b/assets/queries/ansible/gcp/serial_ports_enabled_for_vm_instances/metadata.json @@ -1,9 +1,9 @@ { "id": "c6fc6f29-dc04-46b6-99ba-683c01aff350", - "queryName": "Serial Ports Are Enabled For VM Instances", + "queryName": "Serial ports are enabled for VM instances", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Google Compute Engine VM instances should not enable serial ports. When enabled, anyone can access your VM, if they know the username, project ID, SSH key, instance name and zone", + "descriptionText": "Enabling the serial console on Google Compute Engine VMs grants low-level interactive access to the instance console. This can bypass network and SSH controls, allowing actors who know project or instance details to interact with or tamper with the VM.\n\nIn Ansible, check tasks using `google.cloud.gcp_compute_instance` or `gcp_compute_instance` and ensure the `metadata.serial-port-enable` property is either undefined or explicitly set to `false`. Tasks with `metadata.serial-port-enable: true` are flagged. Remediate by removing the metadata key or setting it to `false`.\n\nSecure Ansible example:\n\n```yaml\n- name: Create GCE instance with serial port disabled\n google.cloud.gcp_compute_instance:\n name: my-vm\n machine_type: e2-medium\n metadata:\n \"serial-port-enable\": false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/serial_ports_enabled_for_vm_instances", "platform": "Ansible", "descriptionID": "7f8ab7a4", diff --git a/assets/queries/ansible/gcp/serial_ports_enabled_for_vm_instances/test/positive_expected_result.json b/assets/queries/ansible/gcp/serial_ports_enabled_for_vm_instances/test/positive_expected_result.json index b6bfd1cd..87a71682 100644 --- a/assets/queries/ansible/gcp/serial_ports_enabled_for_vm_instances/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/serial_ports_enabled_for_vm_instances/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ - { - "queryName": "Serial Ports Are Enabled For VM Instances", - "severity": "MEDIUM", - "line": 5 - } + { + "queryName": "Serial ports are enabled for VM instances", + "severity": "MEDIUM", + "line": 5 + } ] diff --git a/assets/queries/ansible/gcp/shielded_vm_disabled/metadata.json b/assets/queries/ansible/gcp/shielded_vm_disabled/metadata.json index 8ac64766..0e9093d0 100644 --- a/assets/queries/ansible/gcp/shielded_vm_disabled/metadata.json +++ b/assets/queries/ansible/gcp/shielded_vm_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "18d3a83d-4414-49dc-90ea-f0387b2856cc", - "queryName": "Shielded VM Disabled", + "queryName": "Shielded VM disabled", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Compute instances must be launched with Shielded VM enabled, which means the attribute 'shielded_instance_config' must be defined and its sub attributes 'enable_secure_boot', 'enable_vtpm' and 'enable_integrity_monitoring' must be set to true", + "descriptionText": "Compute instances must have Shielded VM features enabled to protect boot integrity and prevent or detect kernel and firmware tampering.\n\nFor Ansible resources using `google.cloud.gcp_compute_instance` or `gcp_compute_instance`, the `shielded_instance_config` property must be defined with `enable_secure_boot`, `enable_vtpm`, and `enable_integrity_monitoring` set to `true`. Resources missing `shielded_instance_config` or with any of these attributes undefined or set to `false` are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create GCP compute instance with Shielded VM enabled\n google.cloud.gcp_compute_instance:\n name: my-instance\n machine_type: e2-medium\n zone: us-central1-a\n shielded_instance_config:\n enable_secure_boot: true\n enable_vtpm: true\n enable_integrity_monitoring: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/shielded_vm_disabled", "platform": "Ansible", "descriptionID": "096b3fbe", diff --git a/assets/queries/ansible/gcp/shielded_vm_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/shielded_vm_disabled/test/positive_expected_result.json index eca3f4bb..a5a9f890 100644 --- a/assets/queries/ansible/gcp/shielded_vm_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/shielded_vm_disabled/test/positive_expected_result.json @@ -1,36 +1,36 @@ [ { - "queryName": "Shielded VM Disabled", + "queryName": "Shielded VM disabled", "severity": "MEDIUM", "line": 3 }, { - "queryName": "Shielded VM Disabled", + "queryName": "Shielded VM disabled", "severity": "MEDIUM", "line": 42 }, { - "queryName": "Shielded VM Disabled", + "queryName": "Shielded VM disabled", "severity": "MEDIUM", "line": 65 }, { - "queryName": "Shielded VM Disabled", + "queryName": "Shielded VM disabled", "severity": "MEDIUM", "line": 88 }, { - "queryName": "Shielded VM Disabled", + "queryName": "Shielded VM disabled", "severity": "MEDIUM", "line": 112 }, { - "queryName": "Shielded VM Disabled", + "queryName": "Shielded VM disabled", "severity": "MEDIUM", "line": 137 }, { - "queryName": "Shielded VM Disabled", + "queryName": "Shielded VM disabled", "severity": "MEDIUM", "line": 162 } diff --git a/assets/queries/ansible/gcp/sql_db_instance_backup_disabled/metadata.json b/assets/queries/ansible/gcp/sql_db_instance_backup_disabled/metadata.json index 312014c7..64914fb3 100644 --- a/assets/queries/ansible/gcp/sql_db_instance_backup_disabled/metadata.json +++ b/assets/queries/ansible/gcp/sql_db_instance_backup_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "0c82eae2-aca0-401f-93e4-fb37a0f9e5e8", - "queryName": "SQL DB Instance Backup Disabled", + "queryName": "SQL DB instance backup disabled", "severity": "MEDIUM", "category": "Backup", - "descriptionText": "Checks if backup configuration is enabled for all Cloud SQL Database instances", + "descriptionText": "Cloud SQL instances must have backups enabled so you can recover from accidental deletion, data corruption, or ransomware. Without backups, data loss can be permanent and service restoration time increases.\n\nFor Ansible resources using `google.cloud.gcp_sql_instance` or `gcp_sql_instance`, ensure the `settings.backup_configuration.enabled` property is present and set to `true`. Resources missing `settings`, `settings.backup_configuration`, or `settings.backup_configuration.enabled`, or where `enabled` is `false`, are flagged. \n\nSecure configuration example:\n\n```yaml\n- name: Create Cloud SQL instance with backups enabled\n google.cloud.gcp_sql_instance:\n name: my-instance\n settings:\n tier: db-f1-micro\n backup_configuration:\n enabled: true\n start_time: \"03:00\"\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/sql_db_instance_backup_disabled", "platform": "Ansible", "descriptionID": "006274d4", diff --git a/assets/queries/ansible/gcp/sql_db_instance_backup_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/sql_db_instance_backup_disabled/test/positive_expected_result.json index 4beca497..884952df 100644 --- a/assets/queries/ansible/gcp/sql_db_instance_backup_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/sql_db_instance_backup_disabled/test/positive_expected_result.json @@ -1,22 +1,22 @@ [ { - "queryName": "SQL DB Instance Backup Disabled", + "queryName": "SQL DB instance backup disabled", "severity": "MEDIUM", "line": 3 }, { - "queryName": "SQL DB Instance Backup Disabled", + "queryName": "SQL DB instance backup disabled", "severity": "MEDIUM", "line": 13 }, { - "queryName": "SQL DB Instance Backup Disabled", + "queryName": "SQL DB instance backup disabled", "severity": "MEDIUM", "line": 24 }, { - "queryName": "SQL DB Instance Backup Disabled", + "queryName": "SQL DB instance backup disabled", "severity": "MEDIUM", "line": 38 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/sql_db_instance_is_publicly_accessible/metadata.json b/assets/queries/ansible/gcp/sql_db_instance_is_publicly_accessible/metadata.json index 31b72f5f..a045940e 100644 --- a/assets/queries/ansible/gcp/sql_db_instance_is_publicly_accessible/metadata.json +++ b/assets/queries/ansible/gcp/sql_db_instance_is_publicly_accessible/metadata.json @@ -1,9 +1,9 @@ { "id": "7d7054c0-3a52-4e9b-b9ff-cbfe16a2378b", - "queryName": "SQL DB Instance Publicly Accessible", + "queryName": "SQL DB instance publicly accessible", "severity": "CRITICAL", "category": "Insecure Configurations", - "descriptionText": "Cloud SQL instances should not be publicly accessible.", + "descriptionText": "Cloud SQL instances must not be publicly accessible. Allowing access from `0.0.0.0/0` or enabling public IPv4 without restricted networks exposes databases to unauthorized access and data exfiltration.\n\nFor Ansible tasks using the `google.cloud.gcp_sql_instance` or `gcp_sql_instance` module, ensure `settings.ip_configuration.authorized_networks` does not contain an entry with `value: \"0.0.0.0\"`. Authorized networks should be explicit trusted CIDRs. If no `authorized_networks` are defined, `settings.ip_configuration.ipv4_enabled` must be set to `false` (or omitted/disabled) to prevent public IPv4 access. Resources missing `settings.ip_configuration` should be defined with a restricted `authorized_networks` list or have `ipv4_enabled: false`. Instances with `value` set to `\"0.0.0.0\"` or with IPv4 enabled and no authorized networks are flagged.\n\nSecure configuration examples:\n\n```yaml\n- name: create Cloud SQL instance with restricted authorized networks\n google.cloud.gcp_sql_instance:\n name: my-sql\n settings:\n ip_configuration:\n authorized_networks:\n - name: office\n value: 203.0.113.0/24\n ipv4_enabled: true\n```\n\n```yaml\n- name: create Cloud SQL instance without public IPv4\n google.cloud.gcp_sql_instance:\n name: my-sql\n settings:\n ip_configuration:\n ipv4_enabled: false\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/sql_db_instance_is_publicly_accessible", "platform": "Ansible", "descriptionID": "c1bb13ce", diff --git a/assets/queries/ansible/gcp/sql_db_instance_is_publicly_accessible/test/positive_expected_result.json b/assets/queries/ansible/gcp/sql_db_instance_is_publicly_accessible/test/positive_expected_result.json index 895e3ab0..c1f9e1c8 100644 --- a/assets/queries/ansible/gcp/sql_db_instance_is_publicly_accessible/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/sql_db_instance_is_publicly_accessible/test/positive_expected_result.json @@ -1,17 +1,17 @@ [ { - "queryName": "SQL DB Instance Publicly Accessible", + "queryName": "SQL DB instance publicly accessible", "severity": "CRITICAL", "line": 12 }, { - "queryName": "SQL DB Instance Publicly Accessible", + "queryName": "SQL DB instance publicly accessible", "severity": "CRITICAL", "line": 24 }, { - "queryName": "SQL DB Instance Publicly Accessible", + "queryName": "SQL DB instance publicly accessible", "severity": "CRITICAL", "line": 34 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/sql_db_instance_with_ssl_disabled/metadata.json b/assets/queries/ansible/gcp/sql_db_instance_with_ssl_disabled/metadata.json index 4f94b9bd..0330b63c 100644 --- a/assets/queries/ansible/gcp/sql_db_instance_with_ssl_disabled/metadata.json +++ b/assets/queries/ansible/gcp/sql_db_instance_with_ssl_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "d0f7da39-a2d5-4c78-bb85-4b7f338b3cbb", - "queryName": "SQL DB Instance With SSL Disabled", + "queryName": "SQL DB instance with SSL disabled", "severity": "HIGH", "category": "Encryption", - "descriptionText": "Cloud SQL Database Instance should have SSL enabled", + "descriptionText": "Cloud SQL instances must require SSL for client connections to protect data in transit and prevent unauthorized or unencrypted access to the database. In Ansible tasks using the `google.cloud.gcp_sql_instance` or `gcp_sql_instance` module, the `settings.ip_configuration.require_ssl` property must be set to `true`. Resources that omit `settings.ip_configuration.require_ssl` or set it to `false` are flagged as a misconfiguration.\n\nSecure Ansible task example:\n\n```yaml\n- name: Create Cloud SQL instance with SSL required\n google.cloud.gcp_sql_instance:\n project: my-project\n name: my-sql-instance\n settings:\n tier: db-f1-micro\n ip_configuration:\n require_ssl: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/sql_db_instance_with_ssl_disabled", "platform": "Ansible", "descriptionID": "50bb06d6", diff --git a/assets/queries/ansible/gcp/sql_db_instance_with_ssl_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/sql_db_instance_with_ssl_disabled/test/positive_expected_result.json index f5797092..2d85963b 100644 --- a/assets/queries/ansible/gcp/sql_db_instance_with_ssl_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/sql_db_instance_with_ssl_disabled/test/positive_expected_result.json @@ -1,21 +1,21 @@ [ { - "queryName": "SQL DB Instance With SSL Disabled", + "queryName": "SQL DB instance with SSL disabled", "severity": "HIGH", "line": 3 }, { - "queryName": "SQL DB Instance With SSL Disabled", + "queryName": "SQL DB instance with SSL disabled", "severity": "HIGH", "line": 13 }, { - "queryName": "SQL DB Instance With SSL Disabled", + "queryName": "SQL DB instance with SSL disabled", "severity": "HIGH", "line": 24 }, { - "queryName": "SQL DB Instance With SSL Disabled", + "queryName": "SQL DB instance with SSL disabled", "severity": "HIGH", "line": 39 } diff --git a/assets/queries/ansible/gcp/ssh_access_is_not_restricted/metadata.json b/assets/queries/ansible/gcp/ssh_access_is_not_restricted/metadata.json index c9d52aa8..5eb49b59 100644 --- a/assets/queries/ansible/gcp/ssh_access_is_not_restricted/metadata.json +++ b/assets/queries/ansible/gcp/ssh_access_is_not_restricted/metadata.json @@ -1,9 +1,9 @@ { "id": "b2fbf1df-76dd-4d78-a6c0-e538f4a9b016", - "queryName": "SSH Access Is Not Restricted", + "queryName": "SSH access is not restricted", "severity": "MEDIUM", "category": "Networking and Firewall", - "descriptionText": "Google Firewall should not allow SSH access (port 22) from the Internet (public CIDR block) to ensure the principle of least privileges", + "descriptionText": "Allowing SSH (port 22) from the public Internet exposes instances to brute-force attacks and unauthorized access. This can lead to credential compromise and lateral movement across your network.\n\nIn Ansible tasks using the `google.cloud.gcp_compute_firewall` or `gcp_compute_firewall` modules, this rule flags ingress rules where `source_ranges` includes `0.0.0.0/0` or `::/0` and an `allowed` entry specifies port `22` (for example, `allowed[].ip_protocol='tcp'` and `allowed[].ports` contains `22`).\n\nRestrict SSH access to specific trusted CIDR ranges, place SSH behind a bastion host or VPN, or use identity-aware access methods instead of allowing unrestricted Internet access.\n\nSecure example restricting SSH to a single admin IP:\n\n```yaml\n- name: allow-ssh-from-admin\n google.cloud.gcp_compute_firewall:\n name: allow-ssh-from-admin\n network: default\n direction: INGRESS\n source_ranges:\n - 203.0.113.5/32\n allowed:\n - ip_protocol: tcp\n ports: ['22']\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/ssh_access_is_not_restricted", "platform": "Ansible", "descriptionID": "1b0564ad", diff --git a/assets/queries/ansible/gcp/ssh_access_is_not_restricted/test/positive_expected_result.json b/assets/queries/ansible/gcp/ssh_access_is_not_restricted/test/positive_expected_result.json index 87ae2e25..3c7b1be7 100644 --- a/assets/queries/ansible/gcp/ssh_access_is_not_restricted/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/ssh_access_is_not_restricted/test/positive_expected_result.json @@ -1,6 +1,6 @@ [ { - "queryName": "SSH Access Is Not Restricted", + "queryName": "SSH access is not restricted", "severity": "MEDIUM", "line": 6 } diff --git a/assets/queries/ansible/gcp/stackdriver_logging_disabled/metadata.json b/assets/queries/ansible/gcp/stackdriver_logging_disabled/metadata.json index 6fa96ca2..719c7dcd 100644 --- a/assets/queries/ansible/gcp/stackdriver_logging_disabled/metadata.json +++ b/assets/queries/ansible/gcp/stackdriver_logging_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "19c9e2a0-fc33-4264-bba1-e3682661e8f7", - "queryName": "Stackdriver Logging Disabled", + "queryName": "Stackdriver logging disabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Kubernetes Engine Clusters must have Stackdriver Logging enabled, which means the attribute 'logging_service' must be defined and different from 'none'", + "descriptionText": "GKE clusters must have Cloud Logging (Stackdriver) enabled so cluster control plane and node logs are centrally collected for monitoring, alerting, incident response, and forensic analysis. Without central logging, audit trails and operational diagnostics can be lost or unavailable during security investigations.\n\nFor the Ansible GCP modules `google.cloud.gcp_container_cluster` and `gcp_container_cluster`, the `logging_service` property must be defined and must not be set to `\"none\"` (case-insensitive), since `\"none\"` disables Cloud Logging. Resources missing `logging_service` or with `logging_service: \"none\"` are flagged. \n\nSecure example configuration:\n\n```yaml\n- name: Create GKE cluster with logging enabled\n google.cloud.gcp_container_cluster:\n name: my-cluster\n zone: us-central1-a\n logging_service: logging.googleapis.com\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/stackdriver_logging_disabled", "platform": "Ansible", "descriptionID": "aad4eec4", diff --git a/assets/queries/ansible/gcp/stackdriver_logging_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/stackdriver_logging_disabled/test/positive_expected_result.json index 73738c59..7f0ee19e 100644 --- a/assets/queries/ansible/gcp/stackdriver_logging_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/stackdriver_logging_disabled/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Stackdriver Logging Disabled", + "queryName": "Stackdriver logging disabled", "severity": "MEDIUM", "line": 3 }, { - "queryName": "Stackdriver Logging Disabled", + "queryName": "Stackdriver logging disabled", "severity": "MEDIUM", "line": 32 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/stackdriver_monitoring_disabled/metadata.json b/assets/queries/ansible/gcp/stackdriver_monitoring_disabled/metadata.json index b5dbc080..355b99b1 100644 --- a/assets/queries/ansible/gcp/stackdriver_monitoring_disabled/metadata.json +++ b/assets/queries/ansible/gcp/stackdriver_monitoring_disabled/metadata.json @@ -1,9 +1,9 @@ { "id": "20dcd953-a8b8-4892-9026-9afa6d05a525", - "queryName": "Stackdriver Monitoring Disabled", + "queryName": "Stackdriver monitoring disabled", "severity": "MEDIUM", "category": "Observability", - "descriptionText": "Kubernetes Engine Clusters must have Stackdriver Monitoring enabled, which means the attribute 'monitoring_service' must be defined and different than 'none'", + "descriptionText": "GKE clusters must have Cloud Monitoring (Stackdriver) enabled to provide observability and support timely incident detection and response. Disabling monitoring removes metrics and logs needed for alerting, troubleshooting, and forensic analysis.\n\nFor Ansible resources using the `google.cloud.gcp_container_cluster` or `gcp_container_cluster` modules, the `monitoring_service` property must be defined and must not be set to `'none'`. Resources that omit `monitoring_service` or explicitly set `monitoring_service: 'none'` are flagged.\n\nSecure configuration example:\n\n```yaml\n- name: Create GKE cluster with monitoring enabled\n google.cloud.gcp_container_cluster:\n name: my-cluster\n monitoring_service: monitoring.googleapis.com/kubernetes\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/stackdriver_monitoring_disabled", "platform": "Ansible", "descriptionID": "212e4955", diff --git a/assets/queries/ansible/gcp/stackdriver_monitoring_disabled/test/positive_expected_result.json b/assets/queries/ansible/gcp/stackdriver_monitoring_disabled/test/positive_expected_result.json index 325445d9..e861a1d5 100644 --- a/assets/queries/ansible/gcp/stackdriver_monitoring_disabled/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/stackdriver_monitoring_disabled/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Stackdriver Monitoring Disabled", + "queryName": "Stackdriver monitoring disabled", "severity": "MEDIUM", "line": 3 }, { - "queryName": "Stackdriver Monitoring Disabled", + "queryName": "Stackdriver monitoring disabled", "severity": "MEDIUM", "line": 32 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/gcp/using_default_service_account/metadata.json b/assets/queries/ansible/gcp/using_default_service_account/metadata.json index 775127c7..f4ef13f4 100644 --- a/assets/queries/ansible/gcp/using_default_service_account/metadata.json +++ b/assets/queries/ansible/gcp/using_default_service_account/metadata.json @@ -1,9 +1,9 @@ { "id": "2775e169-e708-42a9-9305-b58aadd2c4dd", - "queryName": "Using Default Service Account", + "queryName": "Using default service account", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Instances must not be configured to use the Default Service Account, that has full access to all Cloud APIs, which means the attribute 'service_account_email' must be defined. Additionally, it must not be empty and must also not be a default Google Compute Engine service account.", + "descriptionText": "Compute instances must not use the default Google Compute Engine service account. That account often has broad Cloud API privileges, which can lead to unintended privilege escalation or overly permissive access. For Ansible tasks using the `google.cloud.gcp_compute_instance` or `gcp_compute_instance` module with `auth_kind: serviceaccount`, the `service_account_email` property must be defined, must be a non-empty string containing an `@`, and must not reference a default Compute Engine service account (values containing `@developer.gserviceaccount.com`). Resources missing `service_account_email`, with an empty value, lacking an `@` character, or using a default developer service account are flagged.\n\nSecure example:\n\n```yaml\n- name: Create instance with explicit service account\n google.cloud.gcp_compute_instance:\n name: my-instance\n auth_kind: serviceaccount\n service_account_email: my-sa@my-project.iam.gserviceaccount.com\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/using_default_service_account", "platform": "Ansible", "descriptionID": "a5896260", diff --git a/assets/queries/ansible/gcp/using_default_service_account/test/positive_expected_result.json b/assets/queries/ansible/gcp/using_default_service_account/test/positive_expected_result.json index 8c434d44..35fe73c8 100644 --- a/assets/queries/ansible/gcp/using_default_service_account/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/using_default_service_account/test/positive_expected_result.json @@ -1,22 +1,22 @@ [ - { - "queryName": "Using Default Service Account", - "severity": "MEDIUM", - "line": 3 - }, - { - "queryName": "Using Default Service Account", - "severity": "MEDIUM", - "line": 57 - }, - { - "queryName": "Using Default Service Account", - "severity": "MEDIUM", - "line": 86 - }, - { - "queryName": "Using Default Service Account", - "severity": "MEDIUM", - "line": 115 - } + { + "queryName": "Using default service account", + "severity": "MEDIUM", + "line": 3 + }, + { + "queryName": "Using default service account", + "severity": "MEDIUM", + "line": 57 + }, + { + "queryName": "Using default service account", + "severity": "MEDIUM", + "line": 86 + }, + { + "queryName": "Using default service account", + "severity": "MEDIUM", + "line": 115 + } ] diff --git a/assets/queries/ansible/gcp/vm_with_full_cloud_access/metadata.json b/assets/queries/ansible/gcp/vm_with_full_cloud_access/metadata.json index 9229b99d..d67815f8 100644 --- a/assets/queries/ansible/gcp/vm_with_full_cloud_access/metadata.json +++ b/assets/queries/ansible/gcp/vm_with_full_cloud_access/metadata.json @@ -1,9 +1,9 @@ { "id": "bc20bbc6-0697-4568-9a73-85af1dd97bdd", - "queryName": "VM With Full Cloud Access", + "queryName": "VM with full cloud access", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "A VM instance is configured to use the default service account with full access to all Cloud APIs", + "descriptionText": "Granting the `cloud-platform` OAuth scope to a VM's service account gives that instance full access to all Google Cloud APIs. This increases the blast radius if the VM or its credentials are compromised and enables unintended lateral movement or data access.\n\nIn Ansible tasks using `google.cloud.gcp_compute_instance` or `gcp_compute_instance`, inspect the `service_accounts` property's `scopes` list and ensure it does not contain the `cloud-platform` scope (for example, `cloud-platform` or `https://www.googleapis.com/auth/cloud-platform`). Resources with `service_accounts.scopes` containing the `cloud-platform` scope are flagged.\n\nSpecify only the minimal OAuth scopes required for the workload, or avoid broad instance-level scopes by assigning appropriate IAM roles to the service account or using Workload Identity.\n\nSecure configuration example with a limited scope:\n\n```yaml\n- name: Create VM with minimal OAuth scopes\n google.cloud.gcp_compute_instance:\n name: my-instance\n machine_type: n1-standard-1\n service_accounts:\n - email: my-service-account@project.iam.gserviceaccount.com\n scopes:\n - https://www.googleapis.com/auth/compute.readonly\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/gcp/vm_with_full_cloud_access", "platform": "Ansible", "descriptionID": "5eba6fad", diff --git a/assets/queries/ansible/gcp/vm_with_full_cloud_access/test/positive_expected_result.json b/assets/queries/ansible/gcp/vm_with_full_cloud_access/test/positive_expected_result.json index ace8a7b9..0902a44a 100644 --- a/assets/queries/ansible/gcp/vm_with_full_cloud_access/test/positive_expected_result.json +++ b/assets/queries/ansible/gcp/vm_with_full_cloud_access/test/positive_expected_result.json @@ -1,7 +1,7 @@ [ { - "queryName": "VM With Full Cloud Access", + "queryName": "VM with full cloud access", "severity": "MEDIUM", "line": 7 } -] \ No newline at end of file +] diff --git a/assets/queries/ansible/general/communication_over_http/metadata.json b/assets/queries/ansible/general/communication_over_http/metadata.json index 196f8a00..5b0836da 100644 --- a/assets/queries/ansible/general/communication_over_http/metadata.json +++ b/assets/queries/ansible/general/communication_over_http/metadata.json @@ -1,9 +1,9 @@ { "id": "2e8d4922-8362-4606-8c14-aa10466a1ce3", - "queryName": "Communication Over HTTP", + "queryName": "Communication over HTTP", "severity": "MEDIUM", "category": "Insecure Configurations", - "descriptionText": "Using HTTP URLs (without encryption) could lead to security vulnerabilities and risks", + "descriptionText": "Using HTTP URLs in Ansible uri tasks exposes requests and any sensitive data (tokens, credentials, or cookies) to interception and tampering because traffic is sent in plaintext. Tasks that use the `ansible.builtin.uri` module should have a `url` property that begins with `https://`. Tasks whose `url` starts with `http://` are flagged and should be updated to use `https://` endpoints or other secure transport.\n\nSecure example:\n\n```yaml\n- name: Call API over HTTPS\n ansible.builtin.uri:\n url: \"https://api.example.com/endpoint\"\n method: GET\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/general/communication_over_http", "platform": "Ansible", "descriptionID": "04892b9b", diff --git a/assets/queries/ansible/general/communication_over_http/test/positive_expected_result.json b/assets/queries/ansible/general/communication_over_http/test/positive_expected_result.json index 82b40927..d2195c0c 100644 --- a/assets/queries/ansible/general/communication_over_http/test/positive_expected_result.json +++ b/assets/queries/ansible/general/communication_over_http/test/positive_expected_result.json @@ -1,8 +1,8 @@ [ - { - "queryName": "Communication Over HTTP", - "severity": "MEDIUM", - "line": 6, - "filename": "positive1.yaml" - } -] \ No newline at end of file + { + "queryName": "Communication over HTTP", + "severity": "MEDIUM", + "line": 6, + "filename": "positive1.yaml" + } +] diff --git a/assets/queries/ansible/general/insecure_relative_path_resolution/metadata.json b/assets/queries/ansible/general/insecure_relative_path_resolution/metadata.json index 9714c0ac..d1946ec2 100644 --- a/assets/queries/ansible/general/insecure_relative_path_resolution/metadata.json +++ b/assets/queries/ansible/general/insecure_relative_path_resolution/metadata.json @@ -1,9 +1,9 @@ { "id": "8d22ae91-6ac1-459f-95be-d37bd373f244", - "queryName": "Insecure Relative Path Resolution", + "queryName": "Insecure relative path resolution", "severity": "LOW", "category": "Best Practices", - "descriptionText": "Using relative paths can lead to unexpected behavior as the path is resolved relative to the current working directory, which can change.", + "descriptionText": "Using upward-relative src paths in Ansible copy or template tasks (for example, `../templates` or `../files`) can cause unpredictable file selection and accidental inclusion of sensitive files. The path is resolved against the current working directory, which may differ across control hosts or CI runs.\n\nThis rule examines tasks that use the modules `copy`, `win_copy`, `template`, `win_template`, `ansible.builtin.copy`, and `ansible.builtin.template`. Any task whose `src` property contains a `../` segment referencing role folders (for example, `../files`, `../templates`, `../win_templates`) is flagged.\n\nFix by placing assets in the role's `files`/`templates` directories and referencing them by name, or use absolute paths or `{{ role_path }}` when necessary so `src` does not include upward-traversal segments.\n\nSecure examples:\n\n```yaml\n- name: Deploy config file\n copy:\n src: myapp.conf\n dest: /etc/myapp/myapp.conf\n\n- name: Deploy template\n template:\n src: myapp.conf.j2\n dest: /etc/myapp/config.conf\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/general/insecure_relative_path_resolution", "platform": "Ansible", "descriptionID": "84ea91c8", diff --git a/assets/queries/ansible/general/insecure_relative_path_resolution/test/positive_expected_result.json b/assets/queries/ansible/general/insecure_relative_path_resolution/test/positive_expected_result.json index d1862587..b03e05c5 100644 --- a/assets/queries/ansible/general/insecure_relative_path_resolution/test/positive_expected_result.json +++ b/assets/queries/ansible/general/insecure_relative_path_resolution/test/positive_expected_result.json @@ -1,14 +1,14 @@ [ - { - "queryName": "Insecure Relative Path Resolution", - "severity": "LOW", - "line": 7, - "fileName": "positive1.yaml" - }, - { - "queryName": "Insecure Relative Path Resolution", - "severity": "LOW", - "line": 12, - "fileName": "positive1.yaml" - } + { + "queryName": "Insecure relative path resolution", + "severity": "LOW", + "line": 7, + "fileName": "positive1.yaml" + }, + { + "queryName": "Insecure relative path resolution", + "severity": "LOW", + "line": 12, + "fileName": "positive1.yaml" + } ] diff --git a/assets/queries/ansible/general/logging_of_sensitive_data/metadata.json b/assets/queries/ansible/general/logging_of_sensitive_data/metadata.json index 54bade97..d663660c 100644 --- a/assets/queries/ansible/general/logging_of_sensitive_data/metadata.json +++ b/assets/queries/ansible/general/logging_of_sensitive_data/metadata.json @@ -1,9 +1,9 @@ { "id": "59029ddf-e651-412b-ae7b-ff6d403184bc", - "queryName": "Logging of Sensitive Data", + "queryName": "Logging of sensitive data", "severity": "LOW", "category": "Best Practices", - "descriptionText": "To keep sensitive values out of logs, tasks that expose them need to be marked defining 'no_log' and setting to True", + "descriptionText": "Tasks that create or modify users and set a `password` can emit plaintext credentials in playbook output and logs, risking credential leakage. For `ansible.builtin.user` tasks that include the `password` property, the task-level `no_log` attribute must be set to `true`. Tasks missing `no_log` or with `no_log: false` are flagged by this rule. Apply `no_log: true` to any task that handles plaintext secrets or templated variables that resolve to secrets.\n\n```yaml\n- name: Create application user without exposing password\n ansible.builtin.user:\n name: appuser\n password: \"{{ appuser_password }}\"\n no_log: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/general/logging_of_sensitive_data", "platform": "Ansible", "descriptionID": "a700e724", diff --git a/assets/queries/ansible/general/logging_of_sensitive_data/test/positive_expected_result.json b/assets/queries/ansible/general/logging_of_sensitive_data/test/positive_expected_result.json index fd7f628c..53ed8387 100644 --- a/assets/queries/ansible/general/logging_of_sensitive_data/test/positive_expected_result.json +++ b/assets/queries/ansible/general/logging_of_sensitive_data/test/positive_expected_result.json @@ -1,14 +1,14 @@ [ - { - "queryName": "Logging of Sensitive Data", - "severity": "LOW", - "line": 14, - "fileName": "positive1.yaml" - }, - { - "queryName": "Logging of Sensitive Data", - "severity": "LOW", - "line": 5, - "fileName": "positive2.yaml" - } -] \ No newline at end of file + { + "queryName": "Logging of sensitive data", + "severity": "LOW", + "line": 14, + "fileName": "positive1.yaml" + }, + { + "queryName": "Logging of sensitive data", + "severity": "LOW", + "line": 5, + "fileName": "positive2.yaml" + } +] diff --git a/assets/queries/ansible/general/privilege_escalation_using_become_plugin/metadata.json b/assets/queries/ansible/general/privilege_escalation_using_become_plugin/metadata.json index d2f67aaf..85e82f1e 100644 --- a/assets/queries/ansible/general/privilege_escalation_using_become_plugin/metadata.json +++ b/assets/queries/ansible/general/privilege_escalation_using_become_plugin/metadata.json @@ -1,9 +1,9 @@ { "id": "0e75052f-cc02-41b8-ac39-a78017527e95", - "queryName": "Privilege Escalation Using Become Plugin", + "queryName": "Privilege escalation using become plugin", "severity": "MEDIUM", "category": "Access Control", - "descriptionText": "In order to perform an action as a different user with the become_user, 'become' must be defined and set to 'true'", + "descriptionText": "Playbooks and tasks that specify a target user with `become_user` must also enable privilege escalation so actions execute with the intended elevated privileges. Without `become: true`, commands run as the unprivileged connection user or fail. This can lead to misconfiguration, failed security controls, or unintended access to sensitive resources. Verify the `become` property is defined and set to `true` on `ansible_playbook` and `ansible_task` resources whenever `become_user` is present. Resources where `become_user` is defined but `become` is missing or `false` are flagged for correction.\n\nSecure examples:\n\n```yaml\n- hosts: servers\n become: true\n become_user: root\n tasks:\n - name: Perform privileged action\n command: /usr/bin/some-command\n```\n\n```yaml\n- name: Install package\n become: true\n become_user: root\n apt:\n name: nginx\n state: present\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/general/privilege_escalation_using_become_plugin", "platform": "Ansible", "descriptionID": "11502e38", diff --git a/assets/queries/ansible/general/privilege_escalation_using_become_plugin/test/positive_expected_result.json b/assets/queries/ansible/general/privilege_escalation_using_become_plugin/test/positive_expected_result.json index 9f87ac08..cddb7cea 100644 --- a/assets/queries/ansible/general/privilege_escalation_using_become_plugin/test/positive_expected_result.json +++ b/assets/queries/ansible/general/privilege_escalation_using_become_plugin/test/positive_expected_result.json @@ -1,38 +1,38 @@ [ - { - "queryName": "Privilege Escalation Using Become Plugin", - "severity": "MEDIUM", - "line": 4, - "fileName": "positive1.yaml" - }, - { - "queryName": "Privilege Escalation Using Become Plugin", - "severity": "MEDIUM", - "line": 15, - "fileName": "positive1.yaml" - }, - { - "queryName": "Privilege Escalation Using Become Plugin", - "severity": "MEDIUM", - "line": 31, - "fileName": "positive1.yaml" - }, - { - "queryName": "Privilege Escalation Using Become Plugin", - "severity": "MEDIUM", - "line": 44, - "fileName": "positive1.yaml" - }, - { - "queryName": "Privilege Escalation Using Become Plugin", - "severity": "MEDIUM", - "line": 53, - "fileName": "positive1.yaml" - }, - { - "queryName": "Privilege Escalation Using Become Plugin", - "severity": "MEDIUM", - "line": 61, - "fileName": "positive1.yaml" - } -] \ No newline at end of file + { + "queryName": "Privilege escalation using become plugin", + "severity": "MEDIUM", + "line": 4, + "fileName": "positive1.yaml" + }, + { + "queryName": "Privilege escalation using become plugin", + "severity": "MEDIUM", + "line": 15, + "fileName": "positive1.yaml" + }, + { + "queryName": "Privilege escalation using become plugin", + "severity": "MEDIUM", + "line": 31, + "fileName": "positive1.yaml" + }, + { + "queryName": "Privilege escalation using become plugin", + "severity": "MEDIUM", + "line": 44, + "fileName": "positive1.yaml" + }, + { + "queryName": "Privilege escalation using become plugin", + "severity": "MEDIUM", + "line": 53, + "fileName": "positive1.yaml" + }, + { + "queryName": "Privilege escalation using become plugin", + "severity": "MEDIUM", + "line": 61, + "fileName": "positive1.yaml" + } +] diff --git a/assets/queries/ansible/general/risky_file_permissions/metadata.json b/assets/queries/ansible/general/risky_file_permissions/metadata.json index b231c77d..0f0dc984 100644 --- a/assets/queries/ansible/general/risky_file_permissions/metadata.json +++ b/assets/queries/ansible/general/risky_file_permissions/metadata.json @@ -1,9 +1,9 @@ { "id": "88841d5c-d22d-4b7e-a6a0-89ca50e44b9f", - "queryName": "Risky File Permissions", + "queryName": "Risky file permissions", "severity": "LOW", "category": "Supply-Chain", - "descriptionText": "Some modules could end up creating new files on disk with permissions that might be too open or unpredictable", + "descriptionText": "Files and directories created or modified by Ansible tasks must have explicit, least-privilege file modes. Omitting the `mode` or relying on preserved/default permissions can leave artifacts world-readable or writable, increasing the risk of data exposure and privilege escalation.\n\nThis rule checks file-related modules—`archive`, `assemble`, `copy`, `file`, `get_url`, `template` (including their FQCNs) and content-creation modules like `htpasswd` and `ini_file`. Tasks that create files (task `state` not `absent`/`link`) without defining the `mode` property are flagged.\n\nFor modules that provide a `create` boolean (for example `htpasswd` and `ini_file`), tasks where `create` is true (or defaults to true) and `mode` is not defined are also flagged. `mode: preserve` is only allowed for `copy` and `template` modules. Any other module using `mode: preserve` is reported as invalid.\n\nTo remediate, add an explicit `mode` with an appropriate octal value, or set `create: false` when creation is not desired.\n\n```yaml\n- name: Create config file with restrictive permissions\n ansible.builtin.file:\n path: /etc/myapp/config.yml\n state: file\n mode: '0640'\n\n- name: Create ini file with explicit mode\n community.general.ini_file:\n path: /etc/myapp/settings.ini\n create: true\n mode: '0640'\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/general/risky_file_permissions", "platform": "Ansible", "descriptionID": "1f0e1485", diff --git a/assets/queries/ansible/general/risky_file_permissions/test/positive_expected_result.json b/assets/queries/ansible/general/risky_file_permissions/test/positive_expected_result.json index 885e32e9..6fc5f863 100644 --- a/assets/queries/ansible/general/risky_file_permissions/test/positive_expected_result.json +++ b/assets/queries/ansible/general/risky_file_permissions/test/positive_expected_result.json @@ -1,62 +1,62 @@ [ - { - "queryName": "Risky File Permissions", - "severity": "LOW", - "file": "positive1.yaml", - "line": 5 - }, - { - "queryName": "Risky File Permissions", - "severity": "LOW", - "file": "positive1.yaml", - "line": 13 - }, - { - "queryName": "Risky File Permissions", - "severity": "LOW", - "file": "positive1.yaml", - "line": 17 - }, - { - "queryName": "Risky File Permissions", - "severity": "LOW", - "file": "positive1.yaml", - "line": 25 - }, - { - "queryName": "Risky File Permissions", - "severity": "LOW", - "file": "positive1.yaml", - "line": 29 - }, - { - "queryName": "Risky File Permissions", - "severity": "LOW", - "file": "positive1.yaml", - "line": 38 - }, - { - "queryName": "Risky File Permissions", - "severity": "LOW", - "file": "positive1.yaml", - "line": 46 - }, - { - "queryName": "Risky File Permissions", - "severity": "LOW", - "file": "positive1.yaml", - "line": 55 - }, - { - "queryName": "Risky File Permissions", - "severity": "LOW", - "file": "positive1.yaml", - "line": 64 - }, - { - "queryName": "Risky File Permissions", - "severity": "LOW", - "file": "positive1.yaml", - "line": 74 - } -] \ No newline at end of file + { + "queryName": "Risky file permissions", + "severity": "LOW", + "file": "positive1.yaml", + "line": 5 + }, + { + "queryName": "Risky file permissions", + "severity": "LOW", + "file": "positive1.yaml", + "line": 13 + }, + { + "queryName": "Risky file permissions", + "severity": "LOW", + "file": "positive1.yaml", + "line": 17 + }, + { + "queryName": "Risky file permissions", + "severity": "LOW", + "file": "positive1.yaml", + "line": 25 + }, + { + "queryName": "Risky file permissions", + "severity": "LOW", + "file": "positive1.yaml", + "line": 29 + }, + { + "queryName": "Risky file permissions", + "severity": "LOW", + "file": "positive1.yaml", + "line": 38 + }, + { + "queryName": "Risky file permissions", + "severity": "LOW", + "file": "positive1.yaml", + "line": 46 + }, + { + "queryName": "Risky file permissions", + "severity": "LOW", + "file": "positive1.yaml", + "line": 55 + }, + { + "queryName": "Risky file permissions", + "severity": "LOW", + "file": "positive1.yaml", + "line": 64 + }, + { + "queryName": "Risky file permissions", + "severity": "LOW", + "file": "positive1.yaml", + "line": 74 + } +] diff --git a/assets/queries/ansible/general/unpinned_package_version/metadata.json b/assets/queries/ansible/general/unpinned_package_version/metadata.json index 69b134ca..f9ffccd4 100644 --- a/assets/queries/ansible/general/unpinned_package_version/metadata.json +++ b/assets/queries/ansible/general/unpinned_package_version/metadata.json @@ -1,9 +1,9 @@ { "id": "c05e2c20-0a2c-4686-b1f8-5f0a5612d4e8", - "queryName": "Unpinned Package Version", + "queryName": "Unpinned package version", "severity": "LOW", "category": "Supply-Chain", - "descriptionText": "Setting state to latest performs an update and installs additional packages possibly resulting in performance degradation or loss of service", + "descriptionText": "Package installer tasks that set `state: latest` without pinning a `version` or enabling `update_only` can cause unintended upgrades. This may introduce breaking changes, regressions, or service disruptions and make deployments non-reproducible.\n\nAnsible package installer modules (for example `apt`, `yum`, `dnf`, `pip`) are checked for the following task properties: `state` must not be `latest` unless a `version` is specified or `update_only` is set to `true`. Tasks with `state: latest` and no `version` and missing or `false` `update_only` are flagged.\n\nRemediate by pinning packages to explicit versions for deterministic installs, or set `update_only: true` when you only want to upgrade already-installed packages.\n\nSecure example — pin a version:\n\n```yaml\n- name: Install mypkg at a specific version\n apt:\n name: mypkg=1.2.3\n state: present\n```Secure example — allow only updates to already-installed packages:\n\n```yaml\n- name: Update installed packages only\n yum:\n name: mypkg\n state: latest\n update_only: true\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/general/unpinned_package_version", "platform": "Ansible", "descriptionID": "43e877b3", diff --git a/assets/queries/ansible/general/unpinned_package_version/test/positive_expected_result.json b/assets/queries/ansible/general/unpinned_package_version/test/positive_expected_result.json index f67cb49c..434f11d3 100644 --- a/assets/queries/ansible/general/unpinned_package_version/test/positive_expected_result.json +++ b/assets/queries/ansible/general/unpinned_package_version/test/positive_expected_result.json @@ -1,158 +1,158 @@ [ - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 8 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 13 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 18 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 23 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 29 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 34 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 40 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 44 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 50 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 55 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 60 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 65 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 74 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 79 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 84 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 89 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 94 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 101 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 106 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 111 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 116 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 121 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 130 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 136 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 144 - }, - { - "queryName": "Unpinned Package Version", - "severity": "LOW", - "filename": "positive1.yaml", - "line": 149 - } -] \ No newline at end of file + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 8 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 13 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 18 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 23 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 29 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 34 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 40 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 44 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 50 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 55 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 60 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 65 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 74 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 79 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 84 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 89 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 94 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 101 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 106 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 111 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 116 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 121 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 130 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 136 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 144 + }, + { + "queryName": "Unpinned package version", + "severity": "LOW", + "filename": "positive1.yaml", + "line": 149 + } +] diff --git a/assets/queries/ansible/hosts/ansible_tower_exposed_to_internet/metadata.json b/assets/queries/ansible/hosts/ansible_tower_exposed_to_internet/metadata.json index d103ce51..26f8e7d4 100644 --- a/assets/queries/ansible/hosts/ansible_tower_exposed_to_internet/metadata.json +++ b/assets/queries/ansible/hosts/ansible_tower_exposed_to_internet/metadata.json @@ -1,9 +1,9 @@ { "id": "1b2bf3ff-31e9-460e-bbfb-45e48f4f20cc", - "queryName": "Ansible Tower Exposed To Internet", + "queryName": "Ansible Tower exposed to the internet", "severity": "MEDIUM", "category": "Best Practices", - "descriptionText": "Avoid exposing Ansible Tower to the public internet, effectively reducing the potential attack surface of your deployment", + "descriptionText": "Ansible Tower hosts must not be assigned public IP addresses. Exposing Tower to the public internet increases the risk of unauthorized access and credential compromise of your automation infrastructure. Check the Ansible inventory resource (`ansible_inventory`) for entries under `all.children.tower.hosts` and ensure each host value is a private IP address (RFC1918) or an internal DNS name rather than a public IP. Resources with hosts set to public IPs are flagged.\n\nUse private IP ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`) or internal hostnames, and place Tower behind a VPN, bastion host, or firewall/security-group restrictions to limit exposure.\n\nSecure inventory example with a private IP:\n\n```yaml\nall:\n children:\n tower:\n hosts:\n tower.internal.example.com:\n ansible_host: 10.0.1.5\n```", "descriptionUrl": "https://docs.datadoghq.com/security/code_security/iac_security/iac_rules/ansible/hosts/ansible_tower_exposed_to_internet", "platform": "Ansible", "descriptionID": "657a8b1d", diff --git a/assets/queries/ansible/hosts/ansible_tower_exposed_to_internet/test/positive_expected_result.json b/assets/queries/ansible/hosts/ansible_tower_exposed_to_internet/test/positive_expected_result.json index 1f10687f..ab6dc1ae 100644 --- a/assets/queries/ansible/hosts/ansible_tower_exposed_to_internet/test/positive_expected_result.json +++ b/assets/queries/ansible/hosts/ansible_tower_exposed_to_internet/test/positive_expected_result.json @@ -1,12 +1,12 @@ [ { - "queryName": "Ansible Tower Exposed To Internet", + "queryName": "Ansible Tower exposed to the internet", "severity": "MEDIUM", "line": 1, "fileName": "positive1.ini" }, { - "queryName": "Ansible Tower Exposed To Internet", + "queryName": "Ansible Tower exposed to the internet", "severity": "MEDIUM", "line": 24, "fileName": "positive2.yaml" diff --git a/documentation/frontmatter.yaml b/documentation/frontmatter.yaml index 4e9e779b..a6a2f586 100644 --- a/documentation/frontmatter.yaml +++ b/documentation/frontmatter.yaml @@ -1,4 +1,689 @@ rules: + ansible: + aws: + alb_listening_on_http: + description: ALB Listening on HTTP + title: ALB Listening on HTTP + ami_not_encrypted: + description: AMI Not Encrypted + title: AMI Not Encrypted + ami_shared_with_multiple_accounts: + description: AMI Shared With Multiple Accounts + title: AMI Shared With Multiple Accounts + api_gateway_endpoint_config_is_not_private: + description: API Gateway Endpoint Config is Not Private + title: API Gateway Endpoint Config is Not Private + api_gateway_with_cloudwatch_logging_disabled: + description: API Gateway With CloudWatch Logging Disabled + title: API Gateway With CloudWatch Logging Disabled + api_gateway_without_configured_authorizer: + description: API Gateway Without Configured Authorizer + title: API Gateway Without Configured Authorizer + api_gateway_without_ssl_certificate: + description: API Gateway Without SSL Certificate + title: API Gateway Without SSL Certificate + api_gateway_without_waf: + description: API Gateway without WAF + title: API Gateway without WAF + api_gateway_xray_disabled: + description: API Gateway X-Ray Disabled + title: API Gateway X-Ray Disabled + authentication_without_mfa: + description: Authentication Without MFA + title: Authentication Without MFA + auto_scaling_group_with_no_associated_elb: + description: Auto Scaling Group With No Associated ELB + title: Auto Scaling Group With No Associated ELB + automatic_minor_upgrades_disabled: + description: Automatic Minor Upgrades Disabled + title: Automatic Minor Upgrades Disabled + aws_password_policy_with_unchangeable_passwords: + description: AWS Password Policy With Unchangeable Passwords + title: AWS Password Policy With Unchangeable Passwords + batch_job_definition_with_privileged_container_properties: + description: Batch Job Definition With Privileged Container Properties + title: Batch Job Definition With Privileged Container Properties + ca_certificate_identifier_is_outdated: + description: CA Certificate Identifier Is Outdated + title: CA Certificate Identifier Is Outdated + cdn_configuration_is_missing: + description: CDN Configuration Is Missing + title: CDN Configuration Is Missing + certificate_has_expired: + description: Certificate Has Expired + title: Certificate Has Expired + certificate_rsa_key_bytes_lower_than_256: + description: Certificate RSA Key Bytes Lower Than 256 + title: Certificate RSA Key Bytes Lower Than 256 + cloudfront_logging_disabled: + description: CloudFront Logging Disabled + title: CloudFront Logging Disabled + cloudfront_without_minimum_protocol_tls_1.2: + description: CloudFront Without Minimum Protocol TLS 1.2 + title: CloudFront Without Minimum Protocol TLS 1.2 + cloudfront_without_waf: + description: CloudFront Without WAF + title: CloudFront Without WAF + cloudtrail_log_file_validation_disabled: + description: CloudTrail Log File Validation Disabled + title: CloudTrail Log File Validation Disabled + cloudtrail_log_files_not_encrypted_with_kms: + description: CloudTrail Log Files Not Encrypted With KMS + title: CloudTrail Log Files Not Encrypted With KMS + cloudtrail_logging_disabled: + description: CloudTrail Logging Disabled + title: CloudTrail Logging Disabled + cloudtrail_multi_region_disabled: + description: CloudTrail Multi Region Disabled + title: CloudTrail Multi Region Disabled + cloudtrail_not_integrated_with_cloudwatch: + description: CloudTrail Not Integrated With CloudWatch + title: CloudTrail Not Integrated With CloudWatch + cloudtrail_sns_topic_name_undefined: + description: CloudTrail SNS Topic Name Undefined + title: CloudTrail SNS Topic Name Undefined + cloudwatch_without_retention_period_specified: + description: CloudWatch Without Retention Period Specified + title: CloudWatch Without Retention Period Specified + cmk_is_unusable: + description: CMK Is Unusable + title: CMK Is Unusable + cmk_rotation_disabled: + description: CMK Rotation Disabled + title: CMK Rotation Disabled + codebuild_not_encrypted: + description: CodeBuild Not Encrypted + title: CodeBuild Not Encrypted + config_configuration_aggregator_to_all_regions_disabled: + description: Configuration Aggregator to All Regions Disabled + title: Configuration Aggregator to All Regions Disabled + config_rule_for_encrypted_volumes_is_disabled: + description: Config Rule For Encrypted Volumes Disabled + title: Config Rule For Encrypted Volumes Disabled + cross_account_iam_assume_role_policy_without_external_id_or_mfa: + description: Cross-Account IAM Assume Role Policy Without ExternalId or MFA + title: Cross-Account IAM Assume Role Policy Without ExternalId or MFA + db_instance_storage_not_encrypted: + description: DB Instance Storage Not Encrypted + title: DB Instance Storage Not Encrypted + db_security_group_open_to_large_scope: + description: DB Security Group Open To Large Scope + title: DB Security Group Open To Large Scope + db_security_group_with_public_scope: + description: DB Security Group With Public Scope + title: DB Security Group With Public Scope + default_security_groups_with_unrestricted_traffic: + description: Default Security Groups With Unrestricted Traffic + title: Default Security Groups With Unrestricted Traffic + ebs_volume_encryption_disabled: + description: EBS Volume Encryption Disabled + title: EBS Volume Encryption Disabled + ec2_group_has_public_interface: + description: EC2 Group Has Public Interface + title: EC2 Group Has Public Interface + ec2_instance_has_public_ip: + description: EC2 Instance Has Public IP + title: EC2 Instance Has Public IP + ec2_instance_using_default_security_group: + description: EC2 Instance Using Default Security Group + title: EC2 Instance Using Default Security Group + ec2_instance_using_default_vpc: + description: EC2 Instance Using Default VPC + title: EC2 Instance Using Default VPC + ec2_not_ebs_optimized: + description: EC2 Not EBS Optimized + title: EC2 Not EBS Optimized + ecr_image_tag_not_immutable: + description: ECR Image Tag Not Immutable + title: ECR Image Tag Not Immutable + ecr_repository_is_publicly_accessible: + description: ECR Repository Is Publicly Accessible + title: ECR Repository Is Publicly Accessible + ecs_service_admin_role_is_present: + description: ECS Service Admin Role Is Present + title: ECS Service Admin Role Is Present + ecs_service_without_running_tasks: + description: ECS Service Without Running Tasks + title: ECS Service Without Running Tasks + ecs_services_assigned_with_public_ip_address: + description: ECS Services assigned with public IP address + title: ECS Services assigned with public IP address + ecs_task_definition_network_mode_not_recommended: + description: ECS Task Definition Network Mode Not Recommended + title: ECS Task Definition Network Mode Not Recommended + efs_not_encrypted: + description: EFS Not Encrypted + title: EFS Not Encrypted + efs_without_kms: + description: EFS Without KMS + title: EFS Without KMS + efs_without_tags: + description: EFS Without Tags + title: EFS Without Tags + elasticache_using_default_port: + description: ElastiCache Using Default Port + title: ElastiCache Using Default Port + elasticache_without_vpc: + description: ElastiCache Without VPC + title: ElastiCache Without VPC + elasticsearch_with_https_disabled: + description: Elasticsearch with HTTPS disabled + title: Elasticsearch with HTTPS disabled + elb_using_insecure_protocols: + description: ELB Using Insecure Protocols + title: ELB Using Insecure Protocols + elb_using_weak_ciphers: + description: ELB Using Weak Ciphers + title: ELB Using Weak Ciphers + hardcoded_aws_access_key: + description: Hardcoded AWS Access Key + title: Hardcoded AWS Access Key + hardcoded_aws_access_key_in_lambda: + description: Hardcoded AWS Access Key In Lambda + title: Hardcoded AWS Access Key In Lambda + http_port_open_to_internet: + description: HTTP Port Open To Internet + title: HTTP Port Open To Internet + iam_access_key_is_exposed: + description: IAM Access Key Is Exposed + title: IAM Access Key Is Exposed + iam_database_auth_not_enabled: + description: IAM Database Auth Not Enabled + title: IAM Database Auth Not Enabled + iam_group_without_users: + description: IAM Group Without Users + title: IAM Group Without Users + iam_password_without_minimum_length: + description: IAM Password Without Minimum Length + title: IAM Password Without Minimum Length + iam_policies_attached_to_user: + description: IAM Policies Attached To User + title: IAM Policies Attached To User + iam_policies_with_full_privileges: + description: IAM Policies With Full Privileges + title: IAM Policies With Full Privileges + iam_policy_grants_assumerole_permission_across_all_services: + description: IAM Policy Grants 'AssumeRole' Permission Across All Services + title: IAM Policy Grants 'AssumeRole' Permission Across All Services + iam_policy_grants_full_permissions: + description: IAM Policy Grants Full Permissions + title: IAM Policy Grants Full Permissions + iam_role_allows_all_principals_to_assume: + description: IAM Role Allows All Principals To Assume + title: IAM Role Allows All Principals To Assume + instance_uses_metadata_service_IMDSv1: + description: Instance Uses Metadata Service IMDSv1 + title: Instance Uses Metadata Service IMDSv1 + instance_with_no_vpc: + description: Instance With No VPC + title: Instance With No VPC + kinesis_not_encrypted_with_kms: + description: Kinesis Not Encrypted With KMS + title: Kinesis Not Encrypted With KMS + kms_key_with_full_permissions: + description: KMS Key With Vulnerable Policy + title: KMS Key With Vulnerable Policy + lambda_function_without_tags: + description: Lambda Function Without Tags + title: Lambda Function Without Tags + lambda_functions_without_x-ray_tracing: + description: Lambda Functions Without X-Ray Tracing + title: Lambda Functions Without X-Ray Tracing + lambda_permission_misconfigured: + description: Lambda Permission Misconfigured + title: Lambda Permission Misconfigured + lambda_permission_principal_is_wildcard: + description: Lambda Permission Principal Is Wildcard + title: Lambda Permission Principal Is Wildcard + launch_configuration_is_not_encrypted: + description: Launch Configuration Is Not Encrypted + title: Launch Configuration Is Not Encrypted + misconfigured_password_policy_expiration: + description: Misconfigured Password Policy Expiration + title: Misconfigured Password Policy Expiration + no_stack_policy: + description: No Stack Policy + title: No Stack Policy + password_without_reuse_prevention: + description: Password Without Reuse Prevention + title: Password Without Reuse Prevention + public_lambda_via_api_gateway: + description: Public Lambda via API Gateway + title: Public Lambda via API Gateway + public_port_wide: + description: Public Port Wide + title: Public Port Wide + rds_associated_with_public_subnet: + description: RDS Associated with Public Subnet + title: RDS Associated with Public Subnet + rds_db_instance_publicly_accessible: + description: RDS DB Instance Publicly Accessible + title: RDS DB Instance Publicly Accessible + rds_using_default_port: + description: RDS Using Default Port + title: RDS Using Default Port + rds_with_backup_disabled: + description: RDS With Backup Disabled + title: RDS With Backup Disabled + redis_not_compliant: + description: Redis Not Compliant + title: Redis Not Compliant + redshift_not_encrypted: + description: Redshift Not Encrypted + title: Redshift Not Encrypted + redshift_publicly_accessible: + description: Redshift Publicly Accessible + title: Redshift Publicly Accessible + redshift_using_default_port: + description: Redshift Using Default Port + title: Redshift Using Default Port + remote_desktop_port_open: + description: Remote Desktop Port Open To Internet + title: Remote Desktop Port Open To Internet + root_account_has_active_access_keys: + description: Root Account Has Active Access Keys + title: Root Account Has Active Access Keys + route53_record_undefined: + description: Route53 Record Undefined + title: Route53 Record Undefined + s3_bucket_access_to_any_principal: + description: S3 Bucket Access to Any Principal + title: S3 Bucket Access to Any Principal + s3_bucket_acl_allows_read_to_all_users: + description: S3 Bucket ACL Allows Read to All Users + title: S3 Bucket ACL Allows Read to All Users + s3_bucket_acl_allows_read_to_any_authenticated_user: + description: S3 Bucket ACL Allows Read to Any Authenticated User + title: S3 Bucket ACL Allows Read to Any Authenticated User + s3_bucket_allows_delete_action_from_all_principals: + description: S3 Bucket Allows Delete Action From All Principals + title: S3 Bucket Allows Delete Action From All Principals + s3_bucket_allows_get_action_from_all_principals: + description: S3 Bucket Allows Get Action From All Principals + title: S3 Bucket Allows Get Action From All Principals + s3_bucket_allows_list_action_from_all_principals: + description: S3 Bucket Allows List Action From All Principals + title: S3 Bucket Allows List Action From All Principals + s3_bucket_allows_put_action_from_all_principals: + description: S3 Bucket Allows Put Action From All Principals + title: S3 Bucket Allows Put Action From All Principals + s3_bucket_logging_disabled: + description: S3 Bucket Logging Disabled + title: S3 Bucket Logging Disabled + s3_bucket_with_all_permissions: + description: S3 Bucket With All Permissions + title: S3 Bucket With All Permissions + s3_bucket_with_public_access: + description: S3 Bucket With Public Access + title: S3 Bucket With Public Access + s3_bucket_with_unsecured_cors_rule: + description: S3 Bucket with Unsecured CORS Rule + title: S3 Bucket with Unsecured CORS Rule + s3_bucket_without_server-side_encryption: + description: S3 Bucket Without Server-side-encryption + title: S3 Bucket Without Server-side-encryption + s3_bucket_without_versioning: + description: S3 Bucket Without Versioning + title: S3 Bucket Without Versioning + secure_ciphers_disabled: + description: Secure Ciphers Disabled + title: Secure Ciphers Disabled + security_group_ingress_not_restricted: + description: Security Group Ingress Not Restricted + title: Security Group Ingress Not Restricted + security_group_with_unrestricted_access_to_ssh: + description: Security Group With Unrestricted Access To SSH + title: Security Group With Unrestricted Access To SSH + ses_policy_with_allowed_iam_actions: + description: SES Policy With Allowed IAM Actions + title: SES Policy With Allowed IAM Actions + sns_topic_is_publicly_accessible: + description: SNS Topic is Publicly Accessible + title: SNS Topic is Publicly Accessible + sql_analysis_services_port_2383_is_publicly_accessible: + description: SQL Analysis Services Port 2383 (TCP) Is Publicly Accessible + title: SQL Analysis Services Port 2383 (TCP) Is Publicly Accessible + sqs_policy_allows_all_actions: + description: SQS Policy Allows All Actions + title: SQS Policy Allows All Actions + sqs_policy_with_public_access: + description: SQS Policy With Public Access + title: SQS Policy With Public Access + sqs_queue_exposed: + description: SQS Queue Exposed + title: SQS Queue Exposed + sqs_with_sse_disabled: + description: SQS With SSE Disabled + title: SQS With SSE Disabled + stack_notifications_disabled: + description: Stack Notifications Disabled + title: Stack Notifications Disabled + stack_retention_disabled: + description: Stack Retention Disabled + title: Stack Retention Disabled + stack_without_template: + description: Stack Without Template + title: Stack Without Template + unknown_port_exposed_to_internet: + description: Unknown Port Exposed To Internet + title: Unknown Port Exposed To Internet + unrestricted_security_group_ingress: + description: Unrestricted Security Group Ingress + title: Unrestricted Security Group Ingress + user_data_contains_encoded_private_key: + description: User Data Contains Encoded Private Key + title: User Data Contains Encoded Private Key + viewer_protocol_policy_allows_http: + description: Cloudfront Viewer Protocol Policy Allows HTTP + title: Cloudfront Viewer Protocol Policy Allows HTTP + vulnerable_default_ssl_certificate: + description: Vulnerable Default SSL Certificate + title: Vulnerable Default SSL Certificate + azure: + ad_admin_not_configured_for_sql_server: + description: AD Admin Not Configured For SQL Server + title: AD Admin Not Configured For SQL Server + admin_user_enabled_for_container_registry: + description: Admin User Enabled For Container Registry + title: Admin User Enabled For Container Registry + aks_monitoring_logging_disabled: + description: AKS Monitoring Logging Disabled + title: AKS Monitoring Logging Disabled + aks_network_policy_misconfigured: + description: AKS Network Policy Misconfigured + title: AKS Network Policy Misconfigured + aks_rbac_disabled: + description: AKS RBAC Disabled + title: AKS RBAC Disabled + azure_container_registry_with_no_locks: + description: Azure Container Registry With No Locks + title: Azure Container Registry With No Locks + azure_instance_using_basic_authentication: + description: Azure Instance Using Basic Authentication + title: Azure Instance Using Basic Authentication + cosmosdb_account_ip_range_filter_not_set: + description: CosmosDB Account IP Range Filter Not Set + title: CosmosDB Account IP Range Filter Not Set + cosmosdb_account_without_tags: + description: Cosmos DB Account Without Tags + title: Cosmos DB Account Without Tags + default_azure_storage_account_network_access_is_too_permissive: + description: Default Azure Storage Account Network Access Is Too Permissive + title: Default Azure Storage Account Network Access Is Too Permissive + firewall_rule_allows_too_many_hosts_to_access_redis_cache: + description: Firewall Rule Allows Too Many Hosts To Access Redis Cache + title: Firewall Rule Allows Too Many Hosts To Access Redis Cache + key_vault_soft_delete_is_disabled: + description: Key Vault Soft Delete Is Disabled + title: Key Vault Soft Delete Is Disabled + log_retention_is_not_set: + description: Log Retention Is Not Set + title: Log Retention Is Not Set + monitoring_log_profile_without_all_activities: + description: Monitoring Log Profile Without All Activities + title: Monitoring Log Profile Without All Activities + mysql_ssl_connection_disabled: + description: MySQL SSL Connection Disabled + title: MySQL SSL Connection Disabled + postgresql_log_checkpoints_disabled: + description: PostgreSQL Log Checkpoints Disabled + title: PostgreSQL Log Checkpoints Disabled + postgresql_log_connections_not_set: + description: PostgreSQL Log Connections Not Set + title: PostgreSQL Log Connections Not Set + postgresql_log_disconnections_not_set: + description: PostgreSQL Log Disconnections Not Set + title: PostgreSQL Log Disconnections Not Set + postgresql_log_duration_not_set: + description: PostgreSQL Log Duration Not Set + title: PostgreSQL Log Duration Not Set + postgresql_server_without_connection_throttling: + description: PostgreSQL Server Without Connection Throttling + title: PostgreSQL Server Without Connection Throttling + public_storage_account: + description: Public Storage Account + title: Public Storage Account + redis_cache_allows_non_ssl_connections: + description: Redis Cache Allows Non SSL Connections + title: Redis Cache Allows Non SSL Connections + redis_entirely_accessible: + description: Redis Entirely Accessible + title: Redis Entirely Accessible + redis_publicly_accessible: + description: Redis Publicly Accessible + title: Redis Publicly Accessible + role_definition_allows_custom_role_creation: + description: Role Definition Allows Custom Role Creation + title: Role Definition Allows Custom Role Creation + security_group_is_not_configured: + description: Security Group is Not Configured + title: Security Group is Not Configured + sensitive_port_is_exposed_to_entire_network: + description: Sensitive Port Is Exposed To Entire Network + title: Sensitive Port Is Exposed To Entire Network + small_activity_log_retention_period: + description: Small Activity Log Retention Period + title: Small Activity Log Retention Period + sql_server_ingress_from_any_ip: + description: SQLServer Ingress From Any IP + title: SQLServer Ingress From Any IP + sql_server_predictable_active_directory_admin_account_name: + description: SQL Server Predictable Active Directory Account Name + title: SQL Server Predictable Active Directory Account Name + sql_server_predictable_admin_account_name: + description: SQL Server Predictable Admin Account Name + title: SQL Server Predictable Admin Account Name + ssl_enforce_is_disabled: + description: SSL Enforce Disabled + title: SSL Enforce Disabled + storage_account_not_forcing_https: + description: Storage Account Not Forcing HTTPS + title: Storage Account Not Forcing HTTPS + storage_account_not_using_latest_tls_encryption_version: + description: Storage Account Not Using Latest TLS Encryption Version + title: Storage Account Not Using Latest TLS Encryption Version + storage_container_is_publicly_accessible: + description: Storage Container Is Publicly Accessible + title: Storage Container Is Publicly Accessible + trusted_microsoft_services_not_enabled: + description: Trusted Microsoft Services Not Enabled + title: Trusted Microsoft Services Not Enabled + unrestricted_sql_server_acess: + description: Unrestricted SQL Server Access + title: Unrestricted SQL Server Access + vm_not_attached_to_network: + description: VM Not Attached To Network + title: VM Not Attached To Network + waf_is_disabled_for_azure_application_gateway: + description: WAF Is Disabled For Azure Application Gateway + title: WAF Is Disabled For Azure Application Gateway + web_app_accepting_traffic_other_than_https: + description: Web App Accepting Traffic Other Than HTTPS + title: Web App Accepting Traffic Other Than HTTPS + config: + allow_unsafe_lookups_enabled_in_defaults: + description: Allow Unsafe Lookups Enabled In Defaults + title: Allow Unsafe Lookups Enabled In Defaults + communication_over_http_in_defaults: + description: Communication Over HTTP In Defaults + title: Communication Over HTTP In Defaults + logging_of_sensitive_data_in_defaults: + description: Logging of Sensitive Data In Defaults + title: Logging of Sensitive Data In Defaults + privilege_escalation_using_become_plugin_in_defaults: + description: Privilege Escalation Using Become Plugin In Defaults + title: Privilege Escalation Using Become Plugin In Defaults + gcp: + bigquery_dataset_is_public: + description: BigQuery Dataset Is Public + title: BigQuery Dataset Is Public + client_certificate_disabled: + description: Client Certificate Disabled + title: Client Certificate Disabled + cloud_dns_without_dnnsec: + description: Cloud DNS Without DNSSEC + title: Cloud DNS Without DNSSEC + cloud_sql_instance_with_contained_database_authentication_on: + description: Cloud SQL Instance With Contained Database Authentication On + title: Cloud SQL Instance With Contained Database Authentication On + cloud_sql_instance_with_cross_db_ownership_chaining_on: + description: Cloud SQL Instance With Cross DB Ownership Chaining On + title: Cloud SQL Instance With Cross DB Ownership Chaining On + cloud_storage_anonymous_or_publicly_accessible: + description: Cloud Storage Anonymous or Publicly Accessible + title: Cloud Storage Anonymous or Publicly Accessible + cloud_storage_bucket_logging_not_enabled: + description: Cloud Storage Bucket Logging Not Enabled + title: Cloud Storage Bucket Logging Not Enabled + cloud_storage_bucket_versioning_disabled: + description: Cloud Storage Bucket Versioning Disabled + title: Cloud Storage Bucket Versioning Disabled + cluster_labels_disabled: + description: Cluster Labels Disabled + title: Cluster Labels Disabled + cluster_master_authentication_disabled: + description: Cluster Master Authentication Disabled + title: Cluster Master Authentication Disabled + compute_instance_is_publicly_accessible: + description: Compute Instance Is Publicly Accessible + title: Compute Instance Is Publicly Accessible + cos_node_image_not_used: + description: COS Node Image Not Used + title: COS Node Image Not Used + disk_encryption_disabled: + description: Disk Encryption Disabled + title: Disk Encryption Disabled + dnssec_using_rsasha1: + description: DNSSEC Using RSASHA1 + title: DNSSEC Using RSASHA1 + gke_basic_authentication_enabled: + description: GKE Basic Authentication Enabled + title: GKE Basic Authentication Enabled + gke_legacy_authorization_enabled: + description: GKE Legacy Authorization Enabled + title: GKE Legacy Authorization Enabled + gke_master_authorized_networks_disabled: + description: GKE Master Authorized Networks Disabled + title: GKE Master Authorized Networks Disabled + gke_using_default_service_account: + description: GKE Using Default Service Account + title: GKE Using Default Service Account + google_compute_network_using_default_firewall_rule: + description: Google Compute Network Using Default Firewall Rule + title: Google Compute Network Using Default Firewall Rule + google_compute_network_using_firewall_allows_port_range: + description: Google Compute Network Using Firewall Rule that Allows Port Range + title: Google Compute Network Using Firewall Rule that Allows Port Range + google_compute_network_using_firewall_rule_allows_all_ports: + description: Google Compute Network Using Firewall Rule that Allows All Ports + title: Google Compute Network Using Firewall Rule that Allows All Ports + google_compute_ssl_policy_weak_cipher_in_use: + description: Google Compute SSL Policy Weak Cipher In Use + title: Google Compute SSL Policy Weak Cipher In Use + google_compute_subnetwork_with_private_google_access_disabled: + description: Google Compute Subnetwork with Private Google Access Disabled + title: Google Compute Subnetwork with Private Google Access Disabled + google_container_node_pool_auto_repair_disabled: + description: Google Container Node Pool Auto Repair Disabled + title: Google Container Node Pool Auto Repair Disabled + high_google_kms_crypto_key_rotation_period: + description: High Google KMS Crypto Key Rotation Period + title: High Google KMS Crypto Key Rotation Period + ip_aliasing_disabled: + description: IP Aliasing Disabled + title: IP Aliasing Disabled + ip_forwarding_enabled: + description: IP Forwarding Enabled + title: IP Forwarding Enabled + mysql_instance_with_local_infile_on: + description: MySQL Instance With Local Infile On + title: MySQL Instance With Local Infile On + network_policy_disabled: + description: Network Policy Disabled + title: Network Policy Disabled + node_auto_upgrade_disabled: + description: Node Auto Upgrade Disabled + title: Node Auto Upgrade Disabled + oslogin_is_disabled_for_vm_instance: + description: OSLogin Is Disabled In VM Instance + title: OSLogin Is Disabled In VM Instance + postgresql_log_checkpoints_flag_not_set_to_on: + description: PostgreSQL log_checkpoints Flag Not Set To ON + title: PostgreSQL log_checkpoints Flag Not Set To ON + postgresql_log_connections_disabled: + description: PostgreSQL Log Connections Disabled + title: PostgreSQL Log Connections Disabled + postgresql_logging_of_temporary_files_disabled: + description: PostgreSQL Logging Of Temporary Files Disabled + title: PostgreSQL Logging Of Temporary Files Disabled + postgresql_misconfigured_log_messages_flag: + description: PostgreSQL Misconfigured Log Messages Flag + title: PostgreSQL Misconfigured Log Messages Flag + postgresql_misconfigured_logging_duration_flag: + description: PostgreSQL Misconfigured Logging Duration Flag + title: PostgreSQL Misconfigured Logging Duration Flag + private_cluster_disabled: + description: Private Cluster Disabled + title: Private Cluster Disabled + project_wide_ssh_keys_are_enabled_in_vm_instances: + description: Project-wide SSH Keys Are Enabled In VM Instances + title: Project-wide SSH Keys Are Enabled In VM Instances + rdp_access_is_not_restricted: + description: RDP Access Is Not Restricted + title: RDP Access Is Not Restricted + serial_ports_enabled_for_vm_instances: + description: Serial Ports Are Enabled For VM Instances + title: Serial Ports Are Enabled For VM Instances + shielded_vm_disabled: + description: Shielded VM Disabled + title: Shielded VM Disabled + sql_db_instance_backup_disabled: + description: SQL DB Instance Backup Disabled + title: SQL DB Instance Backup Disabled + sql_db_instance_is_publicly_accessible: + description: SQL DB Instance Publicly Accessible + title: SQL DB Instance Publicly Accessible + sql_db_instance_with_ssl_disabled: + description: SQL DB Instance With SSL Disabled + title: SQL DB Instance With SSL Disabled + ssh_access_is_not_restricted: + description: SSH Access Is Not Restricted + title: SSH Access Is Not Restricted + stackdriver_logging_disabled: + description: Stackdriver Logging Disabled + title: Stackdriver Logging Disabled + stackdriver_monitoring_disabled: + description: Stackdriver Monitoring Disabled + title: Stackdriver Monitoring Disabled + using_default_service_account: + description: Using Default Service Account + title: Using Default Service Account + vm_with_full_cloud_access: + description: VM With Full Cloud Access + title: VM With Full Cloud Access + general: + communication_over_http: + description: Communication Over HTTP + title: Communication Over HTTP + insecure_relative_path_resolution: + description: Insecure Relative Path Resolution + title: Insecure Relative Path Resolution + logging_of_sensitive_data: + description: Logging of Sensitive Data + title: Logging of Sensitive Data + privilege_escalation_using_become_plugin: + description: Privilege Escalation Using Become Plugin + title: Privilege Escalation Using Become Plugin + risky_file_permissions: + description: Risky File Permissions + title: Risky File Permissions + unpinned_package_version: + description: Unpinned Package Version + title: Unpinned Package Version + hosts: + ansible_tower_exposed_to_internet: + description: Ansible Tower Exposed To Internet + title: Ansible Tower Exposed To Internet cicd: github: run_block_injection: diff --git a/documentation/list.json b/documentation/list.json index 63408b96..53de2a2c 100644 --- a/documentation/list.json +++ b/documentation/list.json @@ -1,4 +1,949 @@ [ + { + "name": "ansible", + "providers": [ + { + "name": "aws", + "short_description": "AWS Rules", + "rules": [ + { + "name": "alb_listening_on_http", + "short_description": "ALB Listening on HTTP" + }, + { + "name": "ami_not_encrypted", + "short_description": "AMI Not Encrypted" + }, + { + "name": "ami_shared_with_multiple_accounts", + "short_description": "AMI Shared With Multiple Accounts" + }, + { + "name": "api_gateway_endpoint_config_is_not_private", + "short_description": "API Gateway Endpoint Config is Not Private" + }, + { + "name": "api_gateway_with_cloudwatch_logging_disabled", + "short_description": "API Gateway With CloudWatch Logging Disabled" + }, + { + "name": "api_gateway_without_configured_authorizer", + "short_description": "API Gateway Without Configured Authorizer" + }, + { + "name": "api_gateway_without_ssl_certificate", + "short_description": "API Gateway Without SSL Certificate" + }, + { + "name": "api_gateway_without_waf", + "short_description": "API Gateway without WAF" + }, + { + "name": "api_gateway_xray_disabled", + "short_description": "API Gateway X-Ray Disabled" + }, + { + "name": "authentication_without_mfa", + "short_description": "Authentication Without MFA" + }, + { + "name": "auto_scaling_group_with_no_associated_elb", + "short_description": "Auto Scaling Group With No Associated ELB" + }, + { + "name": "automatic_minor_upgrades_disabled", + "short_description": "Automatic Minor Upgrades Disabled" + }, + { + "name": "aws_password_policy_with_unchangeable_passwords", + "short_description": "AWS Password Policy With Unchangeable Passwords" + }, + { + "name": "batch_job_definition_with_privileged_container_properties", + "short_description": "Batch Job Definition With Privileged Container Properties" + }, + { + "name": "ca_certificate_identifier_is_outdated", + "short_description": "CA Certificate Identifier Is Outdated" + }, + { + "name": "cdn_configuration_is_missing", + "short_description": "CDN Configuration Is Missing" + }, + { + "name": "certificate_has_expired", + "short_description": "Certificate Has Expired" + }, + { + "name": "certificate_rsa_key_bytes_lower_than_256", + "short_description": "Certificate RSA Key Bytes Lower Than 256" + }, + { + "name": "cloudfront_logging_disabled", + "short_description": "CloudFront Logging Disabled" + }, + { + "name": "cloudfront_without_minimum_protocol_tls_1.2", + "short_description": "CloudFront Without Minimum Protocol TLS 1.2" + }, + { + "name": "cloudfront_without_waf", + "short_description": "CloudFront Without WAF" + }, + { + "name": "cloudtrail_log_file_validation_disabled", + "short_description": "CloudTrail Log File Validation Disabled" + }, + { + "name": "cloudtrail_log_files_not_encrypted_with_kms", + "short_description": "CloudTrail Log Files Not Encrypted With KMS" + }, + { + "name": "cloudtrail_logging_disabled", + "short_description": "CloudTrail Logging Disabled" + }, + { + "name": "cloudtrail_multi_region_disabled", + "short_description": "CloudTrail Multi Region Disabled" + }, + { + "name": "cloudtrail_not_integrated_with_cloudwatch", + "short_description": "CloudTrail Not Integrated With CloudWatch" + }, + { + "name": "cloudtrail_sns_topic_name_undefined", + "short_description": "CloudTrail SNS Topic Name Undefined" + }, + { + "name": "cloudwatch_without_retention_period_specified", + "short_description": "CloudWatch Without Retention Period Specified" + }, + { + "name": "cmk_is_unusable", + "short_description": "CMK Is Unusable" + }, + { + "name": "cmk_rotation_disabled", + "short_description": "CMK Rotation Disabled" + }, + { + "name": "codebuild_not_encrypted", + "short_description": "CodeBuild Not Encrypted" + }, + { + "name": "config_configuration_aggregator_to_all_regions_disabled", + "short_description": "Configuration Aggregator to All Regions Disabled" + }, + { + "name": "config_rule_for_encrypted_volumes_is_disabled", + "short_description": "Config Rule For Encrypted Volumes Disabled" + }, + { + "name": "cross_account_iam_assume_role_policy_without_external_id_or_mfa", + "short_description": "Cross-Account IAM Assume Role Policy Without ExternalId or MFA" + }, + { + "name": "db_instance_storage_not_encrypted", + "short_description": "DB Instance Storage Not Encrypted" + }, + { + "name": "db_security_group_open_to_large_scope", + "short_description": "DB Security Group Open To Large Scope" + }, + { + "name": "db_security_group_with_public_scope", + "short_description": "DB Security Group With Public Scope" + }, + { + "name": "default_security_groups_with_unrestricted_traffic", + "short_description": "Default Security Groups With Unrestricted Traffic" + }, + { + "name": "ebs_volume_encryption_disabled", + "short_description": "EBS Volume Encryption Disabled" + }, + { + "name": "ec2_group_has_public_interface", + "short_description": "EC2 Group Has Public Interface" + }, + { + "name": "ec2_instance_has_public_ip", + "short_description": "EC2 Instance Has Public IP" + }, + { + "name": "ec2_instance_using_default_security_group", + "short_description": "EC2 Instance Using Default Security Group" + }, + { + "name": "ec2_instance_using_default_vpc", + "short_description": "EC2 Instance Using Default VPC" + }, + { + "name": "ec2_not_ebs_optimized", + "short_description": "EC2 Not EBS Optimized" + }, + { + "name": "ecr_image_tag_not_immutable", + "short_description": "ECR Image Tag Not Immutable" + }, + { + "name": "ecr_repository_is_publicly_accessible", + "short_description": "ECR Repository Is Publicly Accessible" + }, + { + "name": "ecs_service_admin_role_is_present", + "short_description": "ECS Service Admin Role Is Present" + }, + { + "name": "ecs_service_without_running_tasks", + "short_description": "ECS Service Without Running Tasks" + }, + { + "name": "ecs_services_assigned_with_public_ip_address", + "short_description": "ECS Services assigned with public IP address" + }, + { + "name": "ecs_task_definition_network_mode_not_recommended", + "short_description": "ECS Task Definition Network Mode Not Recommended" + }, + { + "name": "efs_not_encrypted", + "short_description": "EFS Not Encrypted" + }, + { + "name": "efs_without_kms", + "short_description": "EFS Without KMS" + }, + { + "name": "efs_without_tags", + "short_description": "EFS Without Tags" + }, + { + "name": "elasticache_using_default_port", + "short_description": "ElastiCache Using Default Port" + }, + { + "name": "elasticache_without_vpc", + "short_description": "ElastiCache Without VPC" + }, + { + "name": "elasticsearch_with_https_disabled", + "short_description": "Elasticsearch with HTTPS disabled" + }, + { + "name": "elb_using_insecure_protocols", + "short_description": "ELB Using Insecure Protocols" + }, + { + "name": "elb_using_weak_ciphers", + "short_description": "ELB Using Weak Ciphers" + }, + { + "name": "hardcoded_aws_access_key", + "short_description": "Hardcoded AWS Access Key" + }, + { + "name": "hardcoded_aws_access_key_in_lambda", + "short_description": "Hardcoded AWS Access Key In Lambda" + }, + { + "name": "http_port_open_to_internet", + "short_description": "HTTP Port Open To Internet" + }, + { + "name": "iam_access_key_is_exposed", + "short_description": "IAM Access Key Is Exposed" + }, + { + "name": "iam_database_auth_not_enabled", + "short_description": "IAM Database Auth Not Enabled" + }, + { + "name": "iam_group_without_users", + "short_description": "IAM Group Without Users" + }, + { + "name": "iam_password_without_minimum_length", + "short_description": "IAM Password Without Minimum Length" + }, + { + "name": "iam_policies_attached_to_user", + "short_description": "IAM Policies Attached To User" + }, + { + "name": "iam_policies_with_full_privileges", + "short_description": "IAM Policies With Full Privileges" + }, + { + "name": "iam_policy_grants_assumerole_permission_across_all_services", + "short_description": "IAM Policy Grants 'AssumeRole' Permission Across All Services" + }, + { + "name": "iam_policy_grants_full_permissions", + "short_description": "IAM Policy Grants Full Permissions" + }, + { + "name": "iam_role_allows_all_principals_to_assume", + "short_description": "IAM Role Allows All Principals To Assume" + }, + { + "name": "instance_uses_metadata_service_IMDSv1", + "short_description": "Instance Uses Metadata Service IMDSv1" + }, + { + "name": "instance_with_no_vpc", + "short_description": "Instance With No VPC" + }, + { + "name": "kinesis_not_encrypted_with_kms", + "short_description": "Kinesis Not Encrypted With KMS" + }, + { + "name": "kms_key_with_full_permissions", + "short_description": "KMS Key With Vulnerable Policy" + }, + { + "name": "lambda_function_without_tags", + "short_description": "Lambda Function Without Tags" + }, + { + "name": "lambda_functions_without_x-ray_tracing", + "short_description": "Lambda Functions Without X-Ray Tracing" + }, + { + "name": "lambda_permission_misconfigured", + "short_description": "Lambda Permission Misconfigured" + }, + { + "name": "lambda_permission_principal_is_wildcard", + "short_description": "Lambda Permission Principal Is Wildcard" + }, + { + "name": "launch_configuration_is_not_encrypted", + "short_description": "Launch Configuration Is Not Encrypted" + }, + { + "name": "misconfigured_password_policy_expiration", + "short_description": "Misconfigured Password Policy Expiration" + }, + { + "name": "no_stack_policy", + "short_description": "No Stack Policy" + }, + { + "name": "password_without_reuse_prevention", + "short_description": "Password Without Reuse Prevention" + }, + { + "name": "public_lambda_via_api_gateway", + "short_description": "Public Lambda via API Gateway" + }, + { + "name": "public_port_wide", + "short_description": "Public Port Wide" + }, + { + "name": "rds_associated_with_public_subnet", + "short_description": "RDS Associated with Public Subnet" + }, + { + "name": "rds_db_instance_publicly_accessible", + "short_description": "RDS DB Instance Publicly Accessible" + }, + { + "name": "rds_using_default_port", + "short_description": "RDS Using Default Port" + }, + { + "name": "rds_with_backup_disabled", + "short_description": "RDS With Backup Disabled" + }, + { + "name": "redis_not_compliant", + "short_description": "Redis Not Compliant" + }, + { + "name": "redshift_not_encrypted", + "short_description": "Redshift Not Encrypted" + }, + { + "name": "redshift_publicly_accessible", + "short_description": "Redshift Publicly Accessible" + }, + { + "name": "redshift_using_default_port", + "short_description": "Redshift Using Default Port" + }, + { + "name": "remote_desktop_port_open", + "short_description": "Remote Desktop Port Open To Internet" + }, + { + "name": "root_account_has_active_access_keys", + "short_description": "Root Account Has Active Access Keys" + }, + { + "name": "route53_record_undefined", + "short_description": "Route53 Record Undefined" + }, + { + "name": "s3_bucket_access_to_any_principal", + "short_description": "S3 Bucket Access to Any Principal" + }, + { + "name": "s3_bucket_acl_allows_read_to_all_users", + "short_description": "S3 Bucket ACL Allows Read to All Users" + }, + { + "name": "s3_bucket_acl_allows_read_to_any_authenticated_user", + "short_description": "S3 Bucket ACL Allows Read to Any Authenticated User" + }, + { + "name": "s3_bucket_allows_delete_action_from_all_principals", + "short_description": "S3 Bucket Allows Delete Action From All Principals" + }, + { + "name": "s3_bucket_allows_get_action_from_all_principals", + "short_description": "S3 Bucket Allows Get Action From All Principals" + }, + { + "name": "s3_bucket_allows_list_action_from_all_principals", + "short_description": "S3 Bucket Allows List Action From All Principals" + }, + { + "name": "s3_bucket_allows_put_action_from_all_principals", + "short_description": "S3 Bucket Allows Put Action From All Principals" + }, + { + "name": "s3_bucket_logging_disabled", + "short_description": "S3 Bucket Logging Disabled" + }, + { + "name": "s3_bucket_with_all_permissions", + "short_description": "S3 Bucket With All Permissions" + }, + { + "name": "s3_bucket_with_public_access", + "short_description": "S3 Bucket With Public Access" + }, + { + "name": "s3_bucket_with_unsecured_cors_rule", + "short_description": "S3 Bucket with Unsecured CORS Rule" + }, + { + "name": "s3_bucket_without_server-side_encryption", + "short_description": "S3 Bucket Without Server-side-encryption" + }, + { + "name": "s3_bucket_without_versioning", + "short_description": "S3 Bucket Without Versioning" + }, + { + "name": "secure_ciphers_disabled", + "short_description": "Secure Ciphers Disabled" + }, + { + "name": "security_group_ingress_not_restricted", + "short_description": "Security Group Ingress Not Restricted" + }, + { + "name": "security_group_with_unrestricted_access_to_ssh", + "short_description": "Security Group With Unrestricted Access To SSH" + }, + { + "name": "ses_policy_with_allowed_iam_actions", + "short_description": "SES Policy With Allowed IAM Actions" + }, + { + "name": "sns_topic_is_publicly_accessible", + "short_description": "SNS Topic is Publicly Accessible" + }, + { + "name": "sql_analysis_services_port_2383_is_publicly_accessible", + "short_description": "SQL Analysis Services Port 2383 (TCP) Is Publicly Accessible" + }, + { + "name": "sqs_policy_allows_all_actions", + "short_description": "SQS Policy Allows All Actions" + }, + { + "name": "sqs_policy_with_public_access", + "short_description": "SQS Policy With Public Access" + }, + { + "name": "sqs_queue_exposed", + "short_description": "SQS Queue Exposed" + }, + { + "name": "sqs_with_sse_disabled", + "short_description": "SQS With SSE Disabled" + }, + { + "name": "stack_notifications_disabled", + "short_description": "Stack Notifications Disabled" + }, + { + "name": "stack_retention_disabled", + "short_description": "Stack Retention Disabled" + }, + { + "name": "stack_without_template", + "short_description": "Stack Without Template" + }, + { + "name": "unknown_port_exposed_to_internet", + "short_description": "Unknown Port Exposed To Internet" + }, + { + "name": "unrestricted_security_group_ingress", + "short_description": "Unrestricted Security Group Ingress" + }, + { + "name": "user_data_contains_encoded_private_key", + "short_description": "User Data Contains Encoded Private Key" + }, + { + "name": "viewer_protocol_policy_allows_http", + "short_description": "Cloudfront Viewer Protocol Policy Allows HTTP" + }, + { + "name": "vulnerable_default_ssl_certificate", + "short_description": "Vulnerable Default SSL Certificate" + } + ] + }, + { + "name": "azure", + "short_description": "AZURE Rules", + "rules": [ + { + "name": "ad_admin_not_configured_for_sql_server", + "short_description": "AD Admin Not Configured For SQL Server" + }, + { + "name": "admin_user_enabled_for_container_registry", + "short_description": "Admin User Enabled For Container Registry" + }, + { + "name": "aks_monitoring_logging_disabled", + "short_description": "AKS Monitoring Logging Disabled" + }, + { + "name": "aks_network_policy_misconfigured", + "short_description": "AKS Network Policy Misconfigured" + }, + { + "name": "aks_rbac_disabled", + "short_description": "AKS RBAC Disabled" + }, + { + "name": "azure_container_registry_with_no_locks", + "short_description": "Azure Container Registry With No Locks" + }, + { + "name": "azure_instance_using_basic_authentication", + "short_description": "Azure Instance Using Basic Authentication" + }, + { + "name": "cosmosdb_account_ip_range_filter_not_set", + "short_description": "CosmosDB Account IP Range Filter Not Set" + }, + { + "name": "cosmosdb_account_without_tags", + "short_description": "Cosmos DB Account Without Tags" + }, + { + "name": "default_azure_storage_account_network_access_is_too_permissive", + "short_description": "Default Azure Storage Account Network Access Is Too Permissive" + }, + { + "name": "firewall_rule_allows_too_many_hosts_to_access_redis_cache", + "short_description": "Firewall Rule Allows Too Many Hosts To Access Redis Cache" + }, + { + "name": "key_vault_soft_delete_is_disabled", + "short_description": "Key Vault Soft Delete Is Disabled" + }, + { + "name": "log_retention_is_not_set", + "short_description": "Log Retention Is Not Set" + }, + { + "name": "monitoring_log_profile_without_all_activities", + "short_description": "Monitoring Log Profile Without All Activities" + }, + { + "name": "mysql_ssl_connection_disabled", + "short_description": "MySQL SSL Connection Disabled" + }, + { + "name": "postgresql_log_checkpoints_disabled", + "short_description": "PostgreSQL Log Checkpoints Disabled" + }, + { + "name": "postgresql_log_connections_not_set", + "short_description": "PostgreSQL Log Connections Not Set" + }, + { + "name": "postgresql_log_disconnections_not_set", + "short_description": "PostgreSQL Log Disconnections Not Set" + }, + { + "name": "postgresql_log_duration_not_set", + "short_description": "PostgreSQL Log Duration Not Set" + }, + { + "name": "postgresql_server_without_connection_throttling", + "short_description": "PostgreSQL Server Without Connection Throttling" + }, + { + "name": "public_storage_account", + "short_description": "Public Storage Account" + }, + { + "name": "redis_cache_allows_non_ssl_connections", + "short_description": "Redis Cache Allows Non SSL Connections" + }, + { + "name": "redis_entirely_accessible", + "short_description": "Redis Entirely Accessible" + }, + { + "name": "redis_publicly_accessible", + "short_description": "Redis Publicly Accessible" + }, + { + "name": "role_definition_allows_custom_role_creation", + "short_description": "Role Definition Allows Custom Role Creation" + }, + { + "name": "security_group_is_not_configured", + "short_description": "Security Group is Not Configured" + }, + { + "name": "sensitive_port_is_exposed_to_entire_network", + "short_description": "Sensitive Port Is Exposed To Entire Network" + }, + { + "name": "small_activity_log_retention_period", + "short_description": "Small Activity Log Retention Period" + }, + { + "name": "sql_server_ingress_from_any_ip", + "short_description": "SQLServer Ingress From Any IP" + }, + { + "name": "sql_server_predictable_active_directory_admin_account_name", + "short_description": "SQL Server Predictable Active Directory Account Name" + }, + { + "name": "sql_server_predictable_admin_account_name", + "short_description": "SQL Server Predictable Admin Account Name" + }, + { + "name": "ssl_enforce_is_disabled", + "short_description": "SSL Enforce Disabled" + }, + { + "name": "storage_account_not_forcing_https", + "short_description": "Storage Account Not Forcing HTTPS" + }, + { + "name": "storage_account_not_using_latest_tls_encryption_version", + "short_description": "Storage Account Not Using Latest TLS Encryption Version" + }, + { + "name": "storage_container_is_publicly_accessible", + "short_description": "Storage Container Is Publicly Accessible" + }, + { + "name": "trusted_microsoft_services_not_enabled", + "short_description": "Trusted Microsoft Services Not Enabled" + }, + { + "name": "unrestricted_sql_server_acess", + "short_description": "Unrestricted SQL Server Access" + }, + { + "name": "vm_not_attached_to_network", + "short_description": "VM Not Attached To Network" + }, + { + "name": "waf_is_disabled_for_azure_application_gateway", + "short_description": "WAF Is Disabled For Azure Application Gateway" + }, + { + "name": "web_app_accepting_traffic_other_than_https", + "short_description": "Web App Accepting Traffic Other Than HTTPS" + } + ] + }, + { + "name": "config", + "short_description": "CONFIG Rules", + "rules": [ + { + "name": "allow_unsafe_lookups_enabled_in_defaults", + "short_description": "Allow Unsafe Lookups Enabled In Defaults" + }, + { + "name": "communication_over_http_in_defaults", + "short_description": "Communication Over HTTP In Defaults" + }, + { + "name": "logging_of_sensitive_data_in_defaults", + "short_description": "Logging of Sensitive Data In Defaults" + }, + { + "name": "privilege_escalation_using_become_plugin_in_defaults", + "short_description": "Privilege Escalation Using Become Plugin In Defaults" + } + ] + }, + { + "name": "gcp", + "short_description": "GCP Rules", + "rules": [ + { + "name": "bigquery_dataset_is_public", + "short_description": "BigQuery Dataset Is Public" + }, + { + "name": "client_certificate_disabled", + "short_description": "Client Certificate Disabled" + }, + { + "name": "cloud_dns_without_dnnsec", + "short_description": "Cloud DNS Without DNSSEC" + }, + { + "name": "cloud_sql_instance_with_contained_database_authentication_on", + "short_description": "Cloud SQL Instance With Contained Database Authentication On" + }, + { + "name": "cloud_sql_instance_with_cross_db_ownership_chaining_on", + "short_description": "Cloud SQL Instance With Cross DB Ownership Chaining On" + }, + { + "name": "cloud_storage_anonymous_or_publicly_accessible", + "short_description": "Cloud Storage Anonymous or Publicly Accessible" + }, + { + "name": "cloud_storage_bucket_logging_not_enabled", + "short_description": "Cloud Storage Bucket Logging Not Enabled" + }, + { + "name": "cloud_storage_bucket_versioning_disabled", + "short_description": "Cloud Storage Bucket Versioning Disabled" + }, + { + "name": "cluster_labels_disabled", + "short_description": "Cluster Labels Disabled" + }, + { + "name": "cluster_master_authentication_disabled", + "short_description": "Cluster Master Authentication Disabled" + }, + { + "name": "compute_instance_is_publicly_accessible", + "short_description": "Compute Instance Is Publicly Accessible" + }, + { + "name": "cos_node_image_not_used", + "short_description": "COS Node Image Not Used" + }, + { + "name": "disk_encryption_disabled", + "short_description": "Disk Encryption Disabled" + }, + { + "name": "dnssec_using_rsasha1", + "short_description": "DNSSEC Using RSASHA1" + }, + { + "name": "gke_basic_authentication_enabled", + "short_description": "GKE Basic Authentication Enabled" + }, + { + "name": "gke_legacy_authorization_enabled", + "short_description": "GKE Legacy Authorization Enabled" + }, + { + "name": "gke_master_authorized_networks_disabled", + "short_description": "GKE Master Authorized Networks Disabled" + }, + { + "name": "gke_using_default_service_account", + "short_description": "GKE Using Default Service Account" + }, + { + "name": "google_compute_network_using_default_firewall_rule", + "short_description": "Google Compute Network Using Default Firewall Rule" + }, + { + "name": "google_compute_network_using_firewall_allows_port_range", + "short_description": "Google Compute Network Using Firewall Rule that Allows Port Range" + }, + { + "name": "google_compute_network_using_firewall_rule_allows_all_ports", + "short_description": "Google Compute Network Using Firewall Rule that Allows All Ports" + }, + { + "name": "google_compute_ssl_policy_weak_cipher_in_use", + "short_description": "Google Compute SSL Policy Weak Cipher In Use" + }, + { + "name": "google_compute_subnetwork_with_private_google_access_disabled", + "short_description": "Google Compute Subnetwork with Private Google Access Disabled" + }, + { + "name": "google_container_node_pool_auto_repair_disabled", + "short_description": "Google Container Node Pool Auto Repair Disabled" + }, + { + "name": "high_google_kms_crypto_key_rotation_period", + "short_description": "High Google KMS Crypto Key Rotation Period" + }, + { + "name": "ip_aliasing_disabled", + "short_description": "IP Aliasing Disabled" + }, + { + "name": "ip_forwarding_enabled", + "short_description": "IP Forwarding Enabled" + }, + { + "name": "mysql_instance_with_local_infile_on", + "short_description": "MySQL Instance With Local Infile On" + }, + { + "name": "network_policy_disabled", + "short_description": "Network Policy Disabled" + }, + { + "name": "node_auto_upgrade_disabled", + "short_description": "Node Auto Upgrade Disabled" + }, + { + "name": "oslogin_is_disabled_for_vm_instance", + "short_description": "OSLogin Is Disabled In VM Instance" + }, + { + "name": "postgresql_log_checkpoints_flag_not_set_to_on", + "short_description": "PostgreSQL log_checkpoints Flag Not Set To ON" + }, + { + "name": "postgresql_log_connections_disabled", + "short_description": "PostgreSQL Log Connections Disabled" + }, + { + "name": "postgresql_logging_of_temporary_files_disabled", + "short_description": "PostgreSQL Logging Of Temporary Files Disabled" + }, + { + "name": "postgresql_misconfigured_log_messages_flag", + "short_description": "PostgreSQL Misconfigured Log Messages Flag" + }, + { + "name": "postgresql_misconfigured_logging_duration_flag", + "short_description": "PostgreSQL Misconfigured Logging Duration Flag" + }, + { + "name": "private_cluster_disabled", + "short_description": "Private Cluster Disabled" + }, + { + "name": "project_wide_ssh_keys_are_enabled_in_vm_instances", + "short_description": "Project-wide SSH Keys Are Enabled In VM Instances" + }, + { + "name": "rdp_access_is_not_restricted", + "short_description": "RDP Access Is Not Restricted" + }, + { + "name": "serial_ports_enabled_for_vm_instances", + "short_description": "Serial Ports Are Enabled For VM Instances" + }, + { + "name": "shielded_vm_disabled", + "short_description": "Shielded VM Disabled" + }, + { + "name": "sql_db_instance_backup_disabled", + "short_description": "SQL DB Instance Backup Disabled" + }, + { + "name": "sql_db_instance_is_publicly_accessible", + "short_description": "SQL DB Instance Publicly Accessible" + }, + { + "name": "sql_db_instance_with_ssl_disabled", + "short_description": "SQL DB Instance With SSL Disabled" + }, + { + "name": "ssh_access_is_not_restricted", + "short_description": "SSH Access Is Not Restricted" + }, + { + "name": "stackdriver_logging_disabled", + "short_description": "Stackdriver Logging Disabled" + }, + { + "name": "stackdriver_monitoring_disabled", + "short_description": "Stackdriver Monitoring Disabled" + }, + { + "name": "using_default_service_account", + "short_description": "Using Default Service Account" + }, + { + "name": "vm_with_full_cloud_access", + "short_description": "VM With Full Cloud Access" + } + ] + }, + { + "name": "general", + "short_description": "GENERAL Rules", + "rules": [ + { + "name": "communication_over_http", + "short_description": "Communication Over HTTP" + }, + { + "name": "insecure_relative_path_resolution", + "short_description": "Insecure Relative Path Resolution" + }, + { + "name": "logging_of_sensitive_data", + "short_description": "Logging of Sensitive Data" + }, + { + "name": "privilege_escalation_using_become_plugin", + "short_description": "Privilege Escalation Using Become Plugin" + }, + { + "name": "risky_file_permissions", + "short_description": "Risky File Permissions" + }, + { + "name": "unpinned_package_version", + "short_description": "Unpinned Package Version" + } + ] + }, + { + "name": "hosts", + "short_description": "HOSTS Rules", + "rules": [ + { + "name": "ansible_tower_exposed_to_internet", + "short_description": "Ansible Tower Exposed To Internet" + } + ] + } + ] + }, { "name": "cicd", "providers": [ diff --git a/documentation/local_gen.py b/documentation/local_gen.py index 1ca29f8e..bdbb560b 100755 --- a/documentation/local_gen.py +++ b/documentation/local_gen.py @@ -19,6 +19,8 @@ "yaml": "yaml", "json": "json", "dockerfile": "dockerfile", + "cfg": "ini", + "ini": "ini", } CLOUD_PROVIDER = { "alicloud": "Alicloud", @@ -36,6 +38,8 @@ "kubernetes": "Kubernetes", "nifcloud": "Nifcloud", "tencentcloud": "TencentCloud", + "config": "Ansible Config", + "hosts": "Ansible Inventory", } @@ -91,14 +95,16 @@ def get_code_snippets(test_dir, resource_type, max_examples): (f for f in test_dir.iterdir() if NEGATIVE.match(f.name)), max_examples ): if code := read_file_contents(file).replace("```", "\\`\\`\\`"): - compliant.append(f"```{CODE_SUFFIX[file.suffix.lstrip('.')]}\n{code}\n```") + ext = file.suffix.lstrip(".") + lang = CODE_SUFFIX.get(ext, "text") + compliant.append(f"```{lang}\n{code}\n```") for file in islice( (f for f in test_dir.iterdir() if POSITIVE.match(f.name)), max_examples ): if code := read_file_contents(file).replace("```", "\\`\\`\\`"): - non_compliant.append( - f"```{CODE_SUFFIX[file.suffix.lstrip('.')]}\n{code}\n```" - ) + ext = file.suffix.lstrip(".") + lang = CODE_SUFFIX.get(ext, "text") + non_compliant.append(f"```{lang}\n{code}\n```") return compliant, non_compliant diff --git a/documentation/resources.json b/documentation/resources.json index 089c5a13..ae9c85da 100644 --- a/documentation/resources.json +++ b/documentation/resources.json @@ -1,4 +1,12 @@ { + "ansible": [ + "aws", + "azure", + "gcp", + "general", + "config", + "hosts" + ], "terraform": [ "alicloud", "aws", diff --git a/documentation/rules/ansible/aws/alb_listening_on_http.md b/documentation/rules/ansible/aws/alb_listening_on_http.md new file mode 100644 index 00000000..331b0465 --- /dev/null +++ b/documentation/rules/ansible/aws/alb_listening_on_http.md @@ -0,0 +1,116 @@ +--- +title: "ALB listening on HTTP" +group_id: "Ansible / AWS" +meta: + name: "aws/alb_listening_on_http" + id: "f81d63d2-c5d7-43a4-a5b5-66717a41c895" + display_name: "ALB listening on HTTP" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `f81d63d2-c5d7-43a4-a5b5-66717a41c895` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/elb_application_lb_module.html) + +### Description + +Application Load Balancers (ALB) must terminate TLS and use HTTPS listeners to protect traffic in transit and prevent interception or downgrade attacks. Serving application traffic over plain HTTP exposes credentials and sensitive data to eavesdropping. + +For Ansible ALB resources (modules `amazon.aws.elb_application_lb` and `elb_application_lb`), ensure the `listeners[].Protocol` property is set to `"HTTPS"`. Resources missing the `Protocol` property or with `Protocol` set to any value other than `"HTTPS"` are flagged. When using HTTPS, also configure a valid TLS certificate (for example via `Certificates: - CertificateArn: ...`) or implement an HTTP listener only to perform redirects to HTTPS rather than serving plaintext. + +Secure configuration example: + +```yaml +- name: Create ALB with HTTPS listener + amazon.aws.elb_application_lb: + name: my-alb + state: present + listeners: + - Protocol: HTTPS + Port: 443 + Certificates: + - CertificateArn: arn:aws:acm:us-east-1:123456789012:certificate/abcdef01-2345-6789-abcd-ef0123456789 + DefaultActions: + - Type: forward + TargetGroupArn: arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/abcdef0123456789 +``` + +## Compliant Code Examples +```yaml +- name: my_elb_application + amazon.aws.elb_application_lb: + name: myelb + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTPS + Port: 80 + SslPolicy: ELBSecurityPolicy-2015-05 + Certificates: + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward + TargetGroupName: targetname + state: present + # trigger validation + +``` +## Non-Compliant Code Examples +```yaml +- name: my_elb_application + amazon.aws.elb_application_lb: + name: myelb + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP + Port: 80 + SslPolicy: ELBSecurityPolicy-2015-05 + Certificates: + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward + TargetGroupName: targetname + state: present +- name: my_elb_application2 + amazon.aws.elb_application_lb: + name: myelb2 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + Port: 80 + SslPolicy: ELBSecurityPolicy-2015-05 + Certificates: + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward + TargetGroupName: targetname + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ami_not_encrypted.md b/documentation/rules/ansible/aws/ami_not_encrypted.md new file mode 100644 index 00000000..a4834ce9 --- /dev/null +++ b/documentation/rules/ansible/aws/ami_not_encrypted.md @@ -0,0 +1,85 @@ +--- +title: "AMI not encrypted" +group_id: "Ansible / AWS" +meta: + name: "aws/ami_not_encrypted" + id: "97707503-a22c-4cd7-b7c0-f088fa7cf830" + display_name: "AMI not encrypted" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `97707503-a22c-4cd7-b7c0-f088fa7cf830` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_ami_module.html) + +### Description + +AMIs must have their block device mappings encrypted to protect data at rest and prevent sensitive information from being exposed if snapshots are copied, shared, or recovered on different storage. + +For Ansible tasks using the `amazon.aws.ec2_ami` or `ec2_ami` modules, each entry in the `device_mapping` must include `encrypted: true`. Resources missing the `encrypted` attribute or with `encrypted: false` are flagged. Ensure every device mapping explicitly sets `encrypted: true` so AMI snapshots and derived volumes remain encrypted. + +Secure configuration example: + +```yaml +- name: Create AMI with encrypted device mapping + amazon.aws.ec2_ami: + name: my-encrypted-ami + device_mapping: + - device_name: /dev/sda1 + encrypted: true +``` + +## Compliant Code Examples +```yaml +- name: Basic AMI Creation + amazon.aws.ec2_ami: + instance_id: i-xxxxxx + device_mapping: + device_name: /dev/sda + encrypted: yes + wait: yes + name: newtest + tags: + Name: newtest + Service: TestService + +``` +## Non-Compliant Code Examples +```yaml +- name: Basic AMI Creation + amazon.aws.ec2_ami: + instance_id: i-xxxxxx + device_mapping: + device_name: /dev/sda + encrypted: no + wait: yes + name: newtest + tags: + Name: newtest + Service: TestService +- name: Basic AMI Creation2 + amazon.aws.ec2_ami: + instance_id: i-xxxxxx + device_mapping: + device_name: /dev/sda + wait: yes + name: newtest + tags: + Name: newtest + Service: TestService + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ami_shared_with_multiple_accounts.md b/documentation/rules/ansible/aws/ami_shared_with_multiple_accounts.md new file mode 100644 index 00000000..6b12b495 --- /dev/null +++ b/documentation/rules/ansible/aws/ami_shared_with_multiple_accounts.md @@ -0,0 +1,72 @@ +--- +title: "AMI shared with multiple accounts" +group_id: "Ansible / AWS" +meta: + name: "aws/ami_shared_with_multiple_accounts" + id: "a19b2942-142e-4e2b-93b7-6cf6a6c8d90f" + display_name: "AMI shared with multiple accounts" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `a19b2942-142e-4e2b-93b7-6cf6a6c8d90f` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_ami_module.html) + +### Description + +AMIs must not be broadly shared. Granting multiple AWS accounts or group-based access increases the attack surface and can expose embedded credentials, custom configurations, or vulnerable images to unintended parties. + +For Ansible tasks using the `amazon.aws.ec2_ami` or `ec2_ami` modules, `launch_permissions` should be restricted to at most one explicit AWS account and must not include `group_names`. This rule flags tasks where `launch_permissions.group_names` is present or where `launch_permissions.user_ids` contains more than one entry. + +Secure example with a single allowed account: + +```yaml +- name: Register AMI with restricted launch permissions + amazon.aws.ec2_ami: + name: my-ami + image_id: ami-0123456789abcdef0 + launch_permissions: + user_ids: + - "123456789012" +``` + +## Compliant Code Examples +```yaml +- name: Allow AMI to be launched by another account V2 + amazon.aws.ec2_ami: + image_id: '{{ instance.image_id }}' + state: present + launch_permissions: + user_ids: ['123456789012'] + +``` +## Non-Compliant Code Examples +```yaml +- name: Update AMI Launch Permissions, making it public + amazon.aws.ec2_ami: + image_id: "{{ instance.image_id }}" + state: present + launch_permissions: + group_names: ['all'] +- name: Allow AMI to be launched by another account + amazon.aws.ec2_ami: + image_id: "{{ instance.image_id }}" + state: present + launch_permissions: + user_ids: ['123456789012', '121212'] + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/api_gateway_endpoint_config_is_not_private.md b/documentation/rules/ansible/aws/api_gateway_endpoint_config_is_not_private.md new file mode 100644 index 00000000..ed97f0f4 --- /dev/null +++ b/documentation/rules/ansible/aws/api_gateway_endpoint_config_is_not_private.md @@ -0,0 +1,70 @@ +--- +title: "API Gateway endpoint config is not private" +group_id: "Ansible / AWS" +meta: + name: "aws/api_gateway_endpoint_config_is_not_private" + id: "559439b2-3e9c-4739-ac46-17e3b24ec215" + display_name: "API Gateway endpoint config is not private" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `559439b2-3e9c-4739-ac46-17e3b24ec215` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/api_gateway_module.html) + +### Description + +API Gateway endpoint type must be set to `PRIVATE` to prevent the API from being exposed to the public internet, which increases attack surface and can enable unauthorized access or data exfiltration. + +For Ansible tasks using the `community.aws.api_gateway` or `api_gateway` modules, the `endpoint_type` property must be defined and set to `PRIVATE`. Tasks missing this property or with `endpoint_type` not set to `PRIVATE` are flagged. A `PRIVATE` endpoint restricts access to VPC endpoints, so ensure the required VPC endpoint and networking is configured to allow authorized clients to reach the API. + +Secure Ansible task example: + +```yaml +- name: Create private API Gateway + community.aws.api_gateway: + name: my-private-api + endpoint_type: PRIVATE + state: present +``` + +## Compliant Code Examples +```yaml +- name: Setup AWS API Gateway setup on AWS and deploy API definition + community.aws.api_gateway: + swagger_file: my_api.yml + stage: production + cache_enabled: true + cache_size: '1.6' + tracing_enabled: true + endpoint_type: PRIVATE + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: Setup AWS API Gateway setup on AWS and deploy API definition + community.aws.api_gateway: + swagger_file: my_api.yml + stage: production + cache_enabled: true + cache_size: '1.6' + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/api_gateway_with_cloudwatch_logging_disabled.md b/documentation/rules/ansible/aws/api_gateway_with_cloudwatch_logging_disabled.md new file mode 100644 index 00000000..d2a1f161 --- /dev/null +++ b/documentation/rules/ansible/aws/api_gateway_with_cloudwatch_logging_disabled.md @@ -0,0 +1,63 @@ +--- +title: "API Gateway with CloudWatch Logs disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/api_gateway_with_cloudwatch_logging_disabled" + id: "72a931c2-12f5-40d1-93cc-47bff2f7aa2a" + display_name: "API gateway with CloudWatch Logs disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `72a931c2-12f5-40d1-93cc-47bff2f7aa2a` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudwatchlogs_log_group_module.html#ansible-collections-community-aws-cloudwatchlogs-log-group-module) + +### Description + +APIs must send request logs and execution traces to CloudWatch Logs so activity, errors, and suspicious behavior can be detected and investigated. Without a configured log group, you lose critical visibility for incident response and troubleshooting. + +In Ansible, tasks using the `amazon.aws.cloudwatchlogs_log_group` or `cloudwatchlogs_log_group` modules must include the `log_group_name` property to create or reference a specific CloudWatch Logs group. Tasks missing `log_group_name` (or with it unset) are flagged. Set `log_group_name` to a stable, descriptive string and ensure API Gateway access logging or tracing is pointed to that group. + +Secure configuration example: + +```yaml +- name: Create CloudWatch log group for API Gateway + amazon.aws.cloudwatchlogs_log_group: + log_group_name: "/aws/apigateway/my-api" + state: present + retention_in_days: 30 +``` + +## Compliant Code Examples +```yaml +- name: Setup AWS API Gateway setup on AWS cloudwatchlogs + amazon.aws.cloudwatchlogs_log_group: + state: present + log_group_name: test-log-group + tags: {Name: test-log-group, Env: QA} + kms_key_id: arn:aws:kms:region:account-id:key/key-id + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Setup AWS API Gateway setup on AWS cloudwatchlogs + amazon.aws.cloudwatchlogs_log_group: + state: present + kms_key_id: arn:aws:kms:region:account-id:key/key-id + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/api_gateway_without_configured_authorizer.md b/documentation/rules/ansible/aws/api_gateway_without_configured_authorizer.md new file mode 100644 index 00000000..1e364488 --- /dev/null +++ b/documentation/rules/ansible/aws/api_gateway_without_configured_authorizer.md @@ -0,0 +1,192 @@ +--- +title: "API Gateway without configured authorizer" +group_id: "Ansible / AWS" +meta: + name: "aws/api_gateway_without_configured_authorizer" + id: "b16cdb37-ce15-4ab2-8401-d42b05d123fc" + display_name: "API Gateway without configured authorizer" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `b16cdb37-ce15-4ab2-8401-d42b05d123fc` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/api_gateway_module.html) + +### Description + +API Gateway REST APIs must have an API Gateway authorizer configured so that requests are authenticated before reaching backend integrations. Without an authorizer, APIs can be invoked anonymously, increasing the risk of unauthorized access, data exposure, and abuse of backend services. + +For Ansible resources using `community.aws.api_gateway` or `api_gateway`, ensure the API's Swagger/OpenAPI definition—provided via the `swagger_file`, `swagger_dict`, or `swagger_text` property—includes an `x-amazon-apigateway-authorizer` entry in `components.securitySchemes` and that operations reference the authorizer (via `security` at the operation or global level). + +Resources that omit all three swagger properties, or whose Swagger/OpenAPI content does not contain `x-amazon-apigateway-authorizer`, are flagged as missing an authorizer. Include a valid authorizer definition and reference it from your paths to remediate the finding. + +Secure example with an OpenAPI components authorizer and operation-level security: + +```yaml +openapi: "3.0.1" +components: + securitySchemes: + MyLambdaAuthorizer: + x-amazon-apigateway-authorizer: + type: token + authorizerUri: arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:MyAuthFunction/invocations +security: + - MyLambdaAuthorizer: [] +paths: + /resource: + get: + security: + - MyLambdaAuthorizer: [] +``` + +## Compliant Code Examples +```yaml +- name: Setup AWS API Gateway setup on AWS and deploy API definition3 + community.aws.api_gateway: + swagger_file: swaggerFile.yaml + stage: production + cache_enabled: true + cache_size: "1.6" + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` + +```yaml +- name: Setup AWS API Gateway setup on AWS and deploy API definition22222 + community.aws.api_gateway: + swagger_dict: + { + "openapi": "3.0.0", + "info": + { + "title": "Simple API Overview", + "version": "1.0.0", + "contact": { "name": "contact", "email": "user@gmail.com" }, + }, + "components": + { + "securitySchemes": + { + "request_authorizer_single_stagevar": + { + "type": "apiKey", + "name": "Unused", + "in": "header", + "x-amazon-apigateway-authtype": "custom", + "x-amazon-apigateway-authorizer": + { + "type": "request", + "identitySource": "stageVariables.stage", + "authorizerCredentials": "arn:aws:iam::123456789012:role/AWSepIntegTest-CS-LambdaRole", + "authorizerUri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:APIGateway-Request-Authorizer:vtwo/invocations", + "authorizerResultTtlInSeconds": 300, + }, + }, + }, + }, + } + stage: production + cache_enabled: true + cache_size: "1.6" + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` + +```yaml +- name: Setup AWS API Gateway setup on AWS and deploy API 222 + community.aws.api_gateway: + swagger_text: | + openapi: 3.0.0 + info: + title: Sample API + description: Optional multiline or single-line description + version: 0.1.9 + components: + securitySchemes: + request_authorizer_single_stagevar: + type: apiKey + name: Unused + in: header + x-amazon-apigateway-authtype: custom + x-amazon-apigateway-authorizer: + type: request + identitySource: stageVariables.stage + authorizerCredentials: arn:aws:iam::123456789012:role/AWSepIntegTest-CS-LambdaRole + authorizerUri: arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:APIGateway-Request-Authorizer:vtwo/invocations + authorizerResultTtlInSeconds: 300 + stage: production + cache_enabled: true + cache_size: "1.6" + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: Setup AWS API Gateway setup on AWS and deploy API definition2 + community.aws.api_gateway: + stage: production + cache_enabled: true + cache_size: "1.6" + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` + +```yaml +- name: Setup AWS API Gateway setup on AWS and deploy API 222 + community.aws.api_gateway: + swagger_file: swaggerFileWithoutAuthorizer.yaml + stage: production + cache_enabled: true + cache_size: "1.6" + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` + +```yaml +- name: Setup AWS API Gateway setup on AWS and deploy API 222 + community.aws.api_gateway: + swagger_text: | + openapi: 3.0.0 + info: + title: Sample API + description: Optional multiline or single-line description + version: 0.1.9 + components: + ssecuritySchemes: + request_authorizer_single_stagevar: + type: apiKey + name: Unused + in: header + x-amazon-apigateway-authtype: custom + stage: production + cache_enabled: true + cache_size: "1.6" + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/api_gateway_without_ssl_certificate.md b/documentation/rules/ansible/aws/api_gateway_without_ssl_certificate.md new file mode 100644 index 00000000..25f3fa29 --- /dev/null +++ b/documentation/rules/ansible/aws/api_gateway_without_ssl_certificate.md @@ -0,0 +1,100 @@ +--- +title: "API Gateway without SSL certificate" +group_id: "Ansible / AWS" +meta: + name: "aws/api_gateway_without_ssl_certificate" + id: "b47b98ab-e481-4a82-8bb1-1ab39fd36e33" + display_name: "API Gateway without SSL certificate" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `b47b98ab-e481-4a82-8bb1-1ab39fd36e33` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/api_gateway_module.html) + +### Description + +API Gateway integrations must validate TLS/SSL certificates to ensure backend endpoints are authentic and prevent man-in-the-middle attacks that can expose credentials or sensitive data. + +The `validate_certs` property in Ansible `community.aws.api_gateway` and `api_gateway` tasks must be defined and set to a truthy value (Ansible `yes` or `true`). Resources missing this property or with `validate_certs` set to `no` or `false` are flagged. + +If your backend uses self-signed certificates, prefer adding the CA to a trusted store or using proper certificate management rather than disabling certificate validation. + +Secure example Ansible task: + +```yaml +- name: Create API Gateway with TLS validation + community.aws.api_gateway: + name: my-api + state: present + validate_certs: yes +``` + +## Compliant Code Examples +```yaml +- name: update API v2 + community.aws.api_gateway: + api_id: abc123321cba + state: present + swagger_file: my_api.yml + validate_certs: yes +- name: Setup AWS API Gateway setup on AWS and deploy API definition v2 + community.aws.api_gateway: + swagger_file: my_api.yml + stage: production + cache_enabled: true + cache_size: '1.6' + tracing_enabled: true + endpoint_type: EDGE + state: present + validate_certs: yes + +``` +## Non-Compliant Code Examples +```yaml +- name: update API + community.aws.api_gateway: + api_id: 'abc123321cba' + state: present + swagger_file: my_api.yml + validate_certs: no +- name: update API v1 + community.aws.api_gateway: + api_id: 'abc123321cba' + state: present + swagger_file: my_api.yml +- name: Setup AWS API Gateway setup on AWS and deploy API definition + community.aws.api_gateway: + swagger_file: my_api.yml + stage: production + cache_enabled: true + cache_size: '1.6' + tracing_enabled: true + endpoint_type: EDGE + state: present + validate_certs: no +- name: Setup AWS API Gateway setup on AWS and deploy API definition v1 + community.aws.api_gateway: + swagger_file: my_api.yml + stage: production + cache_enabled: true + cache_size: '1.6' + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/api_gateway_without_waf.md b/documentation/rules/ansible/aws/api_gateway_without_waf.md new file mode 100644 index 00000000..4d689688 --- /dev/null +++ b/documentation/rules/ansible/aws/api_gateway_without_waf.md @@ -0,0 +1,82 @@ +--- +title: "API Gateway without WAF" +group_id: "Ansible / AWS" +meta: + name: "aws/api_gateway_without_waf" + id: "f5f38943-664b-4acc-ab11-f292fa10ed0b" + display_name: "API gateway without WAF" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `f5f38943-664b-4acc-ab11-f292fa10ed0b` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/wafv2_resources_module.html#parameter-arn) + +### Description + +API Gateway stages should be protected by an AWS WAF Web ACL to block common web threats (for example SQL injection, XSS, and malicious request patterns) before they reach backend services. Ensure your IaC defines a WAFv2 WebACLAssociation that links a Web ACL to the API Gateway stage. The association's `ResourceArn` (or Terraform `resource_arn`) must reference the API Gateway stage ARN (for REST APIs: arn:aws:apigateway:::/restapis//stages/). + +This rule checks Ansible API Gateway resources (modules `community.aws.api_gateway` or `api_gateway`) and expects a corresponding WAFv2 association (for example, `community.aws.wafv2_resources`/`wafv2_resources`) that targets the same stage. Resources missing a WebACLAssociation or where `ResourceArn` does not point to the stage are flagged. + +Secure CloudFormation example: + +```yaml +WebACLAssociation: + Type: AWS::WAFv2::WebACLAssociation + Properties: + ResourceArn: !Sub "arn:aws:apigateway:${AWS::Region}::/restapis/${ApiId}/stages/${StageName}" + WebACLArn: !Ref MyWebACL +``` + +## Compliant Code Examples +```yaml +- name: add test alb to waf string03 + community.aws.wafv2_resources: + name: string03 + scope: REGIONAL + state: present + arn: "arn:aws:apigateway:region::/restapis/api-id/stages/produ" +- name: Setup AWS API Gateway setup on AWS and deploy API definition + community.aws.api_gateway: + swagger_file: my_api.yml + stage: produ + cache_enabled: true + cache_size: '1.6' + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: add test alb to waf string032 + community.aws.wafv2_resources: + name: string03 + scope: REGIONAL + state: present + arn: "arn:aws:apigateway:region::/restapis/api-id/stages/prod" +- name: Setup AWS API Gateway setup on AWS and deploy API definition2 + community.aws.api_gateway: + swagger_file: my_api.yml + stage: production + cache_enabled: true + cache_size: '1.6' + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/api_gateway_xray_disabled.md b/documentation/rules/ansible/aws/api_gateway_xray_disabled.md new file mode 100644 index 00000000..0354b451 --- /dev/null +++ b/documentation/rules/ansible/aws/api_gateway_xray_disabled.md @@ -0,0 +1,77 @@ +--- +title: "API Gateway X-Ray disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/api_gateway_xray_disabled" + id: "2059155b-27fd-441e-b616-6966c468561f" + display_name: "API Gateway X-Ray disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `2059155b-27fd-441e-b616-6966c468561f` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/api_gateway_module.html#parameter-tracing_enabled) + +### Description + +API Gateway resources should have AWS X-Ray tracing enabled to provide end-to-end request visibility and support detection of anomalous or malicious activity. For Ansible tasks that use the `community.aws.api_gateway` or `api_gateway` modules, set the `tracing_enabled` property to `true`. Tasks missing `tracing_enabled` or with `tracing_enabled: false` are flagged because they disable observability needed for effective incident response and root-cause analysis. + +Secure Ansible task example: + +```yaml +- name: Configure API Gateway with X-Ray tracing + community.aws.api_gateway: + name: my-api + tracing_enabled: true +``` + +## Compliant Code Examples +```yaml +- name: Setup AWS API Gateway setup on AWS and deploy API definition + community.aws.api_gateway: + swagger_file: my_api.yml + stage: production + cache_enabled: true + cache_size: '1.6' + tracing_enabled: true + endpoint_type: EDGE + state: present + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Setup AWS API Gateway setup on AWS and deploy API definition + community.aws.api_gateway: + swagger_file: my_api.yml + stage: production + cache_enabled: true + cache_size: '1.6' + tracing_enabled: false + endpoint_type: EDGE + state: present +- name: Update API definition to deploy new version + community.aws.api_gateway: + api_id: 'abc123321cba' + swagger_file: my_api.yml + deploy_desc: Make auth fix available. + cache_enabled: true + cache_size: '1.6' + endpoint_type: EDGE + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/authentication_without_mfa.md b/documentation/rules/ansible/aws/authentication_without_mfa.md new file mode 100644 index 00000000..986bb0e7 --- /dev/null +++ b/documentation/rules/ansible/aws/authentication_without_mfa.md @@ -0,0 +1,71 @@ +--- +title: "Authentication without MFA" +group_id: "Ansible / AWS" +meta: + name: "aws/authentication_without_mfa" + id: "eee107f9-b3d8-45d3-b9c6-43b5a7263ce1" + display_name: "Authentication without MFA" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Access Control" +--- +## Metadata + +**Id:** `eee107f9-b3d8-45d3-b9c6-43b5a7263ce1` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/iam_mfa_device_info_module.html) + +### Description + +Assume-role operations should require multi-factor authentication (MFA) to provide a second authentication factor and reduce the risk that compromised credentials or automated workflows can silently assume privileged roles. + +In Ansible, tasks using the `amazon.aws.sts_assume_role` or `sts_assume_role` modules must define both `mfa_serial_number` (the IAM MFA device ARN or serial) and `mfa_token` (the one-time MFA code). Tasks missing either property or with those properties undefined are flagged. + +Supply `mfa_token` securely at runtime (for example via Ansible Vault, environment variables, or an interactive prompt) and ensure `mfa_serial_number` references the correct MFA device ARN (for example, `arn:aws:iam::123456789012:mfa/username`). + +## Compliant Code Examples +```yaml +- name: Assume an existing role + amazon.aws.sts_assume_role: + mfa_serial_number: '{{ mfa_devices.mfa_devices[0].serial_number }}' + mfa_token: weewew + role_arn: arn:aws:iam::123456789012:role/someRole + role_session_name: someRoleSession + register: assumed_role + +- name: Hello + sts_assume_role: + mfa_serial_number: '{{ mfa_devices.mfa_devices[0].serial_number }}' + mfa_token: weewew + role_arn: arn:aws:iam::123456789012:role/someRole + role_session_name: someRoleSession + register: assumed_role + +``` +## Non-Compliant Code Examples +```yaml +- name: Assume an existing role + amazon.aws.sts_assume_role: + mfa_serial_number: "{{ mfa_devices.mfa_devices[0].serial_number }}" + role_arn: "arn:aws:iam::123456789012:role/someRole" + role_session_name: "someRoleSession" + register: assumed_role + +- name: Hello + sts_assume_role: + role_arn: "arn:aws:iam::123456789012:role/someRole" + role_session_name: "someRoleSession" + register: assumed_role + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/auto_scaling_group_with_no_associated_elb.md b/documentation/rules/ansible/aws/auto_scaling_group_with_no_associated_elb.md new file mode 100644 index 00000000..f253c54a --- /dev/null +++ b/documentation/rules/ansible/aws/auto_scaling_group_with_no_associated_elb.md @@ -0,0 +1,114 @@ +--- +title: "Auto Scaling Group with no associated ELB" +group_id: "Ansible / AWS" +meta: + name: "aws/auto_scaling_group_with_no_associated_elb" + id: "050f085f-a8db-4072-9010-2cca235cc02f" + display_name: "Auto Scaling Group with no associated ELB" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Availability" +--- +## Metadata + +**Id:** `050f085f-a8db-4072-9010-2cca235cc02f` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Availability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/autoscaling_group_module.html#parameter-load_balancers) + +### Description + +Auto Scaling Groups must be associated with a load balancer so new instances receive traffic and health checks can detect and replace unhealthy instances. Without a load balancer, instances may not serve requests, and application availability and scaling behavior can be impacted. + +For Ansible `autoscaling_group` tasks (modules `amazon.aws.autoscaling_group` and `autoscaling_group`), the `load_balancers` property must be defined and set to a non-empty list of Classic ELB names. Tasks missing the `load_balancers` property or with `load_balancers: []` are flagged. If you use Application Load Balancers with target groups instead of Classic ELBs, configure `target_group_arns` accordingly—this rule only validates the `load_balancers` attribute. + +Secure example: + +```yaml +- name: Create Auto Scaling Group with ELB + amazon.aws.autoscaling_group: + name: my-asg + launch_template: my-launch-template + min_size: 2 + max_size: 5 + load_balancers: + - my-classic-elb +``` + +## Compliant Code Examples +```yaml +- name: elb12 + amazon.aws.autoscaling_group: + name: special + load_balancers: [ 'lb1', 'lb2' ] + availability_zones: [ 'eu-west-1a', 'eu-west-1b' ] + launch_config_name: 'lc-1' + min_size: 1 + max_size: 10 + desired_capacity: 5 + vpc_zone_identifier: [ 'subnet-abcd1234', 'subnet-1a2b3c4d' ] + tags: + - environment: production + propagate_at_launch: no + +``` + +```yaml +- name: elb22 + amazon.aws.autoscaling_group: + name: special + load_balancers: [ 'lb1', 'lb2' ] + availability_zones: [ 'eu-west-1a', 'eu-west-1b' ] + launch_config_name: 'lc-1' + min_size: 1 + max_size: 10 + desired_capacity: 5 + vpc_zone_identifier: [ 'subnet-abcd1234', 'subnet-1a2b3c4d' ] + tags: + - environment: production + propagate_at_launch: no + +``` +## Non-Compliant Code Examples +```yaml +- name: elb2 + amazon.aws.autoscaling_group: + name: special + availability_zones: [ 'eu-west-1a', 'eu-west-1b' ] + launch_config_name: 'lc-1' + min_size: 1 + max_size: 10 + desired_capacity: 5 + vpc_zone_identifier: [ 'subnet-abcd1234', 'subnet-1a2b3c4d' ] + tags: + - environment: production + propagate_at_launch: no + +``` + +```yaml +- name: elb1 + amazon.aws.autoscaling_group: + name: special + load_balancers: [] + availability_zones: [ 'eu-west-1a', 'eu-west-1b' ] + launch_config_name: 'lc-1' + min_size: 1 + max_size: 10 + desired_capacity: 5 + vpc_zone_identifier: [ 'subnet-abcd1234', 'subnet-1a2b3c4d' ] + tags: + - environment: production + propagate_at_launch: no + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/automatic_minor_upgrades_disabled.md b/documentation/rules/ansible/aws/automatic_minor_upgrades_disabled.md new file mode 100644 index 00000000..95c79d6f --- /dev/null +++ b/documentation/rules/ansible/aws/automatic_minor_upgrades_disabled.md @@ -0,0 +1,104 @@ +--- +title: "Automatic minor upgrades disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/automatic_minor_upgrades_disabled" + id: "857f8808-e96a-4ba8-a9b7-f2d4ec6cad94" + display_name: "Automatic minor upgrades disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `857f8808-e96a-4ba8-a9b7-f2d4ec6cad94` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-auto_minor_version_upgrade) + +### Description + +RDS instances should have automatic minor engine upgrades enabled so critical security patches and bug fixes are applied promptly, preventing exposure to known vulnerabilities or compliance drift. + +For Ansible RDS tasks using the `amazon.aws.rds_instance` or `rds_instance` modules, the `auto_minor_version_upgrade` property must be defined and set to `true`. Tasks that omit this property or set `auto_minor_version_upgrade: false` are flagged. Enabling this setting ensures minor engine patches are applied automatically during the instance's maintenance window. + +Secure Ansible example: + +```yaml +- name: create RDS instance with automatic minor upgrades + amazon.aws.rds_instance: + name: mydb + engine: postgres + instance_type: db.t3.medium + auto_minor_version_upgrade: true +``` + +## Compliant Code Examples +```yaml +- name: negative - create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: '{{ password }}' + username: '{{ username }}' + cluster_id: ansible-test-cluster + auto_minor_version_upgrade: true +- name: negative - Create a DB instance using the default AWS KMS encryption key + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: true + db_instance_class: db.t2.medium + username: '{{ username }}' + password: '{{ password }}' + allocated_storage: '{{ allocated_storage }}' + auto_minor_version_upgrade: yes +- name: negative - Create a DB instance using the default AWS KMS encryption key + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: true + db_instance_class: db.t2.medium + username: '{{ username }}' + password: '{{ password }}' + allocated_storage: '{{ allocated_storage }}' + auto_minor_version_upgrade: true + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: community - create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster + auto_minor_version_upgrade: false +- name: community - Create a DB instance using the default AWS KMS encryption key + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: True + db_instance_class: db.t2.medium + username: "{{ username }}" + password: "{{ password }}" + allocated_storage: "{{ allocated_storage }}" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/aws_password_policy_with_unchangeable_passwords.md b/documentation/rules/ansible/aws/aws_password_policy_with_unchangeable_passwords.md new file mode 100644 index 00000000..0414dc6a --- /dev/null +++ b/documentation/rules/ansible/aws/aws_password_policy_with_unchangeable_passwords.md @@ -0,0 +1,86 @@ +--- +title: "AWS password policy with unchangeable passwords" +group_id: "Ansible / AWS" +meta: + name: "aws/aws_password_policy_with_unchangeable_passwords" + id: "e28ceb92-d588-4166-aac5-766c8f5b7472" + display_name: "AWS password policy with unchangeable passwords" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `e28ceb92-d588-4166-aac5-766c8f5b7472` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_password_policy_module.html) + +### Description + +IAM password policies must permit users to change their own passwords so compromised, expired, or weak credentials can be rotated and account recovery workflows remain effective. In Ansible tasks using the `amazon.aws.iam_password_policy` or `iam_password_policy` modules, the boolean property controlling this must be defined and set to `true` — either `allow_pw_change` or `allow_password_change` depending on module version. + +Tasks that omit these properties or set them to `false`/`no` are flagged because disabling password changes prevents credential rotation and hampers incident response and account hygiene. + +Secure Ansible example: + +```yaml +- name: Ensure IAM password policy allows user password changes + amazon.aws.iam_password_policy: + allow_password_change: true +``` + +## Compliant Code Examples +```yaml +- name: Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + pw_reuse_prevent: 5 + pw_expire: false + +``` +## Non-Compliant Code Examples +```yaml +- name: Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: false + pw_max_age: 60 + pw_reuse_prevent: 5 + pw_expire: false +- name: Alias Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_password_change: false + pw_max_age: 60 + pw_reuse_prevent: 5 + pw_expire: false + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/batch_job_definition_with_privileged_container_properties.md b/documentation/rules/ansible/aws/batch_job_definition_with_privileged_container_properties.md new file mode 100644 index 00000000..9106d2d6 --- /dev/null +++ b/documentation/rules/ansible/aws/batch_job_definition_with_privileged_container_properties.md @@ -0,0 +1,111 @@ +--- +title: "Batch job definition with privileged container properties" +group_id: "Ansible / AWS" +meta: + name: "aws/batch_job_definition_with_privileged_container_properties" + id: "defe5b18-978d-4722-9325-4d1975d3699f" + display_name: "Batch job definition with privileged container properties" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `defe5b18-978d-4722-9325-4d1975d3699f` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/batch_job_definition_module.html) + +### Description + +Batch job definitions must not enable privileged containers. Privileged mode weakens container isolation and can allow containers to access host resources or escalate privileges, increasing the risk of host compromise and lateral movement. + +For Ansible, tasks using the `community.aws.batch_job_definition` or `aws_batch_job_definition` modules must not set the `privileged` parameter to `true`. The `privileged` setting should be omitted or explicitly set to `false` in the job definition's container properties. Resources with `privileged: true` are flagged. Only enable privileged mode when absolutely required and after applying additional host hardening, access controls, and justification. + +Secure example: + +```yaml +- name: Register Batch job definition without privileged mode + community.aws.batch_job_definition: + name: my-batch-job + container_properties: + image: my-image:latest + vcpus: 1 + memory: 1024 + privileged: false +``` + +## Compliant Code Examples +```yaml +- name: My Batch Job Definition + community.aws.batch_job_definition: + job_definition_name: My Batch Job Definition without privilege + state: present + type: container + parameters: + Param1: Val1 + Param2: Val2 + privileged: false + image: + vcpus: 1 + memory: 512 + command: + - python + - run_my_script.py + - arg1 + job_role_arn: + attempts: 3 + register: job_definition_create_result +- name: My Batch Job Definition without explicit privilege + community.aws.batch_job_definition: + job_definition_name: My Batch Job Definition + state: present + type: container + parameters: + Param1: Val1 + Param2: Val2 + image: + vcpus: 1 + memory: 512 + command: + - python + - run_my_script.py + - arg1 + job_role_arn: + attempts: 3 + register: job_definition_create_result + +``` +## Non-Compliant Code Examples +```yaml +- name: My Batch Job Definition + community.aws.batch_job_definition: + job_definition_name: My Batch Job Definition + state: present + type: container + parameters: + Param1: Val1 + Param2: Val2 + privileged: true + image: + vcpus: 1 + memory: 512 + command: + - python + - run_my_script.py + - arg1 + job_role_arn: + attempts: 3 + register: job_definition_create_result + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ca_certificate_identifier_is_outdated.md b/documentation/rules/ansible/aws/ca_certificate_identifier_is_outdated.md new file mode 100644 index 00000000..3030518d --- /dev/null +++ b/documentation/rules/ansible/aws/ca_certificate_identifier_is_outdated.md @@ -0,0 +1,94 @@ +--- +title: "CA certificate identifier is outdated" +group_id: "Ansible / AWS" +meta: + name: "aws/ca_certificate_identifier_is_outdated" + id: "5eccd62d-8b4d-46d3-83ea-1879f3cbd3ce" + display_name: "CA certificate identifier is outdated" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `5eccd62d-8b4d-46d3-83ea-1879f3cbd3ce` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-ca_certificate_identifier) + +### Description + +RDS instances must specify a CA certificate identifier so the database uses a known AWS CA for TLS connections and avoids broken or insecure certificate chains during CA rotations. For Ansible RDS resources (modules `amazon.aws.rds_instance` and `rds_instance`), the `ca_certificate_identifier` property must be defined and set to `rds-ca-2019`. Resources missing this property or specifying a different value are flagged. Update the value if AWS publishes a newer CA identifier. + +Secure Ansible task example: + +```yaml +- name: create RDS instance with CA + amazon.aws.rds_instance: + db_instance_identifier: my-db + engine: mysql + instance_class: db.t3.medium + allocated_storage: 20 + username: admin + password: secret + ca_certificate_identifier: rds-ca-2019 +``` + +## Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: '{{ password }}' + username: '{{ username }}' + cluster_id: ansible-test-cluster + ca_certificate_identifier: rds-ca-2019 +- name: Create a DB instance using the default AWS KMS encryption key + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: true + db_instance_class: db.t2.medium + username: '{{ username }}' + password: '{{ password }}' + allocated_storage: '{{ allocated_storage }}' + ca_certificate_identifier: rds-ca-2019 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster + ca_certificate_identifier: rds-ca-2015 +- name: create a DB instance using the default AWS KMS encryption key + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: True + db_instance_class: db.t2.medium + username: "{{ username }}" + password: "{{ password }}" + allocated_storage: "{{ allocated_storage }}" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cdn_configuration_is_missing.md b/documentation/rules/ansible/aws/cdn_configuration_is_missing.md new file mode 100644 index 00000000..cbed2cc7 --- /dev/null +++ b/documentation/rules/ansible/aws/cdn_configuration_is_missing.md @@ -0,0 +1,122 @@ +--- +title: "CDN configuration is missing" +group_id: "Ansible / AWS" +meta: + name: "aws/cdn_configuration_is_missing" + id: "b25398a2-0625-4e61-8e4d-a1bb23905bf6" + display_name: "CDN configuration is missing" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `b25398a2-0625-4e61-8e4d-a1bb23905bf6` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/cloudfront_distribution_module.html) + +### Description + +CloudFront distributions must be enabled and include at least one origin so traffic is routed through the CDN. This ensures requests benefit from CloudFront protections such as caching, TLS termination, WAF rules, and DDoS mitigation. A disabled or origin-less distribution can cause traffic to bypass the CDN and expose origin servers. + +This rule inspects Ansible tasks using the `community.aws.cloudfront_distribution` or `cloudfront_distribution` modules. It requires the `enabled` property to be present and set to `true`, and the `origins` property to be defined with at least one origin entry. Tasks missing `enabled` or `origins`, or with `enabled: false`, are flagged as misconfigured. + +Secure example: + +```yaml +- name: create cloudfront distribution + community.aws.cloudfront_distribution: + enabled: true + comment: "Secure distribution" + origins: + - id: my-origin + domain_name: origin.example.com + custom_origin_config: + origin_protocol_policy: https-only + http_port: 80 + https_port: 443 +``` + +## Compliant Code Examples +```yaml +- name: create a distribution with an origin, logging and default cache behavior + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: 'my test origin-000111' + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + default_cache_behavior: + target_origin_id: 'my test origin-000111' + forwarded_values: + query_string: true + cookies: + forward: all + headers: + - '*' + viewer_protocol_policy: allow-all + smooth_streaming: true + compress: true + allowed_methods: + items: + - GET + - HEAD + cached_methods: + - GET + - HEAD + logging: + enabled: true + include_cookies: false + bucket: mylogbucket.s3.amazonaws.com + prefix: myprefix/ + enabled: true + comment: this is a CloudFront distribution with logging + +``` +## Non-Compliant Code Examples +```yaml +- name: create a distribution without an origin and with enabled=false + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + default_cache_behavior: + target_origin_id: 'my test origin-000111' + forwarded_values: + query_string: true + cookies: + forward: all + headers: + - '*' + viewer_protocol_policy: allow-all + smooth_streaming: true + compress: true + allowed_methods: + items: + - GET + - HEAD + cached_methods: + - GET + - HEAD + enabled: false + logging: + enabled: true + include_cookies: false + bucket: mylogbucket.s3.amazonaws.com + prefix: myprefix/ + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/certificate_has_expired.md b/documentation/rules/ansible/aws/certificate_has_expired.md new file mode 100644 index 00000000..ff926cfa --- /dev/null +++ b/documentation/rules/ansible/aws/certificate_has_expired.md @@ -0,0 +1,52 @@ +--- +title: "Certificate has expired" +group_id: "Ansible / AWS" +meta: + name: "aws/certificate_has_expired" + id: "5a443297-19d4-4381-9e5b-24faf947ec22" + display_name: "Certificate has expired" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `5a443297-19d4-4381-9e5b-24faf947ec22` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/acm_certificate_module.html) + +### Description + +Expired SSL/TLS certificates cause service outages by breaking TLS handshakes and undermine trust in encrypted connections. This can result in failed client connections and compliance or security issues. In Ansible, tasks using the `community.aws.acm_certificate` module must reference a certificate whose `certificate.expiration_date` is a future date. This rule flags `community.aws.acm_certificate` tasks where `certificate.expiration_date` is in the past. Renew or replace any expired certificates—for example, request a new ACM certificate or update the task to point to a renewed certificate—so `certificate.expiration_date` reflects a valid future date. + +## Compliant Code Examples +```yaml +- name: upload a self-signed certificate2 + community.aws.acm_certificate: + certificate: "{{ lookup('file', 'validCertificate.pem' ) }}" + privateKey: "{{ lookup('file', 'key.pem' ) }}" + name_tag: my_cert + region: ap-southeast-2 + +``` +## Non-Compliant Code Examples +```yaml +- name: upload a self-signed certificate + community.aws.acm_certificate: + certificate: "{{ lookup('file', 'expiredCertificate.pem' ) }}" + privateKey: "{{ lookup('file', 'key.pem' ) }}" + name_tag: my_cert + region: ap-southeast-2 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/certificate_rsa_key_bytes_lower_than_256.md b/documentation/rules/ansible/aws/certificate_rsa_key_bytes_lower_than_256.md new file mode 100644 index 00000000..a38e4279 --- /dev/null +++ b/documentation/rules/ansible/aws/certificate_rsa_key_bytes_lower_than_256.md @@ -0,0 +1,65 @@ +--- +title: "Certificate RSA key bytes lower than 256" +group_id: "Ansible / AWS" +meta: + name: "aws/certificate_rsa_key_bytes_lower_than_256" + id: "d5ec2080-340a-4259-b885-f833c4ea6a31" + display_name: "Certificate RSA key bytes lower than 256" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `d5ec2080-340a-4259-b885-f833c4ea6a31` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/acm_certificate_module.html) + +### Description + +Certificates must use sufficiently strong RSA keys to prevent cryptographic compromise. RSA keys smaller than 2048 bits can be factored with modern compute, enabling certificate impersonation and decryption of TLS traffic. + +For Ansible tasks using the `community.aws.acm_certificate` module, ensure the `certificate.rsa_key_bytes` property is defined and set to at least `256` (bytes), which corresponds to 2048 bits. Resources missing this property or with `rsa_key_bytes < 256` are flagged as insecure. Larger values (for example, `rsa_key_bytes: 512` for 4096-bit keys) are acceptable. + +Secure example: + +```yaml +- name: Request ACM certificate with 2048-bit RSA key + community.aws.acm_certificate: + name: example-cert + certificate: + rsa_key_bytes: 256 + state: present +``` + +## Compliant Code Examples +```yaml +- name: upload a self-signed certificate2 + community.aws.acm_certificate: + certificate: "{{ lookup('file', 'rsa4096.pem' ) }}" + privateKey: "{{ lookup('file', 'key.pem' ) }}" + name_tag: my_cert + region: ap-southeast-2 + +``` +## Non-Compliant Code Examples +```yaml +- name: upload a self-signed certificate + community.aws.acm_certificate: + certificate: "{{ lookup('file', 'rsa1024.pem' ) }}" + privateKey: "{{ lookup('file', 'key.pem' ) }}" + name_tag: my_cert + region: ap-southeast-2 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cloudfront_logging_disabled.md b/documentation/rules/ansible/aws/cloudfront_logging_disabled.md new file mode 100644 index 00000000..49cb8ace --- /dev/null +++ b/documentation/rules/ansible/aws/cloudfront_logging_disabled.md @@ -0,0 +1,160 @@ +--- +title: "CloudFront logging disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/cloudfront_logging_disabled" + id: "d31cb911-bf5b-4eb6-9fc3-16780c77c7bd" + display_name: "CloudFront logging disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `d31cb911-bf5b-4eb6-9fc3-16780c77c7bd` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/cloudfront_distribution_module.html) + +### Description + +CloudFront distributions must have access logging enabled to record viewer requests for incident investigation and auditing. Without logs, you cannot reliably detect abuse, investigate incidents, or meet audit requirements. + +For Ansible CloudFront distribution resources (modules `community.aws.cloudfront_distribution` and `cloudfront_distribution`), the `logging` property must be defined and `logging.enabled` set to `true`. Tasks missing `logging` or with `logging.enabled: false` are flagged. Ensure a valid S3 bucket is specified in `logging.bucket` as the log destination. + +Secure configuration example: + +```yaml +- name: Create CloudFront distribution with logging enabled + community.aws.cloudfront_distribution: + origin: + - id: my-origin + domain_name: origin.example.com + enabled: yes + logging: + enabled: true + bucket: my-log-bucket.s3.amazonaws.com + include_cookies: false +``` + +## Compliant Code Examples +```yaml +- name: create a distribution with an origin, logging and default cache behavior + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: my test origin-000111 + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + default_cache_behavior: + target_origin_id: my test origin-000111 + forwarded_values: + query_string: true + cookies: + forward: all + headers: + - '*' + viewer_protocol_policy: allow-all + smooth_streaming: true + compress: true + allowed_methods: + items: + - GET + - HEAD + cached_methods: + - GET + - HEAD + logging: + enabled: true + include_cookies: false + bucket: mylogbucket.s3.amazonaws.com + prefix: myprefix/ + enabled: false + comment: this is a CloudFront distribution with logging + +``` +## Non-Compliant Code Examples +```yaml +- name: create a distribution with an origin, logging and default cache behavior + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: 'my test origin-000111' + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + default_cache_behavior: + target_origin_id: 'my test origin-000111' + forwarded_values: + query_string: true + cookies: + forward: all + headers: + - '*' + viewer_protocol_policy: allow-all + smooth_streaming: true + compress: true + allowed_methods: + items: + - GET + - HEAD + cached_methods: + - GET + - HEAD + enabled: false + comment: this is a CloudFront distribution with logging +- name: create a second distribution with an origin, logging and default cache behavior + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: 'my test origin-000111' + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + default_cache_behavior: + target_origin_id: 'my test origin-000111' + forwarded_values: + query_string: true + cookies: + forward: all + headers: + - '*' + viewer_protocol_policy: allow-all + smooth_streaming: true + compress: true + allowed_methods: + items: + - GET + - HEAD + cached_methods: + - GET + - HEAD + logging: + enabled: false + include_cookies: false + bucket: mylogbucket.s3.amazonaws.com + prefix: myprefix/ + enabled: false + comment: this is a CloudFront distribution with logging + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2.md b/documentation/rules/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2.md new file mode 100644 index 00000000..a16100f7 --- /dev/null +++ b/documentation/rules/ansible/aws/cloudfront_without_minimum_protocol_tls_1.2.md @@ -0,0 +1,132 @@ +--- +title: "CloudFront without minimum protocol TLS 1.2" +group_id: "Ansible / AWS" +meta: + name: "aws/cloudfront_without_minimum_protocol_tls_1.2" + id: "d0c13053-d2c8-44a6-95da-d592996e9e67" + display_name: "CloudFront without minimum protocol TLS 1.2" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `d0c13053-d2c8-44a6-95da-d592996e9e67` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/cloudfront_distribution_module.html#parameter-viewer_certificate/minimum_protocol_version) + +### Description + +CloudFront distributions must enforce modern TLS for viewer connections to prevent interception and protocol downgrades. The Ansible `community.aws.cloudfront_distribution` (or `cloudfront_distribution`) resource must include a `viewer_certificate` block with `minimum_protocol_version` set to a TLS 1.2 variant (for example, `TLSv1.2_2018` or `TLSv1.2_2019`). + +Tasks that omit `viewer_certificate` or specify a `minimum_protocol_version` that is not a TLS 1.2 variant are flagged. + +Secure configuration example: + +```yaml +- name: Create CloudFront distribution with TLS 1.2 minimum + community.aws.cloudfront_distribution: + state: present + enabled: yes + origins: + - id: myOrigin + domain_name: origin.example.com + viewer_certificate: + acm_certificate_arn: arn:aws:acm:us-east-1:123456789012:certificate/abcd-ef01-2345-6789 + ssl_support_method: sni-only + minimum_protocol_version: TLSv1.2_2018 +``` + +## Compliant Code Examples +```yaml +- name: create a distribution with an origin and logging + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: 'my test origin-000111' + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + logging: + enabled: true + include_cookies: false + bucket: mylogbucket.s3.amazonaws.com + prefix: myprefix/ + viewer_certificate: + minimum_protocol_version: TLSv1.2_2018 + comment: this is a CloudFront distribution with logging + +``` +## Non-Compliant Code Examples +```yaml +- name: create a distribution with an origin and logging + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: 'my test origin-000111' + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + logging: + enabled: true + include_cookies: false + bucket: mylogbucket.s3.amazonaws.com + prefix: myprefix/ + viewer_certificate: + minimum_protocol_version: TLSv1 + comment: this is a CloudFront distribution with logging +- name: create another distribution with an origin and logging + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: 'my test origin-000111' + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + logging: + enabled: true + include_cookies: false + bucket: mylogbucket.s3.amazonaws.com + prefix: myprefix/ + viewer_certificate: + minimum_protocol_version: TLSv1.1_2016 + comment: this is a CloudFront distribution with logging +- name: create a third distribution + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: 'my test origin-000111' + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + logging: + enabled: true + include_cookies: false + bucket: mylogbucket.s3.amazonaws.com + prefix: myprefix/ + comment: this is a CloudFront distribution with logging + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cloudfront_without_waf.md b/documentation/rules/ansible/aws/cloudfront_without_waf.md new file mode 100644 index 00000000..448a7e21 --- /dev/null +++ b/documentation/rules/ansible/aws/cloudfront_without_waf.md @@ -0,0 +1,71 @@ +--- +title: "CloudFront without WAF" +group_id: "Ansible / AWS" +meta: + name: "aws/cloudfront_without_waf" + id: "22c80725-e390-4055-8d14-a872230f6607" + display_name: "CloudFront without WAF" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `22c80725-e390-4055-8d14-a872230f6607` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/cloudfront_distribution_module.html) + +### Description + +CloudFront distributions must be associated with an AWS WAF Web ACL to filter malicious HTTP traffic and reduce the risk of application-layer attacks such as SQL injection, cross-site scripting, and automated bot abuse. + +For Ansible tasks using the `community.aws.cloudfront_distribution` or `cloudfront_distribution` module, the `web_acl_id` property must be defined and set to the ARN of a WAFv2 Web ACL (global scope). This rule flags distributions where `web_acl_id` is missing or undefined. Ensure the attached WAFv2 Web ACL ARN is compatible with CloudFront. + +Secure example (Ansible): + +```yaml +- name: create cloudfront distribution with WAF + community.aws.cloudfront_distribution: + state: present + alias: + - example.com + web_acl_id: arn:aws:wafv2:global:123456789012:regional/webacl/example-web-acl/abcd1234-ef56-7890-gh12-ijklmnopqrst + # other required distribution properties... +``` + +## Compliant Code Examples +```yaml +- name: create a basic distribution with defaults and tags + community.aws.cloudfront_distribution: + state: present + default_origin_domain_name: www.my-cloudfront-origin.com + tags: + Name: example distribution + Project: example project + Priority: '1' + web_acl_id: my-web-acl-id + +``` +## Non-Compliant Code Examples +```yaml +- name: create a basic distribution with defaults and tags + community.aws.cloudfront_distribution: + state: present + default_origin_domain_name: www.my-cloudfront-origin.com + tags: + Name: example distribution + Project: example project + Priority: '1' + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cloudtrail_log_file_validation_disabled.md b/documentation/rules/ansible/aws/cloudtrail_log_file_validation_disabled.md new file mode 100644 index 00000000..a2b184ea --- /dev/null +++ b/documentation/rules/ansible/aws/cloudtrail_log_file_validation_disabled.md @@ -0,0 +1,107 @@ +--- +title: "CloudTrail log file validation disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/cloudtrail_log_file_validation_disabled" + id: "4d8681a2-3d30-4c89-8070-08acd142748e" + display_name: "CloudTrail log file validation disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `4d8681a2-3d30-4c89-8070-08acd142748e` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html) + +### Description + +CloudTrail log file validation must be enabled to detect tampering of delivered log files and preserve the integrity of audit data used for incident response and compliance. + +For Ansible tasks using the `amazon.aws.cloudtrail` or `cloudtrail` module, one of the properties `enable_log_file_validation` or `log_file_validation_enabled` must be defined and set to `true` (or `yes`). Resources missing both properties or with these properties set to `false`, `no`, or any non-`true` value are flagged as insecure. + +Secure Ansible example: + +```yaml +- name: Create CloudTrail with log file validation enabled + amazon.aws.cloudtrail: + name: my-trail + s3_bucket_name: my-trail-bucket + enable_log_file_validation: true +``` + +## Compliant Code Examples +```yaml +- name: create multi-region trail with validation and tags v2 + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + enable_log_file_validation: true + cloudwatch_logs_role_arn: arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role + cloudwatch_logs_log_group_arn: arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:* + kms_key_id: alias/MyAliasName + tags: + environment: dev + Name: default +- name: create multi-region trail with validation and tags v3 + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + log_file_validation_enabled: true + cloudwatch_logs_role_arn: arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role + cloudwatch_logs_log_group_arn: arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:* + kms_key_id: alias/MyAliasName + tags: + environment: dev + Name: default + +``` +## Non-Compliant Code Examples +```yaml +- name: create multi-region trail with validation and tags + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + cloudwatch_logs_role_arn: "arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role" + cloudwatch_logs_log_group_arn: "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:*" + kms_key_id: "alias/MyAliasName" + tags: + environment: dev + Name: default +- name: create multi-region trail with validation and tags v7 + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + enable_log_file_validation: false + cloudwatch_logs_role_arn: "arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role" + cloudwatch_logs_log_group_arn: "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:*" + kms_key_id: "alias/MyAliasName" + tags: + environment: dev + Name: default + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms.md b/documentation/rules/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms.md new file mode 100644 index 00000000..e8debb85 --- /dev/null +++ b/documentation/rules/ansible/aws/cloudtrail_log_files_not_encrypted_with_kms.md @@ -0,0 +1,73 @@ +--- +title: "CloudTrail log files not encrypted with KMS" +group_id: "Ansible / AWS" +meta: + name: "aws/cloudtrail_log_files_not_encrypted_with_kms" + id: "f5587077-3f57-4370-9b4e-4eb5b1bac85b" + display_name: "CloudTrail log files not encrypted with KMS" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Encryption" +--- +## Metadata + +**Id:** `f5587077-3f57-4370-9b4e-4eb5b1bac85b` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html) + +### Description + +CloudTrail log deliveries must be encrypted with an AWS KMS customer-managed key to protect audit logs at rest and ensure strict key access control, rotation, and usage auditing. In Ansible tasks using the `amazon.aws.cloudtrail` or `cloudtrail` module, the `kms_key_id` parameter must be defined and set to a KMS key ARN or alias (for example `arn:aws:kms:region:account-id:key/KEY-ID` or `alias/my-key`). + +Tasks missing `kms_key_id` are flagged. Without a customer-managed key, you lose control over key access, rotation, and usage auditing. + +Secure configuration example: + +```yaml +- name: Create CloudTrail with KMS encryption + amazon.aws.cloudtrail: + name: my-trail + s3_bucket_name: my-cloudtrail-bucket + kms_key_id: arn:aws:kms:us-east-1:123456789012:key/abcd1234-ef56-7890-abcd-EXAMPLE +``` + +## Compliant Code Examples +```yaml +- name: create multi-region trail with validation and tags v2 + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + enable_log_file_validation: true + cloudwatch_logs_role_arn: arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role + cloudwatch_logs_log_group_arn: arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:* + kms_key_id: alias/MyAliasName + tags: + environment: dev + Name: default + +``` +## Non-Compliant Code Examples +```yaml +- name: no sns topic name + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + s3_key_prefix: cloudtrail + region: us-east-1 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cloudtrail_logging_disabled.md b/documentation/rules/ansible/aws/cloudtrail_logging_disabled.md new file mode 100644 index 00000000..aee37eac --- /dev/null +++ b/documentation/rules/ansible/aws/cloudtrail_logging_disabled.md @@ -0,0 +1,60 @@ +--- +title: "CloudTrail logging disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/cloudtrail_logging_disabled" + id: "d4a73c49-cbaa-4c6f-80ee-d6ef5a3a26f5" + display_name: "CloudTrail logging disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `d4a73c49-cbaa-4c6f-80ee-d6ef5a3a26f5` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html#parameter-enable_logging) + +### Description + +CloudTrail logging must be enabled to record AWS API activity for detection, auditing, and forensic investigations, and to meet compliance requirements. Disabling logging can allow malicious or accidental changes to go undetected. + +In Ansible, tasks using the `amazon.aws.cloudtrail` or `cloudtrail` modules must have the `enable_logging` property set to `true`. This rule flags tasks where `enable_logging` is explicitly set to `false`. Ensure the property is present and set to `true` to enable delivery of management events and logs. Example secure Ansible task: + +```yaml +- name: Ensure CloudTrail logging is enabled + amazon.aws.cloudtrail: + name: my-trail + s3_bucket_name: my-cloudtrail-bucket + enable_logging: true +``` + +## Compliant Code Examples +```yaml +- name: example + amazon.aws.cloudtrail: + state: present + name: default + enable_logging: true + +``` +## Non-Compliant Code Examples +```yaml +- name: example + amazon.aws.cloudtrail: + state: present + name: default + enable_logging: false + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cloudtrail_multi_region_disabled.md b/documentation/rules/ansible/aws/cloudtrail_multi_region_disabled.md new file mode 100644 index 00000000..d017902c --- /dev/null +++ b/documentation/rules/ansible/aws/cloudtrail_multi_region_disabled.md @@ -0,0 +1,81 @@ +--- +title: "CloudTrail multi-region is disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/cloudtrail_multi_region_disabled" + id: "6ad087d7-a509-4b20-b853-9ef6f5ebaa98" + display_name: "CloudTrail multi-region is disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `6ad087d7-a509-4b20-b853-9ef6f5ebaa98` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html#parameter-is_multi_region_trail) + +### Description + +CloudTrail must be configured as a multi-region trail so that API activity across all AWS regions is captured. This ensures comprehensive auditing and timely incident response. Without multi-region logging, cross-region activity can be missed, hindering detection, forensics, and compliance. + +For Ansible CloudTrail resources (modules `amazon.aws.cloudtrail` or `cloudtrail`), the `is_multi_region_trail` property must be defined and set to `true`. Resources that omit `is_multi_region_trail` or have `is_multi_region_trail: false` are flagged. + +Secure example (Ansible): + +```yaml +- name: Create multi-region CloudTrail + amazon.aws.cloudtrail: + name: my-trail + s3_bucket_name: my-trail-bucket + is_multi_region_trail: true + state: present +``` + +## Compliant Code Examples +```yaml +- name: example1 + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + enable_log_file_validation: true + cloudwatch_logs_role_arn: arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role + cloudwatch_logs_log_group_arn: arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:* + kms_key_id: alias/MyAliasName + tags: + environment: dev + Name: default + +``` +## Non-Compliant Code Examples +```yaml +- name: example1 + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: false + enable_log_file_validation: true + cloudwatch_logs_role_arn: "arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role" + cloudwatch_logs_log_group_arn: "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:*" + kms_key_id: "alias/MyAliasName" + tags: + environment: dev + Name: default + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cloudtrail_not_integrated_with_cloudwatch.md b/documentation/rules/ansible/aws/cloudtrail_not_integrated_with_cloudwatch.md new file mode 100644 index 00000000..03bda259 --- /dev/null +++ b/documentation/rules/ansible/aws/cloudtrail_not_integrated_with_cloudwatch.md @@ -0,0 +1,106 @@ +--- +title: "CloudTrail not integrated with CloudWatch" +group_id: "Ansible / AWS" +meta: + name: "aws/cloudtrail_not_integrated_with_cloudwatch" + id: "ebb2118a-03bc-4d53-ab43-d8750f5cb8d3" + display_name: "CloudTrail not integrated with CloudWatch" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `ebb2118a-03bc-4d53-ab43-d8750f5cb8d3` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html) + +### Description + +CloudTrail must be integrated with CloudWatch Logs so events are available for real-time detection, alerting, and centralized log analysis, and so forensic evidence is retained for incident investigation. + +For Ansible tasks using the `amazon.aws.cloudtrail` or `cloudtrail` modules, the `cloudwatch_logs_role_arn` and `cloudwatch_logs_log_group_arn` properties must be defined. `cloudwatch_logs_role_arn` should be an IAM role ARN that allows CloudTrail to publish to CloudWatch Logs. `cloudwatch_logs_log_group_arn` should reference the destination Log Group ARN. Tasks missing either property are flagged. + +Secure configuration example: + +```yaml +- name: Create CloudTrail with CloudWatch Logs integration + amazon.aws.cloudtrail: + name: my-trail + s3_bucket_name: my-bucket + is_multi_region_trail: yes + cloudwatch_logs_role_arn: arn:aws:iam::123456789012:role/CloudTrail_CloudWatch_Logs_Role + cloudwatch_logs_log_group_arn: arn:aws:logs:us-east-1:123456789012:log-group:/aws/cloudtrail +``` + +## Compliant Code Examples +```yaml +- name: create multi-region trail with validation and tags negative + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + enable_log_file_validation: true + cloudwatch_logs_role_arn: "arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role" + cloudwatch_logs_log_group_arn: "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:*" + kms_key_id: "alias/MyAliasName" + tags: + environment: dev + Name: default + +``` +## Non-Compliant Code Examples +```yaml +- name: positive1 + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + enable_log_file_validation: true + kms_key_id: "alias/MyAliasName" + tags: + environment: dev + Name: default +- name: positive2 + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + enable_log_file_validation: true + cloudwatch_logs_role_arn: "arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role" + kms_key_id: "alias/MyAliasName" + tags: + environment: dev + Name: default +- name: positive3 + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + region: us-east-1 + is_multi_region_trail: true + enable_log_file_validation: true + cloudwatch_logs_log_group_arn: "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:*" + kms_key_id: "alias/MyAliasName" + tags: + environment: dev + Name: default + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cloudtrail_sns_topic_name_undefined.md b/documentation/rules/ansible/aws/cloudtrail_sns_topic_name_undefined.md new file mode 100644 index 00000000..d818f91f --- /dev/null +++ b/documentation/rules/ansible/aws/cloudtrail_sns_topic_name_undefined.md @@ -0,0 +1,78 @@ +--- +title: "CloudTrail SNS topic name undefined" +group_id: "Ansible / AWS" +meta: + name: "aws/cloudtrail_sns_topic_name_undefined" + id: "5ba316a9-c466-4ec1-8d5b-bc6107dc9a92" + display_name: "CloudTrail SNS topic name undefined" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `5ba316a9-c466-4ec1-8d5b-bc6107dc9a92` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudtrail_module.html) + +### Description + +CloudTrail should be configured to publish notifications to an SNS topic so trail events and log delivery issues can trigger alerts and automated responses. Without an SNS target, you may miss timely notifications about suspicious activity or failures. + +For Ansible CloudTrail tasks (modules `amazon.aws.cloudtrail` or `cloudtrail`), the `sns_topic_name` property must be defined and non-null. Tasks missing `sns_topic_name` or with it set to `null`/empty are flagged. Ensure the value references an existing SNS topic (or create one in the same playbook) so CloudTrail can publish notifications. + +Secure example: + +```yaml +- name: Create CloudTrail with SNS notifications + amazon.aws.cloudtrail: + name: my-trail + s3_bucket_name: my-cloudtrail-bucket + sns_topic_name: my-cloudtrail-topic + is_multi_region_trail: true + include_global_service_events: true + state: present +``` + +## Compliant Code Examples +```yaml +- name: sns topic name defined + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + s3_key_prefix: cloudtrail + region: us-east-1 + sns_topic_name: some_topic_name + +``` +## Non-Compliant Code Examples +```yaml +- name: no sns topic name + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + s3_key_prefix: cloudtrail + region: us-east-1 +- name: sns topic name defined + amazon.aws.cloudtrail: + state: present + name: default + s3_bucket_name: mylogbucket + s3_key_prefix: cloudtrail + region: us-east-1 + sns_topic_name: + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cloudwatch_without_retention_period_specified.md b/documentation/rules/ansible/aws/cloudwatch_without_retention_period_specified.md new file mode 100644 index 00000000..c4b7c5c8 --- /dev/null +++ b/documentation/rules/ansible/aws/cloudwatch_without_retention_period_specified.md @@ -0,0 +1,62 @@ +--- +title: "CloudWatch without retention period specified" +group_id: "Ansible / AWS" +meta: + name: "aws/cloudwatch_without_retention_period_specified" + id: "e24e18d9-4c2b-4649-b3d0-18c088145e24" + display_name: "CloudWatch without retention period specified" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `e24e18d9-4c2b-4649-b3d0-18c088145e24` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudwatchlogs_log_group_module.html) + +### Description + +CloudWatch Log Groups must have a defined retention period to retain logs for incident investigation and regulatory compliance. Without one, indefinite retention increases storage costs and the risk of long-term data exposure. + +For Ansible tasks using `amazon.aws.cloudwatchlogs_log_group` or `cloudwatchlogs_log_group`, the `retention` property must be set to one of the AWS-supported retention periods: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653]. Resources missing `retention` or with a value not in this list are flagged as misconfigured. + +Secure configuration example: + +``` +- name: Create CloudWatch log group with retention + amazon.aws.cloudwatchlogs_log_group: + name: my-log-group + retention: 365 +``` + +## Compliant Code Examples +```yaml +- name: example3 ec2 group + amazon.aws.cloudwatchlogs_log_group: + log_group_name: test-log-group + retention: 5 + +``` +## Non-Compliant Code Examples +```yaml +- name: example ec2 group + amazon.aws.cloudwatchlogs_log_group: + log_group_name: test-log-group +- name: example2 ec2 group + amazon.aws.cloudwatchlogs_log_group: + log_group_name: test-log-group + retention: 111111 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cmk_is_unusable.md b/documentation/rules/ansible/aws/cmk_is_unusable.md new file mode 100644 index 00000000..cbe90dd5 --- /dev/null +++ b/documentation/rules/ansible/aws/cmk_is_unusable.md @@ -0,0 +1,75 @@ +--- +title: "CMK is unusable" +group_id: "Ansible / AWS" +meta: + name: "aws/cmk_is_unusable" + id: "133fee21-37ef-45df-a563-4d07edc169f4" + display_name: "CMK is unusable" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Availability" +--- +## Metadata + +**Id:** `133fee21-37ef-45df-a563-4d07edc169f4` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Availability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/kms_key_module.html#parameter-enabled) + +### Description + +KMS Customer Master Keys (CMKs) must be usable, as disabled or scheduled-for-deletion keys cannot decrypt data and may cause service outages or data inaccessibility. + +In Ansible `amazon.aws.kms_key` tasks, ensure `enabled` is defined and set to `true`, and that `pending_window` is not defined. Tasks with `enabled` set to `false` or with `enabled` undefined are flagged. Any task that sets `pending_window` (scheduling the key for deletion) is also flagged because it renders the key unusable after the pending window expires. + +Secure example for Ansible: + +```yaml +- name: create KMS key + amazon.aws.kms_key: + name: my-key + description: "Key for encrypting secrets" + state: present + enabled: true +``` + +## Compliant Code Examples +```yaml +- name: Update IAM policy on an existing KMS key + amazon.aws.kms_key: + alias: my-kms-key + policy: '{"Version": "2012-10-17", "Id": "my-kms-key-permissions", "Statement": [ { } ]}' + state: present + enabled: true + +``` +## Non-Compliant Code Examples +```yaml +- name: Update IAM policy on an existing KMS key2 + amazon.aws.kms_key: + alias: my-kms-key + policy: '{"Version": "2012-10-17", "Id": "my-kms-key-permissions", "Statement": [ { } ]}' + state: present + pending_window: 8 + +``` + +```yaml +- name: Update IAM policy on an existing KMS key1 + amazon.aws.kms_key: + alias: my-kms-key + policy: '{"Version": "2012-10-17", "Id": "my-kms-key-permissions", "Statement": [ { } ]}' + state: present + enabled: false + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cmk_rotation_disabled.md b/documentation/rules/ansible/aws/cmk_rotation_disabled.md new file mode 100644 index 00000000..281ea362 --- /dev/null +++ b/documentation/rules/ansible/aws/cmk_rotation_disabled.md @@ -0,0 +1,76 @@ +--- +title: "CMK rotation disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/cmk_rotation_disabled" + id: "af96d737-0818-4162-8c41-40d969bd65d1" + display_name: "CMK rotation disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `af96d737-0818-4162-8c41-40d969bd65d1` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/kms_key_module.html#parameter-enable_key_rotation) + +### Description + +Customer Master Keys (CMKs) must have automatic key rotation enabled to limit how long a compromised key can be used and to meet key lifecycle and compliance requirements. + +In Ansible, for tasks using the `amazon.aws.kms_key` module, when `enabled: true` and the key is not scheduled for deletion (no `pending_window` defined), the `enable_key_rotation` property must be present and set to `true`. Resources missing `enable_key_rotation` or with `enable_key_rotation: false` are flagged as misconfigured. + +Secure configuration example: + +``` +- name: Create CMK with rotation enabled + amazon.aws.kms_key: + name: my-key + enabled: true + enable_key_rotation: true +``` + +## Compliant Code Examples +```yaml +- name: Update IAM policy on an existing KMS key3 + amazon.aws.kms_key: + alias: my-kms-key + policy: '{"Version": "2012-10-17", "Id": "my-kms-key-permissions", "Statement": [ { } ]}' + state: present + enabled: true + enable_key_rotation: true + +``` +## Non-Compliant Code Examples +```yaml +- name: Update IAM policy on an existing KMS key2 + amazon.aws.kms_key: + alias: my-kms-key + policy: '{"Version": "2012-10-17", "Id": "my-kms-key-permissions", "Statement": [ { } ]}' + state: present + enabled: true + enable_key_rotation: false + +``` + +```yaml +- name: Update IAM policy on an existing KMS key + amazon.aws.kms_key: + alias: my-kms-key + policy: '{"Version": "2012-10-17", "Id": "my-kms-key-permissions", "Statement": [ { } ]}' + state: present + enabled: true + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/codebuild_not_encrypted.md b/documentation/rules/ansible/aws/codebuild_not_encrypted.md new file mode 100644 index 00000000..aef5a5c9 --- /dev/null +++ b/documentation/rules/ansible/aws/codebuild_not_encrypted.md @@ -0,0 +1,91 @@ +--- +title: "CodeBuild project is not encrypted" +group_id: "Ansible / AWS" +meta: + name: "aws/codebuild_not_encrypted" + id: "a1423864-2fbc-4f46-bfe1-fbbf125c71c9" + display_name: "CodeBuild project is not encrypted" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `a1423864-2fbc-4f46-bfe1-fbbf125c71c9` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/codebuild_project_module.html) + +### Description + +CodeBuild projects must have a KMS encryption key configured so build artifacts, cached data, and logs are protected at rest. + +For Ansible resources using the `community.aws.codebuild_project` or `aws_codebuild` modules, the `encryption_key` property must be defined and set to a valid AWS KMS key ARN or alias (for example `arn:aws:kms:...` or `alias/your-key-alias`). Resources missing `encryption_key` or with it undefined are flagged. + +Example secure task: + +```yaml +- name: create codebuild project + community.aws.codebuild_project: + name: my-build + encryption_key: arn:aws:kms:us-east-1:123456789012:key/abcd1234-ef56-7890-abcd-123456ef7890 + # other required properties... +``` + +## Compliant Code Examples +```yaml +- name: My project v2 + community.aws.codebuild_project: + description: My nice little project + service_role: arn:aws:iam::123123:role/service-role/code-build-service-role + source: + type: CODEPIPELINE + buildspec: '' + artifacts: + namespaceType: NONE + packaging: NONE + type: CODEPIPELINE + name: my_project + environment: + computeType: BUILD_GENERAL1_SMALL + privilegedMode: 'true' + image: aws/codebuild/docker:17.09.0 + type: LINUX_CONTAINER + encryption_key: arn:aws:kms:us-east-1:123123:alias/aws/s3 + region: us-east-1 + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: My project + community.aws.codebuild_project: + description: My nice little project v2 + service_role: "arn:aws:iam::123123:role/service-role/code-build-service-role" + source: + type: CODEPIPELINE + buildspec: '' + artifacts: + namespaceType: NONE + packaging: NONE + type: CODEPIPELINE + name: my_project + environment: + computeType: BUILD_GENERAL1_SMALL + privilegedMode: "true" + image: "aws/codebuild/docker:17.09.0" + type: LINUX_CONTAINER + region: us-east-1 + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/config_configuration_aggregator_to_all_regions_disabled.md b/documentation/rules/ansible/aws/config_configuration_aggregator_to_all_regions_disabled.md new file mode 100644 index 00000000..1546cf44 --- /dev/null +++ b/documentation/rules/ansible/aws/config_configuration_aggregator_to_all_regions_disabled.md @@ -0,0 +1,96 @@ +--- +title: "Configuration aggregator to all regions disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/config_configuration_aggregator_to_all_regions_disabled" + id: "a2fdf451-89dd-451e-af92-bf6c0f4bab96" + display_name: "Configuration aggregator to all regions disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `a2fdf451-89dd-451e-af92-bf6c0f4bab96` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/config_aggregator_module.html#parameter-organization_source) + +### Description + +AWS Config aggregators must collect configuration data from all AWS Regions to provide centralized, complete visibility of resource state. This ensures cross-region misconfigurations and compliance violations are detected. + +For Ansible tasks using the `community.aws.config_aggregator` or `aws_config_aggregator` modules, set the `all_aws_regions` property to `true` under the relevant `account_sources` entries or the `organization_source` block. Resources that omit `all_aws_regions` or have it set to `false` are flagged, as they do not provide full regional coverage. + +Secure examples for Ansible (account and organization sources): + +```yaml +- name: Create AWS Config Aggregator (account sources) + community.aws.config_aggregator: + name: my-config-aggregator + account_sources: + - account_ids: ['123456789012'] + all_aws_regions: true + +- name: Create AWS Config Aggregator (organization source) + community.aws.config_aggregator: + name: org-config-aggregator + organization_source: + role_arn: arn:aws:iam::111122223333:role/ConfigAggregatorRole + all_aws_regions: true +``` + +## Compliant Code Examples +```yaml +- name: Create cross-account aggregator + community.aws.config_aggregator: + name: test_config_rule + state: present + account_sources: + account_ids: + - 1234567890 + - 0123456789 + - 9012345678 + all_aws_regions: yes + organization_source: + all_aws_regions: yes + +``` +## Non-Compliant Code Examples +```yaml +- name: Create cross-account aggregator + community.aws.config_aggregator: + name: test_config_rule + state: present + account_sources: + account_ids: + - 1234567890 + - 0123456789 + - 9012345678 + all_aws_regions: no + organization_source: + all_aws_regions: yes +- name: Create cross-account aggregator2 + community.aws.config_aggregator: + name: test_config_rule + state: present + account_sources: + account_ids: + - 1234567890 + - 0123456789 + - 9012345678 + all_aws_regions: yes + organization_source: + all_aws_regions: no + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/config_rule_for_encrypted_volumes_is_disabled.md b/documentation/rules/ansible/aws/config_rule_for_encrypted_volumes_is_disabled.md new file mode 100644 index 00000000..214ce2d5 --- /dev/null +++ b/documentation/rules/ansible/aws/config_rule_for_encrypted_volumes_is_disabled.md @@ -0,0 +1,76 @@ +--- +title: "Config rule for encrypted volumes disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/config_rule_for_encrypted_volumes_is_disabled" + id: "7674a686-e4b1-4a95-83d4-1fd53c623d84" + display_name: "Config rule for encrypted volumes disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `7674a686-e4b1-4a95-83d4-1fd53c623d84` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/config_rule_module.html#parameter-source/identifier) + +### Description + +Missing an AWS Config rule for encrypted volumes prevents automated detection of unencrypted block storage and snapshots, leaving data at rest vulnerable to exposure if storage is compromised. + +For Ansible-managed resources, define an `aws_config_rule` (module `community.aws.config_rule` or `aws_config_rule`) with `source.identifier` set to `ENCRYPTED_VOLUMES`. The check is case-insensitive. Tasks that omit this `aws_config_rule` or set `source.identifier` to a different value are flagged. + +Secure Ansible example: + +```yaml +- name: Ensure AWS Config rule for encrypted volumes exists + community.aws.config_rule: + name: encrypted-volumes-rule + source: + owner: AWS + identifier: ENCRYPTED_VOLUMES +``` + +## Compliant Code Examples +```yaml +- name: foo + community.aws.config_rule: + name: test_config_rule + state: present + description: This AWS Config rule checks for public write access on S3 buckets + scope: + compliance_types: + - AWS::S3::Bucket + source: + owner: AWS + identifier: ENCRYPTED_VOLUMES + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: foo + community.aws.config_rule: + name: test_config_rule + state: present + description: 'This AWS Config rule checks for public write access on S3 buckets' + scope: + compliance_types: + - 'AWS::S3::Bucket' + source: + owner: AWS + identifier: 'S3_BUCKET_PUBLIC_WRITE_PROHIBITED' + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa.md b/documentation/rules/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa.md new file mode 100644 index 00000000..40c66337 --- /dev/null +++ b/documentation/rules/ansible/aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa.md @@ -0,0 +1,209 @@ +--- +title: "Cross-account IAM assume role policy without ExternalId or MFA" +group_id: "Ansible / AWS" +meta: + name: "aws/cross_account_iam_assume_role_policy_without_external_id_or_mfa" + id: "af167837-9636-4086-b815-c239186b9dda" + display_name: "Cross-account IAM assume role policy without ExternalId or MFA" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `af167837-9636-4086-b815-c239186b9dda` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_role_module.html#parameter-assume_role_policy_document) + +### Description + +Cross-account IAM role trust policies that allow `sts:AssumeRole` to external principals must require an `ExternalId` or MFA to prevent unintended or unauthorized access from third-party accounts. Without an `ExternalId` or a `Condition` requiring MFA, an external principal (including other-account root principals) that can assume the role may gain access to sensitive resources or perform privileged actions. + +In Ansible `amazon.aws.iam_role` and `iam_role` tasks, the `assume_role_policy_document` `Statement` with `Effect: Allow` and `Action: sts:AssumeRole` that names a cross-account `Principal` (for example, an ARN that includes another account or `:root`) must include a `Condition` containing either `sts:ExternalId` (for example, `StringEquals`) or `aws:MultiFactorAuthPresent` set to `true`. Resources missing the required `Condition` or that allow cross-account assume-role without `ExternalId` or MFA are flagged. + +Secure trust policy examples: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { "sts:ExternalId": "your-external-id-value" } + } + } + ] +} +``` + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, + "Action": "sts:AssumeRole", + "Condition": { + "Bool": { "aws:MultiFactorAuthPresent": "true" } + } + } + ] +} +``` + +## Compliant Code Examples +```yaml +- name: Create a role with description and tags4 + amazon.aws.iam_role: + name: mynewrole4 + assume_role_policy_document: > + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Principal": { + "AWS": "arn:aws:iam::987654321145:root" + }, + "Effect": "Allow", + "Resource": "*", + "Sid": "", + "Condition": { + "StringEquals": { + "sts:ExternalId": "98765" + } + } + } + ] + } + description: This is My New Role + tags: + env: dev + +``` + +```yaml +- name: Create a role with description and tags5 + amazon.aws.iam_role: + name: mynewrole5 + assume_role_policy_document: > + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Principal": { + "AWS": "arn:aws:iam::987654321145:root" + }, + "Effect": "Allow", + "Resource": "*", + "Sid": "", + "Condition": { + "Bool": { + "aws:MultiFactorAuthPresent": "true" + } + } + } + ] + } + description: This is My New Role + tags: + env: dev + +``` +## Non-Compliant Code Examples +```yaml +- name: Create a role with description and tags2 + amazon.aws.iam_role: + name: mynewrole2 + assume_role_policy_document: > + { + "Version": "2012-10-17", + "Statement": { + "Action": "sts:AssumeRole", + "Principal": { + "AWS": "arn:aws:iam::987654321145:root" + }, + "Effect": "Allow", + "Resource": "*", + "Sid": "", + "Condition": { + "Bool": { + "aws:MultiFactorAuthPresent": "false" + } + } + } + } + description: This is My New Role + tags: + env: dev + +``` + +```yaml +- name: Create a role with description and tags3 + amazon.aws.iam_role: + name: mynewrole3 + assume_role_policy_document: > + { + "Version": "2012-10-17", + "Statement": { + "Action": "sts:AssumeRole", + "Principal": { + "AWS": "arn:aws:iam::987654321145:root" + }, + "Effect": "Allow", + "Resource": "*", + "Sid": "", + "Condition": { + "StringEquals": { + "sts:ExternalId": "" + } + } + } + } + description: This is My New Role + tags: + env: dev + +``` + +```yaml +- name: Create a role with description and tags + amazon.aws.iam_role: + name: mynewrole + assume_role_policy_document: > + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Principal": { + "AWS": "arn:aws:iam::987654321145:root" + }, + "Effect": "Allow", + "Resource": "*", + "Sid": "" + } + ] + } + description: This is My New Role + tags: + env: dev + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/db_instance_storage_not_encrypted.md b/documentation/rules/ansible/aws/db_instance_storage_not_encrypted.md new file mode 100644 index 00000000..97dcc43a --- /dev/null +++ b/documentation/rules/ansible/aws/db_instance_storage_not_encrypted.md @@ -0,0 +1,114 @@ +--- +title: "DB instance storage not encrypted" +group_id: "Ansible / AWS" +meta: + name: "aws/db_instance_storage_not_encrypted" + id: "7dfb316c-a6c2-454d-b8a2-97f147b0c0ff" + display_name: "DB instance storage not encrypted" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `7dfb316c-a6c2-454d-b8a2-97f147b0c0ff` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html) + +### Description + +RDS instances must have storage encryption enabled to protect data at rest, including database files, automated backups, and snapshots. Without encryption, this data is exposed to unauthorized access if storage media or snapshots are compromised. + +For Ansible resources using the `amazon.aws.rds_instance` or `rds_instance` modules, set `storage_encrypted` to `true`. If you are using a customer-managed key, also define `kms_key_id`. This rule flags instances where `storage_encrypted` is undefined or set to `false` and no `kms_key_id` is provided. + +```yaml +- name: Create encrypted RDS instance + amazon.aws.rds_instance: + db_instance_identifier: mydb + engine: mysql + allocated_storage: 20 + master_username: admin + master_user_password: secret + storage_encrypted: true + kms_key_id: arn:aws:kms:us-east-1:123456789012:key/abcd-ef01-2345-6789-abcd +``` + +## Compliant Code Examples +```yaml +- name: foo + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: true + db_instance_class: db.t2.medium + username: '{{ username }}' + password: '{{ password }}' + allocated_storage: '{{ allocated_storage }}' +- name: foo2 + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: yes + db_instance_class: db.t2.medium + username: '{{ username }}' + password: '{{ password }}' + allocated_storage: '{{ allocated_storage }}' +- name: foo3 + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + kms_key_id: sup3rstr0ngK3y + db_instance_class: db.t2.medium + username: '{{ username }}' + password: '{{ password }}' + allocated_storage: '{{ allocated_storage }}' + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: foo + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: False + db_instance_class: db.t2.medium + username: "{{ username }}" + password: "{{ password }}" + allocated_storage: "{{ allocated_storage }}" +- name: foo2 + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: no + db_instance_class: db.t2.medium + username: "{{ username }}" + password: "{{ password }}" + allocated_storage: "{{ allocated_storage }}" +- name: foo3 + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + db_instance_class: db.t2.medium + username: "{{ username }}" + password: "{{ password }}" + allocated_storage: "{{ allocated_storage }}" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/db_security_group_open_to_large_scope.md b/documentation/rules/ansible/aws/db_security_group_open_to_large_scope.md new file mode 100644 index 00000000..b6454117 --- /dev/null +++ b/documentation/rules/ansible/aws/db_security_group_open_to_large_scope.md @@ -0,0 +1,117 @@ +--- +title: "DB security group open to large scope" +group_id: "Ansible / AWS" +meta: + name: "aws/db_security_group_open_to_large_scope" + id: "ea0ed1c7-9aef-4464-b7c7-94c762da3640" + display_name: "DB security group open to large scope" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `ea0ed1c7-9aef-4464-b7c7-94c762da3640` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html#ansible-collections-amazon-aws-ec2-group-module) + +### Description + +Security group rules that use CIDR blocks containing 256 or more IP addresses broaden the attack surface and make unauthorized access or lateral movement easier. + +For Ansible EC2 security groups (modules `amazon.aws.ec2_group` and `ec2_group`), ensure each rule's `cidr_ip` is a CIDR with a prefix length greater than 24 (for example `/25`–`/32`) so the subnet contains fewer than 256 addresses. This rule flags any task where `rules[].cidr_ip` has a prefix length of 24 or less (for example, `10.0.0.0/24`, `10.0.0.0/16`, or `0.0.0.0/0`). If broader access is required, prefer tighter subnetting, explicit host IPs, or security-group references instead of large CIDR ranges. + +Secure Ansible example with a narrow CIDR (/32 single host): + +```yaml +- name: create restrictive security group + amazon.aws.ec2_group: + name: my-sg + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 10.0.0.5/32 +``` + +## Compliant Code Examples +```yaml +- name: example ec2 group2 + ec2_group: + name: example1 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1a + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 10.1.1.1/32 + +``` +## Non-Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster + db_security_groups: ["example"] +- name: example ec2 group + ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1a + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 0.0.0.0/0 + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 10.0.0.0/8 + - proto: tcp + from_port: 443 + to_port: 443 + group_id: amazon-elb/sg-87654321/amazon-elb-sg + - proto: tcp + from_port: 3306 + to_port: 3306 + group_id: 123412341234/sg-87654321/exact-name-of-sg + - proto: udp + from_port: 10050 + to_port: 10050 + cidr_ip: 10.0.0.0/8 + - proto: udp + from_port: 10051 + to_port: 10051 + group_id: sg-12345678 + - proto: icmp + from_port: 8 # icmp type, -1 = any type + to_port: -1 # icmp subtype, -1 = any subtype + cidr_ip: 192.168.1.0/24 + - proto: all + # the containing group name may be specified here + group_name: example + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/db_security_group_with_public_scope.md b/documentation/rules/ansible/aws/db_security_group_with_public_scope.md new file mode 100644 index 00000000..5c2834b1 --- /dev/null +++ b/documentation/rules/ansible/aws/db_security_group_with_public_scope.md @@ -0,0 +1,139 @@ +--- +title: "DB security group with public scope" +group_id: "Ansible / AWS" +meta: + name: "aws/db_security_group_with_public_scope" + id: "0956aedf-6a7a-478b-ab56-63e2b19923ad" + display_name: "DB security group with public scope" + cloud_provider: "AWS" + platform: "Ansible" + severity: "CRITICAL" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `0956aedf-6a7a-478b-ab56-63e2b19923ad` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html) + +### Description + +Security groups must not allow unrestricted IP ranges because a `cidr_ip` of `0.0.0.0/0` grants access from the entire Internet and exposes instances to unauthorized access, brute-force attacks, and data exfiltration. + +For Ansible tasks using the `amazon.aws.ec2_group` or `ec2_group` modules, check the `rules` (ingress) and `rules_egress` (egress) entries and ensure each `cidr_ip` is not `0.0.0.0/0`. Prefer specific trusted CIDRs, private address ranges (for example `10.0.0.0/8`, `192.168.0.0/16`, `172.16.0.0/12`), or references to other security groups. + +This rule flags any `ec2_group.rules[].cidr_ip` or `ec2_group.rules_egress[].cidr_ip` set to a public scope such as `0.0.0.0/0`. Review and replace wide-open CIDRs with least-privilege network ranges or security-group references. + +Secure Ansible example with restricted CIDRs: + +```yaml +- name: Create internal security group + amazon.aws.ec2_group: + name: my-internal-sg + description: Allow internal SSH only + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 10.0.0.0/8 + rules_egress: + - proto: -1 + from_port: 0 + to_port: 0 + cidr_ip: 10.0.0.0/8 +``` + +## Compliant Code Examples +```yaml +- name: example ec2 group2 + ec2_group: + name: example1 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1a + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 10.1.1.1/32 + rules_egress: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 10.1.1.1/32 + group_name: example-other + # description to use if example-other needs to be created + group_desc: other example EC2 group + +``` +## Non-Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster + db_security_groups: ["example"] +- name: example ec2 group + ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1a + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 0.0.0.0/0 + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 10.0.0.0/8 + - proto: tcp + from_port: 443 + to_port: 443 + group_id: amazon-elb/sg-87654321/amazon-elb-sg + - proto: tcp + from_port: 3306 + to_port: 3306 + group_id: 123412341234/sg-87654321/exact-name-of-sg + - proto: udp + from_port: 10050 + to_port: 10050 + cidr_ip: 10.0.0.0/8 + - proto: udp + from_port: 10051 + to_port: 10051 + group_id: sg-12345678 + - proto: icmp + from_port: 8 # icmp type, -1 = any type + to_port: -1 # icmp subtype, -1 = any subtype + cidr_ip: 192.168.1.0/24 + - proto: all + group_name: example + rules_egress: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 0.0.0.0/0 + group_name: example-other + group_desc: other example EC2 group + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/default_security_groups_with_unrestricted_traffic.md b/documentation/rules/ansible/aws/default_security_groups_with_unrestricted_traffic.md new file mode 100644 index 00000000..d273b09a --- /dev/null +++ b/documentation/rules/ansible/aws/default_security_groups_with_unrestricted_traffic.md @@ -0,0 +1,171 @@ +--- +title: "Default security groups with unrestricted traffic" +group_id: "Ansible / AWS" +meta: + name: "aws/default_security_groups_with_unrestricted_traffic" + id: "8010e17a-00e9-4635-a692-90d6bcec68bd" + display_name: "Default security groups with unrestricted traffic" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `8010e17a-00e9-4635-a692-90d6bcec68bd` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html) + +### Description + +Security groups that allow inbound or outbound CIDR ranges of `0.0.0.0/0` or `::/0` expose resources to the entire internet, increasing the risk of unauthorized access, brute-force attacks, and data exfiltration. + +For Ansible `amazon.aws.ec2_group` or `ec2_group` tasks, inspect the `rules` and `rules_egress` entries and ensure the `cidr_ip` and `cidr_ipv6` properties are not set to `0.0.0.0/0` or `::/0`. Tasks containing `cidr_ip: 0.0.0.0/0` or `cidr_ipv6: ::/0` are flagged. Restrict access to specific CIDR ranges or reference other security groups instead of using global open CIDRs. + +Secure configuration example: + +```yaml +my_security_group: + amazon.aws.ec2_group: + name: my-sg + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 203.0.113.0/24 + rules_egress: + - proto: -1 + from_port: 0 + to_port: 0 + cidr_ip: 10.0.0.0/16 +``` + +## Compliant Code Examples +```yaml +- name: example ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: all + # in the 'proto' attribute, if you specify -1, all, or a protocol number other than tcp, udp, icmp, or 58 (ICMPv6), + # traffic on all ports is allowed, regardless of any ports you specify + from_port: 10050 # this value is ignored + to_port: 10050 # this value is ignored + cidr_ip: 10.1.0.0/16 + cidr_ipv6: 64:ff9b::/96 + rules_egress: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 10.1.0.0/16 + cidr_ipv6: 64:ff9b::/96 + group_name: example-other + # description to use if example-other needs to be created + group_desc: other example EC2 group + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: example ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: all + # in the 'proto' attribute, if you specify -1, all, or a protocol number other than tcp, udp, icmp, or 58 (ICMPv6), + # traffic on all ports is allowed, regardless of any ports you specify + from_port: 10050 # this value is ignored + to_port: 10050 # this value is ignored + cidr_ip: + - 0.0.0.0/0 +- name: example2 ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules_egress: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 0.0.0.0/0 + group_name: example-other + # description to use if example-other needs to be created + group_desc: other example EC2 group +- name: example3 ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: all + # in the 'proto' attribute, if you specify -1, all, or a protocol number other than tcp, udp, icmp, or 58 (ICMPv6), + # traffic on all ports is allowed, regardless of any ports you specify + from_port: 10050 # this value is ignored + to_port: 10050 # this value is ignored + cidr_ipv6: ::/0 +- name: example4 ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules_egress: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ipv6: ::/0 + group_name: example-other + # description to use if example-other needs to be created + group_desc: other example EC2 group +- name: example5 ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + # 'ports' rule keyword was introduced in version 2.4. It accepts a single port value or a list of values including ranges (from_port-to_port). + - proto: tcp + ports: 22 + group_name: example-vpn + rules_egress: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ipv6: + - ::/0 + group_name: example-other + # description to use if example-other needs to be created + group_desc: other example EC2 group + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ebs_volume_encryption_disabled.md b/documentation/rules/ansible/aws/ebs_volume_encryption_disabled.md new file mode 100644 index 00000000..2e3074e2 --- /dev/null +++ b/documentation/rules/ansible/aws/ebs_volume_encryption_disabled.md @@ -0,0 +1,92 @@ +--- +title: "EBS volume encryption disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/ebs_volume_encryption_disabled" + id: "4b6012e7-7176-46e4-8108-e441785eae57" + display_name: "EBS volume encryption disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `4b6012e7-7176-46e4-8108-e441785eae57` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_vol_module.html#parameter-encrypted) + +### Description + +Encrypt EBS volumes to protect data at rest and ensure snapshots and backups are also encrypted. Unencrypted volumes and their snapshots risk exposure if storage media or backups are compromised. For Ansible, tasks using the `amazon.aws.ec2_vol` or legacy `ec2_vol` modules must define the `encrypted` property and set it to `true` (or `yes`). Tasks with `state` set to `absent` or `list` are ignored. Resources with `encrypted` set to `false` or missing the `encrypted` attribute are flagged. + +Secure Ansible example: + +```yaml +- name: Create encrypted EBS volume + amazon.aws.ec2_vol: + volume_size: 10 + region: us-east-1 + encrypted: yes +``` + +## Compliant Code Examples +```yaml +- name: Creating EBS volume05 + amazon.aws.ec2_vol: + instance: XXXXXX + encrypted: yes + volume_size: 50 + volume_type: gp2 + device_name: /dev/xvdf +- name: Creating EBS volume06 + amazon.aws.ec2_vol: + instance: XXXXXX + encrypted: 'True' + volume_size: 50 + volume_type: gp2 + device_name: /dev/xvdf + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Creating EBS volume01 + amazon.aws.ec2_vol: + instance: XXXXXX + encrypted: no + volume_size: 50 + volume_type: gp2 + device_name: /dev/xvdf +- name: Creating EBS volume02 + amazon.aws.ec2_vol: + instance: XXXXXX + encrypted: false + volume_size: 50 + volume_type: gp2 + device_name: /dev/xvdf +- name: Creating EBS volume03 + amazon.aws.ec2_vol: + instance: XXXXXX + encrypted: "false" + volume_size: 50 + volume_type: gp2 + device_name: /dev/xvdf +- name: Creating EBS volume04 + amazon.aws.ec2_vol: + instance: XXXXXX + volume_size: 50 + volume_type: gp2 + device_name: /dev/xvdf + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ec2_group_has_public_interface.md b/documentation/rules/ansible/aws/ec2_group_has_public_interface.md new file mode 100644 index 00000000..2b076a5e --- /dev/null +++ b/documentation/rules/ansible/aws/ec2_group_has_public_interface.md @@ -0,0 +1,88 @@ +--- +title: "EC2 security group allows public access" +group_id: "Ansible / AWS" +meta: + name: "aws/ec2_group_has_public_interface" + id: "5330b503-3319-44ff-9b1c-00ee873f728a" + display_name: "EC2 security group allows public access" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `5330b503-3319-44ff-9b1c-00ee873f728a` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html) + +### Description + +Security group rules must not permit ingress from the public internet (`0.0.0.0/0` or `::/0`). Open rules expose instances to unauthorized access and automated attacks. In Ansible tasks using the `amazon.aws.ec2_group` or `ec2_group` modules, each entry in the `rules` list must not set `cidr_ip` to `0.0.0.0/0` or `cidr_ipv6` to `::/0`. This rule flags any `rules` item with those values. Instead, restrict access to specific CIDR ranges, reference other security groups, or require access via a bastion/VPN. + +Secure example with a restricted CIDR: + +```yaml +- name: create ssh access for admin network + amazon.aws.ec2_group: + name: my-secgroup + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 203.0.113.0/24 +``` + +## Compliant Code Examples +```yaml +- name: example ec2 group2 + ec2_group1: + name: example1 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1a + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 10.1.1.1/32 + +``` +## Non-Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster + db_security_groups: ["example"] +- name: example ec2 group + ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1a + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 0.0.0.0/0 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ec2_instance_has_public_ip.md b/documentation/rules/ansible/aws/ec2_instance_has_public_ip.md new file mode 100644 index 00000000..88295dde --- /dev/null +++ b/documentation/rules/ansible/aws/ec2_instance_has_public_ip.md @@ -0,0 +1,117 @@ +--- +title: "EC2 instance has public IP" +group_id: "Ansible / AWS" +meta: + name: "aws/ec2_instance_has_public_ip" + id: "a8b0c58b-cd25-4b53-9ad0-55bca0be0bc1" + display_name: "EC2 instance has public IP" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `a8b0c58b-cd25-4b53-9ad0-55bca0be0bc1` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html) + +### Description + +EC2 instances and launch templates that automatically receive a public IPv4 address are exposed directly to the internet, increasing the attack surface and the risk of unauthorized access or exploitation. + +For Ansible tasks, check the following module properties: + +- For `amazon.aws.ec2_launch_template` / `ec2_launch_template`: `network_interfaces.associate_public_ip_address` +- For `amazon.aws.ec2_instance` / `ec2_instance`: `network.assign_public_ip` + +Each property must be explicitly set to `false` (or `'no'`) or omitted. The rule flags resources where the property is truthy (for example, `true`, `yes`) because there is no safe default. + +Secure examples: + +```yaml +- name: Launch instance without public IP (ec2_instance) + amazon.aws.ec2_instance: + name: my-instance + network: + assign_public_ip: false + +- name: Create launch template without public IP + amazon.aws.ec2_launch_template: + name: my-template + network_interfaces: + - device_index: 0 + associate_public_ip_address: false +``` + +## Compliant Code Examples +```yaml +- name: Launch instance without public IP + amazon.aws.ec2_instance: + name: my-instance + key_name: mykey + instance_type: t2.micro + vpc_subnet_id: subnet-29e63245 + network: + assign_public_ip: false +- name: Create an ec2 launch template + amazon.aws.ec2_launch_template: + name: my_template + image_id: ami-04b762b4289fba92b + key_name: my_ssh_key + instance_type: t2.micro +- name: Create an ec2 launch template + amazon.aws.ec2_launch_template: + name: "my_template" + image_id: "ami-04b762b4289fba92b" + key_name: my_ssh_key + instance_type: t2.micro + network_interfaces: + - interface_type: interface + ipv6_addresses: [] + mac_address: '0 e: 0 e: 36: 60: 67: cf' + network_interface_id: eni - 061 dee20eba3b445a + owner_id: '721066863947' + source_dest_check: true + status: " in -use" + +``` +## Non-Compliant Code Examples +```yaml +- name: example + amazon.aws.ec2_instance: + name: my-instance + key_name: mykey + instance_type: t2.micro + vpc_subnet_id: subnet-29e63245 + network: + assign_public_ip: yes +- name: Create an ec2 launch template + amazon.aws.ec2_launch_template: + name: "my_template" + image_id: "ami-04b762b4289fba92b" + key_name: my_ssh_key + instance_type: t2.micro + network_interfaces: + associate_public_ip_address: true +- name: start an instance with a public IP address + amazon.aws.ec2_instance: + name: "public-compute-instance" + key_name: "prod-ssh-key" + vpc_subnet_id: subnet-5ca1ab1e + instance_type: c5.large + security_group: default + network: + assign_public_ip: true + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ec2_instance_using_default_security_group.md b/documentation/rules/ansible/aws/ec2_instance_using_default_security_group.md new file mode 100644 index 00000000..56cc77eb --- /dev/null +++ b/documentation/rules/ansible/aws/ec2_instance_using_default_security_group.md @@ -0,0 +1,95 @@ +--- +title: "EC2 instance using default security group" +group_id: "Ansible / AWS" +meta: + name: "aws/ec2_instance_using_default_security_group" + id: "8d03993b-8384-419b-a681-d1f55149397c" + display_name: "EC2 instance using default security group" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `8d03993b-8384-419b-a681-d1f55149397c` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html#parameter-security_group) + +### Description + +Using the default security group for EC2 instances is unsafe. The default group is shared across the VPC, often broadly permissive for intra-VPC traffic, and cannot be scoped to least-privilege rules. This increases the risk of lateral movement and unintended exposure. + +This rule inspects Ansible tasks that use the `amazon.aws.ec2_instance` or `ec2_instance` module and flags `security_group` or `security_groups` properties that reference the default security group. Both string and list forms are evaluated. Any value containing the word "default" (case-insensitive) is flagged and should be replaced with explicit, purpose-built security group names or IDs that restrict ingress and egress to only the required sources and ports. + +Secure example using an explicit security group ID: + +```yaml +- name: Launch EC2 with dedicated security group + amazon.aws.ec2_instance: + name: my-instance + image_id: ami-0123456789abcdef0 + instance_type: t3.micro + vpc_subnet_id: subnet-29e63245 + security_groups: + - sg-0123456789abcdef0 + network: + assign_public_ip: false +``` + +## Compliant Code Examples +```yaml +- name: example2 + amazon.aws.ec2_instance: + name: web-server + key_name: mykey + instance_type: t2.micro + image_id: ami-123456 + wait: yes + security_group: my_sg + vpc_subnet_id: subnet-29e63245 + network: + assign_public_ip: true + +``` +## Non-Compliant Code Examples +```yaml +- name: example2 + amazon.aws.ec2_instance: + name: web-server + key_name: mykey + instance_type: t2.micro + image_id: ami-123456 + wait: yes + security_groups: + - default + vpc_subnet_id: subnet-29e63245 + network: + assign_public_ip: true + +``` + +```yaml +- name: example + amazon.aws.ec2_instance: + name: web-server + key_name: mykey + instance_type: t2.micro + image_id: ami-123456 + wait: yes + security_group: default + vpc_subnet_id: subnet-29e63245 + network: + assign_public_ip: true + +``` diff --git a/documentation/rules/ansible/aws/ec2_instance_using_default_vpc.md b/documentation/rules/ansible/aws/ec2_instance_using_default_vpc.md new file mode 100644 index 00000000..3d62465b --- /dev/null +++ b/documentation/rules/ansible/aws/ec2_instance_using_default_vpc.md @@ -0,0 +1,97 @@ +--- +title: "EC2 instance using default VPC" +group_id: "Ansible / AWS" +meta: + name: "aws/ec2_instance_using_default_vpc" + id: "8833f180-96f1-46f4-9147-849aafa56029" + display_name: "EC2 instance using default VPC" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `8833f180-96f1-46f4-9147-849aafa56029` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html#parameter-vpc_subnet_id) + +### Description + +Launching EC2 instances into a default VPC increases exposure because default VPCs often have permissive networking defaults that are not tailored with least-privilege network controls. This makes it harder to enforce isolation and audit access. In Ansible playbooks using the `amazon.aws.ec2_instance` or `ec2_instance` module, the `vpc_subnet_id` parameter must not reference a subnet that belongs to a default VPC. This rule flags EC2 tasks where `vpc_subnet_id` is templated to a registered `amazon.aws.ec2_vpc_subnet`/`ec2_vpc_subnet` and the corresponding subnet's `vpc_id` contains the string "default". Ensure subnets referenced by `vpc_subnet_id` are created in a non-default VPC (for example, `vpc-0abc1234`) rather than a value containing "default". + +Secure example with a subnet in a non-default VPC: + +```yaml +- name: create subnet in custom VPC + amazon.aws.ec2_vpc_subnet: + vpc_id: vpc-0abc1234 + cidr: 10.0.1.0/24 + state: present + register: my_subnet + +- name: launch instance in the custom subnet + amazon.aws.ec2_instance: + name: my-instance + image_id: ami-0123456789abcdef0 + instance_type: t3.micro + vpc_subnet_id: "{{ my_subnet.subnet.id }}" + wait: true + network: + assign_public_ip: false +``` + +## Compliant Code Examples +```yaml +- name: Create subnet for database server2 + amazon.aws.ec2_vpc_subnet: + state: present + vpc_id: "{{ myVPC.vpcs.0.id }}" + cidr: 10.0.1.16/28 + tags: + Name: Database Subnet + register: my_subnet2 +- name: example2 + amazon.aws.ec2_instance: + name: db-instance + key_name: mykey + instance_type: t2.micro + image_id: ami-123456 + wait: yes + vpc_subnet_id: "{{ my_subnet2.subnet.id }}" + network: + assign_public_ip: true + +``` +## Non-Compliant Code Examples +```yaml +- name: Create subnet for database server + amazon.aws.ec2_vpc_subnet: + state: present + vpc_id: "{{ defaultVPC.vpcs.0.id }}" + cidr: 10.0.1.16/28 + tags: + Name: Database Subnet + register: my_subnet +- name: example + amazon.aws.ec2_instance: + name: db-instance + key_name: mykey + instance_type: t2.micro + image_id: ami-123456 + wait: yes + vpc_subnet_id: "{{ my_subnet.subnet.id }}" + network: + assign_public_ip: true + +``` diff --git a/documentation/rules/ansible/aws/ec2_not_ebs_optimized.md b/documentation/rules/ansible/aws/ec2_not_ebs_optimized.md new file mode 100644 index 00000000..89dc3c23 --- /dev/null +++ b/documentation/rules/ansible/aws/ec2_not_ebs_optimized.md @@ -0,0 +1,132 @@ +--- +title: "EC2 instance is not EBS optimized" +group_id: "Ansible / AWS" +meta: + name: "aws/ec2_not_ebs_optimized" + id: "338b6cab-961d-4998-bb49-e5b6a11c9a5c" + display_name: "EC2 instance is not EBS optimized" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `338b6cab-961d-4998-bb49-e5b6a11c9a5c` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html#parameter-ebs_optimized) + +### Description + +EC2 instances must be EBS-optimized to ensure consistent, high-performance EBS I/O and reduce contention between EBS traffic and other instance operations. + +For Ansible EC2 tasks using the `amazon.aws.ec2_instance` or `ec2_instance` module, the `ebs_optimized` property must be defined and set to `true` for instance types that are not EBS-optimized by default. If `instance_type` is omitted, the default `t2.micro` is assumed. Instance types that are EBS-optimized by default are exempt and are not flagged. Tasks missing the `ebs_optimized` property or with `ebs_optimized: false` are reported. + +Secure configuration example: + +```yaml +- name: Launch EBS-optimized EC2 + amazon.aws.ec2_instance: + name: my-instance + instance_type: m5.large + image_id: ami-0123456789abcdef0 + vpc_subnet_id: subnet-29e63245 + ebs_optimized: true +``` + +## Compliant Code Examples +```yaml +- name: example4 + amazon.aws.ec2_instance: + name: app-server + key_name: mykey + image_id: ami-123456 + instance_type: t2.micro + vpc_subnet_id: subnet-29e63245 + security_group: my_sg + ebs_optimized: true + network: + assign_public_ip: false + +``` + +```yaml +- name: example5 + amazon.aws.ec2_instance: + name: app-server + key_name: mykey + instance_type: m5.large + image_id: ami-123456 + vpc_subnet_id: subnet-29e63245 + security_group: my_sg + network: + assign_public_ip: false + +``` +## Non-Compliant Code Examples +```yaml +- name: example2 + amazon.aws.ec2_instance: + name: app-server + key_name: mykey + instance_type: t2.micro + image_id: ami-123456 + vpc_subnet_id: subnet-29e63245 + security_group: default + ebs_optimized: false + network: + assign_public_ip: false + +``` + +```yaml +- name: example3 + amazon.aws.ec2_instance: + name: app-server + key_name: mykey + image_id: ami-123456 + vpc_subnet_id: subnet-29e63245 + security_group: default + network: + assign_public_ip: false + +``` + +```yaml +- name: example + amazon.aws.ec2_instance: + name: app-server + key_name: mykey + instance_type: t2.micro + image_id: ami-123456 + vpc_subnet_id: subnet-29e63245 + security_group: default + network: + assign_public_ip: false + +``` + +```yaml +- name: example t3 with ebs disabled + amazon.aws.ec2_instance: + name: app-server + key_name: mykey + instance_type: t3.nano + image_id: ami-123456 + vpc_subnet_id: subnet-29e63245 + security_group: my_sg + ebs_optimized: false + network: + assign_public_ip: false + +``` diff --git a/documentation/rules/ansible/aws/ecr_image_tag_not_immutable.md b/documentation/rules/ansible/aws/ecr_image_tag_not_immutable.md new file mode 100644 index 00000000..5ce1d978 --- /dev/null +++ b/documentation/rules/ansible/aws/ecr_image_tag_not_immutable.md @@ -0,0 +1,61 @@ +--- +title: "ECR image tag not immutable" +group_id: "Ansible / AWS" +meta: + name: "aws/ecr_image_tag_not_immutable" + id: "60bfbb8a-c72f-467f-a6dd-a46b7d612789" + display_name: "ECR image tag not immutable" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `60bfbb8a-c72f-467f-a6dd-a46b7d612789` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/ecs_ecr_module.html) + +### Description + +ECR repositories should enforce immutable image tags to prevent tags from being overwritten. Allowing mutable tags can enable accidental or malicious replacement of images, facilitating supply-chain tampering or execution of unexpected code. For Ansible tasks using the `community.aws.ecs_ecr` or `ecs_ecr` modules, the `image_tag_mutability` property must be defined and set to the literal string `"immutable"`. Resources missing this property or set to any other value are flagged. + +Secure Ansible task example: + +```yaml +- name: Create ECR repository with immutable tags + community.aws.ecs_ecr: + name: my-repo + image_tag_mutability: immutable + state: present +``` + +## Compliant Code Examples +```yaml +- name: create immutable ecr-repo v4 + community.aws.ecs_ecr: + name: super/cool + image_tag_mutability: immutable + +``` +## Non-Compliant Code Examples +```yaml +- name: create immutable ecr-repo + community.aws.ecs_ecr: + name: super/cool +- name: create immutable ecr-repo v2 + community.aws.ecs_ecr: + name: super/cool + image_tag_mutability: mutable + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ecr_repository_is_publicly_accessible.md b/documentation/rules/ansible/aws/ecr_repository_is_publicly_accessible.md new file mode 100644 index 00000000..68c8a562 --- /dev/null +++ b/documentation/rules/ansible/aws/ecr_repository_is_publicly_accessible.md @@ -0,0 +1,100 @@ +--- +title: "ECR repository is publicly accessible" +group_id: "Ansible / AWS" +meta: + name: "aws/ecr_repository_is_publicly_accessible" + id: "fb5a5df7-6d74-4243-ab82-ff779a958bfd" + display_name: "ECR repository is publicly accessible" + cloud_provider: "AWS" + platform: "Ansible" + severity: "CRITICAL" + category: "Access Control" +--- +## Metadata + +**Id:** `fb5a5df7-6d74-4243-ab82-ff779a958bfd` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/ecs_ecr_module.html#parameter-policy) + +### Description + +ECR repository policies must not grant Allow permissions to the wildcard principal (`*`). This makes repositories publicly accessible and allows unauthorized accounts to pull or push container images, increasing the risk of data exposure and supply-chain compromise. + +Check Ansible ECS/ECR tasks using the `community.aws.ecs_ecr` or `ecs_ecr` modules: in the resource `policy` document, any statement with `"Effect": "Allow"` must not have `Principal` equal to `"*"`. Resources with an Allow statement whose `Principal` is `"*"` are flagged. Instead, specify explicit principals such as AWS account ARNs, IAM role ARNs, or service principals, or restrict access using condition keys (for example, `aws:PrincipalOrgID`). + +Secure example with an explicit AWS account principal: + +```json +{ + "Statement": [ + { + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, + "Action": [ "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage" ], + "Resource": "arn:aws:ecr:us-east-1:123456789012:repository/my-repo" + } + ] +} +``` + +## Compliant Code Examples +```yaml +- name: set-policy as object + community.aws.ecs_ecr: + name: needs-policy-object + policy: + Version: '2008-10-17' + Statement: + - Sid: read-only + Effect: Allow + Action: + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + - ecr:BatchCheckLayerAvailability + +``` +## Non-Compliant Code Examples +```yaml +- name: set-policy as object + community.aws.ecs_ecr: + name: needs-policy-object + policy: + Version: '2008-10-17' + Statement: + - Sid: read-only + Effect: Allow + Principal: '*' + Action: + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + - ecr:BatchCheckLayerAvailability +- name: set-policy as string + community.aws.ecs_ecr: + name: needs-policy-string + policy: > + { + "Id": "id113", + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "s3:put" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::S3B_181355/*", + "Principal": "*" + } + ] + } + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ecs_service_admin_role_is_present.md b/documentation/rules/ansible/aws/ecs_service_admin_role_is_present.md new file mode 100644 index 00000000..d3f8086b --- /dev/null +++ b/documentation/rules/ansible/aws/ecs_service_admin_role_is_present.md @@ -0,0 +1,68 @@ +--- +title: "ECS service admin role is present" +group_id: "Ansible / AWS" +meta: + name: "aws/ecs_service_admin_role_is_present" + id: "7db727c1-1720-468e-b80e-06697f71e09e" + display_name: "ECS service admin role is present" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `7db727c1-1720-468e-b80e-06697f71e09e` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/ecs_service_module.html) + +### Description + +ECS services must not be assigned administrative IAM roles. Admin-level privileges grant containers broad account-wide access and increase the risk of privilege escalation and lateral movement if the service is compromised. In Ansible tasks using `community.aws.ecs_service` or `ecs_service`, the `role` property must reference a least-privilege IAM role or ARN and must not contain the substring "admin" (case-insensitive). This rule flags tasks where `role` is a string that includes "admin". Roles omitted or defined via non-string constructs may not be detected and should be reviewed to ensure they do not attach the `AdministratorAccess` policy. + +Secure example referencing a non-admin role: + +```yaml +- name: my-ecs-service + community.aws.ecs_service: + name: my-service + cluster: my-cluster + task_definition: my-task:1 + role: arn:aws:iam::123456789012:role/ecsTaskRole +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: ECS Service + community.aws.ecs_service: + state: present + name: console-test-service + cluster: new_cluster + task_definition: new_cluster-task:1 + desired_count: 0 + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: ECS Service + community.aws.ecs_service: + state: present + name: console-test-service + cluster: new_cluster + task_definition: 'new_cluster-task:1' + desired_count: 0 + role: admin + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ecs_service_without_running_tasks.md b/documentation/rules/ansible/aws/ecs_service_without_running_tasks.md new file mode 100644 index 00000000..d9e812fa --- /dev/null +++ b/documentation/rules/ansible/aws/ecs_service_without_running_tasks.md @@ -0,0 +1,85 @@ +--- +title: "ECS service without running tasks" +group_id: "Ansible / AWS" +meta: + name: "aws/ecs_service_without_running_tasks" + id: "f5c45127-1d28-4b49-a692-0b97da1c3a84" + display_name: "ECS service without running tasks" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Availability" +--- +## Metadata + +**Id:** `f5c45127-1d28-4b49-a692-0b97da1c3a84` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Availability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/ecs_service_module.html#ansible-collections-community-aws-ecs-service-module) + +### Description + +ECS services must define a deployment configuration to avoid deployments or scaling events from temporarily leaving zero tasks running, which can cause application downtime and loss of availability. + +For Ansible ECS tasks using the `community.aws.ecs_service` or `ecs_service` modules, the `deployment_configuration` property must be present and include the `minimum_healthy_percent` and `maximum_percent` keys. Resources missing `deployment_configuration` or missing either `minimum_healthy_percent` or `maximum_percent` are flagged. This rule checks for the presence of those keys and does not validate numeric ranges. Ensure `minimum_healthy_percent` is set so at least one task remains running during deployments according to your desired task count. + +Secure example (Ansible task): + +```yaml +- name: my-ecs-service + community.aws.ecs_service: + name: my-service + cluster: my-cluster + task_definition: my-task:1 + desired_count: 2 + deployment_configuration: + maximum_percent: 200 + minimum_healthy_percent: 50 +``` + +## Compliant Code Examples +```yaml +- name: ECS Service + community.aws.ecs_service: + state: present + name: test-service + cluster: test-cluster + task_definition: test-task-definition + desired_count: 3 + deployment_configuration: + minimum_healthy_percent: 75 + maximum_percent: 150 + placement_constraints: + - type: memberOf + expression: 'attribute:flavor==test' + placement_strategy: + - type: binpack + field: memory + +``` +## Non-Compliant Code Examples +```yaml +- name: ECS Service + community.aws.ecs_service: + state: present + name: test-service + cluster: test-cluster + task_definition: test-task-definition + desired_count: 3 + placement_constraints: + - type: memberOf + expression: 'attribute:flavor==test' + placement_strategy: + - type: binpack + field: memory + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ecs_services_assigned_with_public_ip_address.md b/documentation/rules/ansible/aws/ecs_services_assigned_with_public_ip_address.md new file mode 100644 index 00000000..f8ce56bd --- /dev/null +++ b/documentation/rules/ansible/aws/ecs_services_assigned_with_public_ip_address.md @@ -0,0 +1,96 @@ +--- +title: "ECS services should not be assigned public IP addresses" +group_id: "Ansible / AWS" +meta: + name: "aws/ecs_services_assigned_with_public_ip_address" + id: "560f256b-0b45-4496-bcb5-733681e7d38d" + display_name: "ECS services should not be assigned public IP addresses" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `560f256b-0b45-4496-bcb5-733681e7d38d` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/ecs_service_module.html) + +### Description + +Amazon ECS services should not be assigned public IP addresses. Attaching public IPs exposes tasks directly to the internet, increasing the attack surface and the risk of unauthorized access. + +For Ansible tasks using the `community.aws.ecs_service` or `ecs_service` modules, the `network_configuration.assign_public_ip` property must be defined and set to `false`. Tasks with `assign_public_ip: true` are flagged. If services require outbound internet access, use private subnets with a NAT Gateway or expose services via a load balancer instead of assigning public IPs. + +Secure configuration example: + +```yaml +- name: Create ECS service with no public IP + community.aws.ecs_service: + name: my-service + cluster: my-cluster + task_definition: my-task:1 + network_configuration: + subnets: + - subnet-0123456789abcdef0 + security_groups: + - sg-0123456789abcdef0 + assign_public_ip: false +``` + +## Compliant Code Examples +```yaml +- name: negative1 + hosts: localhost + gather_facts: false + tasks: + - name: Create ECS service with network configuration + community.aws.ecs_service: + state: present + name: example-public-ip-service + cluster: my-ecs-cluster + task_definition: my-task-def:1 + desired_count: 2 + launch_type: FARGATE + network_configuration: + subnets: + - subnet-aaaa1111 + - subnet-bbbb2222 + security_groups: + - sg-cccc3333 + assign_public_ip: false + +``` +## Non-Compliant Code Examples +```yaml +- name: positive1 + hosts: localhost + gather_facts: false + tasks: + - name: Create ECS service with network configuration + community.aws.ecs_service: + state: present + name: example-public-ip-service + cluster: my-ecs-cluster + task_definition: my-task-def:1 + desired_count: 2 + launch_type: FARGATE + network_configuration: + subnets: + - subnet-aaaa1111 + - subnet-bbbb2222 + security_groups: + - sg-cccc3333 + assign_public_ip: true + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ecs_task_definition_network_mode_not_recommended.md b/documentation/rules/ansible/aws/ecs_task_definition_network_mode_not_recommended.md new file mode 100644 index 00000000..39388be8 --- /dev/null +++ b/documentation/rules/ansible/aws/ecs_task_definition_network_mode_not_recommended.md @@ -0,0 +1,98 @@ +--- +title: "ECS task definition network mode not recommended" +group_id: "Ansible / AWS" +meta: + name: "aws/ecs_task_definition_network_mode_not_recommended" + id: "01aec7c2-3e4d-4274-ae47-2b8fea22fd1f" + display_name: "ECS task definition network mode not recommended" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `01aec7c2-3e4d-4274-ae47-2b8fea22fd1f` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/ecs_taskdefinition_module.html#parameter-network_mode) + +### Description + +ECS task definitions must use the `awsvpc` network mode so each task receives its own ENI and can be isolated with security groups and VPC controls. Without `awsvpc`, tasks may share the host network namespace or lack per-task security group enforcement, increasing exposure to lateral movement and unintended network access. + +The `network_mode` property in Ansible `community.aws.ecs_taskdefinition` or `ecs_taskdefinition` resources must be set to `"awsvpc"`. Resources missing `network_mode` or with values such as `"host"`, `"bridge"`, or `"none"` are flagged. AWS Fargate requires `awsvpc`, and using legacy modes causes tasks to share host networking and bypass per-task security group rules. + +Secure configuration example: + +```yaml +- name: Register ECS task definition with awsvpc + community.aws.ecs_taskdefinition: + family: my-task + network_mode: awsvpc + container_definitions: "{{ lookup('file', 'containers.json') }}" +``` + +## Compliant Code Examples +```yaml +- name: Create task definition + community.aws.ecs_taskdefinition: + family: nginx + containers: + - name: nginx + essential: true + image: nginx + portMappings: + - containerPort: 8080 + hostPort: 8080 + launch_type: FARGATE + cpu: 512 + memory: 1024 + state: present + network_mode: awsvpc + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create task definition + community.aws.ecs_taskdefinition: + family: nginx + containers: + - name: nginx + essential: true + image: "nginx" + portMappings: + - containerPort: 8080 + hostPort: 8080 + cpu: 512 + memory: 1024 + state: present + network_mode: default + +- name: Create task definition2 + community.aws.ecs_taskdefinition: + family: nginx + containers: + - name: nginx + essential: true + image: "nginx" + portMappings: + - containerPort: 8080 + hostPort: 8080 + launch_type: FARGATE + cpu: 512 + memory: 1024 + state: present + network_mode: none + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/efs_not_encrypted.md b/documentation/rules/ansible/aws/efs_not_encrypted.md new file mode 100644 index 00000000..7f8885d1 --- /dev/null +++ b/documentation/rules/ansible/aws/efs_not_encrypted.md @@ -0,0 +1,104 @@ +--- +title: "EFS not encrypted" +group_id: "Ansible / AWS" +meta: + name: "aws/efs_not_encrypted" + id: "727c4fd4-d604-4df6-a179-7713d3c85e20" + display_name: "EFS not encrypted" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `727c4fd4-d604-4df6-a179-7713d3c85e20` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/efs_module.html#parameter-encrypt) + +### Description + +EFS file systems must have encryption enabled to protect data at rest and prevent exposure of file system contents, snapshots, and backups if storage media is compromised. For Ansible tasks using the `community.aws.efs` or `efs` modules, the `encrypt` property must be defined and set to `true`. Resources that omit `encrypt` or have `encrypt: false` are flagged as misconfigured. + +Secure example: + +```yaml +- name: Create encrypted EFS filesystem + community.aws.efs: + name: my-efs + encrypt: true +``` + +## Compliant Code Examples +```yaml +- name: foo + community.aws.efs: + state: present + name: myTestEFS + encrypt: yes + tags: + Name: myTestNameTag + purpose: file-storage + targets: + - subnet_id: subnet-748c5d03 + security_groups: [sg-1a2b3c4d] +- name: foo2 + community.aws.efs: + state: present + name: myTestEFS + encrypt: true + tags: + Name: myTestNameTag + purpose: file-storage + targets: + - subnet_id: subnet-748c5d03 + security_groups: [sg-1a2b3c4d] + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: foo + community.aws.efs: + state: present + name: myTestEFS + encrypt: no + tags: + Name: myTestNameTag + purpose: file-storage + targets: + - subnet_id: subnet-748c5d03 + security_groups: ["sg-1a2b3c4d"] +- name: foo2 + community.aws.efs: + state: present + name: myTestEFS + encrypt: false + tags: + Name: myTestNameTag + purpose: file-storage + targets: + - subnet_id: subnet-748c5d03 + security_groups: ["sg-1a2b3c4d"] +- name: foo3 + community.aws.efs: + state: present + name: myTestEFS + tags: + Name: myTestNameTag + purpose: file-storage + targets: + - subnet_id: subnet-748c5d03 + security_groups: ["sg-1a2b3c4d"] + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/efs_without_kms.md b/documentation/rules/ansible/aws/efs_without_kms.md new file mode 100644 index 00000000..6290089d --- /dev/null +++ b/documentation/rules/ansible/aws/efs_without_kms.md @@ -0,0 +1,77 @@ +--- +title: "EFS without KMS" +group_id: "Ansible / AWS" +meta: + name: "aws/efs_without_kms" + id: "bd77554e-f138-40c5-91b2-2a09f878608e" + display_name: "EFS without KMS" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Encryption" +--- +## Metadata + +**Id:** `bd77554e-f138-40c5-91b2-2a09f878608e` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/efs_module.html#parameter-kms_key_id) + +### Description + +EFS filesystems should be encrypted with a customer-managed AWS KMS CMK to protect data at rest and maintain control over key rotation, access policies, and audit logging. + +In Ansible, the `kms_key_id` option on the `community.aws.efs` (or legacy `efs`) module must be defined and set to a customer-managed key identifier (KMS key ID, key ARN, or alias) rather than relying on the AWS-managed key. Tasks that omit `kms_key_id` or leave it undefined default to an AWS-managed key and are flagged by this rule. + +Secure configuration example: + +```yaml +- name: Create encrypted EFS filesystem + community.aws.efs: + name: my-efs + performance_mode: generalPurpose + kms_key_id: arn:aws:kms:us-east-1:123456789012:key/abcdef12-3456-7890-abcd-ef1234567890 + state: present +``` + +## Compliant Code Examples +```yaml +- name: foo + community.aws.efs: + state: present + name: myTestEFS + encrypt: yes + tags: + Name: myTestNameTag + purpose: file-storage + targets: + - subnet_id: subnet-748c5d03 + security_groups: [sg-1a2b3c4d] + kms_key_id: "some-key-id" + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: foo + community.aws.efs: + state: present + name: myTestEFS + encrypt: no + tags: + Name: myTestNameTag + purpose: file-storage + targets: + - subnet_id: subnet-748c5d03 + security_groups: ["sg-1a2b3c4d"] + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/efs_without_tags.md b/documentation/rules/ansible/aws/efs_without_tags.md new file mode 100644 index 00000000..9ffd30f7 --- /dev/null +++ b/documentation/rules/ansible/aws/efs_without_tags.md @@ -0,0 +1,70 @@ +--- +title: "EFS without tags" +group_id: "Ansible / AWS" +meta: + name: "aws/efs_without_tags" + id: "b8a9852c-9943-4973-b8d5-77dae9352851" + display_name: "EFS without tags" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Build Process" +--- +## Metadata + +**Id:** `b8a9852c-9943-4973-b8d5-77dae9352851` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Build Process + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/efs_module.html) + +### Description + +EFS filesystems must have tags defined to support asset identification, tag-based access control, cost allocation, and automated lifecycle or compliance policies. For Ansible tasks using the `community.aws.efs` or `efs` modules, the `tags` property must be present and contain at least one key/value pair. Tasks that omit the `tags` property or provide an empty mapping are flagged as missing required metadata. + +Secure example: + +```yaml +- name: Create EFS filesystem + community.aws.efs: + state: present + name: my-efs + performance_mode: generalPurpose + tags: + Name: my-efs + Environment: production +``` + +## Compliant Code Examples +```yaml +- name: EFS provisioning + community.aws.efs: + state: present + name: myTestEFS + tags: + Name: myTestNameTag + purpose: file-storage + targets: + - subnet_id: subnet-748c5d03 + security_groups: [ "sg-1a2b3c4d" ] + +``` +## Non-Compliant Code Examples +```yaml +- name: EFS provisioning without tags + community.aws.efs: + state: present + name: myTestEFS + targets: + - subnet_id: subnet-748c5d03 + security_groups: [ "sg-1a2b3c4d" ] + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/elasticache_using_default_port.md b/documentation/rules/ansible/aws/elasticache_using_default_port.md new file mode 100644 index 00000000..2a1225b3 --- /dev/null +++ b/documentation/rules/ansible/aws/elasticache_using_default_port.md @@ -0,0 +1,105 @@ +--- +title: "ElastiCache using default port" +group_id: "Ansible / AWS" +meta: + name: "aws/elasticache_using_default_port" + id: "7cc6c791-5f68-4816-a564-b9b699f9d26e" + display_name: "ElastiCache using default port" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `7cc6c791-5f68-4816-a564-b9b699f9d26e` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/elasticache_module.html#parameter-cache_port) + +### Description + +ElastiCache instances using engine default ports are easy for attackers and automated scanners to discover and target, increasing the risk of unauthorized access and automated exploitation. + +In Ansible, tasks that use the `community.aws.elasticache` or `elasticache` module must not set the `cache_port` property to the engine defaults: `6379` when `engine: redis` and `11211` when `engine: memcached`. Resources with `cache_port` equal to these default values are flagged. Choose a non-standard port and enforce network access controls (security groups/subnets) to limit exposure. + +Secure example changing the default port: + +```yaml +- name: Create Redis ElastiCache cluster with non-default port + community.aws.elasticache: + name: my-redis-cluster + engine: redis + cache_port: 6380 + # other required properties... +``` + +## Compliant Code Examples +```yaml +- name: Basic example2 + community.aws.elasticache: + name: "test-please-delete" + state: present + engine: memcached + cache_engine_version: 1.4.14 + node_type: cache.m1.small + num_nodes: 1 + cache_port: 11212 + cache_subnet_group: default + zone: us-east-1d + +``` + +```yaml +- name: Basic example2 + community.aws.elasticache: + name: "test-please-delete" + state: present + engine: redis + cache_engine_version: 1.4.14 + node_type: cache.m1.small + num_nodes: 1 + cache_port: 6380 + cache_subnet_group: default + zone: us-east-1d + +``` +## Non-Compliant Code Examples +```yaml +- name: Basic example2 + community.aws.elasticache: + name: "test-please-delete" + state: present + engine: redis + cache_engine_version: 1.4.14 + node_type: cache.m1.small + num_nodes: 1 + cache_port: 6379 + cache_subnet_group: default + zone: us-east-1d + +``` + +```yaml +- name: Basic example + community.aws.elasticache: + name: "test-please-delete" + state: present + engine: memcached + cache_engine_version: 1.4.14 + node_type: cache.m1.small + num_nodes: 1 + cache_port: 11211 + cache_subnet_group: default + zone: us-east-1d + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/elasticache_without_vpc.md b/documentation/rules/ansible/aws/elasticache_without_vpc.md new file mode 100644 index 00000000..b7cc0646 --- /dev/null +++ b/documentation/rules/ansible/aws/elasticache_without_vpc.md @@ -0,0 +1,75 @@ +--- +title: "ElastiCache without VPC" +group_id: "Ansible / AWS" +meta: + name: "aws/elasticache_without_vpc" + id: "5527dcfc-94f9-4bf6-b7d4-1b78850cf41f" + display_name: "ElastiCache without VPC" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `5527dcfc-94f9-4bf6-b7d4-1b78850cf41f` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/elasticache_module.html#parameter-cache_subnet_group) + +### Description + +ElastiCache clusters must be launched in a VPC to provide network isolation and reduce the risk of unauthorized access to cached data or lateral movement within your environment. + +In Ansible playbooks, tasks using the `community.aws.elasticache` or `elasticache` modules must set the `cache_subnet_group` property to the name of an existing ElastiCache subnet group. A task where `cache_subnet_group` is undefined or null is flagged because omission typically results in resources being created outside a VPC or without the intended subnet isolation. + +Secure Ansible example: + +```yaml +- name: Create ElastiCache cluster in VPC + community.aws.elasticache: + name: my-cache + engine: redis + cache_subnet_group: my-cache-subnet-group +``` + +## Compliant Code Examples +```yaml +- name: Basic example2 + community.aws.elasticache: + name: "test-please-delete" + state: present + engine: memcached + cache_engine_version: 1.4.14 + node_type: cache.m1.small + num_nodes: 1 + cache_port: 11211 + cache_subnet_group: default + zone: us-east-1d + +``` +## Non-Compliant Code Examples +```yaml +- name: Basic example + community.aws.elasticache: + name: "test-please-delete" + state: present + engine: memcached + cache_engine_version: 1.4.14 + node_type: cache.m1.small + num_nodes: 1 + cache_port: 11211 + cache_security_groups: + - default + zone: us-east-1d + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/elasticsearch_with_https_disabled.md b/documentation/rules/ansible/aws/elasticsearch_with_https_disabled.md new file mode 100644 index 00000000..d337989a --- /dev/null +++ b/documentation/rules/ansible/aws/elasticsearch_with_https_disabled.md @@ -0,0 +1,146 @@ +--- +title: "Elasticsearch with HTTPS disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/elasticsearch_with_https_disabled" + id: "d6c2d06f-43c1-488a-9ba1-8d75b40fc62d" + display_name: "Elasticsearch with HTTPS disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `d6c2d06f-43c1-488a-9ba1-8d75b40fc62d` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/devel/collections/community/aws/opensearch_module.html) + +### Description + +OpenSearch domain endpoints must enforce HTTPS to ensure client connections use TLS and prevent interception or tampering of sensitive data such as queries and credentials. In Ansible tasks using the `community.aws.opensearch` or `opensearch` modules, the `domain_endpoint_options.enforce_https` property must be set to `true`. Tasks that omit `domain_endpoint_options` or `enforce_https`, or that set `enforce_https: false`, are flagged. + +Secure Ansible task example: + +```yaml +- name: create opensearch domain with HTTPS enforced + community.aws.opensearch: + domain_name: my-domain + domain_endpoint_options: + enforce_https: true +``` + +## Compliant Code Examples +```yaml +- name: Create OpenSearch domain with dedicated masters + community.aws.opensearch: + domain_name: "my-domain" + engine_version: OpenSearch_1.1 + cluster_config: + instance_type: "t2.small.search" + instance_count: 12 + dedicated_master: true + zone_awareness: true + availability_zone_count: 2 + dedicated_master_instance_type: "t2.small.search" + dedicated_master_instance_count: 3 + warm_enabled: true + warm_type: "ultrawarm1.medium.search" + warm_count: 1 + cold_storage_options: + enabled: false + domain_endpoint_options: + enforce_https: true + ebs_options: + ebs_enabled: true + volume_type: "io1" + volume_size: 10 + iops: 1000 + vpc_options: + subnets: + - "subnet-e537d64a" + - "subnet-e537d64b" + security_groups: + - "sg-dd2f13cb" + - "sg-dd2f13cc" + snapshot_options: + automated_snapshot_start_hour: 13 + access_policies: "{{ lookup('file', 'policy.json') | from_json }}" + encryption_at_rest_options: + enabled: false + node_to_node_encryption_options: + enabled: false + tags: + Environment: Development + Application: Search + wait: true + +``` +## Non-Compliant Code Examples +```yaml +- name: Create OpenSearch domain for dev environment, no zone awareness, no dedicated masters + community.aws.opensearch: + domain_name: "dev-cluster" + engine_version: Elasticsearch_1.1 + cluster_config: + instance_type: "t2.small.search" + instance_count: 2 + zone_awareness: false + dedicated_master: false + domain_endpoint_options: + custom_endpoint_enabled: false + ebs_options: + ebs_enabled: true + volume_type: "gp2" + volume_size: 10 + access_policies: "{{ lookup('file', 'policy.json') | from_json }}" + +``` + +```yaml +- name: Create OpenSearch domain for dev environment, no zone awareness, no dedicated masters + community.aws.opensearch: + domain_name: "dev-cluster" + engine_version: Elasticsearch_1.1 + cluster_config: + instance_type: "t2.small.search" + instance_count: 2 + zone_awareness: false + dedicated_master: false + ebs_options: + ebs_enabled: true + volume_type: "gp2" + volume_size: 10 + access_policies: "{{ lookup('file', 'policy.json') | from_json }}" + +``` + +```yaml +- name: Create OpenSearch domain for dev environment, no zone awareness, no dedicated masters + community.aws.opensearch: + domain_name: "dev-cluster" + engine_version: Elasticsearch_1.1 + cluster_config: + instance_type: "t2.small.search" + instance_count: 2 + zone_awareness: false + dedicated_master: false + domain_endpoint_options: + enforce_https: false + ebs_options: + ebs_enabled: true + volume_type: "gp2" + volume_size: 10 + access_policies: "{{ lookup('file', 'policy.json') | from_json }}" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/elb_using_insecure_protocols.md b/documentation/rules/ansible/aws/elb_using_insecure_protocols.md new file mode 100644 index 00000000..b29335c3 --- /dev/null +++ b/documentation/rules/ansible/aws/elb_using_insecure_protocols.md @@ -0,0 +1,197 @@ +--- +title: "ELB using insecure protocols" +group_id: "Ansible / AWS" +meta: + name: "aws/elb_using_insecure_protocols" + id: "730a5951-2760-407a-b032-dd629b55c23a" + display_name: "ELB using insecure protocols" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `730a5951-2760-407a-b032-dd629b55c23a` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/elb_application_lb_module.html) + +### Description + +Load balancer listeners must use secure TLS policies to prevent protocol downgrade and known cryptographic vulnerabilities that could allow interception or decryption of client traffic. + +For Ansible ELB modules (`community.aws.elb_network_lb`, `elb_network_lb`, `amazon.aws.elb_application_lb`, `elb_application_lb`), the `listeners` property must be defined and each listener must include `SslPolicy` set to a modern, secure policy (not legacy SSL/TLS protocol policies). + +This rule flags resources missing `listeners`, listeners missing `SslPolicy`, or any `SslPolicy` set to `Protocol-SSLv2`, `Protocol-SSLv3`, `Protocol-TLSv1`, or `Protocol-TLSv1.1`. + +Secure example (use a TLS 1.2+ policy): + +```yaml +- name: create application load balancer + amazon.aws.elb_application_lb: + name: my-alb + listeners: + - Protocol: HTTPS + Port: 443 + SslPolicy: ELBSecurityPolicy-TLS-1-2-2017-01 + CertificateArn: arn:aws:acm:us-east-1:123456789012:certificate/abcd-1234 +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: elb1 + amazon.aws.elb_application_lb: + name: myelb1 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + SslPolicy: ELBSecurityPolicy-2015-05 + Certificates: # The ARN of the certificate (only one certficate ARN should be provided) + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward # Required. + TargetGroupName: # Required. The name of the target group + state: present +- name: elb2 + community.aws.elb_network_lb: + name: myelb2 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + SslPolicy: ELBSecurityPolicy-2015-05 + Certificates: # The ARN of the certificate (only one certficate ARN should be provided) + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward # Required. + TargetGroupName: # Required. The name of the target group + state: present + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: elb1 + amazon.aws.elb_application_lb: + name: myelb1 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + state: present +- name: elb2 + amazon.aws.elb_application_lb: + name: myelb2 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + Certificates: # The ARN of the certificate (only one certficate ARN should be provided) + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward # Required. + TargetGroupName: # Required. The name of the target group + state: present +- name: elb3 + amazon.aws.elb_application_lb: + name: myelb3 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + SslPolicy: Protocol-SSLv2 + Certificates: # The ARN of the certificate (only one certficate ARN should be provided) + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward # Required. + TargetGroupName: # Required. The name of the target group + state: present +- name: elb4 + community.aws.elb_network_lb: + name: myelb4 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + state: present +- name: elb5 + community.aws.elb_network_lb: + name: myelb5 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + Certificates: # The ARN of the certificate (only one certficate ARN should be provided) + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward # Required. + TargetGroupName: # Required. The name of the target group + state: present +- name: elb6 + community.aws.elb_network_lb: + name: myelb6 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + SslPolicy: Protocol-TLSv1.1 + Certificates: # The ARN of the certificate (only one certficate ARN should be provided) + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward # Required. + TargetGroupName: # Required. The name of the target group + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/elb_using_weak_ciphers.md b/documentation/rules/ansible/aws/elb_using_weak_ciphers.md new file mode 100644 index 00000000..52a37592 --- /dev/null +++ b/documentation/rules/ansible/aws/elb_using_weak_ciphers.md @@ -0,0 +1,195 @@ +--- +title: "ELB using weak ciphers" +group_id: "Ansible / AWS" +meta: + name: "aws/elb_using_weak_ciphers" + id: "2034fb37-bc23-4ca0-8d95-2b9f15829ab5" + display_name: "ELB using weak ciphers" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `2034fb37-bc23-4ca0-8d95-2b9f15829ab5` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/elb_application_lb_module.html) + +### Description + +ELB listeners must specify a strong SSL/TLS policy because weak cipher suites can enable protocol downgrade, interception, or decryption of traffic between clients and the load balancer. For Ansible ELB Application and Network load balancer modules (`amazon.aws.elb_application_lb`, `elb_application_lb`, `community.aws.elb_network_lb`, `elb_network_lb`), the `listeners` list must be defined and each listener must include the `SslPolicy` property set to a non-weak policy. + +Resources missing `listeners` or listener entries missing `SslPolicy` are flagged. Any `SslPolicy` that matches a known weak policy in your baseline should be replaced with an AWS-managed strong policy (for example, a TLS 1.2+ policy) or a custom policy that excludes weak ciphers. + +Secure configuration example: + +```yaml +- name: Create ALB with strong TLS policy + amazon.aws.elb_application_lb: + name: my-alb + listeners: + - Protocol: HTTPS + Port: 443 + SslPolicy: ELBSecurityPolicy-TLS-1-2-2017-01 + CertificateArn: arn:aws:acm:us-west-2:123456789012:certificate/abcd-1234 +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: elb1 + amazon.aws.elb_application_lb: + name: myelb1 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + SslPolicy: ELBSecurityPolicy-2015-05 + Certificates: # The ARN of the certificate (only one certficate ARN should be provided) + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward # Required. + TargetGroupName: # Required. The name of the target group + state: present +- name: elb2 + community.aws.elb_network_lb: + name: myelb2 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + SslPolicy: ELBSecurityPolicy-2015-05 + Certificates: # The ARN of the certificate (only one certficate ARN should be provided) + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward # Required. + TargetGroupName: # Required. The name of the target group + state: present + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: elb1 + amazon.aws.elb_application_lb: + name: myelb1 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + state: present +- name: elb2 + amazon.aws.elb_application_lb: + name: myelb2 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + Certificates: # The ARN of the certificate (only one certficate ARN should be provided) + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward # Required. + TargetGroupName: # Required. The name of the target group + state: present +- name: elb3 + amazon.aws.elb_application_lb: + name: myelb3 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + SslPolicy: DHE-DSS-DES-CBC3-SHA + Certificates: # The ARN of the certificate (only one certficate ARN should be provided) + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward # Required. + TargetGroupName: # Required. The name of the target group + state: present +- name: elb4 + community.aws.elb_network_lb: + name: myelb4 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + state: present +- name: elb5 + community.aws.elb_network_lb: + name: myelb5 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP + Port: 80 + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + Certificates: + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward + TargetGroupName: target + state: present +- name: elb6 + community.aws.elb_network_lb: + name: myelb6 + security_groups: + - sg-12345678 + - my-sec-group + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: HTTP + Port: 80 + # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy. + SslPolicy: TLS_RSA_NULL_MD5 + Certificates: + - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com + DefaultActions: + - Type: forward + TargetGroupName: target + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/hardcoded_aws_access_key.md b/documentation/rules/ansible/aws/hardcoded_aws_access_key.md new file mode 100644 index 00000000..e7a498bb --- /dev/null +++ b/documentation/rules/ansible/aws/hardcoded_aws_access_key.md @@ -0,0 +1,79 @@ +--- +title: "Hardcoded AWS access key" +group_id: "Ansible / AWS" +meta: + name: "aws/hardcoded_aws_access_key" + id: "c2f15af3-66a0-4176-a56e-e4711e502e5c" + display_name: "Hardcoded AWS access key" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Secret Management" +--- +## Metadata + +**Id:** `c2f15af3-66a0-4176-a56e-e4711e502e5c` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Secret Management + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html) + +### Description + +Embedding AWS access keys in EC2 user data exposes credentials to source control, logs, and anyone with access to the instance. Attackers can use these credentials to access or escalate privileges in your AWS account. + +This rule checks Ansible tasks using the `amazon.aws.ec2_instance` or `ec2_instance` modules and flags the `user_data` property when it contains patterns matching AWS Access Key IDs (20 uppercase alphanumeric characters) or Secret Access Keys (40-character base64-like strings). Resources whose `user_data` contains sequences matching those key patterns are flagged. + +Do not hardcode credentials. Assign an IAM instance profile to the instance or retrieve secrets at runtime from AWS Secrets Manager or AWS Systems Manager Parameter Store and inject them securely. + +Secure example using an instance profile and avoiding embedded keys: + +```yaml +- name: Launch EC2 without hardcoded keys + amazon.aws.ec2_instance: + name: my-instance + image_id: ami-0123456789abcdef0 + instance_type: t3.micro + instance_profile_name: my-iam-instance-profile + user_data: | + #!/bin/bash + # No hardcoded AWS keys here; fetch secrets from SSM or Secrets Manager at runtime +``` + +## Compliant Code Examples +```yaml +- name: start an instance with a cpu_options + amazon.aws.ec2_instance: + name: public-cpuoption-instance + vpc_subnet_id: subnet-5ca1ab1e + tags: + Environment: Testing + instance_type: c4.large + volumes: + - device_name: /dev/sda1 + ebs: + delete_on_termination: true + cpu_options: + core_count: 1 + threads_per_core: 1 + +``` +## Non-Compliant Code Examples +```yaml +- name: start an instance with a cpu_options + amazon.aws.ec2_instance: + name: "public-cpuoption-instance" + vpc_subnet_id: subnet-5ca1ab1e + tags: + Environment: Testing + user_data: "1234567890123456789012345678901234567890$" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/hardcoded_aws_access_key_in_lambda.md b/documentation/rules/ansible/aws/hardcoded_aws_access_key_in_lambda.md new file mode 100644 index 00000000..97c262bd --- /dev/null +++ b/documentation/rules/ansible/aws/hardcoded_aws_access_key_in_lambda.md @@ -0,0 +1,152 @@ +--- +title: "Hardcoded AWS access key in Lambda" +group_id: "Ansible / AWS" +meta: + name: "aws/hardcoded_aws_access_key_in_lambda" + id: "f34508b9-f574-4330-b42d-88c44cced645" + display_name: "Hardcoded AWS access key in Lambda" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Secret Management" +--- +## Metadata + +**Id:** `f34508b9-f574-4330-b42d-88c44cced645` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Secret Management + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_module.html) + +### Description + +Hardcoding AWS secret access keys in Ansible Lambda tasks exposes credentials to source control, logs, and build artifacts. Attackers who obtain the key can impersonate the account and access AWS resources. This check targets Ansible tasks using the `amazon.aws.lambda` or `lambda` modules and flags tasks that include an `aws_access_key` property containing a 40-character plaintext secret (matched by regex `^[A-Za-z0-9/+=]{40}$`). + +Do not set `aws_access_key` or `aws_secret_key` inline. Instead, supply credentials via IAM instance/profile roles, shared AWS credential profiles, environment variables, or encrypted secrets (Ansible Vault or a secrets manager). You can also reference vaulted or lookup variables in the task. Tasks with a literal 40-character `aws_access_key` value are flagged. Omitting the properties to rely on role-based auth or referencing vaulted variables is acceptable. + +Secure examples: + +```yaml +- name: Deploy Lambda using instance profile (no inline credentials) + amazon.aws.lambda: + name: my_function + state: present + region: us-east-1 +``` + +```yaml +- name: Deploy Lambda with credentials stored in Ansible Vault + amazon.aws.lambda: + name: my_function + state: present + region: us-east-1 + aws_access_key: "{{ vault_aws_access_key }}" + aws_secret_key: "{{ vault_aws_secret_key }}" +``` + +## Compliant Code Examples +```yaml +- name: looped creation + amazon.aws.lambda: + name: '{{ item.name }}' + state: present + zip_file: '{{ item.zip_file }}' + runtime: python2.7 + role: arn:aws:iam::987654321012:role/lambda_basic_execution + handler: hello_python.my_handler + vpc_subnet_ids: + - subnet-123abcde + - subnet-edcba321 + vpc_security_group_ids: + - sg-123abcde + - sg-edcba321 + environment_variables: '{{ item.env_vars }}' + tags: + key1: value1 + loop: + - name: HelloWorld + zip_file: hello-code.zip + env_vars: + key1: first + key2: second + - name: ByeBye + zip_file: bye-code.zip + env_vars: + key1: '1' + key2: '2' +- name: remove tags + amazon.aws.lambda: + name: Lambda function + state: present + zip_file: code.zip + runtime: python2.7 + role: arn:aws:iam::987654321012:role/lambda_basic_execution + handler: hello_python.my_handler + tags: {} +- name: Delete Lambda functions HelloWorld and ByeBye + amazon.aws.lambda: + name: '{{ item }}' + state: absent + loop: + - HelloWorld + - ByeBye + +``` +## Non-Compliant Code Examples +```yaml +- name: looped creation + amazon.aws.lambda: + aws_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' + name: '{{ item.name }}' + state: present + zip_file: '{{ item.zip_file }}' + runtime: 'python2.7' + role: 'arn:aws:iam::987654321012:role/lambda_basic_execution' + handler: 'hello_python.my_handler' + vpc_subnet_ids: + - subnet-123abcde + - subnet-edcba321 + vpc_security_group_ids: + - sg-123abcde + - sg-edcba321 + environment_variables: '{{ item.env_vars }}' + tags: + key1: 'value1' + loop: + - name: HelloWorld + zip_file: hello-code.zip + env_vars: + key1: "first" + key2: "second" + - name: ByeBye + zip_file: bye-code.zip + env_vars: + key1: "1" + key2: "2" +- name: remove tags + amazon.aws.lambda: + aws_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' + name: 'Lambda function' + state: present + zip_file: code.zip + runtime: 'python2.7' + role: 'arn:aws:iam::987654321012:role/lambda_basic_execution' + handler: 'hello_python.my_handler' + tags: {} +- name: Delete Lambda functions HelloWorld and ByeBye + amazon.aws.lambda: + name: '{{ item }}' + state: absent + loop: + - HelloWorld + - ByeBye + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/http_port_open_to_internet.md b/documentation/rules/ansible/aws/http_port_open_to_internet.md new file mode 100644 index 00000000..378a73db --- /dev/null +++ b/documentation/rules/ansible/aws/http_port_open_to_internet.md @@ -0,0 +1,250 @@ +--- +title: "HTTP port open to internet" +group_id: "Ansible / AWS" +meta: + name: "aws/http_port_open_to_internet" + id: "a14ad534-acbe-4a8e-9404-2f7e1045646e" + display_name: "HTTP port open to internet" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `a14ad534-acbe-4a8e-9404-2f7e1045646e` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html#ansible-collections-amazon-aws-ec2-group-module) + +### Description + +Allowing HTTP (TCP port 80) from 0.0.0.0/0 in a Security Group exposes services to unauthenticated public access and subjects unencrypted traffic to eavesdropping and automated scanning. In Ansible tasks using `amazon.aws.ec2_group` or `ec2_group`, this rule flags `rules` entries where `cidr_ip` is `0.0.0.0/0` and the entry opens port 80. + +Resources with such `rules` are flagged. To remediate, restrict `cidr_ip` to explicit trusted CIDR ranges or remove the public HTTP rule. Alternatively, serve traffic over HTTPS (port 443) terminated at a load balancer or proxy with appropriate access controls. + +Secure example showing HTTP restricted to a trusted CIDR: + +```yaml +- name: create security group with restricted HTTP + amazon.aws.ec2_group: + name: my-sg + description: "example" + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 10.0.0.0/16 +``` + +## Compliant Code Examples +```yaml +- name: example ec2 group1 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 67 + to_port: 82 + cidr_ip: 0.0.0.0/1 + +- name: example ec2 group2 + amazon.aws.ec2_group: + name: example2 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: 80 + cidr_ip: 0.0.1.0/0 + +- name: example ec2 group3 + amazon.aws.ec2_group: + name: example3 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: 79-90 + cidr_ip: 0.1.0.0/0 + +- name: example ec2 group4 + amazon.aws.ec2_group: + name: example3 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: + - 100 + - 70-90 + cidr_ip: 10.0.0.0/0 + +- name: example ec2 group5 + amazon.aws.ec2_group: + name: example5 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: + - 80 + - 30-31 + cidr_ip: 0.0.0.0/10 + +- name: example ec2 group6 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: -1 + to_port: 82 + cidr_ip: 0.1.0.0/0 + +- name: example ec2 group7 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 67 + to_port: -1 + cidr_ip: 1.0.0.0/0 + +``` +## Non-Compliant Code Examples +```yaml +- name: example ec2 group1 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 67 + to_port: 82 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group2 + amazon.aws.ec2_group: + name: example2 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: 80 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group3 + amazon.aws.ec2_group: + name: example3 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: 79-90 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group4 + amazon.aws.ec2_group: + name: example4 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: + - 100 + - 70-90 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group5 + amazon.aws.ec2_group: + name: example5 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: + - 80 + - 30-31 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group6 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: -1 + to_port: 82 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group7 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 67 + to_port: -1 + cidr_ip: 0.0.0.0/0 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/iam_access_key_is_exposed.md b/documentation/rules/ansible/aws/iam_access_key_is_exposed.md new file mode 100644 index 00000000..f8bc9379 --- /dev/null +++ b/documentation/rules/ansible/aws/iam_access_key_is_exposed.md @@ -0,0 +1,68 @@ +--- +title: "IAM access key is exposed" +group_id: "Ansible / AWS" +meta: + name: "aws/iam_access_key_is_exposed" + id: "7f79f858-fbe8-4186-8a2c-dfd0d958a40f" + display_name: "IAM access key is exposed" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `7f79f858-fbe8-4186-8a2c-dfd0d958a40f` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_access_key_module.html) + +### Description + +Active, long‑lived access keys for non‑root IAM users increase the risk of credential compromise and unauthorized API access because leaked keys can be used to impersonate users and perform privileged actions. This rule inspects Ansible tasks that use the `amazon.aws.iam_access_key` or `iam_access_key` modules and flags tasks where the `active` property is `true` (or absent, since `true` is the default) and the `state` is not `absent`, while the `user_name` property does not contain `root`. + +Resources with active access keys for non‑root users are flagged. Remediate by removing or deactivating unused keys, rotating keys frequently, or replacing long‑lived keys with IAM roles and temporary credentials. The check is case‑insensitive and treats any username containing the substring `root` as the root account exception. + +## Compliant Code Examples +```yaml +# Root user with active key (covered by a separate rule, not flagged here) +- name: Create root access key + amazon.aws.iam_access_key: + user_name: root + state: present + +# Non-root user with inactive key +- name: Create inactive access key + amazon.aws.iam_access_key: + user_name: jcleese + active: false + +# Non-root user with absent state +- name: Remove access key + amazon.aws.iam_access_key: + user_name: jdavila + state: absent + +``` +## Non-Compliant Code Examples +```yaml +- name: Create non-root user with active access key + amazon.aws.iam_access_key: + user_name: jcleese + state: present + +- name: Create another non-root user with active access key + amazon.aws.iam_access_key: + user_name: mpython + state: present + +``` diff --git a/documentation/rules/ansible/aws/iam_database_auth_not_enabled.md b/documentation/rules/ansible/aws/iam_database_auth_not_enabled.md new file mode 100644 index 00000000..3b2fee1c --- /dev/null +++ b/documentation/rules/ansible/aws/iam_database_auth_not_enabled.md @@ -0,0 +1,132 @@ +--- +title: "IAM database authentication is not enabled" +group_id: "Ansible / AWS" +meta: + name: "aws/iam_database_auth_not_enabled" + id: "0ed012a4-9199-43d2-b9e4-9bd049a48aa4" + display_name: "IAM database authentication is not enabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `0ed012a4-9199-43d2-b9e4-9bd049a48aa4` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html) + +### Description + +IAM database authentication should be enabled to avoid reliance on static database passwords and centralize access control. This reduces the risk of credential leakage and makes rotation and auditing easier. + +For Ansible RDS resources using the `amazon.aws.rds_instance` or `rds_instance` modules, the `enable_iam_database_authentication` property must be defined and set to `true`. This check only applies to engines, engine versions, and instance types that support IAM authentication. The policy validates `engine`, `engine_version`, and `instance_type`. Resources where the property is missing or set to `false` are flagged. + +Secure Ansible example: + +```yaml +- name: Create RDS instance with IAM auth enabled + amazon.aws.rds_instance: + db_instance_identifier: mydb + engine: mysql + engine_version: "8.0" + instance_type: db.t3.medium + enable_iam_database_authentication: true +``` + +## Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: '{{ password }}' + username: '{{ username }}' + cluster_id: ansible-test-cluster + enable_iam_database_authentication: true + + +- name: Create a DB instance using the default AWS KMS encryption key + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: true + db_instance_class: db.t2.medium + username: '{{ username }}' + password: '{{ password }}' + allocated_storage: '{{ allocated_storage }}' + enable_iam_database_authentication: true + +- name: remove the DB instance without a final snapshot + amazon.aws.rds_instance: + id: '{{ instance_id }}' + state: absent + skip_final_snapshot: true + enable_iam_database_authentication: true + +- name: remove the DB instance with a final snapshot + amazon.aws.rds_instance: + id: '{{ instance_id }}' + state: absent + final_snapshot_identifier: '{{ snapshot_id }}' + enable_iam_database_authentication: true + +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster + enable_iam_database_authentication: "No" + +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: mariadb + engine_version: 10.2.43 + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster + +``` +## Non-Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: mysql + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster + enable_iam_database_authentication: "No" + + +- name: Create a DB instance using the default AWS KMS encryption key + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: True + db_instance_class: db.t2.medium + username: "{{ username }}" + password: "{{ password }}" + allocated_storage: "{{ allocated_storage }}" + enable_iam_database_authentication: false + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/iam_group_without_users.md b/documentation/rules/ansible/aws/iam_group_without_users.md new file mode 100644 index 00000000..bacde031 --- /dev/null +++ b/documentation/rules/ansible/aws/iam_group_without_users.md @@ -0,0 +1,78 @@ +--- +title: "IAM group without users" +group_id: "Ansible / AWS" +meta: + name: "aws/iam_group_without_users" + id: "f509931b-bbb0-443c-bd9b-10e92ecf2193" + display_name: "IAM group without users" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `f509931b-bbb0-443c-bd9b-10e92ecf2193` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_group_module.html) + +### Description + +IAM groups should include at least one user to ensure group membership and any attached permissions are intentional, auditable, and not left orphaned. + +This rule checks Ansible `amazon.aws.iam_group` and `iam_group` tasks and requires the `users` property to be defined and non-null (a list containing one or more usernames). Resources missing the `users` property or with `users: null` or an empty list are flagged. Either populate the list with the intended usernames or remove unused groups and associated policies. + +Secure configuration example: + +``` +- name: Create developers IAM group with users + amazon.aws.iam_group: + name: developers + users: + - alice + - bob + state: present +``` + +## Compliant Code Examples +```yaml +- name: Group3 + iam_group: + name: testgroup2 + managed_policy: + - arn:aws:iam::aws:policy/AmazonSNSFullAccess + users: + - test_user1 + - test_user2 + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: Group2 + iam_group: + name: testgroup2 + managed_policy: + - arn:aws:iam::aws:policy/AmazonSNSFullAccess + users: + state: present + +``` + +```yaml +- name: Group1 + iam_group: + name: testgroup1 + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/iam_password_without_minimum_length.md b/documentation/rules/ansible/aws/iam_password_without_minimum_length.md new file mode 100644 index 00000000..85dcc94c --- /dev/null +++ b/documentation/rules/ansible/aws/iam_password_without_minimum_length.md @@ -0,0 +1,112 @@ +--- +title: "IAM password without minimum length" +group_id: "Ansible / AWS" +meta: + name: "aws/iam_password_without_minimum_length" + id: "8bc2168c-1723-4eeb-a6f3-a1ba614b9a6d" + display_name: "IAM password without minimum length" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `8bc2168c-1723-4eeb-a6f3-a1ba614b9a6d` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_password_policy_module.html) + +### Description + +IAM password policies must enforce a minimum length to reduce the risk of credential brute-force and credential-stuffing attacks and limit the effectiveness of weak passwords. + +This rule checks Ansible tasks using `amazon.aws.iam_password_policy` or `iam_password_policy` and requires `min_pw_length` or `minimum_password_length` to be set to a numeric value of at least 8. Tasks missing both properties are flagged as MissingAttribute. Tasks where the configured value is less than 8 are flagged as IncorrectValue. Configure the property to 8 or higher. + +Secure example: + +```yaml +- name: Enforce IAM password policy + amazon.aws.iam_password_policy: + min_pw_length: 12 +``` + +## Compliant Code Examples +```yaml +- name: Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + pw_reuse_prevent: 5 + pw_expire: false + +- name: aws_iam_account_password_policy + amazon.aws.iam_password_policy: + state: present + minimum_password_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + pw_reuse_prevent: 5 + pw_expire: false + +``` +## Non-Compliant Code Examples +```yaml +- name: Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + pw_reuse_prevent: 5 + pw_expire: false + +- name: aws_iam_account_password_policy + amazon.aws.iam_password_policy: + state: present + min_pw_length: 3 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + pw_reuse_prevent: 5 + pw_expire: false + +- name: aws_iam_account_password_policy_2 + amazon.aws.iam_password_policy: + state: present + minimum_password_length: 3 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + pw_reuse_prevent: 5 + pw_expire: false + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/iam_policies_attached_to_user.md b/documentation/rules/ansible/aws/iam_policies_attached_to_user.md new file mode 100644 index 00000000..560a1039 --- /dev/null +++ b/documentation/rules/ansible/aws/iam_policies_attached_to_user.md @@ -0,0 +1,67 @@ +--- +title: "IAM policies attached to user" +group_id: "Ansible / AWS" +meta: + name: "aws/iam_policies_attached_to_user" + id: "eafe4bc3-1042-4f88-b988-1939e64bf060" + display_name: "IAM policies attached to user" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `eafe4bc3-1042-4f88-b988-1939e64bf060` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_policy_module.html) + +### Description + +Attaching IAM policies directly to individual IAM users increases the risk of privilege sprawl, makes permissions harder to audit and revoke, and magnifies impact if a user's credentials are compromised. + +For Ansible `amazon.aws.iam_policy` or `iam_policy` tasks, the `iam_type` property must be set to `group` or `role` rather than `user`. Resources missing the `iam_type` property or with `iam_type` set to `user` are flagged. Attach policies to groups or roles to centralize permission management and enable role-based access patterns. + +Secure example (attach policy to a role): + +```yaml +- name: Attach policy to role + amazon.aws.iam_policy: + name: my-policy + policy_document: "{{ lookup('file', 'my-policy.json') }}" + iam_type: role + iam_name: my-role +``` + +## Compliant Code Examples +```yaml +- name: Assign a policy called Admin to the administrators group + amazon.aws.iam_policy: + iam_type: group + iam_name: administrators + policy_name: Admin + state: present + policy_document: admin_policy.json + +``` +## Non-Compliant Code Examples +```yaml +- name: Assign a policy called Admin to user + amazon.aws.iam_policy: + iam_type: user + iam_name: administrators + policy_name: Admin + state: present + policy_document: admin_policy.json + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/iam_policies_with_full_privileges.md b/documentation/rules/ansible/aws/iam_policies_with_full_privileges.md new file mode 100644 index 00000000..2ecdb3f6 --- /dev/null +++ b/documentation/rules/ansible/aws/iam_policies_with_full_privileges.md @@ -0,0 +1,80 @@ +--- +title: "IAM policies with full privileges" +group_id: "Ansible / AWS" +meta: + name: "aws/iam_policies_with_full_privileges" + id: "e401d614-8026-4f4b-9af9-75d1197461ba" + display_name: "IAM policies with full privileges" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `e401d614-8026-4f4b-9af9-75d1197461ba` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_managed_policy_module.html) + +### Description + +IAM policies must not grant full administrative privileges (Allow for all actions on all resources). Such statements enable privilege escalation and allow any principal with the policy to access, modify, or delete resources account-wide. For Ansible managed policy resources (modules `amazon.aws.iam_managed_policy` and `iam_managed_policy`), inspect the `policy` document's `Statement` entries. Ensure no `Statement` has `Effect: Allow` where `Action` is `"*"` and `Resource` is `"*"`. Define explicit action lists and restrict `Resource` to specific ARNs, or use condition keys to enforce least privilege. If full admin rights are truly required, attach AWS-managed administrative policies only to trusted admin roles or groups. Statements matching `Effect` set to `Allow` with both `Action` set to `'*'` and `Resource` set to `'*'` are flagged. + +Secure example with explicit actions and narrowed resources: + +```yaml +- name: Create limited S3 read policy + amazon.aws.iam_managed_policy: + name: ReadOnlyS3Policy + policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - s3:ListBucket + - s3:GetObject + Resource: + - arn:aws:s3:::my-bucket + - arn:aws:s3:::my-bucket/* +``` + +## Compliant Code Examples +```yaml +- name: Create IAM Managed Policy + amazon.aws.iam_managed_policy: + policy_name: ManagedPolicy + policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: logs:CreateLogGroup + Resource: '*' + make_default: false + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: Create IAM Managed Policy + amazon.aws.iam_managed_policy: + policy_name: "ManagedPolicy" + policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: ["*"] + Resource: "*" + make_default: false + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services.md b/documentation/rules/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services.md new file mode 100644 index 00000000..14e13b92 --- /dev/null +++ b/documentation/rules/ansible/aws/iam_policy_grants_assumerole_permission_across_all_services.md @@ -0,0 +1,67 @@ +--- +title: "IAM policy grants 'AssumeRole' permission across all services" +group_id: "Ansible / AWS" +meta: + name: "aws/iam_policy_grants_assumerole_permission_across_all_services" + id: "12a7a7ce-39d6-49dd-923d-aeb4564eb66c" + display_name: "IAM policy grants 'AssumeRole' permission across all services" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `12a7a7ce-39d6-49dd-923d-aeb4564eb66c` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_managed_policy_module.html) + +### Description + +Policy statements that use a wildcard principal (`*`) with `Effect` set to `Allow` grant trust or permissions to any AWS principal. This can enable unauthorized accounts or external services to assume roles or perform actions, increasing the risk of privilege escalation and data exposure. + +In Ansible resources `amazon.aws.iam_managed_policy` and `iam_managed_policy`, check the `policy.Statement[].Effect` and `policy.Statement[].Principal.AWS` properties. Statements must not have an `Allow` effect combined with `Principal.AWS` equal to or containing `"*"`. This rule flags managed policy resources where any statement authorizes `"*"` as a principal. Replace wildcards with explicit principals such as AWS account IDs, ARNs, or specific service principals to limit trust to known entities. + +## Compliant Code Examples +```yaml +- name: Create IAM Managed Policy + amazon.aws.iam_managed_policy: + policy_name: ManagedPolicy + policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: logs:CreateLogGroup + Resource: '*' + make_default: false + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: Create IAM Managed Policy + amazon.aws.iam_managed_policy: + policy_name: "ManagedPolicy" + policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: "logs:CreateLogGroup" + Resource: "*" + Principal: + Service: "ec2.amazonaws.com" + AWS: "*" + make_default: false + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/iam_policy_grants_full_permissions.md b/documentation/rules/ansible/aws/iam_policy_grants_full_permissions.md new file mode 100644 index 00000000..61bfb55e --- /dev/null +++ b/documentation/rules/ansible/aws/iam_policy_grants_full_permissions.md @@ -0,0 +1,97 @@ +--- +title: "IAM policy grants full permissions" +group_id: "Ansible / AWS" +meta: + name: "aws/iam_policy_grants_full_permissions" + id: "b5ed026d-a772-4f07-97f9-664ba0b116f8" + display_name: "IAM policy grants full permissions" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `b5ed026d-a772-4f07-97f9-664ba0b116f8` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_managed_policy_module.html) + +### Description + +IAM managed policies must not include statements that allow all actions on all resources. Wildcard Allow statements grant unrestricted privileges, greatly increase blast radius, and raise the risk of privilege escalation or data exposure. + +For Ansible tasks using the `amazon.aws.iam_managed_policy` or `iam_managed_policy` modules, examine the policy document's `Statement` entries: any statement with `Effect: "Allow"` must not have both `Action` and `Resource` set to `"*"`. This rule flags tasks where `policy.Statement[].Action == "*"` and `policy.Statement[].Resource == "*"`. Instead, scope `Action` to specific API operations and `Resource` to concrete ARNs, or apply conditions to limit access. + +Secure example with scoped actions and resources: + +```yaml +- name: Create IAM managed policy with scoped permissions + amazon.aws.iam_managed_policy: + name: ExampleReadOnlyPolicy + policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: + - "s3:GetObject" + - "s3:ListBucket" + Resource: + - "arn:aws:s3:::example-bucket" + - "arn:aws:s3:::example-bucket/*" +``` + +## Compliant Code Examples +```yaml +- name: Create IAM Managed Policy + amazon.aws.iam_managed_policy: + policy_name: ManagedPolicy + policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: logs:CreateLogGroup + Resource: SomeResource + make_default: false + state: present + +``` + +```yaml +- name: Create IAM Managed Policy + amazon.aws.iam_managed_policy: + policy_name: ManagedPolicy + policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: '*' + Resource: ec2messages:GetEndpoint + make_default: false + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: Create IAM Managed Policy + amazon.aws.iam_managed_policy: + policy_name: "ManagedPolicy" + policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: "*" + Resource: "*" + make_default: false + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/iam_role_allows_all_principals_to_assume.md b/documentation/rules/ansible/aws/iam_role_allows_all_principals_to_assume.md new file mode 100644 index 00000000..9fc835ff --- /dev/null +++ b/documentation/rules/ansible/aws/iam_role_allows_all_principals_to_assume.md @@ -0,0 +1,98 @@ +--- +title: "IAM role allows all principals to assume" +group_id: "Ansible / AWS" +meta: + name: "aws/iam_role_allows_all_principals_to_assume" + id: "babdedcf-d859-43da-9a7b-6d72e661a8fd" + display_name: "IAM role allows all principals to assume" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `babdedcf-d859-43da-9a7b-6d72e661a8fd` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_managed_policy_module.html) + +### Description + +Specifying the account root or an entire AWS account as a principal (ARNs that end with `:root`) grants every identity in that account the ability to assume the role or act as that principal. This increases the risk of privilege escalation, lateral movement, and unauthorized access if any identity is compromised. + +This rule checks Ansible tasks using the `amazon.aws.iam_managed_policy` or `iam_managed_policy` modules and flags policy statements where `policy.Statement[].Principal.AWS` contains `:root`. Principal values must be explicit and least-privileged — use specific IAM role or user ARNs or service principals instead of account-root ARNs (or wildcards). Resources with `Principal.AWS` containing `:root` are flagged. + +Secure example with an explicit principal: + +```json +{ + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::123456789012:role/SpecificRole" + }, + "Action": "sts:AssumeRole" + } + ], + "Version": "2012-10-17" +} +``` + +## Compliant Code Examples +```yaml +- name: Create IAM Managed Policy + amazon.aws.iam_managed_policy: + policy_name: ManagedPolicy + policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: logs:CreateLogGroup + Resource: '*' + make_default: false + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: Create IAM Managed Policy + amazon.aws.iam_managed_policy: + policy_name: "ManagedPolicy" + policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: "logs:CreateLogGroup" + Resource: "*" + Principal: + AWS: "arn:aws:iam::root" + make_default: false + state: present +- name: Create2 IAM Managed Policy + amazon.aws.iam_managed_policy: + policy_name: "ManagedPolicy2" + policy: > + { + "Version": "2012-10-17", + "Statement":[{ + "Effect": "Allow", + "Action": "logs:PutRetentionPolicy", + "Resource": "*", + "Principal" : { "AWS" : "arn:aws:iam::root" } + }] + } + only_version: true + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/instance_uses_metadata_service_IMDSv1.md b/documentation/rules/ansible/aws/instance_uses_metadata_service_IMDSv1.md new file mode 100644 index 00000000..849d42b6 --- /dev/null +++ b/documentation/rules/ansible/aws/instance_uses_metadata_service_IMDSv1.md @@ -0,0 +1,98 @@ +--- +title: "Instance uses metadata service IMDSv1" +group_id: "Ansible / AWS" +meta: + name: "aws/instance_uses_metadata_service_IMDSv1" + id: "b9ef8c0e-1392-4df4-aa84-2e0f95681c75" + display_name: "Instance uses metadata service IMDSv1" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `b9ef8c0e-1392-4df4-aa84-2e0f95681c75` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html) + +### Description + +The EC2 instance metadata service should require IMDSv2 session tokens to reduce the risk of metadata and credential exposure via SSRF or from compromised instances. + +For Ansible-managed EC2 resources (`amazon.aws.ec2_instance`, `community.aws.autoscaling_launch_config`), the `metadata_options.http_tokens` property must be set to `required` to enforce IMDSv2. Resources missing `metadata_options`, missing `metadata_options.http_tokens`, or where `http_tokens` is not `required` are flagged as insecure. + +Secure configuration example: + +```yaml +- name: Launch EC2 instance with IMDSv2 required + amazon.aws.ec2_instance: + name: my-instance + image_id: ami-0123456789abcdef0 + instance_type: t3.micro + metadata_options: + http_tokens: required +``` + +## Compliant Code Examples +```yaml +- name: start an instance with metadata options + amazon.aws.ec2_instance: + name: "public-metadataoptions-instance" + vpc_subnet_id: subnet-5calable + instance_type: t3.small + image_id: ami-123456 + tags: + Environment: Testing + metadata_options: + http_endpoint: enabled + http_tokens: required + +- name: start an instance with legacy naming and metadata options + amazon.aws.ec2_instance: + name: "public-metadataoptions-instance" + vpc_subnet_id: subnet-5calable + instance_type: t3.small + image_id: ami-123456 + tags: + Environment: Testing + metadata_options: + http_endpoint: enabled + http_tokens: required + +``` +## Non-Compliant Code Examples +```yaml +- name: start an instance with metadata options + amazon.aws.ec2_instance: + name: "public-metadataoptions-instance" + vpc_subnet_id: subnet-5calable + instance_type: t3.small + image_id: ami-123456 + tags: + Environment: Testing + metadata_options: + http_tokens: optional + +- name: start an instance with legacy naming and metadata options + amazon.aws.ec2_instance: + name: "public-metadataoptions-instance-legacy" + vpc_subnet_id: subnet-5calable + instance_type: t3.small + image_id: ami-123456 + tags: + Environment: Testing + metadata_options: + http_tokens: optional + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/instance_with_no_vpc.md b/documentation/rules/ansible/aws/instance_with_no_vpc.md new file mode 100644 index 00000000..787003c2 --- /dev/null +++ b/documentation/rules/ansible/aws/instance_with_no_vpc.md @@ -0,0 +1,101 @@ +--- +title: "Instance with no VPC" +group_id: "Ansible / AWS" +meta: + name: "aws/instance_with_no_vpc" + id: "61d1a2d0-4db8-405a-913d-5d2ce49dff6f" + display_name: "Instance with no VPC" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `61d1a2d0-4db8-405a-913d-5d2ce49dff6f` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html) + +### Description + +EC2 instances must be launched into a VPC subnet so they are subject to VPC network controls such as security groups, network ACLs, private addressing, and VPC flow logs. Without a subnet assignment, instances can lack network isolation and be exposed to the public network or miss critical network monitoring. + +For Ansible EC2 modules (`amazon.aws.ec2_instance`, `ec2_instance`), the `vpc_subnet_id` property must be defined and set to a valid VPC subnet ID. Tasks with `state` equal to `absent` or `list` are ignored. Resources missing `vpc_subnet_id` or with it undefined are flagged. + +Secure example Ansible task: + +```yaml +- name: Launch EC2 instance in VPC subnet + amazon.aws.ec2_instance: + name: my-instance + image_id: ami-0123456789abcdef0 + instance_type: t3.micro + vpc_subnet_id: subnet-0abc1234def567890 + security_groups: + - sg-0a1b2c3d4e5f6g7h +``` + +## Compliant Code Examples +```yaml +- name: Start an instance and have it begin a Tower callback on boot v3 + amazon.aws.ec2_instance: + name: tower-callback-test + key_name: prod-ssh-key + vpc_subnet_id: subnet-5ca1ab1e + security_group: default + tower_callback: + # IP or hostname of tower server + tower_address: 1.2.3.4 + job_template_id: 876 + host_config_key: '[secret config key goes here]' + network: + assign_public_ip: true + image_id: ami-123456 + cpu_credit_specification: unlimited + tags: + SomeThing: A value +- name: Start an instance and have it begin a Tower callback on boot v4 + amazon.aws.ec2_instance: + name: my-ec2-instance + key_name: mykey + instance_type: t2.micro + image_id: ami-123456 + vpc_subnet_id: subnet-29e63245 + +``` +## Non-Compliant Code Examples +```yaml +- name: Start an instance and have it begin a Tower callback on boot + amazon.aws.ec2_instance: + name: "tower-callback-test" + key_name: "prod-ssh-key" + security_group: default + tower_callback: + # IP or hostname of tower server + tower_address: 1.2.3.4 + job_template_id: 876 + host_config_key: '[secret config key goes here]' + network: + assign_public_ip: true + image_id: ami-123456 + cpu_credit_specification: unlimited + tags: + SomeThing: "A value" +- name: Start an instance and have it begin a Tower callback on boot v2 + amazon.aws.ec2_instance: + name: my-ec2-instance + key_name: mykey + instance_type: t2.micro + image_id: ami-123456 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/kinesis_not_encrypted_with_kms.md b/documentation/rules/ansible/aws/kinesis_not_encrypted_with_kms.md new file mode 100644 index 00000000..de199169 --- /dev/null +++ b/documentation/rules/ansible/aws/kinesis_not_encrypted_with_kms.md @@ -0,0 +1,118 @@ +--- +title: "Kinesis not encrypted with KMS" +group_id: "Ansible / AWS" +meta: + name: "aws/kinesis_not_encrypted_with_kms" + id: "f2ea6481-1d31-4d40-946a-520dc6321dd7" + display_name: "Kinesis not encrypted with KMS" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `f2ea6481-1d31-4d40-946a-520dc6321dd7` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/kinesis_stream_module.html) + +### Description + +Kinesis Data Streams must have server-side encryption enabled to protect stream data and metadata at rest and reduce the risk of unauthorized access or data exposure. + +For Ansible resources using the `community.aws.kinesis_stream` or `kinesis_stream` module, the `encryption_state` property must be set to `"enabled"` and the `encryption_type` property must be defined and not set to `"NONE"`. If `encryption_type` is `"KMS"`, a valid `key_id` (KMS key ARN or ID) must also be provided. + +Resources missing these properties or with `encryption_state != "enabled"`, `encryption_type == "NONE"`, or `encryption_type == "KMS"` without `key_id` are flagged. + +Secure Ansible configuration example: + +```yaml +- name: Create Kinesis stream with SSE-KMS + community.aws.kinesis_stream: + name: my-stream + shard_count: 1 + encryption_state: enabled + encryption_type: KMS + key_id: arn:aws:kms:us-east-1:123456789012:key/abcd1234-ef56-7890-abcd-ef1234567890 +``` + +## Compliant Code Examples +```yaml +- name: Encrypt Kinesis Stream test-stream. v6 + community.aws.kinesis_stream: + name: test-stream + state: present + shards: 1 + encryption_state: enabled + encryption_type: KMS + key_id: alias/aws/kinesis + wait: yes + wait_timeout: 600 + +``` +## Non-Compliant Code Examples +```yaml +- name: Encrypt Kinesis Stream test-stream. + community.aws.kinesis_stream: + name: test-stream + state: present + shards: 1 + encryption_type: KMS + key_id: alias/aws/kinesis + wait: yes + wait_timeout: 600 + register: test_stream +- name: Encrypt Kinesis Stream test-stream. v2 + community.aws.kinesis_stream: + name: test-stream + state: present + shards: 1 + encryption_state: disabled + encryption_type: KMS + key_id: alias/aws/kinesis + wait: yes + wait_timeout: 600 + register: test_stream +- name: Encrypt Kinesis Stream test-stream. v3 + community.aws.kinesis_stream: + name: test-stream + state: present + shards: 1 + encryption_state: enabled + key_id: alias/aws/kinesis + wait: yes + wait_timeout: 600 + register: test_stream +- name: Encrypt Kinesis Stream test-stream. v4 + community.aws.kinesis_stream: + name: test-stream + state: present + shards: 1 + encryption_state: enabled + encryption_type: NONE + key_id: alias/aws/kinesis + wait: yes + wait_timeout: 600 + register: test_stream +- name: Encrypt Kinesis Stream test-stream. v5 + community.aws.kinesis_stream: + name: test-stream + state: present + shards: 1 + encryption_state: enabled + encryption_type: KMS + wait: yes + wait_timeout: 600 + register: test_stream + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/kms_key_with_full_permissions.md b/documentation/rules/ansible/aws/kms_key_with_full_permissions.md new file mode 100644 index 00000000..bb3c36de --- /dev/null +++ b/documentation/rules/ansible/aws/kms_key_with_full_permissions.md @@ -0,0 +1,102 @@ +--- +title: "KMS key with vulnerable policy" +group_id: "Ansible / AWS" +meta: + name: "aws/kms_key_with_full_permissions" + id: "5b9d237a-57d5-4177-be0e-71434b0fef47" + display_name: "KMS key with vulnerable policy" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `5b9d237a-57d5-4177-be0e-71434b0fef47` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/kms_key_module.html) + +### Description + +KMS key policies that grant broad permissions—such as Allow statements containing `kms:*` or wildcard principals—or that lack conditions can permit unauthorized principals to use, manage, or delete keys. This increases the risk of data exposure or loss. + +For Ansible tasks using the `amazon.aws.kms_key` or `aws_kms` modules, inspect the `policy` property. Either omit a custom `policy` so the key uses a safe default, or ensure any provided `policy` does not include `Effect: "Allow"` statements that lack a `Condition` and contain wildcard actions like `kms:*` or wildcard principals (such as `"*"` or account-wide ARNs). + +This rule flags KMS resources where a custom `policy` contains an Allow statement without a `Condition` that includes wildcard `kms:*` in `Action` or a wildcard `Principal`. It also flags cases where a custom `policy` is supplied when your organization requires the property to be undefined. + +Secure examples — either omit the policy to use safer defaults or supply a restrictive policy that specifies explicit principals, limited actions, and Conditions: + +```yaml +- name: Create KMS key using default policy + amazon.aws.kms_key: + alias: alias/my-key + description: "Encryption key for app" + state: present +``` + +```yaml +- name: Create KMS key with restricted policy + amazon.aws.kms_key: + alias: alias/my-key + policy: + Version: "2012-10-17" + Statement: + - Sid: "AllowSpecificUse" + Effect: "Allow" + Principal: + AWS: "arn:aws:iam::123456789012:role/MyRole" + Action: + - "kms:Encrypt" + - "kms:Decrypt" + Resource: "*" + Condition: + StringEquals: + aws:CalledVia: "my-allowed-service.amazonaws.com" +``` + +## Compliant Code Examples +```yaml +- name: Update IAM policy on an existing KMS key + amazon.aws.kms_key: + alias: my-kms-key + policy: | + { Id: auto-ebs-2, Statement: [{Action: [kms:Encrypt, kms:Decrypt, kms:ReEncrypt*, + kms:GenerateDataKey*, kms:CreateGrant, kms:DescribeKey], Condition: { + StringEquals: {kms:CallerAccount: '111111111111', kms:ViaService: ec2.ap-southeast-2.amazonaws.com}}, + Effect: Allow, Principal: {AWS: '*'}, Resource: '*', + Sid: Allow access through EBS for all principals in the account that are authorized to use EBS }, + { Action: [kms:Describe*, kms:Get*, kms:List*, kms:RevokeGrant], Effect: Allow, + Principal: {AWS: arn:aws:iam::111111111111:root}, Resource: '*', + Sid: Allow direct access to key metadata to the account}], Version: '2012-10-17' } + state: present + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Update IAM policy on an existing KMS key2 + amazon.aws.kms_key: + alias: my-kms-key + state: present + +``` + +```yaml +--- +- name: Update IAM policy on an existing KMS key + amazon.aws.kms_key: + alias: my-kms-key + policy: {'Id': 'auto-ebs-2', 'Statement': [{'Action': ['kms:*'], 'Effect': 'Allow', 'Principal': {'AWS': '*'}, 'Resource': '*', 'Sid': 'Allow access through EBS for all principals in the account that are authorized to use EBS'}, {'Action': ['kms:Describe*', 'kms:Get*', 'kms:List*', 'kms:RevokeGrant'], 'Effect': 'Allow', 'Principal': {'AWS': 'arn:aws:iam::111111111111:root'}, 'Resource': '*', 'Sid': 'Allow direct access to key metadata to the account'}], 'Version': '2012-10-17'} + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/lambda_function_without_tags.md b/documentation/rules/ansible/aws/lambda_function_without_tags.md new file mode 100644 index 00000000..af669fd9 --- /dev/null +++ b/documentation/rules/ansible/aws/lambda_function_without_tags.md @@ -0,0 +1,76 @@ +--- +title: "Lambda function without tags" +group_id: "Ansible / AWS" +meta: + name: "aws/lambda_function_without_tags" + id: "265d9725-2fb8-42a2-bc57-3279c5db82d5" + display_name: "Lambda function without tags" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `265d9725-2fb8-42a2-bc57-3279c5db82d5` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_module.html) + +### Description + +AWS Lambda functions should be tagged so resources can be reliably inventoried and assigned ownership. Tags also enable tag-based access controls and automated security or operational workflows. + +In Ansible playbooks, tasks using the `amazon.aws.lambda` or legacy `lambda` module must define the `tags` property as a mapping/dictionary. Resources where `tags` is undefined are flagged. Ensure `tags` is present on the module invocation and contains at least the necessary keys for your organization (for example, `Owner`, `Environment`, or `Project`). + +Secure example: + +```yaml +- name: create application lambda + amazon.aws.lambda: + name: my-function + state: present + runtime: python3.9 + role: arn:aws:iam::123456789012:role/lambda-exec + handler: app.handler + tags: + Owner: team-foo + Environment: production + Project: billing +``` + +## Compliant Code Examples +```yaml +- name: add tags + amazon.aws.lambda: + name: 'Lambda function' + state: present + zip_file: 'code.zip' + runtime: 'python2.7' + role: 'arn:aws:iam::987654321012:role/lambda_basic_execution' + handler: 'hello_python.my_handler' + tags: + key1: 'value1' + +``` +## Non-Compliant Code Examples +```yaml +- name: add tags + amazon.aws.lambda: + name: 'Lambda function' + state: present + zip_file: 'code.zip' + runtime: 'python2.7' + role: 'arn:aws:iam::987654321012:role/lambda_basic_execution' + handler: 'hello_python.my_handler' + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/lambda_functions_without_x-ray_tracing.md b/documentation/rules/ansible/aws/lambda_functions_without_x-ray_tracing.md new file mode 100644 index 00000000..1b622386 --- /dev/null +++ b/documentation/rules/ansible/aws/lambda_functions_without_x-ray_tracing.md @@ -0,0 +1,138 @@ +--- +title: "Lambda functions without X-Ray tracing" +group_id: "Ansible / AWS" +meta: + name: "aws/lambda_functions_without_x-ray_tracing" + id: "71397b34-1d50-4ee1-97cb-c96c34676f74" + display_name: "Lambda functions without X-Ray tracing" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `71397b34-1d50-4ee1-97cb-c96c34676f74` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_module.html) + +### Description + +Lambda functions should have active AWS X-Ray tracing enabled to provide end-to-end request visibility and help detect performance problems and security incidents. For Ansible `amazon.aws.lambda` or `lambda` module tasks, the `tracing_mode` property must be defined and set to `Active`. Tasks that omit `tracing_mode` or set it to any value other than `Active` are flagged. + +Secure Ansible example: + +```yaml +- name: Create Lambda with active X-Ray tracing + amazon.aws.lambda: + name: my_lambda_function + state: present + runtime: python3.9 + handler: app.handler + tracing_mode: Active +``` + +## Compliant Code Examples +```yaml +- name: looped creation V3 + amazon.aws.lambda: + name: '{{ item.name }}' + state: present + zip_file: '{{ item.zip_file }}' + runtime: python2.7 + role: arn:aws:iam::987654321012:role/lambda_basic_execution + handler: hello_python.my_handler + tracing_mode: Active + vpc_subnet_ids: + - subnet-123abcde + - subnet-edcba321 + vpc_security_group_ids: + - sg-123abcde + - sg-edcba321 + environment_variables: '{{ item.env_vars }}' + tags: + key1: value1 + loop: + - name: HelloWorld + zip_file: hello-code.zip + env_vars: + key1: first + key2: second + - name: ByeBye + zip_file: bye-code.zip + env_vars: + key1: '1' + key2: '2' + +``` +## Non-Compliant Code Examples +```yaml +- name: looped creation + amazon.aws.lambda: + name: '{{ item.name }}' + state: present + zip_file: '{{ item.zip_file }}' + runtime: 'python2.7' + role: 'arn:aws:iam::987654321012:role/lambda_basic_execution' + handler: 'hello_python.my_handler' + vpc_subnet_ids: + - subnet-123abcde + - subnet-edcba321 + vpc_security_group_ids: + - sg-123abcde + - sg-edcba321 + environment_variables: '{{ item.env_vars }}' + tags: + key1: 'value1' + loop: + - name: HelloWorld + zip_file: hello-code.zip + env_vars: + key1: "first" + key2: "second" + - name: ByeBye + zip_file: bye-code.zip + env_vars: + key1: "1" + key2: "2" +- name: looped creation V2 + amazon.aws.lambda: + name: '{{ item.name }}' + state: present + zip_file: '{{ item.zip_file }}' + runtime: 'python2.7' + role: 'arn:aws:iam::987654321012:role/lambda_basic_execution' + handler: 'hello_python.my_handler' + tracing_mode: "PassThrough" + vpc_subnet_ids: + - subnet-123abcde + - subnet-edcba321 + vpc_security_group_ids: + - sg-123abcde + - sg-edcba321 + environment_variables: '{{ item.env_vars }}' + tags: + key1: 'value1' + loop: + - name: HelloWorld + zip_file: hello-code.zip + env_vars: + key1: "first" + key2: "second" + - name: ByeBye + zip_file: bye-code.zip + env_vars: + key1: "1" + key2: "2" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/lambda_permission_misconfigured.md b/documentation/rules/ansible/aws/lambda_permission_misconfigured.md new file mode 100644 index 00000000..cb6c0dc7 --- /dev/null +++ b/documentation/rules/ansible/aws/lambda_permission_misconfigured.md @@ -0,0 +1,74 @@ +--- +title: "Lambda permission misconfigured" +group_id: "Ansible / AWS" +meta: + name: "aws/lambda_permission_misconfigured" + id: "3ddf3417-424d-420d-8275-0724dc426520" + display_name: "Lambda permission misconfigured" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `3ddf3417-424d-420d-8275-0724dc426520` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_policy_module.html) + +### Description + +Lambda permission statements must set the action to `lambda:InvokeFunction` so callers are limited to invoking the function and cannot receive broader or unintended Lambda privileges. + +Check Ansible tasks that use the `amazon.aws.lambda_policy` or `lambda_policy` modules. The `action` property must be defined and set to the exact string `lambda:InvokeFunction`. Tasks missing the `action` property or using any other value (for example `lambda:*`, a different Lambda action, or an empty value) are flagged because they can over-privilege callers or result in misconfigured access. + +Secure example with the action explicitly set: + +```yaml +- name: Allow S3 to invoke my Lambda + amazon.aws.lambda_policy: + name: my_lambda_policy + state: present + principal: s3.amazonaws.com + action: lambda:InvokeFunction + function_name: my-function +``` + +## Compliant Code Examples +```yaml +- name: Lambda S3 notification negative + amazon.aws.lambda_policy: + state: present + function_name: functionName + alias: Dev + statement_id: lambda-s3-myBucket-create-data-log + action: lambda:InvokeFunction + principal: s3.amazonaws.com + source_arn: arn:aws:s3:eu-central-1:123456789012:bucketName + source_account: 123456789012 + +``` +## Non-Compliant Code Examples +```yaml +- name: Lambda S3 notification positive + amazon.aws.lambda_policy: + state: present + function_name: functionName + alias: Dev + statement_id: lambda-s3-myBucket-create-data-log + action: lambda:CreateFunction + principal: s3.amazonaws.com + source_arn: arn:aws:s3:eu-central-1:123456789012:bucketName + source_account: 123456789012 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/lambda_permission_principal_is_wildcard.md b/documentation/rules/ansible/aws/lambda_permission_principal_is_wildcard.md new file mode 100644 index 00000000..e59bc506 --- /dev/null +++ b/documentation/rules/ansible/aws/lambda_permission_principal_is_wildcard.md @@ -0,0 +1,74 @@ +--- +title: "Lambda permission principal is wildcard" +group_id: "Ansible / AWS" +meta: + name: "aws/lambda_permission_principal_is_wildcard" + id: "1d972c56-8ec2-48c1-a578-887adb09c57a" + display_name: "Lambda permission principal is wildcard" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `1d972c56-8ec2-48c1-a578-887adb09c57a` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_policy_module.html) + +### Description + +Lambda function permissions must not use wildcard principals (`*`). This effectively allows any AWS account or anonymous principal to invoke the function, increasing the risk of unauthorized invocations and data exposure. + +In Ansible, check tasks using the `amazon.aws.lambda_policy` or `lambda_policy` modules and ensure the `principal` property does not contain `*` or other wildcard values. The `principal` must specify explicit principals such as an AWS account ARN, role ARN, or service principal (for example, `arn:aws:iam::123456789012:role/MyRole` or `events.amazonaws.com`). Tasks where `principal` includes `*` are flagged. + +Secure example using an explicit service principal: + +```yaml +- name: Allow EventBridge to invoke Lambda + amazon.aws.lambda_policy: + state: present + function_name: my-function + principal: events.amazonaws.com + action: lambda:InvokeFunction + source_arn: arn:aws:events:us-east-1:123456789012:rule/MyRule +``` + +## Compliant Code Examples +```yaml +- name: Lambda S3 event notification negative + amazon.aws.lambda_policy: + state: present + function_name: functionName + alias: Dev + statement_id: lambda-s3-myBucket-create-data-log + action: lambda:AddPermission + principal: s3.amazonaws.com + source_arn: arn:aws:s3:eu-central-1:123456789012:bucketName + source_account: 123456789012 + +``` +## Non-Compliant Code Examples +```yaml +- name: Lambda S3 event notification + amazon.aws.lambda_policy: + state: present + function_name: functionName + alias: Dev + statement_id: lambda-s3-myBucket-create-data-log + action: lambda:AddPermission + principal: "*" + source_arn: arn:aws:s3:eu-central-1:123456789012:bucketName + source_account: 123456789012 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/launch_configuration_is_not_encrypted.md b/documentation/rules/ansible/aws/launch_configuration_is_not_encrypted.md new file mode 100644 index 00000000..179792d7 --- /dev/null +++ b/documentation/rules/ansible/aws/launch_configuration_is_not_encrypted.md @@ -0,0 +1,120 @@ +--- +title: "Launch configuration is not encrypted" +group_id: "Ansible / AWS" +meta: + name: "aws/launch_configuration_is_not_encrypted" + id: "66477506-6abb-49ed-803d-3fa174cd5f6a" + display_name: "Launch configuration is not encrypted" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `66477506-6abb-49ed-803d-3fa174cd5f6a` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/autoscaling_launch_config_module.html) + +### Description + +Block device volumes in EC2 launch configurations must be encrypted to protect data at rest and prevent exposure of snapshots or AMIs if storage media is compromised. + +For Ansible tasks using the `community.aws.autoscaling_launch_config` or `autoscaling_launch_config` modules, ensure the `volumes` list is defined and each volume entry sets `encrypted: true` (Ansible `yes` is also acceptable) under `ec2_lc.volumes`. Ephemeral (instance-store) volumes do not support encryption and are excluded. This rule flags launch configurations missing the `volumes` property, any volume entries without an `encrypted` property, or volumes where `encrypted` is explicitly false. + +Example secure configuration for an Ansible `autoscaling_launch_config` task: + +```yaml +- name: Create launch configuration with encrypted volumes + community.aws.autoscaling_launch_config: + name: my-launch-config + image_id: ami-0123456789abcdef0 + instance_type: t3.medium + volumes: + - device_name: /dev/xvda + volume_size: 50 + volume_type: gp2 + encrypted: true + delete_on_termination: yes +``` + +## Compliant Code Examples +```yaml +- name: note that encrypted volumes are only supported in >= Ansible 2.4 v4 + community.aws.autoscaling_launch_config: + name: special + image_id: ami-XXX + key_name: default + security_groups: [group, group2] + instance_type: t1.micro + volumes: + - device_name: /dev/sda1 + volume_size: 100 + volume_type: io1 + iops: 3000 + delete_on_termination: true + encrypted: yes +- name: note that encrypted volumes are only supported in >= Ansible 2.4 v5 + community.aws.autoscaling_launch_config: + name: special + image_id: ami-XXX + key_name: default + security_groups: [group, group2] + instance_type: t1.micro + volumes: + - device_name: /dev/sda1 + volume_size: 100 + volume_type: io1 + iops: 3000 + delete_on_termination: true + encrypted: yes + +``` +## Non-Compliant Code Examples +```yaml +- name: note that encrypted volumes are only supported in >= Ansible 2.4 + community.aws.autoscaling_launch_config: + name: special + image_id: ami-XXX + key_name: default + security_groups: ['group', 'group2' ] + instance_type: t1.micro + volumes: + - device_name: /dev/sda1 + volume_size: 100 + volume_type: io1 + iops: 3000 + delete_on_termination: true + encrypted: no +- name: note that encrypted volumes are only supported in >= Ansible 2.4 v2 + community.aws.autoscaling_launch_config: + name: special + image_id: ami-XXX + key_name: default + security_groups: ['group', 'group2' ] + instance_type: t1.micro + volumes: + - device_name: /dev/sda1 + volume_size: 100 + volume_type: io1 + iops: 3000 + delete_on_termination: true +- name: note that encrypted volumes are only supported in >= Ansible 2.4 v3 + community.aws.autoscaling_launch_config: + name: special + image_id: ami-XXX + key_name: default + security_groups: ['group', 'group2' ] + instance_type: t1.micro + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/misconfigured_password_policy_expiration.md b/documentation/rules/ansible/aws/misconfigured_password_policy_expiration.md new file mode 100644 index 00000000..d11ab226 --- /dev/null +++ b/documentation/rules/ansible/aws/misconfigured_password_policy_expiration.md @@ -0,0 +1,95 @@ +--- +title: "Misconfigured password policy expiration" +group_id: "Ansible / AWS" +meta: + name: "aws/misconfigured_password_policy_expiration" + id: "3f2cf811-88fa-4eda-be45-7a191a18aba9" + display_name: "Misconfigured password policy expiration" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `3f2cf811-88fa-4eda-be45-7a191a18aba9` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_password_policy_module.html) + +### Description + +IAM account password policies must enforce regular password expiration to limit exposure from compromised or leaked credentials and reduce the risk of long-lived unauthorized access. In Ansible, tasks using the `amazon.aws.iam_password_policy` or `iam_password_policy` modules must define `pw_max_age` or `password_max_age` with a value of 90 days or fewer. Resources that omit both properties or set either to a value greater than 90 are flagged. + +Secure configuration example: + +```yaml +- name: Enforce IAM password expiration + amazon.aws.iam_password_policy: + password_max_age: 90 +``` + +## Compliant Code Examples +```yaml +- name: Missing Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 20 + pw_reuse_prevent: 5 + pw_expire: false + +``` +## Non-Compliant Code Examples +```yaml +- name: Missing Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_reuse_prevent: 5 + pw_expire: false +- name: Extreme Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 180 + pw_reuse_prevent: 5 + pw_expire: false +- name: Alias extreme Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + password_max_age: 95 + pw_reuse_prevent: 5 + pw_expire: false + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/no_stack_policy.md b/documentation/rules/ansible/aws/no_stack_policy.md new file mode 100644 index 00000000..88d53ac4 --- /dev/null +++ b/documentation/rules/ansible/aws/no_stack_policy.md @@ -0,0 +1,92 @@ +--- +title: "No stack policy" +group_id: "Ansible / AWS" +meta: + name: "aws/no_stack_policy" + id: "ffe0fd52-7a8b-4a5c-8fc7-49844418e6c9" + display_name: "No stack policy" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Resource Management" +--- +## Metadata + +**Id:** `ffe0fd52-7a8b-4a5c-8fc7-49844418e6c9` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Resource Management + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudformation_module.html) + +### Description + +CloudFormation stacks should have a stack policy to prevent unintended or unauthorized updates to stack resources, protecting critical resources from accidental changes or deployment mistakes. + +For Ansible tasks using the `amazon.aws.cloudformation` or `cloudformation` modules, the `stack_policy` property must be defined and set to a valid JSON policy that restricts update actions. Resources missing the `stack_policy` property or with it undefined are flagged. Provide a JSON policy string (or file content) that explicitly denies Update actions for any logical resource IDs you want to protect. + +Secure configuration example: + +```yaml +- name: Create CloudFormation stack with stack policy + amazon.aws.cloudformation: + stack_name: my-stack + state: present + template: "{{ lookup('file', 'template.yml') }}" + stack_policy: | + { + "Statement": [ + { + "Effect": "Deny", + "Action": "Update:*", + "Principal": "*", + "Resource": "LogicalResourceId/MyCriticalResource" + } + ] + } +``` + +## Compliant Code Examples +```yaml +- name: create a stack, pass in the template via an URL + amazon.aws.cloudformation: + stack_name: ansible-cloudformation + stack_policy: wowowowoowow + state: present + region: us-east-1 + disable_rollback: true + template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template + template_parameters: + KeyName: jmartin + DiskType: ephemeral + InstanceType: m1.small + ClusterSize: 3 + tags: + Stack: ansible-cloudformation + +``` +## Non-Compliant Code Examples +```yaml +- name: create a stack, pass in the template via an URL + amazon.aws.cloudformation: + stack_name: "ansible-cloudformation" + state: present + region: us-east-1 + disable_rollback: true + template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template + template_parameters: + KeyName: jmartin + DiskType: ephemeral + InstanceType: m1.small + ClusterSize: 3 + tags: + Stack: ansible-cloudformation + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/password_without_reuse_prevention.md b/documentation/rules/ansible/aws/password_without_reuse_prevention.md new file mode 100644 index 00000000..2b139998 --- /dev/null +++ b/documentation/rules/ansible/aws/password_without_reuse_prevention.md @@ -0,0 +1,121 @@ +--- +title: "Password without reuse prevention" +group_id: "Ansible / AWS" +meta: + name: "aws/password_without_reuse_prevention" + id: "6f5f5444-1422-495f-81ef-24cefd61ed2c" + display_name: "Password without reuse prevention" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `6f5f5444-1422-495f-81ef-24cefd61ed2c` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_password_policy_module.html#parameter-pw_reuse_prevent) + +### Description + +IAM password policies must prevent reuse of previous passwords to reduce the risk of account compromise from credential stuffing and replay of older credentials. + +For Ansible tasks using the `amazon.aws.iam_password_policy` or `iam_password_policy` modules, define one of the reuse-prevention properties (`password_reuse_prevent`, `pw_reuse_prevent`, or `prevent_reuse`) and set it to a positive integer greater than 0. This specifies how many prior passwords are disallowed. This rule flags tasks where none of these properties are present or where the property is explicitly set to `0`. + +Secure example (prevents reuse of the last 5 passwords): + +```yaml +- name: Enforce IAM password reuse prevention + amazon.aws.iam_password_policy: + password_reuse_prevent: 5 +``` + +## Compliant Code Examples +```yaml +- name: Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + pw_reuse_prevent: 5 + pw_expire: false +- name: Password policy for AWS account2 + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + password_reuse_prevent: 5 + pw_expire: false +- name: Password policy for AWS account3 + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + prevent_reuse: 5 + pw_expire: false + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Password policy for AWS account + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + pw_expire: false +- name: Password policy for AWS account2 + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + password_reuse_prevent: 0 + pw_expire: false +- name: Password policy for AWS account3 + amazon.aws.iam_password_policy: + state: present + min_pw_length: 8 + require_symbols: false + require_numbers: true + require_uppercase: true + require_lowercase: true + allow_pw_change: true + pw_max_age: 60 + pw_expire: false + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/public_lambda_via_api_gateway.md b/documentation/rules/ansible/aws/public_lambda_via_api_gateway.md new file mode 100644 index 00000000..985152be --- /dev/null +++ b/documentation/rules/ansible/aws/public_lambda_via_api_gateway.md @@ -0,0 +1,73 @@ +--- +title: "Public Lambda via API Gateway" +group_id: "Ansible / AWS" +meta: + name: "aws/public_lambda_via_api_gateway" + id: "5e92d816-2177-4083-85b4-f61b4f7176d9" + display_name: "Public Lambda via API Gateway" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `5e92d816-2177-4083-85b4-f61b4f7176d9` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/lambda_policy_module.html) + +### Description + +Allowing API Gateway to invoke a Lambda using a wildcard source ARN like `/*/*` grants any API, stage, or method the ability to invoke the function. This can result in unintended public or cross-account invocation and increase the risk of unauthorized execution. + +In Ansible tasks using the `amazon.aws.lambda_policy` or `lambda_policy` modules, the `source_arn` property must not be set to `/*/*`. Instead, specify the full execute-api ARN (for example `arn:aws:execute-api:::///`). + +This rule flags policies where `action` is `lambda:InvokeFunction` or `lambda:*` and `principal` is `apigateway.amazonaws.com` or `*` while `source_arn` matches `/*/*`. Avoid using a wildcard principal and prefer the explicit `apigateway.amazonaws.com` principal with a narrowed `source_arn`. + +Secure configuration example: + +```yaml +- name: Allow specific API Gateway to invoke Lambda + amazon.aws.lambda_policy: + function_name: my-function + action: lambda:InvokeFunction + principal: apigateway.amazonaws.com + source_arn: arn:aws:execute-api:us-east-1:123456789012:abcd1234/prod/POST/myresource +``` + +## Compliant Code Examples +```yaml +- name: Lambda S3 event notification + lambda_policy: + state: "{{ state | default('present') }}" + function_name: functionName + alias: Dev + statement_id: lambda-s3-myBucket-create-data-log + action: lambda:InvokeFunction + principal: s3.amazonaws.com + source_arn: arn:aws:s3:eu-central-1:123456789012:bucketname + +``` +## Non-Compliant Code Examples +```yaml +- name: Lambda S3 event notification + lambda_policy: + state: "{{ state | default('present') }}" + function_name: functionName + alias: Dev + statement_id: lambda-s3-myBucket-create-data-log + action: lambda:InvokeFunction + principal: apigateway.amazonaws.com + source_arn: arn:aws:s3:eu-central-1:123456789012/*/* + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/public_port_wide.md b/documentation/rules/ansible/aws/public_port_wide.md new file mode 100644 index 00000000..6de0b51c --- /dev/null +++ b/documentation/rules/ansible/aws/public_port_wide.md @@ -0,0 +1,88 @@ +--- +title: "Public port with wide port range" +group_id: "Ansible / AWS" +meta: + name: "aws/public_port_wide" + id: "71ea648a-d31a-4b5a-a589-5674243f1c33" + display_name: "Public port with wide port range" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `71ea648a-d31a-4b5a-a589-5674243f1c33` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html) + +### Description + +Security groups must not allow a wide port range to the entire internet. Exposing multiple ports publicly increases attack surface and enables broad port scanning, automated exploitation, and easier lateral movement. + +For Ansible `amazon.aws.ec2_group` or `ec2_group` resources, check `rules[].from_port` and `rules[].to_port` and ensure rules where `to_port - from_port > 0` are not paired with `cidr_ip` set to `0.0.0.0/0` or `cidr_ipv6` set to `::/0`. Rules that require external access should restrict CIDR ranges to trusted networks or use specific single-port entries. Any rule defining a port range with an entire-network CIDR is flagged. + +Secure example restricting access to a single port and a specific CIDR: + +```yaml +my_sg: + name: my-security-group + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 203.0.113.5/32 + - proto: tcp + from_port: 443 + to_port: 443 + cidr_ip: 198.51.100.0/24 +``` + +## Compliant Code Examples +```yaml +- name: example ec2 group v2 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 0.0.0.0/0 + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 10.0.0.0/8 + +``` +## Non-Compliant Code Examples +```yaml +- name: example ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + from_port: 80 + to_port: 82 + cidr_ip: 0.0.0.0/0 + - proto: tcp + from_port: 2 + to_port: 22 + cidr_ipv6: ::/0 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/rds_associated_with_public_subnet.md b/documentation/rules/ansible/aws/rds_associated_with_public_subnet.md new file mode 100644 index 00000000..4fc335a7 --- /dev/null +++ b/documentation/rules/ansible/aws/rds_associated_with_public_subnet.md @@ -0,0 +1,124 @@ +--- +title: "RDS instance associated with a public subnet" +group_id: "Ansible / AWS" +meta: + name: "aws/rds_associated_with_public_subnet" + id: "16732649-4ff6-4cd2-8746-e72c13fae4b8" + display_name: "RDS instance associated with a public subnet" + cloud_provider: "AWS" + platform: "Ansible" + severity: "CRITICAL" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `16732649-4ff6-4cd2-8746-e72c13fae4b8` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-db_subnet_group_name) + +### Description + +RDS instances must not be placed in public subnets because an internet-routable subnet exposes the database endpoint to the internet, increasing the risk of unauthorized access and data exfiltration. This rule inspects Ansible tasks that create RDS instances (resource types `amazon.aws.rds_instance` or `rds_instance`) and requires the subnet group property (`db_subnet_group_name` or `subnet_group`) to reference a subnet group composed only of private subnets. + +It verifies the referenced subnet group tasks (`amazon.aws.rds_subnet_group` or `rds_subnet_group`) and the subnet tasks (`amazon.aws.ec2_vpc_subnet` or `ec2_vpc_subnet`). Any subnet with `cidr` equal to `0.0.0.0/0` or `ipv6_cidr` equal to `::/0` is treated as public and triggers a finding. + +Resources that are missing the subnet-group property or that include any public subnet in the subnet group are flagged. Ensure subnet groups list subnets using private CIDR ranges and that registered subnet task names match the entries in the subnet group. + +Secure example with private subnet CIDRs: + +```yaml +- name: Create private subnet + amazon.aws.ec2_vpc_subnet: + vpc_id: vpc-123 + cidr: 10.0.1.0/24 + register: private_subnet_a + +- name: Create RDS subnet group using private subnets + amazon.aws.rds_subnet_group: + name: my-db-subnet-group + subnets: + - "{{ private_subnet_a }}" + +- name: Create RDS instance in private subnet group + amazon.aws.rds_instance: + db_subnet_group_name: my-db-subnet-group + # other RDS properties... +``` + +## Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group2 + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster + db_subnet_group_name: my_subnet_group2 +- name: Add or change a subnet group2 + amazon.aws.rds_subnet_group: + state: present + name: my_subnet_group2 + description: My Fancy Ex Parrot Subnet Group + subnets: + - "{{ subnet22.subnet.id }}" + register: my_subnet_group2 +- name: Create subnet for database servers22 + amazon.aws.ec2_vpc_subnet: + state: present + vpc_id: vpc-123456 + cidr: 10.0.1.16/28 + tags: + Name: Database Subnet + register: subnet22 + +``` +## Non-Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster + db_subnet_group_name: my_subnet_group +- name: Add or change a subnet group + amazon.aws.rds_subnet_group: + state: present + name: my_subnet_group + description: My Fancy Ex Parrot Subnet Group + subnets: + - "{{ subnet1.subnet.id }}" + - "{{ subnet2.subnet.id }}" + register: my_subnet_group +- name: Create subnet for database servers + amazon.aws.ec2_vpc_subnet: + state: present + vpc_id: vpc-123456 + cidr: 0.0.0.0/0 + tags: + Name: Database Subnet + register: subnet1 +- name: Create subnet for database servers2 + amazon.aws.ec2_vpc_subnet: + state: present + vpc_id: vpc-123456 + cidr: 10.0.1.16/28 + tags: + Name: Database Subnet + register: subnet2 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/rds_db_instance_publicly_accessible.md b/documentation/rules/ansible/aws/rds_db_instance_publicly_accessible.md new file mode 100644 index 00000000..bade45b5 --- /dev/null +++ b/documentation/rules/ansible/aws/rds_db_instance_publicly_accessible.md @@ -0,0 +1,91 @@ +--- +title: "RDS DB instance is not publicly accessible" +group_id: "Ansible / AWS" +meta: + name: "aws/rds_db_instance_publicly_accessible" + id: "c09e3ca5-f08a-4717-9c87-3919c5e6d209" + display_name: "RDS DB instance is not publicly accessible" + cloud_provider: "AWS" + platform: "Ansible" + severity: "CRITICAL" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `c09e3ca5-f08a-4717-9c87-3919c5e6d209` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-auto_minor_version_upgrade) + +### Description + +RDS instances must not be configured as publicly accessible. Exposing a database to the public internet increases the risk of unauthorized access and enables brute-force or credential-stuffing attacks. + +In Ansible RDS tasks using the `amazon.aws.rds_instance` or `rds_instance` modules, ensure the `publicly_accessible` property is set to `false`. Tasks with `publicly_accessible: true` are flagged. If the property is omitted, the modules default to `false`, but explicitly setting it to `false` and placing instances in private subnets with restrictive security groups provides defense-in-depth. + +Secure example: + +```yaml +- name: Create RDS instance (private) + amazon.aws.rds_instance: + db_instance_identifier: mydb + engine: postgres + instance_class: db.t3.medium + publicly_accessible: false +``` + +## Compliant Code Examples +```yaml +- name: create RDS instance in default VPC and default subnet group02 + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: '{{ password }}' + username: '{{ username }}' + cluster_id: ansible-test-cluster + publicly_accessible: false +- name: create RDS instance in default VPC and default subnet group03 + rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: '{{ password }}' + username: '{{ username }}' + cluster_id: ansible-test-cluster + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: community - Create a DB instance using the default AWS KMS encryption key + amazon.aws.rds_instance: + id: test-encrypted-db + state: present + engine: mariadb + storage_encrypted: True + db_instance_class: db.t2.medium + username: "{{ username }}" + password: "{{ password }}" + allocated_storage: "{{ allocated_storage }}" + publicly_accessible: Yes +- name: Create RDS instance publicly accessible + amazon.aws.rds_instance: + db_instance_identifier: new-database + engine: mysql + db_instance_class: db.t3.medium + username: admin + password: "{{ password }}" + allocated_storage: 10 + publicly_accessible: true + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/rds_using_default_port.md b/documentation/rules/ansible/aws/rds_using_default_port.md new file mode 100644 index 00000000..4925ede4 --- /dev/null +++ b/documentation/rules/ansible/aws/rds_using_default_port.md @@ -0,0 +1,128 @@ +--- +title: "RDS instance uses a default port" +group_id: "Ansible / AWS" +meta: + name: "aws/rds_using_default_port" + id: "2cb674f6-32f9-40be-97f2-62c0dc38f0d5" + display_name: "RDS instance uses a default port" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `2cb674f6-32f9-40be-97f2-62c0dc38f0d5` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-port) + +### Description + +Using the database engine's default port makes instances easy for attackers to discover and target with automated scanning and exploit tooling, increasing the likelihood of brute-force, credential stuffing, or other network-based attacks. For Ansible RDS tasks using the `amazon.aws.rds_instance` or `rds_instance` modules, the `port` property must not be set to the engine default. Choose a non-default port and ensure access is restricted at the network level (security groups/ACLs). + +This rule flags module tasks where `port` equals the engine default: MySQL/MariaDB/Aurora = 3306, PostgreSQL = 5432, Oracle = 1521, and SQL Server = 1433. This check flags explicit `port` settings that match defaults. If `port` is omitted, the engine may still use its default port, so also verify engine behavior and enforce least-privilege network access. + +Secure configuration example (MySQL using a non-default port): + +```yaml +- name: Create RDS instance with non-default port + amazon.aws.rds_instance: + db_instance_identifier: my-db + engine: mysql + port: 3307 +``` + +## Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster # This cluster must exist - see rds_cluster to manage it + backup_retention_period: 7 + port: 3307 + +``` + +```yaml +- name: create minimal aurora instance in default VPC and default subnet group2 + amazon.aws.rds_instance: + engine: sqlserver-ee + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster # This cluster must exist - see rds_cluster to manage it + backup_retention_period: 7 + port: 1434 + +``` + +```yaml +- name: create minimal aurora instance in default VPC and default subnet group2 + amazon.aws.rds_instance: + engine: postgres + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster # This cluster must exist - see rds_cluster to manage it + backup_retention_period: 7 + port: 5433 + +``` +## Non-Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group2 + amazon.aws.rds_instance: + engine: postgres + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster # This cluster must exist - see rds_cluster to manage it + backup_retention_period: 7 + port: 5432 + +``` + +```yaml +- name: create minimal aurora instance in default VPC and default subnet group2 + amazon.aws.rds_instance: + engine: oracle-ee + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster # This cluster must exist - see rds_cluster to manage it + backup_retention_period: 7 + port: 1521 + +``` + +```yaml +- name: create minimal aurora instance in default VPC and default subnet group2 + amazon.aws.rds_instance: + engine: sqlserver-ee + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster # This cluster must exist - see rds_cluster to manage it + backup_retention_period: 7 + port: 1433 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/rds_with_backup_disabled.md b/documentation/rules/ansible/aws/rds_with_backup_disabled.md new file mode 100644 index 00000000..ea935c46 --- /dev/null +++ b/documentation/rules/ansible/aws/rds_with_backup_disabled.md @@ -0,0 +1,81 @@ +--- +title: "RDS instance with backup disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/rds_with_backup_disabled" + id: "e69890e6-fce5-461d-98ad-cb98318dfc96" + display_name: "RDS instance with backup disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Backup" +--- +## Metadata + +**Id:** `e69890e6-fce5-461d-98ad-cb98318dfc96` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Backup + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/rds_instance_module.html#parameter-backup_retention_period) + +### Description + +An RDS instance with automated backups disabled (`backup_retention_period` set to `0`) cannot perform point-in-time recovery and is at increased risk of permanent data loss and regulatory non‑compliance. + +For Ansible resources using `amazon.aws.rds_instance` or `rds_instance`, the `backup_retention_period` property must be defined and set to an integer greater than `0` (value is in days). Resources missing this property or with `backup_retention_period: 0` are flagged. Set it to at least `1` (commonly 7 or more) based on your recovery objectives. + +Secure configuration example for Ansible: + +```yaml +- name: Create RDS instance with automated backups + amazon.aws.rds_instance: + db_instance_identifier: mydb + engine: postgres + instance_class: db.t3.medium + allocated_storage: 20 + backup_retention_period: 7 +``` + +## Compliant Code Examples +```yaml +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: '{{ password }}' + username: '{{ username }}' + cluster_id: ansible-test-cluster # This cluster must exist - see rds_cluster to manage it + backup_retention_period: 5 +- name: create minimal aurora instance in default VPC and default subnet group2 + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: '{{ password }}' + username: '{{ username }}' + cluster_id: ansible-test-cluster # This cluster must exist - see rds_cluster to manage it + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create minimal aurora instance in default VPC and default subnet group + amazon.aws.rds_instance: + engine: aurora + db_instance_identifier: ansible-test-aurora-db-instance + instance_type: db.t2.small + password: "{{ password }}" + username: "{{ username }}" + cluster_id: ansible-test-cluster # This cluster must exist - see rds_cluster to manage it + backup_retention_period: 0 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/redis_not_compliant.md b/documentation/rules/ansible/aws/redis_not_compliant.md new file mode 100644 index 00000000..d47ccdf3 --- /dev/null +++ b/documentation/rules/ansible/aws/redis_not_compliant.md @@ -0,0 +1,68 @@ +--- +title: "Redis not compliant" +group_id: "Ansible / AWS" +meta: + name: "aws/redis_not_compliant" + id: "9f34885e-c08f-4d13-a7d1-cf190c5bd268" + display_name: "Redis not compliant" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `9f34885e-c08f-4d13-a7d1-cf190c5bd268` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/elasticache_module.html#parameter-cache_engine_version) + +### Description + +ElastiCache Redis engine versions must meet the AWS PCI DSS baseline. Running outdated Redis releases can expose known vulnerabilities and lead to non-compliance. In Ansible, tasks using the `community.aws.elasticache` or `elasticache` modules must define `cache_engine_version` and set it to a version equal to or newer than `4.0.10`. Resources missing `cache_engine_version` or specifying a lower version are flagged as non-compliant. Update to a maintained Redis release that satisfies PCI DSS requirements. + +Secure example for Ansible: + +```yaml +- name: Create ElastiCache Redis cluster + community.aws.elasticache: + name: my-redis-cluster + engine: redis + cache_engine_version: "4.0.10" + node_type: cache.t3.small + num_cache_nodes: 1 +``` + +## Compliant Code Examples +```yaml +- name: Basic example + community.aws.elasticache: + name: test-please-delete + state: present + engine: memcached + cache_engine_version: 5.1.10 + node_type: cache.m1.small + num_nodes: 1 + +``` +## Non-Compliant Code Examples +```yaml +- name: Basic example + community.aws.elasticache: + name: "test-please-delete" + state: present + engine: memcached + cache_engine_version: 1.4.14 + node_type: cache.m1.small + num_nodes: 1 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/redshift_not_encrypted.md b/documentation/rules/ansible/aws/redshift_not_encrypted.md new file mode 100644 index 00000000..39570cb5 --- /dev/null +++ b/documentation/rules/ansible/aws/redshift_not_encrypted.md @@ -0,0 +1,102 @@ +--- +title: "Redshift cluster is not encrypted" +group_id: "Ansible / AWS" +meta: + name: "aws/redshift_not_encrypted" + id: "6a647814-def5-4b85-88f5-897c19f509cd" + display_name: "Redshift cluster is not encrypted" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `6a647814-def5-4b85-88f5-897c19f509cd` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/redshift_cluster#encrypted) + +### Description + +AWS Redshift clusters must have storage encryption enabled to protect sensitive data at rest, including data on cluster disks, automated snapshots, and backups. Without encryption, data can be exposed if storage media or snapshots are compromised. For Ansible, tasks using the `redshift` or `community.aws.redshift` modules that create or modify clusters must set the `encrypted` parameter to `true`. Resources where `encrypted` is omitted or explicitly set to `false` are flagged because the modules default to unencrypted when the property is not provided. Optionally specify a customer-managed KMS key with `kms_key_id` when `encrypted: true` is required. + +Secure example: + +```yaml +- name: Create encrypted Redshift cluster + community.aws.redshift: + command: create + cluster_identifier: my-cluster + node_type: dc2.large + number_of_nodes: 2 + encrypted: true + kms_key_id: arn:aws:kms:us-east-1:123456789012:key/abcd-ef01-2345-6789 +``` + +## Compliant Code Examples +```yaml +- name: Basic cluster provisioning example + community.aws.redshift: + identifier: tf-redshift-cluster + command: create + db_name: mydb + username: foo + password: Mustbe8characters + node_type: dc1.large + cluster_type: single-node + encrypted: true +- name: Basic cluster provisioning example2 + community.aws.redshift: + identifier: tf-redshift-cluster + command: create + db_name: mydb + username: foo + password: Mustbe8characters + node_type: dc1.large + cluster_type: single-node + encrypted: yes + +``` +## Non-Compliant Code Examples +```yaml +- name: Basic cluster provisioning example + community.aws.redshift: + identifier: tf-redshift-cluster + command: create + db_name: mydb + username: foo + password: Mustbe8characters + node_type: dc1.large + cluster_type: single-node +- name: Basic cluster provisioning example2 + community.aws.redshift: + identifier: tf-redshift-cluster + command: create + db_name: mydb + username: foo + password: Mustbe8characters + node_type: dc1.large + cluster_type: single-node + encrypted: false +- name: Basic cluster provisioning example3 + community.aws.redshift: + identifier: tf-redshift-cluster + command: create + db_name: mydb + username: foo + password: Mustbe8characters + node_type: dc1.large + cluster_type: single-node + encrypted: no + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/redshift_publicly_accessible.md b/documentation/rules/ansible/aws/redshift_publicly_accessible.md new file mode 100644 index 00000000..9c1534f6 --- /dev/null +++ b/documentation/rules/ansible/aws/redshift_publicly_accessible.md @@ -0,0 +1,99 @@ +--- +title: "Redshift publicly accessible" +group_id: "Ansible / AWS" +meta: + name: "aws/redshift_publicly_accessible" + id: "5c6b727b-1382-4629-8ba9-abd1365e5610" + display_name: "Redshift publicly accessible" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `5c6b727b-1382-4629-8ba9-abd1365e5610` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/redshift_module.html) + +### Description + +Redshift clusters must not be publicly accessible. Exposing cluster endpoints to the internet increases the risk of unauthorized access, data exfiltration, and brute-force attacks. For Ansible, check tasks using the `redshift` or `community.aws.redshift` modules: the `publicly_accessible` parameter must be set to `false`. This rule flags any task where `publicly_accessible` is `true`. Explicitly set `publicly_accessible: false` in your task to ensure the cluster is not reachable from the public internet. Relying on implicit defaults may be ambiguous across versions. + +Secure configuration example: + +```yaml +- name: Create Redshift cluster (not publicly accessible) + community.aws.redshift: + cluster_identifier: my-cluster + node_type: dc2.large + number_of_nodes: 2 + publicly_accessible: false +``` + +## Compliant Code Examples +```yaml +- name: Basic cluster provisioning example01 + community.aws.redshift: + command: create + node_type: ds1.xlarge + identifier: new_cluster + username: cluster_admin + password: 1nsecur3 + publicly_accessible: no +- name: Basic cluster provisioning example02 + community.aws.redshift: + command: create + node_type: ds1.xlarge + identifier: new_cluster + username: cluster_admin + password: 1nsecur3 +- name: Basic cluster provisioning example03 + redshift: + command: create + node_type: ds1.xlarge + identifier: new_cluster + username: cluster_admin + password: 1nsecur3 + publicly_accessible: false + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Basic cluster provisioning example04 + community.aws.redshift: + command: create + node_type: ds1.xlarge + identifier: new_cluster + username: cluster_admin + password: 1nsecur3 + publicly_accessible: yes +- name: Basic cluster provisioning example05 + community.aws.redshift: + command: create + node_type: ds1.xlarge + identifier: new_cluster + username: cluster_admin + password: 1nsecur3 + publicly_accessible: True +- name: Basic cluster provisioning example06 + redshift: + command: create + node_type: ds1.xlarge + identifier: new_cluster + username: cluster_admin + password: 1nsecur3 + publicly_accessible: Yes + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/redshift_using_default_port.md b/documentation/rules/ansible/aws/redshift_using_default_port.md new file mode 100644 index 00000000..cad8bafa --- /dev/null +++ b/documentation/rules/ansible/aws/redshift_using_default_port.md @@ -0,0 +1,71 @@ +--- +title: "Redshift using default port" +group_id: "Ansible / AWS" +meta: + name: "aws/redshift_using_default_port" + id: "e01de151-a7bd-4db4-b49b-3c4775a5e881" + display_name: "Redshift using default port" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `e01de151-a7bd-4db4-b49b-3c4775a5e881` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/redshift_module.html#parameter-port) + +### Description + +Using the default Amazon Redshift port (5439) increases exposure because well-known ports are easy to discover and target with automated scanning and brute-force attempts. + +In Ansible playbooks that use the `redshift` or `community.aws.redshift` modules, the `port` property must not be set to `5439`. Tasks with `port: 5439` are flagged. Choose a non-default port and restrict access using VPC private subnets and security group rules to limit which IPs or subnets can reach the cluster. + +Secure example with a non-default port: + +```yaml +- name: Create Redshift cluster with non-default port + community.aws.redshift: + cluster_identifier: my-redshift-cluster + node_type: dc2.large + master_username: masteruser + master_user_password: secretpassword + db_name: mydb + port: 15432 +``` + +## Compliant Code Examples +```yaml +- name: Redshift2 + community.aws.redshift: + command: create + node_type: ds1.xlarge + identifier: new_cluster + username: cluster_admin + password: 1nsecur3 + port: 1150 + +``` +## Non-Compliant Code Examples +```yaml +- name: Redshift + community.aws.redshift: + command: create + node_type: ds1.xlarge + identifier: new_cluster + username: cluster_admin + password: 1nsecur3 + port: 5439 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/remote_desktop_port_open.md b/documentation/rules/ansible/aws/remote_desktop_port_open.md new file mode 100644 index 00000000..f5d14fe3 --- /dev/null +++ b/documentation/rules/ansible/aws/remote_desktop_port_open.md @@ -0,0 +1,250 @@ +--- +title: "Remote desktop port open to internet" +group_id: "Ansible / AWS" +meta: + name: "aws/remote_desktop_port_open" + id: "eda7301d-1f3e-47cf-8d4e-976debc64341" + display_name: "Remote desktop port open to internet" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `eda7301d-1f3e-47cf-8d4e-976debc64341` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html#ansible-collections-amazon-aws-ec2-group-module) + +### Description + +Security groups that allow Remote Desktop (RDP, TCP port 3389) from 0.0.0.0/0 expose Windows hosts to the public internet, increasing the likelihood of brute-force compromise, unauthorized access, and ransomware or lateral movement. + +Ansible EC2 security group resources using the `amazon.aws.ec2_group` or `ec2_group` module must not include a rule where `cidr_ip` is `"0.0.0.0/0"` that permits port 3389 (that is, a rule with `proto: tcp`, `from_port: 3389`, `to_port: 3389`). Tasks with such a rule are flagged. Restrict RDP to specific trusted CIDR ranges, require bastion hosts or VPN access, or remove the rule entirely. + +Secure example restricting RDP to a trusted network: + +```yaml +- name: Create security group with restricted RDP + amazon.aws.ec2_group: + name: my-sg + description: SG with RDP restricted + rules: + - proto: tcp + from_port: 3389 + to_port: 3389 + cidr_ip: 203.0.113.0/24 +``` + +## Compliant Code Examples +```yaml +- name: example ec2 group1 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 3380 + to_port: 3450 + cidr_ip: 0.0.0.0/1 + +- name: example ec2 group2 + amazon.aws.ec2_group: + name: example2 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: 3389 + cidr_ip: 0.0.1.0/0 + +- name: example ec2 group3 + amazon.aws.ec2_group: + name: example3 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: 3380-3450 + cidr_ip: 0.1.0.0/0 + +- name: example ec2 group4 + amazon.aws.ec2_group: + name: example4 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: + - 80 + - 3380-3450 + cidr_ip: 10.0.0.0/0 + +- name: example ec2 group5 + amazon.aws.ec2_group: + name: example5 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: + - 3389 + - 10-50 + cidr_ip: 10.0.0.0/0 + +- name: example ec2 group6 + amazon.aws.ec2_group: + name: example1 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: -1 + to_port: 25 + cidr_ip: 0.1.0.0/0 + +- name: example ec2 group7 + amazon.aws.ec2_group: + name: example1 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 15 + to_port: -1 + cidr_ip: 0.0.0.1/0 + +``` +## Non-Compliant Code Examples +```yaml +- name: example ec2 group1 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 3380 + to_port: 3450 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group2 + amazon.aws.ec2_group: + name: example2 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: 3389 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group3 + amazon.aws.ec2_group: + name: example3 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: 3380-3450 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group4 + amazon.aws.ec2_group: + name: example4 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: + - 80 + - 3380-3450 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group5 + amazon.aws.ec2_group: + name: example5 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + ports: + - 3389 + - 10-50 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group6 + amazon.aws.ec2_group: + name: example1 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: -1 + to_port: 25 + cidr_ip: 0.0.0.0/0 + +- name: example ec2 group7 + amazon.aws.ec2_group: + name: example1 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + aws_secret_key: SECRET + aws_access_key: ACCESS + rules: + - proto: tcp + from_port: 15 + to_port: -1 + cidr_ip: 0.0.0.0/0 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/root_account_has_active_access_keys.md b/documentation/rules/ansible/aws/root_account_has_active_access_keys.md new file mode 100644 index 00000000..835fba22 --- /dev/null +++ b/documentation/rules/ansible/aws/root_account_has_active_access_keys.md @@ -0,0 +1,52 @@ +--- +title: "Root account has active access keys" +group_id: "Ansible / AWS" +meta: + name: "aws/root_account_has_active_access_keys" + id: "e71d0bc7-d9e8-4e6e-ae90-0a4206db6f40" + display_name: "Root account has active access keys" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `e71d0bc7-d9e8-4e6e-ae90-0a4206db6f40` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/iam_access_key_module.html) + +### Description + +Active root access keys grant full, account-wide privileges. A leaked key could lead to immediate and complete compromise of the environment. This rule inspects Ansible tasks using the `amazon.aws.iam_access_key` or `iam_access_key` modules and flags entries where `user_name` contains "root", the `active` property is `true` (or absent, since `true` is the default), and `state` is not `absent`. + +The `active` property must not be `true` for root account entries. Resources should either omit root access keys or set `active` to `false`. Any task with an active root access key is flagged. Remove or deactivate root access keys and use IAM users or roles with least privilege for automation and service access. + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: Create root access key but inactive + amazon.aws.iam_access_key: + user_name: root + active: false + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: Create root access key + amazon.aws.iam_access_key: + user_name: root + state: present + +``` diff --git a/documentation/rules/ansible/aws/route53_record_undefined.md b/documentation/rules/ansible/aws/route53_record_undefined.md new file mode 100644 index 00000000..e1696b2b --- /dev/null +++ b/documentation/rules/ansible/aws/route53_record_undefined.md @@ -0,0 +1,86 @@ +--- +title: "Route 53 record undefined" +group_id: "Ansible / AWS" +meta: + name: "aws/route53_record_undefined" + id: "445dce51-7e53-4e50-80ef-7f94f14169e4" + display_name: "Route 53 record undefined" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `445dce51-7e53-4e50-80ef-7f94f14169e4` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/route53_module.html#parameter-value) + +### Description + +Route 53 record resources must include one or more record values so DNS entries are created and resolve correctly. Missing values can lead to service disruption, broken name resolution, or unintended traffic routing. For Ansible tasks using the `amazon.aws.route53` or `route53` modules, the `value` parameter must be present and non-null, typically as a list of one or more string values. Tasks missing the `value` parameter, with `value: null`, or with an empty list are flagged. + +Secure example Ansible task: + +```yaml +- name: Create A record for app.example.com + amazon.aws.route53: + zone: example.com + record: app + type: A + ttl: 300 + value: + - "203.0.113.10" +``` + +## Compliant Code Examples +```yaml +- name: Use a routing policy to distribute traffic + amazon.aws.route53: + state: present + zone: foo.com + record: www.foo.com + type: CNAME + value: host1.foo.com + ttl: 30 + identifier: host1@www + weight: 100 + health_check: d994b780-3150-49fd-9205-356abdd42e75 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Use a routing policy to distribute traffic02 + amazon.aws.route53: + state: present + zone: foo.com + record: www.foo.com + type: CNAME + value: + ttl: 30 + identifier: "host1@www" + weight: 100 + health_check: "d994b780-3150-49fd-9205-356abdd42e75" +- name: Use a routing policy to distribute traffic03 + amazon.aws.route53: + state: present + zone: foo.com + record: www.foo.com + type: CNAME + ttl: 30 + identifier: "host1@www" + weight: 100 + health_check: "d994b780-3150-49fd-9205-356abdd42e75" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_access_to_any_principal.md b/documentation/rules/ansible/aws/s3_bucket_access_to_any_principal.md new file mode 100644 index 00000000..32120ac1 --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_access_to_any_principal.md @@ -0,0 +1,92 @@ +--- +title: "S3 bucket access to any principal" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_access_to_any_principal" + id: "3ab1f27d-52cc-4943-af1d-43c1939e739a" + display_name: "S3 bucket access to any principal" + cloud_provider: "AWS" + platform: "Ansible" + severity: "CRITICAL" + category: "Access Control" +--- +## Metadata + +**Id:** `3ab1f27d-52cc-4943-af1d-43c1939e739a` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_bucket_module.html#ansible-collections-amazon-aws-s3-bucket-module) + +### Description + +S3 bucket policies must not grant the wildcard principal (`"*"`) `Allow` access. This effectively makes the bucket accessible to any AWS account or anonymous user and can expose sensitive objects or lead to data leakage. This rule checks Ansible tasks using the `amazon.aws.s3_bucket` or `s3_bucket` modules and inspects the `policy` document to ensure no `Statement` has `Effect: "Allow"` with `Principal: "*"`. + +Resources with a policy Statement where `Principal` is `*` and the effect is `Allow` are flagged. Instead, specify explicit principals (account IDs or IAM ARNs) or restrict access using conditions (for example `aws:SourceAccount` or `aws:PrincipalOrgID`) or S3 Block Public Access. + +Secure example with an explicit principal: + +```yaml +- name: Create S3 bucket with restricted policy + amazon.aws.s3_bucket: + name: my-bucket + policy: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::my-bucket/*" + } + ] + } +``` + +## Compliant Code Examples +```yaml +- name: Create a simple s3 bucket with a policy + amazon.aws.s3_bucket: + name: mys3bucket + policy: + Version: '2012-10-17' + Id: sqspolicy + Statement: + - Sid: First + Effect: Deny + Principal: '*' + Action: '*' + Resource: ${aws_sqs_queue.q.arn} + Condition: + ArnEquals: + aws:SourceArn: ${aws_sns_topic.example.arn} + +``` +## Non-Compliant Code Examples +```yaml +- name: Create a simple s3 bucket with a policy + amazon.aws.s3_bucket: + name: mys3bucket + policy: + Version: "2012-10-17" + Id: "sqspolicy" + Statement: + - Sid: First + Effect: Allow + Principal: "*" + Action: "*" + Resource: ${aws_sqs_queue.q.arn} + Condition: + ArnEquals: + aws:SourceArn: ${aws_sns_topic.example.arn} + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_acl_allows_read_to_all_users.md b/documentation/rules/ansible/aws/s3_bucket_acl_allows_read_to_all_users.md new file mode 100644 index 00000000..d36af537 --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_acl_allows_read_to_all_users.md @@ -0,0 +1,70 @@ +--- +title: "S3 bucket ACL allows read access to all users" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_acl_allows_read_to_all_users" + id: "a1ef9d2e-4163-40cb-bd92-04f0d602a15d" + display_name: "S3 bucket ACL allows read access to all users" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `a1ef9d2e-4163-40cb-bd92-04f0d602a15d` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_object_module.html#parameter-permission) + +### Description + +S3 buckets must not be configured to allow read access to all users. Public-read ACLs make objects and metadata accessible to anyone on the internet, risking data exposure and compliance violations. + +For Ansible tasks using the `amazon.aws.s3_object` or `s3_object` modules, the `permission` parameter must not be set to values that start with `public-read` (for example `public-read` or `public-read-write`). Tasks with `permission` omitted or set to restrictive values such as `private`, or that rely on explicit bucket policies to grant scoped access, are acceptable. Resources with `permission` starting with `public-read` are flagged. Secure configuration example: + +```yaml +- name: Create S3 bucket with private ACL + amazon.aws.s3_object: + bucket: my-bucket + permission: private + mode: create +``` + +## Compliant Code Examples +```yaml +- name: Create an empty bucket + amazon.aws.s3_object: + bucket: mybucket + mode: create + permission: private +- name: Create an empty bucket2 + amazon.aws.s3_object: + bucket: mybucket + mode: create + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create an empty bucket + amazon.aws.s3_object: + bucket: mybucket + mode: create + permission: public-read +- name: Create an empty bucket2 + amazon.aws.s3_object: + bucket: mybucket + mode: create + permission: public-read-write + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user.md b/documentation/rules/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user.md new file mode 100644 index 00000000..86290f3a --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_acl_allows_read_to_any_authenticated_user.md @@ -0,0 +1,68 @@ +--- +title: "S3 bucket ACL allows read access to any authenticated user" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_acl_allows_read_to_any_authenticated_user" + id: "75480b31-f349-4b9a-861f-bce19588e674" + display_name: "S3 bucket ACL allows read access to any authenticated user" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `75480b31-f349-4b9a-861f-bce19588e674` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_object_module.html#parameter-permission) + +### Description + +S3 objects or buckets configured with the `authenticated-read` ACL allow any AWS authenticated user to read your data. This exposes content beyond your account boundary and increases the risk of unauthorized data access or leakage. + +In Ansible, tasks using the `amazon.aws.s3_object` or `s3_object` modules must not set the `permission` parameter to `authenticated-read`. Prefer `permission: private` or enforce access via explicit bucket policies or IAM roles. This rule flags Ansible tasks where `permission` is exactly `authenticated-read`. + +Secure example: + +```yaml +- name: Upload file to S3 with private ACL + amazon.aws.s3_object: + bucket: my-bucket + object: path/file.txt + src: /local/file.txt + permission: private +``` + +## Compliant Code Examples +```yaml +- name: Create an empty bucket + amazon.aws.s3_object: + bucket: mybucket + mode: create +- name: Create an empty bucket2 + amazon.aws.s3_object: + bucket: mybucket + mode: create + permission: private + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create an empty bucket2 + amazon.aws.s3_object: + bucket: mybucket + mode: create + permission: authenticated-read + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_allows_delete_action_from_all_principals.md b/documentation/rules/ansible/aws/s3_bucket_allows_delete_action_from_all_principals.md new file mode 100644 index 00000000..030537bc --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_allows_delete_action_from_all_principals.md @@ -0,0 +1,87 @@ +--- +title: "S3 bucket allows delete action from all principals" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_allows_delete_action_from_all_principals" + id: "6fa44721-ef21-41c6-8665-330d59461163" + display_name: "S3 bucket allows delete action from all principals" + cloud_provider: "AWS" + platform: "Ansible" + severity: "CRITICAL" + category: "Access Control" +--- +## Metadata + +**Id:** `6fa44721-ef21-41c6-8665-330d59461163` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_bucket_module.html) + +### Description + +S3 bucket policies must not grant delete permissions to all principals (`*`). Public delete rights can enable unauthorized data tampering or complete data loss by allowing anyone on the internet to remove objects or buckets. + +For Ansible S3 resources (`amazon.aws.s3_bucket` or `s3_bucket`), ensure the `policy` document contains no Statement with `Effect: "Allow"`, `Principal: "*"`, and an `Action` that includes delete operations (for example `s3:DeleteObject` or `s3:DeleteBucket`). + +This rule flags bucket resources whose `policy` includes an Allow statement granting delete-related actions to the wildcard principal. Instead, restrict delete permissions to specific AWS account IDs, IAM roles/ARNs, or remove delete actions for public principals. + +Secure example restricting delete to a specific AWS account: + +```yaml +- name: Create S3 bucket with restricted delete permissions + amazon.aws.s3_bucket: + name: my-bucket + policy: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowSpecificAccountDelete", + "Effect": "Allow", + "Principal": {"AWS": "arn:aws:iam::123456789012:root"}, + "Action": ["s3:DeleteObject", "s3:DeleteBucket"], + "Resource": ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*"] + } + ] + } +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: Bucket + amazon.aws.s3_bucket: + name: mys3bucket + state: present + policy: + Version: '2020-10-07' + Statement: + - Effect: Deny + Action: DeleteObject + Principal: '*' + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: Bucket + amazon.aws.s3_bucket: + name: mys3bucket + state: present + policy: + Version: "2020-10-07" + Statement: + - Effect: Allow + Action: DeleteObject + Principal: "*" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_allows_get_action_from_all_principals.md b/documentation/rules/ansible/aws/s3_bucket_allows_get_action_from_all_principals.md new file mode 100644 index 00000000..777554af --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_allows_get_action_from_all_principals.md @@ -0,0 +1,82 @@ +--- +title: "S3 bucket allows GET action from all principals" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_allows_get_action_from_all_principals" + id: "53bce6a8-5492-4b1b-81cf-664385f0c4bf" + display_name: "S3 bucket allows GET action from all principals" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `53bce6a8-5492-4b1b-81cf-664385f0c4bf` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_bucket_module.html) + +### Description + +S3 bucket policies must not grant Get actions to all principals ("*"). Allowing public read access exposes bucket objects to unauthorized disclosure and accidental data leaks. For Ansible S3 bucket resources (modules `amazon.aws.s3_bucket` and `s3_bucket`), inspect the `policy` property for any Statement with `Effect: "Allow"`, `Principal: "*"`, and an `Action` that includes Get operations (for example, `s3:GetObject` or any action name containing "Get"). Such statements are flagged. + +Restrict access by specifying explicit principals (AWS account IDs, roles, or ARNs), narrowing the allowed actions, or adding conditions (IP/VPC, MFA, or other constraints). If public access is required, use presigned URLs or a controlled distribution layer rather than a public bucket policy. + +Secure example with an explicit principal: + +```yaml +- name: Create S3 bucket with restricted policy + amazon.aws.s3_bucket: + name: my-bucket + policy: + Version: "2012-10-17" + Statement: + - Sid: "AllowSpecificAccountGet" + Effect: "Allow" + Principal: + AWS: "arn:aws:iam::123456789012:role/ReadOnlyRole" + Action: + - "s3:GetObject" + Resource: "arn:aws:s3:::my-bucket/*" +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: Bucket + amazon.aws.s3_bucket: + name: mys3bucket + state: present + policy: + Version: '2020-10-07' + Statement: + - Effect: Allow + Action: GetObject + Principal: NotAll + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: Bucket + amazon.aws.s3_bucket: + name: mys3bucket + state: present + policy: + Version: "2020-10-07" + Statement: + - Effect: Allow + Action: GetObject + Principal: "*" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_allows_list_action_from_all_principals.md b/documentation/rules/ansible/aws/s3_bucket_allows_list_action_from_all_principals.md new file mode 100644 index 00000000..3cb3ad5c --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_allows_list_action_from_all_principals.md @@ -0,0 +1,83 @@ +--- +title: "S3 bucket allows list action from all principals" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_allows_list_action_from_all_principals" + id: "d395a950-12ce-4314-a742-ac5a785ab44e" + display_name: "S3 bucket allows list action from all principals" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `d395a950-12ce-4314-a742-ac5a785ab44e` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_bucket_module.html) + +### Description + +S3 bucket policies must not allow list actions to all principals ('*'). Exposing bucket listings to everyone reveals object inventories and metadata, enabling data discovery and potential unauthorized access or exfiltration. + +For Ansible resources using `amazon.aws.s3_bucket` or `s3_bucket`, inspect the bucket `policy` document. Ensure there are no policy statements with `Effect` set to `Allow`, `Principal` set to `"*"`, and `Action` that includes list operations such as `s3:ListBucket`. + +Resources with a statement that combines `Effect: Allow`, `Principal: "*"`, and a list action are flagged. Instead, restrict access to explicit principals (account IDs, role or service ARNs), apply IAM policies, or use S3 Public Access Block settings to prevent public listing. + +Secure example policy that grants List only to a specific principal: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowListToSpecificPrincipal", + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::123456789012:role/AllowedRole" }, + "Action": "s3:ListBucket", + "Resource": "arn:aws:s3:::my-bucket" + } + ] +} +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: Bucket + amazon.aws.s3_bucket: + name: mys3bucket + state: present + policy: + Version: '2020-10-07' + Statement: + - Effect: Allow + Action: ListObject + Principal: NotAll + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: Bucket + amazon.aws.s3_bucket: + name: mys3bucket + state: present + policy: + Version: "2020-10-07" + Statement: + - Effect: Allow + Action: ListObject + Principal: "*" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_allows_put_action_from_all_principals.md b/documentation/rules/ansible/aws/s3_bucket_allows_put_action_from_all_principals.md new file mode 100644 index 00000000..07e2d74e --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_allows_put_action_from_all_principals.md @@ -0,0 +1,87 @@ +--- +title: "S3 bucket allows put action from all principals" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_allows_put_action_from_all_principals" + id: "a0f1bfe0-741e-473f-b3b2-13e66f856fab" + display_name: "S3 bucket allows put action from all principals" + cloud_provider: "AWS" + platform: "Ansible" + severity: "CRITICAL" + category: "Access Control" +--- +## Metadata + +**Id:** `a0f1bfe0-741e-473f-b3b2-13e66f856fab` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_bucket_module.html) + +### Description + +S3 bucket policy statements that allow put actions to all principals (`Principal='*'` and `Effect='Allow'`) let anyone upload or overwrite objects, risking data tampering, malware injection, and unauthorized exposure of sensitive data. + +This rule inspects Ansible `amazon.aws.s3_bucket` and `s3_bucket` resources' `policy` statements and flags any statement where `Effect` is `"Allow"`, `Principal` is `"*"`, and `Action` includes Put operations (for example `s3:PutObject` or any action name containing "Put"). + +Remediate by restricting Put permissions to explicit principals, such as AWS account ARNs, IAM role ARNs, or service principals. Apply least-privilege permissions and conditions, or remove public Put permissions entirely. + +Secure example with a restricted principal: + +```yaml +- name: Create S3 bucket with restricted Put permissions + amazon.aws.s3_bucket: + name: my-bucket + policy: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowPutForSpecificAccount", + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, + "Action": [ "s3:PutObject" ], + "Resource": "arn:aws:s3:::my-bucket/*" + } + ] + } +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: Bucket + amazon.aws.s3_bucket: + name: mys3bucket + state: present + policy: + Version: '2020-10-07' + Statement: + - Effect: Allow + Action: PutObject + Principal: NotAll + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: Bucket + amazon.aws.s3_bucket: + name: mys3bucket + state: present + policy: + Version: "2020-10-07" + Statement: + - Effect: Allow + Action: PutObject + Principal: "*" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_logging_disabled.md b/documentation/rules/ansible/aws/s3_bucket_logging_disabled.md new file mode 100644 index 00000000..9ca3d366 --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_logging_disabled.md @@ -0,0 +1,62 @@ +--- +title: "S3 bucket logging disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_logging_disabled" + id: "c3b9f7b0-f5a0-49ec-9cbc-f1e346b7274d" + display_name: "S3 bucket logging disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `c3b9f7b0-f5a0-49ec-9cbc-f1e346b7274d` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_bucket_module.html#parameter-debug_botocore_endpoint_logs) + +### Description + +Enabling botocore endpoint debug logs for S3 operations captures detailed client request and response traces useful for detecting suspicious activity and supporting incident investigation. For Ansible tasks using the `amazon.aws.s3_bucket` or `s3_bucket` modules, the `debug_botocore_endpoint_logs` property must be defined and set to `true`. Tasks where this property is missing or set to `false` are flagged. + +Debug logs can contain sensitive request data. Ensure they are collected, transmitted, and stored securely with appropriate access controls and retention policies. + +Secure configuration example: + +```yaml +- name: Create S3 bucket with botocore endpoint debug logs enabled + amazon.aws.s3_bucket: + name: my-bucket + state: present + debug_botocore_endpoint_logs: true +``` + +## Compliant Code Examples +```yaml +- amazon.aws.s3_bucket: + name: mys3bucket + state: present + debug_botocore_endpoint_logs: true + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: "Create S3 bucket" + amazon.aws.s3_bucket: + name: mys3bucket + state: present + debug_botocore_endpoint_logs: false + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_with_all_permissions.md b/documentation/rules/ansible/aws/s3_bucket_with_all_permissions.md new file mode 100644 index 00000000..a2c1cc5d --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_with_all_permissions.md @@ -0,0 +1,87 @@ +--- +title: "S3 bucket with all permissions" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_with_all_permissions" + id: "6a6d7e56-c913-4549-b5c5-5221e624d2ec" + display_name: "S3 bucket with all permissions" + cloud_provider: "AWS" + platform: "Ansible" + severity: "CRITICAL" + category: "Access Control" +--- +## Metadata + +**Id:** `6a6d7e56-c913-4549-b5c5-5221e624d2ec` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_bucket_module.html#parameter-policy) + +### Description + +S3 bucket policies must not grant all actions to all principals. A statement that sets `Effect`=`Allow` with both `Action`=`*` and `Principal`=`*` effectively makes the bucket publicly accessible and can enable data exfiltration or unauthorized modification/deletion. + +For Ansible resources using the `amazon.aws.s3_bucket` or `s3_bucket` modules, inspect the resource `policy` document's `Statement` entries. Any statement where `Effect` is `Allow` and both `Action` and `Principal` contain the wildcard `*` (including arrays that include `*`) is flagged. + +Restrict `Principal` to explicit ARNs, account IDs, or service principals and scope `Action` to the minimum required permissions following least privilege. + +Secure example policy statement: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, + "Action": [ "s3:GetObject" ], + "Resource": "arn:aws:s3:::example-bucket/*" + } + ] +} +``` + +## Compliant Code Examples +```yaml +- name: Create s3 bucket + amazon.aws.s3_bucket: + name: mys3bucket + policy: + Id: id113 + Version: '2012-10-17' + Statement: + - Action: s3:put + Effect: Allow + Resource: arn:aws:s3:::S3B_181355/* + Principal: '*' + requester_pays: yes + versioning: yes + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create s3 bucket + amazon.aws.s3_bucket: + name: mys3bucket + policy: + Id: "id113" + Version: "2012-10-17" + Statement: + - Action: "s3:*" + Effect: "Allow" + Resource: "arn:aws:s3:::S3B_181355/*" + Principal: "*" + requester_pays: yes + versioning: yes + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_with_public_access.md b/documentation/rules/ansible/aws/s3_bucket_with_public_access.md new file mode 100644 index 00000000..d179ee45 --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_with_public_access.md @@ -0,0 +1,72 @@ +--- +title: "S3 bucket with public access" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_with_public_access" + id: "c3e073c1-f65e-4d18-bd67-4a8f20ad1ab9" + display_name: "S3 bucket with public access" + cloud_provider: "AWS" + platform: "Ansible" + severity: "CRITICAL" + category: "Access Control" +--- +## Metadata + +**Id:** `c3e073c1-f65e-4d18-bd67-4a8f20ad1ab9` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_object_module.html#parameter-permission) + +### Description + +Ansible tasks that set S3 `permission` to `public` create publicly accessible buckets or objects, risking data exposure and regulatory non‑compliance. For the `amazon.aws.s3_object` and `s3_object` modules, the `permission` property must be defined and must not contain the value `public`. Use `private` or other restricted values (for example, `authenticated-read`) as appropriate. + +This rule flags tasks where `permission` contains `public`. Tasks missing an explicit `permission` should be reviewed and set to a non‑public value. + +Secure example: + +```yaml +- name: Create private S3 bucket + amazon.aws.s3_object: + bucket: my-bucket + permission: private + mode: create +``` + +## Compliant Code Examples +```yaml +- name: Create an empty bucket + amazon.aws.s3_object: + bucket: mybucket + mode: create + permission: private +- name: Create an empty bucket 02 + amazon.aws.s3_object: + bucket: mybucket + mode: create + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create an empty bucket + amazon.aws.s3_object: + bucket: mybucket + mode: create + permission: public-read +- name: Create an empty bucket 01 + amazon.aws.s3_object: + bucket: mybucket 01 + mode: create + permission: public-read-write + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_with_unsecured_cors_rule.md b/documentation/rules/ansible/aws/s3_bucket_with_unsecured_cors_rule.md new file mode 100644 index 00000000..be096682 --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_with_unsecured_cors_rule.md @@ -0,0 +1,139 @@ +--- +title: "S3 bucket with unsecured CORS rule" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_with_unsecured_cors_rule" + id: "3505094c-f77c-4ba0-95da-f83db712f86c" + display_name: "S3 bucket with unsecured CORS rule" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `3505094c-f77c-4ba0-95da-f83db712f86c` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/s3_cors_module.html#parameter-rules) + +### Description + +S3 CORS rules must restrict allowed origins, methods, and headers to prevent unintended cross-origin access and data exfiltration. Overly permissive CORS (wildcard origins, all methods, or all headers) can allow arbitrary web pages to interact with or read bucket resources. + +For Ansible resources `community.aws.s3_cors` and `s3_cors`, inspect each `rules` entry. `allowed_origins` should specify trusted origins (avoid `"*"` or unnecessarily broad lists). `allowed_methods` must not be `["*"]` and should include only the HTTP verbs required by your application. `allowed_headers` must not be `["*"]` and should be limited to the headers actually needed. + +Rules with wildcard `allowed_methods` or `allowed_headers`, or with wildcard or overly broad origins are flagged. Prefer a single explicit origin or a narrowly-scoped set and the minimal set of methods and headers. + +Secure example: + +```yaml +- name: Configure S3 CORS + community.aws.s3_cors: + name: my-bucket + rules: + - allowed_origins: + - https://app.example.com + allowed_methods: + - GET + - HEAD + allowed_headers: + - Authorization + - Content-Type +``` + +## Compliant Code Examples +```yaml +- name: Create s3 bucket + community.aws.s3_cors: + name: mys3bucket3 + state: present + rules: + - allowed_origins: + - http://www.example.com/ + allowed_methods: + - GET + - POST + allowed_headers: + - Authorization + expose_headers: + - x-amz-server-side-encryption + - x-amz-request-id + max_age_seconds: 30000 + +``` + +```yaml +- name: Create s3 bucket1 + community.aws.s3_cors: + name: mys3bucket4 + state: present + rules: + - allowed_origins: + - http://www.example.com/ + allowed_methods: + - GET + - POST + allowed_headers: + - Authorization + expose_headers: + - x-amz-server-side-encryption + - x-amz-request-id + max_age_seconds: 30000 + +``` +## Non-Compliant Code Examples +```yaml +- name: Create s3 bucket4 + community.aws.s3_cors: + name: mys3bucket2 + state: present + rules: + - allowed_origins: + - http://www.example.com/ + allowed_methods: + - GET + - POST + - PUT + - DELETE + - HEAD + allowed_headers: + - Authorization + expose_headers: + - x-amz-server-side-encryption + - x-amz-request-id + max_age_seconds: 30000 + +``` + +```yaml +- name: Create s3 bucket2 + community.aws.s3_cors: + name: mys3bucket + state: present + rules: + - allowed_origins: + - http://www.example.com/ + allowed_methods: + - GET + - POST + - PUT + - DELETE + - HEAD + allowed_headers: + - Authorization + expose_headers: + - x-amz-server-side-encryption + - x-amz-request-id + max_age_seconds: 30000 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_without_server-side_encryption.md b/documentation/rules/ansible/aws/s3_bucket_without_server-side_encryption.md new file mode 100644 index 00000000..602e896a --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_without_server-side_encryption.md @@ -0,0 +1,64 @@ +--- +title: "S3 bucket without server-side encryption" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_without_server-side_encryption" + id: "594f54e7-f744-45ab-93e4-c6dbaf6cd571" + display_name: "S3 bucket without server-side encryption" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `594f54e7-f744-45ab-93e4-c6dbaf6cd571` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_bucket_module.html) + +### Description + +S3 buckets should have server-side encryption (SSE) enabled to protect data at rest and prevent exposure of sensitive objects if a bucket is misconfigured or storage media is accessed. + +For Ansible tasks using the amazon.aws.s3_bucket or s3_bucket modules, the `encryption` property must not be set to `'none'` and should be configured to a valid SSE algorithm such as `'AES256'` or `'aws:kms'`. Resources that omit the `encryption` property or explicitly set `encryption: 'none'` are flagged. + +When using `'aws:kms'`, also specify and manage a KMS key (for example via `kms_key_id`) to retain control over encryption keys and meet organizational access requirements. + +Secure example using KMS-managed keys: + +```yaml +- name: Create S3 bucket with KMS encryption + amazon.aws.s3_bucket: + name: my-secure-bucket + encryption: aws:kms + kms_key_id: arn:aws:kms:us-east-1:123456789012:key/abcd-ef01-2345-6789 +``` + +## Compliant Code Examples +```yaml +- name: Create a simple s3 bucket v2 + amazon.aws.s3_bucket: + name: mys3bucket + state: present + encryption: aws:kms + +``` +## Non-Compliant Code Examples +```yaml +- name: Create a simple s3 bucket + amazon.aws.s3_bucket: + name: mys3bucket + state: present + encryption: "none" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/s3_bucket_without_versioning.md b/documentation/rules/ansible/aws/s3_bucket_without_versioning.md new file mode 100644 index 00000000..34242dc8 --- /dev/null +++ b/documentation/rules/ansible/aws/s3_bucket_without_versioning.md @@ -0,0 +1,76 @@ +--- +title: "S3 bucket without versioning" +group_id: "Ansible / AWS" +meta: + name: "aws/s3_bucket_without_versioning" + id: "9232306a-f839-40aa-b3ef-b352001da9a5" + display_name: "S3 bucket without versioning" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Backup" +--- +## Metadata + +**Id:** `9232306a-f839-40aa-b3ef-b352001da9a5` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Backup + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/s3_bucket_module.html#parameter-versioning) + +### Description + +S3 buckets must have versioning enabled to protect objects from accidental or malicious deletion and retain prior versions for recovery, forensics, and compliance. For Ansible tasks using the `amazon.aws.s3_bucket` or `s3_bucket` modules, the `versioning` property must be defined and set to `true`. When omitted, the module defaults to versioning disabled. This rule flags tasks where the `versioning` key is missing or explicitly set to `false`. + +Secure configuration example: + +```yaml +- name: Ensure S3 bucket with versioning enabled + amazon.aws.s3_bucket: + name: my-bucket + versioning: true +``` + +## Compliant Code Examples +```yaml +- name: foo + amazon.aws.s3_bucket: + name: mys3bucket + policy: "{{ lookup('file','policy.json') }}" + requester_pays: yes + versioning: yes + tags: + example: tag1 + another: tag2 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: foo + amazon.aws.s3_bucket: + name: mys3bucket + policy: "{{ lookup('file','policy.json') }}" + requester_pays: yes + tags: + example: tag1 + another: tag2 +- name: foo2 + amazon.aws.s3_bucket: + name: mys3bucket + policy: "{{ lookup('file','policy.json') }}" + requester_pays: yes + versioning: no + tags: + example: tag1 + another: tag2 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/secure_ciphers_disabled.md b/documentation/rules/ansible/aws/secure_ciphers_disabled.md new file mode 100644 index 00000000..ae6b8479 --- /dev/null +++ b/documentation/rules/ansible/aws/secure_ciphers_disabled.md @@ -0,0 +1,81 @@ +--- +title: "Secure ciphers disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/secure_ciphers_disabled" + id: "218413a0-c716-4b94-9e08-0bb70d854709" + display_name: "Secure ciphers disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `218413a0-c716-4b94-9e08-0bb70d854709` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/cloudfront_distribution_module.html) + +### Description + +CloudFront distributions that do not enforce a modern minimum TLS protocol version can allow legacy TLS/SSL versions, increasing the risk of downgrade attacks and interception of data in transit. + +For Ansible CloudFront resources (modules `community.aws.cloudfront_distribution` and `cloudfront_distribution`), the `viewer_certificate.minimum_protocol_version` property must be defined and set to `TLSv1.1` or `TLSv1.2` (preferably `TLSv1.2`) when using a custom certificate (`viewer_certificate.cloudfront_default_certificate` set to `false`). Resources that omit `minimum_protocol_version` or specify any other value are flagged. + +Secure configuration example for Ansible: + +```yaml +- name: Create CloudFront distribution with secure TLS + community.aws.cloudfront_distribution: + name: my-distribution + viewer_certificate: + cloudfront_default_certificate: false + acm_certificate_arn: arn:aws:acm:region:acct:certificate/your-cert-id + minimum_protocol_version: TLSv1.2 +``` + +## Compliant Code Examples +```yaml +- name: example + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: my test origin-000111 + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + viewer_certificate: + cloudfront_default_certificate: true + +``` +## Non-Compliant Code Examples +```yaml +- name: example + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: 'my test origin-000111' + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + viewer_certificate: + cloudfront_default_certificate: false + minimum_protocol_version: 'SSLv3' + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/security_group_ingress_not_restricted.md b/documentation/rules/ansible/aws/security_group_ingress_not_restricted.md new file mode 100644 index 00000000..9b682f5b --- /dev/null +++ b/documentation/rules/ansible/aws/security_group_ingress_not_restricted.md @@ -0,0 +1,124 @@ +--- +title: "Security group ingress not restricted" +group_id: "Ansible / AWS" +meta: + name: "aws/security_group_ingress_not_restricted" + id: "ea6bc7a6-d696-4dcf-a788-17fa03c17c81" + display_name: "Security group ingress not restricted" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `ea6bc7a6-d696-4dcf-a788-17fa03c17c81` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html) + +### Description + +Security groups must not allow unrestricted ingress from the public internet to all protocols and ports. Such rules expose instances to network scanning, exploitation, and unauthorized access. + +In Ansible `amazon.aws.ec2_group` and `ec2_group` resources, each `rules` entry must not combine `from_port: 0` and `to_port: 0` with a non-explicit `proto` and an entire-network CIDR such as `cidr_ip: 0.0.0.0/0` or `cidr_ipv6: ::/0`. + +The `proto` property must be an explicit protocol such as `tcp`, `udp`, `icmp`, `icmpv6`, or numeric values `1`, `6`, `17`, `58`. Rules where `proto` is missing or set to a catch-all (`-1`/`all`) with ports `0-0` and an entire-network CIDR are flagged. + +To fix this, restrict the CIDR to trusted IP ranges or specify the exact protocol and port range required for the service. + +Secure configuration example: + +```yaml +- name: secure security group + amazon.aws.ec2_group: + name: my_sg + description: "Allow SSH from admin network and HTTPS from anywhere" + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 203.0.113.0/24 + - proto: tcp + from_port: 443 + to_port: 443 + cidr_ip: 0.0.0.0/0 +``` + +## Compliant Code Examples +```yaml +- name: example ec2 group v3 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 10.0.0.0/8 +- name: example ec2 group v4 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ipv6: 2001:DB8:8086:6502::/32 + +``` +## Non-Compliant Code Examples +```yaml +- name: example ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: -1 + from_port: 0 + to_port: 0 + cidr_ip: 0.0.0.0/0 + - proto: all + from_port: 0 + to_port: 0 + cidr_ip: 0.0.0.0/0 + - proto: 12121 + from_port: 0 + to_port: 0 + cidr_ip: 0.0.0.0/0 +- name: example ec2 group v2 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: -1 + from_port: 0 + to_port: 0 + cidr_ipv6: ::/0 + - proto: all + from_port: 0 + to_port: 0 + cidr_ipv6: ::/0 + - proto: 121212 + from_port: 0 + to_port: 0 + cidr_ipv6: ::/0 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/security_group_with_unrestricted_access_to_ssh.md b/documentation/rules/ansible/aws/security_group_with_unrestricted_access_to_ssh.md new file mode 100644 index 00000000..e4b38c8b --- /dev/null +++ b/documentation/rules/ansible/aws/security_group_with_unrestricted_access_to_ssh.md @@ -0,0 +1,89 @@ +--- +title: "Security group with unrestricted access to SSH" +group_id: "Ansible / AWS" +meta: + name: "aws/security_group_with_unrestricted_access_to_ssh" + id: "57ced4b9-6ba4-487b-8843-b65562b90c77" + display_name: "Security group with unrestricted access to SSH" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `57ced4b9-6ba4-487b-8843-b65562b90c77` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html) + +### Description + +SSH (TCP port 22) must not be exposed to public CIDR ranges because it enables unauthorized remote access and increases the risk of brute-force or credential-stuffing attacks and lateral movement. + +This check inspects Ansible tasks using `amazon.aws.ec2_group` or `ec2_group` and flags entries in the `rules` list where `from_port`/`to_port` cover port 22 (or are both `-1` indicating all ports) and `cidr_ip` or `cidr_ipv6` specify public CIDRs such as `0.0.0.0/0` or `::/0`. Limit `cidr_ip`/`cidr_ipv6` to specific trusted IP ranges, or remove SSH from the security group and enforce access through a bastion host or VPN. Any rule that leaves SSH open to public CIDRs is flagged. + +Secure example restricting SSH to a single trusted address: + +```yaml +- name: my-secure-sg + amazon.aws.ec2_group: + name: my-secure-sg + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 203.0.113.4/32 +``` + +## Compliant Code Examples +```yaml +- name: example ec2 group v2 + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 79.32.0.0/8 + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ipv6: 64:ff9b::/96 + +``` +## Non-Compliant Code Examples +```yaml +- name: example ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 79.32.0.0/12 + - proto: tcp + from_port: -1 + to_port: -1 + cidr_ip: 79.32.0.0/12 + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ipv6: 2607:F8B0::/24 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/ses_policy_with_allowed_iam_actions.md b/documentation/rules/ansible/aws/ses_policy_with_allowed_iam_actions.md new file mode 100644 index 00000000..31d93599 --- /dev/null +++ b/documentation/rules/ansible/aws/ses_policy_with_allowed_iam_actions.md @@ -0,0 +1,102 @@ +--- +title: "SES policy with allowed IAM actions" +group_id: "Ansible / AWS" +meta: + name: "aws/ses_policy_with_allowed_iam_actions" + id: "8ed0bfce-f780-46d4-b086-21c3628f09ad" + display_name: "SES policy with allowed IAM actions" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `8ed0bfce-f780-46d4-b086-21c3628f09ad` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/ses_identity_policy_module.html#parameter-policy) + +### Description + +SES identity policies must not grant Allow permissions for all actions to all principals. A wildcard Action (`*`) combined with a wildcard Principal (`*`) lets any actor perform any API operation on the identity, enabling email spoofing, unauthorized sending, and potential privilege escalation. + +This rule checks Ansible resources of type `community.aws.ses_identity_policy` and `aws.aws_ses_identity_policy`. The `policy` document must not contain statements with `"Effect": "Allow"` where `Action` is `"*"` (or contains `"*"`) and `Principal` is a wildcard (for example `"*"` or `{"AWS":"*"}`). Resources with such statements are flagged. + +Specify explicit principals (AWS account ARNs or service principals) and restrict `Action` to the minimum required SES API operations. Secure example showing a restricted policy: + +```yaml +- name: Attach SES identity policy + community.aws.ses_identity_policy: + identity: "example.com" + policy: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, + "Action": [ "ses:SendEmail", "ses:SendRawEmail" ], + "Resource": "arn:aws:ses:us-east-1:123456789012:identity/example.com" + } + ] + } +``` + +## Compliant Code Examples +```yaml +- name: add sending authorization policy to email identity2 + community.aws.ses_identity_policy: + identity: example@example.com + policy_name: ExamplePolicy + policy: > + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "*", + "Principal": { + "AWS": "arn:aws:iam::987654321145:root" + }, + "Effect": "Allow", + "Resource": "*", + "Sid": "" + } + ] + } + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: add sending authorization policy to email identityyy + community.aws.ses_identity_policy: + identity: example@example.com + policy_name: ExamplePolicy + policy: > + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "*", + "Principal": { + "AWS": "*" + }, + "Effect": "Allow", + "Resource": "*", + "Sid": "" + } + ] + } + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/sns_topic_is_publicly_accessible.md b/documentation/rules/ansible/aws/sns_topic_is_publicly_accessible.md new file mode 100644 index 00000000..9cb9d5d5 --- /dev/null +++ b/documentation/rules/ansible/aws/sns_topic_is_publicly_accessible.md @@ -0,0 +1,175 @@ +--- +title: "SNS topic is publicly accessible" +group_id: "Ansible / AWS" +meta: + name: "aws/sns_topic_is_publicly_accessible" + id: "905f4741-f965-45c1-98db-f7a00a0e5c73" + display_name: "SNS topic is publicly accessible" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `905f4741-f965-45c1-98db-f7a00a0e5c73` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/sns_topic_module.html) + +### Description + +SNS topic policies must not allow any principal (`*`). Making a topic public permits unauthorized publishing or subscription, which can lead to message injection, data exfiltration, or unintended triggering of downstream systems. + +In Ansible tasks using the `community.aws.sns_topic` or `sns_topic` modules, check the `policy` property and flag any `Statement` with `"Effect": "Allow"` where `Principal` is the wildcard (`"*"`) or contains `"AWS": "*"`. Policy statements must instead specify explicit principals such as AWS account IDs, ARNs, or service principals. Statements that use a wildcard principal or are not limited to a specific account ID are flagged. + +Secure configuration example for an Ansible task (explicit principal): + +```yaml +- name: create sns topic with restricted policy + community.aws.sns_topic: + name: my-topic + policy: + Version: "2012-10-17" + Statement: + - Sid: AllowSpecificAccount + Effect: Allow + Principal: + AWS: "arn:aws:iam::123456789012:root" + Action: "SNS:Publish" + Resource: "arn:aws:sns:us-east-1:123456789012:my-topic" +``` + +## Compliant Code Examples +```yaml +- name: Create alarm SNS topic community + community.aws.sns_topic: + name: alarms + state: present + display_name: alarm SNS topic + delivery_policy: + http: + defaultHealthyRetryPolicy: + minDelayTarget: 2 + maxDelayTarget: 4 + numRetries: 3 + numMaxDelayRetries: 5 + backoffFunction: + disableSubscriptionOverrides: true + defaultThrottlePolicy: + maxReceivesPerSecond: 10 + policy: + Version: '2022-05-02' + Statement: + - Effect: Allow + Action: Publish + Principal: NotAll + +- name: Create alarm SNS topic + sns_topic: + name: alarms + state: present + display_name: alarm SNS topic + delivery_policy: + http: + defaultHealthyRetryPolicy: + minDelayTarget: 2 + maxDelayTarget: 4 + numRetries: 3 + numMaxDelayRetries: 5 + backoffFunction: + disableSubscriptionOverrides: true + defaultThrottlePolicy: + maxReceivesPerSecond: 10 + policy: + Version: '2022-05-02' + Statement: + - Effect: Allow + Action: Publish + Principal: NotAll + +# Principal "*" but limited to account ID via Condition - should NOT be flagged (is_access_limited_to_an_account_id) +- name: SNS topic with star principal but aws:SourceAccount condition + community.aws.sns_topic: + name: account-scoped-topic + state: present + policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: sns:Publish + Principal: "*" + Resource: "*" + Condition: + StringEquals: + aws:SourceAccount: "123456789012" + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create alarm SNS topic community + community.aws.sns_topic: + name: "alarms" + state: present + display_name: "alarm SNS topic" + delivery_policy: + http: + defaultHealthyRetryPolicy: + minDelayTarget: 2 + maxDelayTarget: 4 + numRetries: 3 + numMaxDelayRetries: 5 + backoffFunction: "" + disableSubscriptionOverrides: True + defaultThrottlePolicy: + maxReceivesPerSecond: 10 + subscriptions: + - endpoint: "my_email_address@example.com" + protocol: "email" + - endpoint: "my_mobile_number" + protocol: "sms" + policy: + Version: '2022-05-02' + Statement: + - Action: Publish + Effect: Allow + Principal: "*" +- name: Create alarm SNS topic + sns_topic: + name: "alarms" + state: present + display_name: "alarm SNS topic" + delivery_policy: + http: + defaultHealthyRetryPolicy: + minDelayTarget: 2 + maxDelayTarget: 4 + numRetries: 3 + numMaxDelayRetries: 5 + backoffFunction: "" + disableSubscriptionOverrides: True + defaultThrottlePolicy: + maxReceivesPerSecond: 10 + subscriptions: + - endpoint: "my_email_address@example.com" + protocol: "email" + - endpoint: "my_mobile_number" + protocol: "sms" + policy: + Version: '2022-05-02' + Statement: + - Effect: Allow + Action: Publish + Principal: '*' + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible.md b/documentation/rules/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible.md new file mode 100644 index 00000000..efd381da --- /dev/null +++ b/documentation/rules/ansible/aws/sql_analysis_services_port_2383_is_publicly_accessible.md @@ -0,0 +1,166 @@ +--- +title: "SQL Analysis Services port 2383 (TCP) is publicly accessible" +group_id: "Ansible / AWS" +meta: + name: "aws/sql_analysis_services_port_2383_is_publicly_accessible" + id: "7af1c447-c014-4f05-bd8b-ebe3a15734ac" + display_name: "SQL Analysis Services port 2383 (TCP) is publicly accessible" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `7af1c447-c014-4f05-bd8b-ebe3a15734ac` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html) + +### Description + +Allowing TCP port 2383 (SQL Server Analysis Services) from the public internet (CIDR `0.0.0.0/0`) exposes the analysis service to unauthorized connections, increasing the risk of data exposure, unauthorized queries, and lateral movement into your environment. + +For Ansible tasks using the `amazon.aws.ec2_group` or `ec2_group` module, this rule flags any `rules` entry where `cidr_ip` is `0.0.0.0/0`, `proto` is `tcp`, and the rule includes port 2383. Restrict access by specifying a limited CIDR range or referencing internal security groups instead of `0.0.0.0/0`, or remove the rule if public access is not required. + +Secure configuration example: + +```yaml +my_security_group: + amazon.aws.ec2_group: + name: my-sg + rules: + - proto: tcp + from_port: 2383 + to_port: 2383 + cidr_ip: 10.0.0.0/24 +``` + +## Compliant Code Examples +```yaml +- name: example using security group rule descriptions + amazon.aws.ec2_group: + name: awsEc2 + description: sg with rule descriptions + vpc_id: vpc-xxxxxxxx + profile: '{{ aws_profile }}' + region: us-east-1 + rules: + - proto: tcp + ports: + - 2383 + cidr_ip: aws_vpc.main.cidr_block + rule_desc: allow all on port 2383 + +- name: example using security group rule descriptions 2 + amazon.aws.ec2_group: + name: awsEc2 + description: sg with rule descriptions + vpc_id: vpc-xxxxxxxx + profile: '{{ aws_profile }}' + region: us-east-1 + rules: + - proto: udp + ports: + - 2383 + cidr_ip: 0.0.0.0/0 + rule_desc: allow all on port 2383 + +- name: example using security group rule descriptions 3 + amazon.aws.ec2_group: + name: awsEc2 + description: sg with rule descriptions + vpc_id: vpc-xxxxxxxx + profile: '{{ aws_profile }}' + region: us-east-1 + rules: + - proto: tcp + to_port: 4000 + from_port: 3000 + cidr_ip: 0.0.0.0/0 + rule_desc: allow all on port 2383 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: example using security group rule descriptions + amazon.aws.ec2_group: + name: awsEc2 + description: sg with rule descriptions + vpc_id: vpc-xxxxxxxx + profile: "{{ aws_profile }}" + region: us-east-1 + rules: + - proto: tcp + ports: + - 2383 + cidr_ip: 0.0.0.0/0 + rule_desc: allow all on port 2383 + +- name: example using security group rule descriptions 2 + amazon.aws.ec2_group: + name: awsEc2 + description: sg with rule descriptions + vpc_id: vpc-xxxxxxxx + profile: "{{ aws_profile }}" + region: us-east-1 + rules: + - proto: tcp + ports: + - 2383 + cidr_ip: 0.0.0.0/0 + rule_desc: allow all on port 2383 + +- name: example using security group rule descriptions 3 + amazon.aws.ec2_group: + name: awsEc2 + description: sg with rule descriptions + vpc_id: vpc-xxxxxxxx + profile: "{{ aws_profile }}" + region: us-east-1 + rules: + - proto: tcp + to_port: -1 + from_port: -1 + cidr_ip: 0.0.0.0/0 + rule_desc: allow all on port 2383 + +- name: example using security group rule descriptions 4 + amazon.aws.ec2_group: + name: awsEc2 + description: sg with rule descriptions + vpc_id: vpc-xxxxxxxx + profile: "{{ aws_profile }}" + region: us-east-1 + rules: + - proto: tcp + ports: + - 2000-3000 + cidr_ip: 0.0.0.0/0 + rule_desc: allow all on port 2383 + +- name: example using security group rule descriptions 5 + amazon.aws.ec2_group: + name: awsEc2 + description: sg with rule descriptions + vpc_id: vpc-xxxxxxxx + profile: "{{ aws_profile }}" + region: us-east-1 + rules: + - proto: tcp + to_port: 3000 + from_port: 2000 + cidr_ip: 0.0.0.0/0 + rule_desc: allow all on port 2383 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/sqs_policy_allows_all_actions.md b/documentation/rules/ansible/aws/sqs_policy_allows_all_actions.md new file mode 100644 index 00000000..98b0d90e --- /dev/null +++ b/documentation/rules/ansible/aws/sqs_policy_allows_all_actions.md @@ -0,0 +1,99 @@ +--- +title: "SQS policy allows all actions" +group_id: "Ansible / AWS" +meta: + name: "aws/sqs_policy_allows_all_actions" + id: "ed9b3beb-92cf-44d9-a9d2-171eeba569d4" + display_name: "SQS policy allows all actions" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `ed9b3beb-92cf-44d9-a9d2-171eeba569d4` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/sqs_queue_module.html) + +### Description + +SQS queue policies must not grant wildcard (`*`) actions. Allowing all actions on a queue enables unauthorized access, message retrieval or deletion, and queue modification, which can lead to data exposure or service disruption. + +For Ansible SQS resources (`community.aws.sqs_queue` and `sqs_queue`), inspect the `policy` document and ensure no `Statement` with `Effect: "Allow"` has `Action` set to `*` or contains `*`. Resources with `Action` set to `*` or `Action` arrays that include `*` are flagged. Instead, specify explicit SQS actions (for example, `sqs:SendMessage`, `sqs:ReceiveMessage`, `sqs:DeleteMessage`) and restrict principals to the minimum required. + +Secure example with explicit actions and principal: + +```yaml +- name: Create SQS queue with restricted policy + community.aws.sqs_queue: + name: my-queue + policy: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::123456789012:role/MyRole" }, + "Action": ["sqs:SendMessage", "sqs:ReceiveMessage", "sqs:DeleteMessage"], + "Resource": "arn:aws:sqs:us-east-1:123456789012:my-queue" + } + ] + } +``` + +## Compliant Code Examples +```yaml +- name: Create SQS queue with redrive policy + community.aws.sqs_queue: + name: my-queue + region: ap-southeast-2 + default_visibility_timeout: 120 + message_retention_period: 86400 + maximum_message_size: 1024 + delivery_delay: 30 + receive_message_wait_time: 20 + policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: logs:CreateLogGroup + Resource: '*' + make_default: false + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: Second SQS queue with policy + community.aws.sqs_queue: + name: my-queue2 + region: ap-southeast-3 + default_visibility_timeout: 120 + message_retention_period: 86400 + maximum_message_size: 1024 + delivery_delay: 30 + receive_message_wait_time: 20 + policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: "aws:action" + Resource: "*" + - Effect: "Allow" + Action: "*" + Resource: "*" + make_default: false + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/sqs_policy_with_public_access.md b/documentation/rules/ansible/aws/sqs_policy_with_public_access.md new file mode 100644 index 00000000..a84c6c12 --- /dev/null +++ b/documentation/rules/ansible/aws/sqs_policy_with_public_access.md @@ -0,0 +1,116 @@ +--- +title: "SQS policy with public access" +group_id: "Ansible / AWS" +meta: + name: "aws/sqs_policy_with_public_access" + id: "d994585f-defb-4b51-b6d2-c70f020ceb10" + display_name: "SQS policy with public access" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `d994585f-defb-4b51-b6d2-c70f020ceb10` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/sqs_queue_module.html) + +### Description + +SQS queue policies must not grant Allow permissions to a wildcard principal (`*`) combined with wildcard actions, as this gives any principal unrestricted ability to send, receive, delete, or otherwise manipulate queue messages, risking data exposure, message loss, or unauthorized message injection. In Ansible tasks using the `community.aws.sqs_queue` or `sqs_queue` module, inspect the `policy` property for policy statements where `Effect` is `"Allow"`, `Principal` is `"*"` (either `Principal == "*"` or `Principal.AWS` contains `"*"`), and `Action` contains `"*"`. Such statements are flagged. + +Define explicit principals (AWS account ARNs, IAM role/user ARNs, or service principals) and restrict `Action` to the minimal SQS actions required (for example, `sqs:SendMessage`, `sqs:ReceiveMessage`). You can optionally add conditions (source ARN/IP, VPC) to further limit access. + +Secure configuration example: + +```yaml +- name: Create SQS queue with restricted policy + community.aws.sqs_queue: + name: my-queue + policy: + Version: "2012-10-17" + Statement: + - Sid: AllowSpecificAccount + Effect: Allow + Principal: + AWS: "arn:aws:iam::123456789012:root" + Action: + - "sqs:SendMessage" + - "sqs:ReceiveMessage" + Resource: "arn:aws:sqs:us-east-1:123456789012:my-queue" +``` + +## Compliant Code Examples +```yaml +- name: First SQS queue with policy + community.aws.sqs_queue: + name: my-queue1 + region: ap-southeast-1 + default_visibility_timeout: 120 + message_retention_period: 86400 + maximum_message_size: 1024 + delivery_delay: 30 + receive_message_wait_time: 20 + policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: sqs:* + Resource: '*' + Principal: Principal + make_default: false + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: First SQS queue with policy + community.aws.sqs_queue: + name: my-queue1 + region: ap-southeast-1 + default_visibility_timeout: 120 + message_retention_period: 86400 + maximum_message_size: 1024 + delivery_delay: 30 + receive_message_wait_time: 20 + policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: "sqs:*" + Resource: "*" + Principal: "*" + make_default: false + state: present +- name: Second SQS queue with policy + community.aws.sqs_queue: + name: my-queue2 + region: ap-southeast-3 + default_visibility_timeout: 120 + message_retention_period: 86400 + maximum_message_size: 1024 + delivery_delay: 30 + receive_message_wait_time: 20 + policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: "*" + Resource: "*" + Principal: + AWS: "*" + make_default: false + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/sqs_queue_exposed.md b/documentation/rules/ansible/aws/sqs_queue_exposed.md new file mode 100644 index 00000000..f011017f --- /dev/null +++ b/documentation/rules/ansible/aws/sqs_queue_exposed.md @@ -0,0 +1,124 @@ +--- +title: "SQS queue exposed" +group_id: "Ansible / AWS" +meta: + name: "aws/sqs_queue_exposed" + id: "86b0efa7-4901-4edd-a37a-c034bec6645a" + display_name: "SQS queue exposed" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `86b0efa7-4901-4edd-a37a-c034bec6645a` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/sqs_queue_module.html#parameter-policy) + +### Description + +Granting the wildcard principal (`*`) `Allow` access in an SQS queue policy makes the queue publicly accessible. Unauthorized users or principals can send, receive, or modify messages, increasing the risk of data exposure and message injection. + +For Ansible SQS tasks (modules `community.aws.sqs_queue` or `sqs_queue`), inspect the `policy` property and ensure no policy Statement has `"Effect": "Allow"` with `"Principal": "*"`. Statements must specify explicit principals (for example AWS account ARNs) or include restrictive conditions. + +Resources with policy statements where `Principal == "*" ` and `Effect == "Allow"` are flagged. Replace wildcard principals with explicit ARNs or add conditions such as `aws:SourceAccount` or `aws:SourceVpce` to restrict access. + +Secure example (Ansible task with explicit principal): + +```yaml +- name: Create SQS queue with restricted policy + community.aws.sqs_queue: + name: my-queue + policy: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowSpecificAccount", + "Effect": "Allow", + "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, + "Action": ["SQS:SendMessage", "SQS:ReceiveMessage"], + "Resource": "arn:aws:sqs:us-east-1:123456789012:my-queue" + } + ] + } +``` + +## Compliant Code Examples +```yaml +- name: example + community.aws.sqs_queue: + name: my-queue + region: ap-southeast-2 + default_visibility_timeout: 120 + message_retention_period: 86400 + maximum_message_size: 1024 + delivery_delay: 30 + receive_message_wait_time: 20 + policy: + Version: '2012-10-17' + Id: sqspolicy + Statement: + Sid: First + Effect: Allow + Action: sqs:SendMessage + Resource: ${aws_sqs_queue.q.arn} + Condition: + ArnEquals: + aws:SourceArn: ${aws_sns_topic.example.arn} + +``` +## Non-Compliant Code Examples +```yaml +- name: example + community.aws.sqs_queue: + name: my-queue + region: ap-southeast-2 + default_visibility_timeout: 120 + message_retention_period: 86400 + maximum_message_size: 1024 + delivery_delay: 30 + receive_message_wait_time: 20 + policy: + Version: '2012-10-17' + Id: sqspolicy + Statement: + Sid: First + Effect: Allow + Principal: '*' + Action: sqs:SendMessage + Resource: ${aws_sqs_queue.q.arn} + Condition: + ArnEquals: + aws:SourceArn: ${aws_sns_topic.example.arn} +- name: example with list + community.aws.sqs_queue: + name: my-queue12 + region: ap-southeast-1 + default_visibility_timeout: 120 + message_retention_period: 86400 + maximum_message_size: 1024 + delivery_delay: 30 + receive_message_wait_time: 20 + policy: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: "sqs:*" + Resource: "*" + Principal: "*" + make_default: false + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/sqs_with_sse_disabled.md b/documentation/rules/ansible/aws/sqs_with_sse_disabled.md new file mode 100644 index 00000000..bf1ebce2 --- /dev/null +++ b/documentation/rules/ansible/aws/sqs_with_sse_disabled.md @@ -0,0 +1,96 @@ +--- +title: "SQS queue with SSE disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/sqs_with_sse_disabled" + id: "e1e7b278-2a8b-49bd-a26e-66a7f70b17eb" + display_name: "SQS queue with SSE disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `e1e7b278-2a8b-49bd-a26e-66a7f70b17eb` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/sqs_queue_module.html#ansible-collections-community-aws-sqs-queue-module) + +### Description + +SQS queues must have server-side encryption (SSE) enabled to protect message contents at rest and in backups. This reduces the risk of exposing sensitive data if someone accesses the underlying storage or compromises credentials. + +In Ansible, tasks using the `community.aws.sqs_queue` or `sqs_queue` modules must define the `kms_master_key_id` property and set it to a valid KMS key identifier (for example, a KMS ARN, key ID, or alias) to enable KMS-backed SSE. Resources missing this property or with it undefined/empty are flagged. Using a customer-managed KMS key (ARN or key ID) is recommended for granular access control and auditability, though the AWS-managed alias (`alias/aws/sqs`) can be used if customer-managed keys are not required. + +Secure configuration example: + +```yaml +- name: Create encrypted SQS queue + community.aws.sqs_queue: + name: my-queue + kms_master_key_id: arn:aws:kms:us-east-1:123456789012:key/abcd1234-56ef-78gh-90ij-klmnopqrstuv +``` + +## Compliant Code Examples +```yaml +- name: Configure Encryption, automatically uses a new data key every hour + community.aws.sqs_queue: + name: fifo-queue + region: ap-southeast-2 + kms_master_key_id: alias/MyQueueKey + kms_data_key_reuse_period_seconds: 3600 + +- name: Delete SQS queue + community.aws.sqs_queue: + name: my-queue + region: ap-southeast-2 + state: absent + +``` +## Non-Compliant Code Examples +```yaml +- name: Create SQS queue with redrive policy + community.aws.sqs_queue: + name: my-queue + region: ap-southeast-2 + default_visibility_timeout: 120 + message_retention_period: 86400 + maximum_message_size: 1024 + delivery_delay: 30 + receive_message_wait_time: 20 + policy: "{{ json_dict }}" + redrive_policy: + maxReceiveCount: 5 + deadLetterTargetArn: arn:aws:sqs:eu-west-1:123456789012:my-dead-queue + +- name: Drop redrive policy + community.aws.sqs_queue: + name: my-queue + region: ap-southeast-2 + redrive_policy: {} + +- name: Create FIFO queue + community.aws.sqs_queue: + name: fifo-queue + region: ap-southeast-2 + queue_type: fifo + content_based_deduplication: yes + +- name: Tag queue + community.aws.sqs_queue: + name: fifo-queue + region: ap-southeast-2 + tags: + example: SomeValue + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/stack_notifications_disabled.md b/documentation/rules/ansible/aws/stack_notifications_disabled.md new file mode 100644 index 00000000..89317bc0 --- /dev/null +++ b/documentation/rules/ansible/aws/stack_notifications_disabled.md @@ -0,0 +1,82 @@ +--- +title: "Stack notifications disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/stack_notifications_disabled" + id: "d39761d7-94ab-45b0-ab5e-27c44e381d58" + display_name: "Stack notifications disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `d39761d7-94ab-45b0-ab5e-27c44e381d58` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudformation_module.html#parameter-notification_arns) + +### Description + +CloudFormation stacks should publish notifications so operators are alerted to important stack events, such as failed deployments or unexpected stack changes. Without notifications, security incidents or configuration drift can go undetected and response times increase. In Ansible, tasks using the `amazon.aws.cloudformation` or legacy `cloudformation` module must define the `notification_arns` parameter and set it to one or more SNS topic ARNs. Resources missing `notification_arns` are flagged for remediation. + +Secure example: + +```yaml +- name: Create or update CloudFormation stack with notifications + amazon.aws.cloudformation: + stack_name: my-stack + state: present + template_body: "{{ lookup('file', 'template.yaml') }}" + notification_arns: + - arn:aws:sns:us-east-1:123456789012:stack-notifications +``` + +## Compliant Code Examples +```yaml +- name: create a stack, pass in the template via an URL + amazon.aws.cloudformation: + stack_name: ansible-cloudformation + stack_policy: wowowowoowow + notification_arns: a, b + state: present + region: us-east-1 + disable_rollback: true + template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template + template_parameters: + KeyName: jmartin + DiskType: ephemeral + InstanceType: m1.small + ClusterSize: 3 + tags: + Stack: ansible-cloudformation + +``` +## Non-Compliant Code Examples +```yaml +- name: create a stack, pass in the template via an URL + amazon.aws.cloudformation: + stack_name: "ansible-cloudformation" + state: present + region: us-east-1 + disable_rollback: true + template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template + template_parameters: + KeyName: jmartin + DiskType: ephemeral + InstanceType: m1.small + ClusterSize: 3 + tags: + Stack: ansible-cloudformation + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/stack_retention_disabled.md b/documentation/rules/ansible/aws/stack_retention_disabled.md new file mode 100644 index 00000000..73b454a1 --- /dev/null +++ b/documentation/rules/ansible/aws/stack_retention_disabled.md @@ -0,0 +1,83 @@ +--- +title: "Stack retention disabled" +group_id: "Ansible / AWS" +meta: + name: "aws/stack_retention_disabled" + id: "17d5ba1d-7667-4729-b1a6-b11fde3db7f7" + display_name: "Stack retention disabled" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Backup" +--- +## Metadata + +**Id:** `17d5ba1d-7667-4729-b1a6-b11fde3db7f7` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Backup + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/cloudformation_stack_set_module.html#parameter-purge_stacks) + +### Description + +CloudFormation StackSet deletions must not purge stacks and their associated resources. Purging can irreversibly delete resources, causing data loss or service interruption. For Ansible tasks using the `community.aws.cloudformation_stack_set` module, the `purge_stacks` property must be explicitly set to the boolean value `false`. Resources missing `purge_stacks` or with `purge_stacks: true` are flagged. + +```yaml +- name: Create or update StackSet without purging stacks on deletion + community.aws.cloudformation_stack_set: + name: my-stack-set + template: /path/to/template.yaml + parameters: + Param1: value + purge_stacks: false +``` + +## Compliant Code Examples +```yaml +- name: Create a stack set with instances in two accounts + community.aws.cloudformation_stack_set: + name: my-stack + description: Test stack in two accounts + state: present + template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template + accounts: [1234567890, 2345678901] + regions: + - us-east-1 + purge_stacks: false + +``` +## Non-Compliant Code Examples +```yaml +- name: Create a stack set with instances in two accounts + community.aws.cloudformation_stack_set: + name: my-stack2 + description: Test stack in two accounts + state: present + template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template + accounts: [1234567890, 2345678901] + regions: + - us-east-1 + +- name: on subsequent calls, templates are optional but parameters and tags can be altered + community.aws.cloudformation_stack_set: + name: my-stack3 + state: present + parameters: + InstanceName: my_stacked_instance + tags: + foo: bar + test: stack + accounts: [1234567890, 2345678901] + regions: + - us-east-1 + purge_stacks: true + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/stack_without_template.md b/documentation/rules/ansible/aws/stack_without_template.md new file mode 100644 index 00000000..801c1b67 --- /dev/null +++ b/documentation/rules/ansible/aws/stack_without_template.md @@ -0,0 +1,143 @@ +--- +title: "Stack without template" +group_id: "Ansible / AWS" +meta: + name: "aws/stack_without_template" + id: "32d31f1f-0f83-4721-b7ec-1e6948c60145" + display_name: "Stack without template" + cloud_provider: "AWS" + platform: "Ansible" + severity: "LOW" + category: "Build Process" +--- +## Metadata + +**Id:** `32d31f1f-0f83-4721-b7ec-1e6948c60145` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Build Process + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/cloudformation_module.html) + +### Description + +CloudFormation stack tasks must specify exactly one template source. Missing or ambiguous templates can cause failed deployments or unintended resource changes that increase security and availability risks. + +For Ansible modules `amazon.aws.cloudformation`, `cloudformation`, `community.aws.cloudformation_stack_set`, and `cloudformation_stack_set`, one of the properties `template`, `template_body`, or `template_url` must be present and non-empty. Resources that omit all three properties are flagged as missing a template. Resources that set more than one are flagged because multiple template sources are ambiguous and can lead to unexpected template selection. + +Secure examples (valid configurations): + +```yaml +- name: Create CloudFormation stack from local template + amazon.aws.cloudformation: + stack_name: my-stack + template: /path/to/template.yaml + +- name: Create CloudFormation stack from S3 URL + amazon.aws.cloudformation: + stack_name: my-stack + template_url: https://s3.amazonaws.com/bucket/my-template.yaml +``` + +## Compliant Code Examples +```yaml +- name: create a stack, pass in the template body via lookup template v3 + amazon.aws.cloudformation: + stack_name: ansible-cloudformation + state: present + region: us-east-1 + disable_rollback: true + template_body: "{{ lookup('template', 'cloudformation.j2') }}" + template_parameters: + KeyName: jmartin + DiskType: ephemeral + InstanceType: m1.small + ClusterSize: 3 + tags: + Stack: ansible-cloudformation + + +- name: create a stack, pass in the template via an URL v4 + amazon.aws.cloudformation: + stack_name: ansible-cloudformation + state: present + region: us-east-1 + disable_rollback: true + template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template + template_parameters: + KeyName: jmartin + DiskType: ephemeral + InstanceType: m1.small + ClusterSize: 3 + tags: + Stack: ansible-cloudformation + + +- name: Create a stack set with instances in two accounts v5 + community.aws.cloudformation_stack_set: + name: my-stack + description: Test stack in two accounts + state: present + template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template + accounts: [1234567890, 2345678901] + regions: + - us-east-1 + +``` +## Non-Compliant Code Examples +```yaml +- name: create a stack, pass in the template via an URL + amazon.aws.cloudformation: + stack_name: "ansible-cloudformation" + state: present + region: us-east-1 + disable_rollback: true + template_parameters: + KeyName: jmartin + DiskType: ephemeral + InstanceType: m1.small + ClusterSize: 3 + tags: + Stack: ansible-cloudformation +- name: create a stack, pass in the template via an URL v2 + amazon.aws.cloudformation: + stack_name: "ansible-cloudformation" + state: present + region: us-east-1 + disable_rollback: true + template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template + template_body: "{{ lookup('template', 'cloudformation.j2') }}" + template_parameters: + KeyName: jmartin + DiskType: ephemeral + InstanceType: m1.small + ClusterSize: 3 + tags: + Stack: ansible-cloudformation +- name: Create a stack set with instances in two accounts + community.aws.cloudformation_stack_set: + name: my-stack + description: Test stack in two accounts + state: present + template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template + template_body: "{{ lookup('template', 'cloudformation.j2') }}" + accounts: [1234567890, 2345678901] + regions: + - us-east-1 +- name: Create a stack set with instances in two accounts v2 + community.aws.cloudformation_stack_set: + name: my-stack + description: Test stack in two accounts + state: present + accounts: [1234567890, 2345678901] + regions: + - us-east-1 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/unknown_port_exposed_to_internet.md b/documentation/rules/ansible/aws/unknown_port_exposed_to_internet.md new file mode 100644 index 00000000..1ba91c6b --- /dev/null +++ b/documentation/rules/ansible/aws/unknown_port_exposed_to_internet.md @@ -0,0 +1,87 @@ +--- +title: "Unknown port exposed to internet" +group_id: "Ansible / AWS" +meta: + name: "aws/unknown_port_exposed_to_internet" + id: "722b0f24-5a64-4cca-aa96-cfc26b7e3a5b" + display_name: "Unknown port exposed to internet" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `722b0f24-5a64-4cca-aa96-cfc26b7e3a5b` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html) + +### Description + +Security groups must not expose unknown or undocumented TCP ports to the entire Internet. Exposing unexpected ports increases attack surface and makes it easier for attackers to discover and exploit unintended services. + +This rule inspects Ansible tasks using the `amazon.aws.ec2_group` and `ec2_group` modules. It checks each `rules` entry and flags rules where any port in the range from `from_port` to `to_port` is not found in the recognized TCP ports map and where `cidr_ip` equals `0.0.0.0/0` or `cidr_ipv6` equals `::/0` (entire network). + +To remediate, restrict ingress to only known, required ports and limit CIDR ranges to trusted networks or reference other security groups. Review and document any non-standard ports before allowing public access. + +Secure example for Ansible `ec2_group` with a single, known port limited to a specific IPv4 range: + +```yaml +- name: Create security group with restricted HTTPS access + amazon.aws.ec2_group: + name: example-sg + rules: + - proto: tcp + from_port: 443 + to_port: 443 + cidr_ip: 203.0.113.0/24 +``` + +## Compliant Code Examples +```yaml +- name: example ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 0.0.0.0/0 + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 10.0.0.0/8 + +``` +## Non-Compliant Code Examples +```yaml +- name: example ec2 group + amazon.aws.ec2_group: + name: example + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + from_port: 8001 + to_port: 8002 + cidr_ip: 0.0.0.0/0 + - proto: tcp + from_port: 2222 + to_port: 2226 + cidr_ipv6: ::/0 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/unrestricted_security_group_ingress.md b/documentation/rules/ansible/aws/unrestricted_security_group_ingress.md new file mode 100644 index 00000000..ddfa852d --- /dev/null +++ b/documentation/rules/ansible/aws/unrestricted_security_group_ingress.md @@ -0,0 +1,172 @@ +--- +title: "Unrestricted security group ingress" +group_id: "Ansible / AWS" +meta: + name: "aws/unrestricted_security_group_ingress" + id: "83c5fa4c-e098-48fc-84ee-0a537287ddd2" + display_name: "Unrestricted security group ingress" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `83c5fa4c-e098-48fc-84ee-0a537287ddd2` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_group_module.html) + +### Description + +Security group ingress rules must not allow traffic from the entire Internet (IPv4 `0.0.0.0/0` or IPv6 `::/0`) to specific ports. This exposes services to unauthorized access and automated attacks such as brute force and port scanning. + +This rule inspects Ansible `amazon.aws.ec2_group` and `ec2_group` tasks and flags `rules` entries that define ports (via `from_port`/`to_port` or `ports`) where `cidr_ip` is `0.0.0.0/0` or `cidr_ipv6` is `::/0`. It also detects these values when CIDRs are provided as lists. + +To remediate, restrict ingress to specific trusted CIDR ranges, use security group-to-security group references or VPN/bastion hosts, and remove or replace `0.0.0.0/0` and `::/0` from rules that open ports. + +Secure configuration example (restrict SSH to a trusted IPv4 range and allow HTTPS from a specific IPv6 range): + +```yaml +- name: Create restricted SG + amazon.aws.ec2_group: + name: my-sg + description: "Restrict SSH and HTTPS to trusted networks" + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 10.0.0.0/24 + - proto: tcp + from_port: 443 + to_port: 443 + cidr_ipv6: "2001:db8::/32" +``` + +## Compliant Code Examples +```yaml +- name: example1 + amazon.aws.ec2_group: + name: example1 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + ports: + - 80 + - 443 + - 8080-8099 + cidr_ip: 172.16.17.0/24 +- name: example2 + amazon.aws.ec2_group: + name: example2 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + ports: + - 80 + - 443 + - 8080-8099 + cidr_ip: + - 172.16.1.0/24 +- name: example3 + amazon.aws.ec2_group: + name: example3 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + ports: + - 80 + - 443 + - 8080-8099 + cidr_ipv6: 2607:F8B0::/32 +- name: example4 + amazon.aws.ec2_group: + name: example4 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + ports: + - 80 + - 443 + - 8080-8099 + cidr_ipv6: + - 64:ff9b::/96 + - 2607:F8B0::/32 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: example1 + amazon.aws.ec2_group: + name: example1 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + ports: + - 80 + - 443 + - 8080-8099 + cidr_ip: 0.0.0.0/0 +- name: example2 + amazon.aws.ec2_group: + name: example2 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + ports: + - 80 + - 443 + - 8080-8099 + cidr_ip: + - 0.0.0.0/0 +- name: example3 + amazon.aws.ec2_group: + name: example3 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + ports: + - 80 + - 443 + - 8080-8099 + cidr_ipv6: ::/0 +- name: example4 + amazon.aws.ec2_group: + name: example4 + description: an example EC2 group + vpc_id: 12345 + region: eu-west-1 + rules: + - proto: tcp + ports: + - 80 + - 443 + - 8080-8099 + cidr_ipv6: + - ::/0 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/user_data_contains_encoded_private_key.md b/documentation/rules/ansible/aws/user_data_contains_encoded_private_key.md new file mode 100644 index 00000000..81c3d06e --- /dev/null +++ b/documentation/rules/ansible/aws/user_data_contains_encoded_private_key.md @@ -0,0 +1,96 @@ +--- +title: "User data contains encoded private key" +group_id: "Ansible / AWS" +meta: + name: "aws/user_data_contains_encoded_private_key" + id: "c09f4d3e-27d2-4d46-9453-abbe9687a64e" + display_name: "User data contains encoded private key" + cloud_provider: "AWS" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `c09f4d3e-27d2-4d46-9453-abbe9687a64e` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/autoscaling_launch_config_module.html) + +### Description + +Embedding base64-encoded private keys in EC2 launch configuration user data exposes sensitive credentials that can be decoded and used to impersonate instances or access private services, resulting in credential compromise and lateral movement. + +This rule inspects Ansible tasks using the `community.aws.autoscaling_launch_config` or `autoscaling_launch_config` modules and flags the `user_data` property when it contains the base64 prefix `LS0tLS1CR`, which corresponds to the start of an RSA private key header (`-----BEGIN R...`). + +Remove any private keys from `user_data` and instead store secrets in a secure secrets manager or fetch them at runtime using instance IAM roles. Tasks embedding keys are flagged. + +## Compliant Code Examples +```yaml +- name: note that encrypted volumes are only supported in >= Ansible 2.4 + community.aws.autoscaling_launch_config: + name: special + image_id: ami-XXX + key_name: default + security_groups: [group, group2] + instance_type: t1.micro + user_data: dGVzdA== + volumes: + - device_name: /dev/sda1 + volume_size: 100 + volume_type: io1 + iops: 3000 + delete_on_termination: true + encrypted: true + - device_name: /dev/sdb + ephemeral: ephemeral0 +- name: note that encrypted volumes are only supported in >= Ansible 2.4.2 + community.aws.autoscaling_launch_config: + name: special2 + image_id: ami-XXX + key_name: default + security_groups: [group, group2] + instance_type: t1.micro + user_data: + volumes: + - device_name: /dev/sda1 + volume_size: 100 + volume_type: io1 + iops: 3000 + delete_on_termination: true + encrypted: true + - device_name: /dev/sdb + ephemeral: ephemeral0 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: note that encrypted volumes are only supported in >= Ansible 2.4 + community.aws.autoscaling_launch_config: + name: special + image_id: ami-XXX + key_name: default + security_groups: ['group', 'group2' ] + instance_type: t1.micro + user_data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpzb21lS2V5 + volumes: + - device_name: /dev/sda1 + volume_size: 100 + volume_type: io1 + iops: 3000 + delete_on_termination: true + encrypted: true + - device_name: /dev/sdb + ephemeral: ephemeral0 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/viewer_protocol_policy_allows_http.md b/documentation/rules/ansible/aws/viewer_protocol_policy_allows_http.md new file mode 100644 index 00000000..ab5233b5 --- /dev/null +++ b/documentation/rules/ansible/aws/viewer_protocol_policy_allows_http.md @@ -0,0 +1,175 @@ +--- +title: "CloudFront viewer protocol policy allows HTTP" +group_id: "Ansible / AWS" +meta: + name: "aws/viewer_protocol_policy_allows_http" + id: "a6d27cf7-61dc-4bde-ae08-3b353b609f76" + display_name: "CloudFront viewer protocol policy allows HTTP" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `a6d27cf7-61dc-4bde-ae08-3b353b609f76` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/cloudfront_distribution_module.html) + +### Description + +CloudFront distributions must enforce HTTPS for viewer connections to prevent sensitive data from being transmitted in plaintext and reduce the risk of downgrade or man-in-the-middle attacks. + +For Ansible CloudFront resources (modules `community.aws.cloudfront_distribution` or `cloudfront_distribution`), the `viewer_protocol_policy` property in `default_cache_behavior` and in each `cache_behaviors` entry must be set to `https-only` or `redirect-to-https`. Tasks with `viewer_protocol_policy` set to `allow-all` or without an explicit secure setting are flagged. Ensure every cache behavior explicitly specifies a secure policy. + +Secure configuration example: + +```yaml +- name: Create CloudFront distribution + community.aws.cloudfront_distribution: + origin: + - id: origin1 + domain_name: example.com + default_cache_behavior: + viewer_protocol_policy: https-only + cache_behaviors: + - path_pattern: /images/* + viewer_protocol_policy: redirect-to-https +``` + +## Compliant Code Examples +```yaml +- name: example1 + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: my test origin-000111 + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + default_cache_behavior: + target_origin_id: my test origin-000111 + forwarded_values: + query_string: true + cookies: + forward: all + headers: + - '*' + viewer_protocol_policy: https-only + smooth_streaming: true + compress: true + allowed_methods: + items: + - GET + - HEAD + cached_methods: + - GET + - HEAD + +- name: example2 + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: my test origin-000111 + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + cache_behaviors: + target_origin_id: my test origin-000111 + forwarded_values: + query_string: true + cookies: + forward: all + headers: + - '*' + viewer_protocol_policy: https-only + smooth_streaming: true + compress: true + allowed_methods: + items: + - GET + - HEAD + cached_methods: + - GET + - HEAD + +``` +## Non-Compliant Code Examples +```yaml +- name: example1 + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: 'my test origin-000111' + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + default_cache_behavior: + target_origin_id: 'my test origin-000111' + forwarded_values: + query_string: true + cookies: + forward: all + headers: + - '*' + viewer_protocol_policy: allow-all + smooth_streaming: true + compress: true + allowed_methods: + items: + - GET + - HEAD + cached_methods: + - GET + - HEAD + +- name: example2 + community.aws.cloudfront_distribution: + state: present + caller_reference: unique test distribution ID + origins: + - id: 'my test origin-000111' + domain_name: www.example.com + origin_path: /production + custom_headers: + - header_name: MyCustomHeaderName + header_value: MyCustomHeaderValue + cache_behaviors: + target_origin_id: 'my test origin-000111' + forwarded_values: + query_string: true + cookies: + forward: all + headers: + - '*' + viewer_protocol_policy: allow-all + smooth_streaming: true + compress: true + allowed_methods: + items: + - GET + - HEAD + cached_methods: + - GET + - HEAD + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/aws/vulnerable_default_ssl_certificate.md b/documentation/rules/ansible/aws/vulnerable_default_ssl_certificate.md new file mode 100644 index 00000000..587c5402 --- /dev/null +++ b/documentation/rules/ansible/aws/vulnerable_default_ssl_certificate.md @@ -0,0 +1,88 @@ +--- +title: "Vulnerable default SSL certificate" +group_id: "Ansible / AWS" +meta: + name: "aws/vulnerable_default_ssl_certificate" + id: "fb8f8929-afeb-4c46-99f0-a6cf410f7df4" + display_name: "Vulnerable default SSL certificate" + cloud_provider: "AWS" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Defaults" +--- +## Metadata + +**Id:** `fb8f8929-afeb-4c46-99f0-a6cf410f7df4` + +**Cloud Provider:** AWS + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Defaults + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/community/aws/cloudfront_distribution_module.html) + +### Description + +CloudFront distributions should use custom SSL certificates rather than the default CloudFront certificate. Custom certificates enable serving content on custom domain names and enforce strong, managed TLS settings for data in transit. + +For Ansible tasks using `community.aws.cloudfront_distribution` or `cloudfront_distribution`, the `viewer_certificate.cloudfront_default_certificate` property must be `false` or not defined. If `viewer_certificate.acm_certificate_arn` or `viewer_certificate.iam_certificate_id` is provided, then `viewer_certificate.ssl_support_method` and `viewer_certificate.minimum_protocol_version` must also be defined. + +Resources with `cloudfront_default_certificate` set to `true`, or with a custom certificate but missing `ssl_support_method` or `minimum_protocol_version`, are flagged. Use a secure `viewer_certificate` block that references a custom ACM or IAM certificate and explicitly sets the SSL support method and a modern minimum protocol version. + +Secure example for an Ansible CloudFront distribution: + +```yaml +- name: Create CloudFront distribution with custom certificate + community.aws.cloudfront_distribution: + name: my-distribution + viewer_certificate: + acm_certificate_arn: arn:aws:acm:us-east-1:123456789012:certificate/abcd-ef01-2345 + ssl_support_method: sni-only + minimum_protocol_version: TLSv1.2_2019 +``` + +## Compliant Code Examples +```yaml +- name: create a basic distribution with defaults, tags and custom SSL certificate + community.aws.cloudfront_distribution: + state: present + default_origin_domain_name: www.my-cloudfront-origin.com + viewer_certificate: + acm_certificate_arn: arn:aws:acm:region:123456789012:certificate/12345678-1234-1234-1234-123456789012 + ssl_support_method: sni-only + minimum_protocol_version: TLS1.2_2018 + tags: + Name: example distribution + Project: example project + Priority: '1' + +``` +## Non-Compliant Code Examples +```yaml +- name: create a basic distribution with defaults, tags and default SSL certificate + community.aws.cloudfront_distribution: + state: present + default_origin_domain_name: www.my-cloudfront-origin.com + viewer_certificate: + cloudfront_default_certificate: true + tags: + Name: example distribution + Project: example project + Priority: '1' +- name: create a basic distribution with defaults, tags and misconfigured custom SSL certificate + community.aws.cloudfront_distribution: + state: present + default_origin_domain_name: www.my-cloudfront-origin.com + viewer_certificate: + acm_certificate_arn: arn:aws:acm:region:123456789012:certificate/12345678-1234-1234-1234-123456789012 + tags: + Name: example distribution + Project: example project + Priority: '1' + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/ad_admin_not_configured_for_sql_server.md b/documentation/rules/ansible/azure/ad_admin_not_configured_for_sql_server.md new file mode 100644 index 00000000..825ecf61 --- /dev/null +++ b/documentation/rules/ansible/azure/ad_admin_not_configured_for_sql_server.md @@ -0,0 +1,68 @@ +--- +title: "AD admin not configured for SQL server" +group_id: "Ansible / Azure" +meta: + name: "azure/ad_admin_not_configured_for_sql_server" + id: "b176e927-bbe2-44a6-a9c3-041417137e5f" + display_name: "AD admin not configured for SQL server" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `b176e927-bbe2-44a6-a9c3-041417137e5f` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_sqlserver_module.html#parameter-ad_user) + +### Description + +SQL servers should have an Active Directory administrator configured to enforce centralized identity, stronger authentication, and auditable access controls. Relying solely on SQL authentication increases the attack surface and makes access management and auditing more difficult. For Ansible, tasks using the `azure.azcollection.azure_rm_sqlserver` or `azure_rm_sqlserver` module must define the `ad_user` property and set it to a valid Azure AD principal (for example, a user UPN or objectId). Resources missing `ad_user` or with it empty or undefined are flagged. + +Secure example: + +``` +- name: Create Azure SQL Server with AD admin + azure.azcollection.azure_rm_sqlserver: + name: my-sql-server + resource_group: my-rg + location: eastus + ad_user: "adminuser@contoso.com" + admin_password: "secure-password" +``` + +## Compliant Code Examples +```yaml +- name: Create (or update) SQL Server + azure_rm_sqlserver: + resource_group: myResourceGroup + name: server_name + location: westus + admin_username: mylogin + admin_password: Testpasswordxyz12! + ad_user: sqladmin + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create (or update) SQL Server + azure_rm_sqlserver: + resource_group: myResourceGroup + name: server_name + location: westus + admin_username: mylogin + admin_password: Testpasswordxyz12! + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/admin_user_enabled_for_container_registry.md b/documentation/rules/ansible/azure/admin_user_enabled_for_container_registry.md new file mode 100644 index 00000000..761aa932 --- /dev/null +++ b/documentation/rules/ansible/azure/admin_user_enabled_for_container_registry.md @@ -0,0 +1,94 @@ +--- +title: "Admin user enabled for container registry" +group_id: "Ansible / Azure" +meta: + name: "azure/admin_user_enabled_for_container_registry" + id: "29f35127-98e6-43af-8ec1-201b79f99604" + display_name: "Admin user enabled for container registry" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `29f35127-98e6-43af-8ec1-201b79f99604` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_containerregistry_module.html) + +### Description + +Enabling the admin user on an Azure Container Registry creates a shared username/password credential that can be leaked or abused to push or pull images, increasing the risk of unauthorized access and lateral movement. + +For Ansible resources using `azure_rm_containerregistry` or `azure.azcollection.azure_rm_containerregistry`, the `admin_user_enabled` property must be set to `false` or omitted (it defaults to `false`). Tasks with `admin_user_enabled: true` are flagged. Use Azure AD RBAC with scoped service principals or managed identities for registry access instead. + +Secure example (explicitly disabling the admin user): + +```yaml +- name: Create secure Azure Container Registry + azure.azcollection.azure_rm_containerregistry: + name: myRegistry + resource_group: myResourceGroup + sku: Basic + admin_user_enabled: false +``` + +## Compliant Code Examples +```yaml +- name: Create an azure container registry + azure.azcollection.azure_rm_containerregistry: + name: myRegistry + location: eastus + resource_group: myResourceGroup + admin_user_enabled: false + sku: Premium + tags: + Release: beta1 + Environment: Production +- name: Create an azure container registry2 + azure.azcollection.azure_rm_containerregistry: + name: myRegistry + location: eastus + resource_group: myResourceGroup + admin_user_enabled: false + sku: Premium + tags: + Release: beta1 + Environment: Production + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create an azure container registry + azure.azcollection.azure_rm_containerregistry: + name: myRegistry + location: eastus + resource_group: myResourceGroup + admin_user_enabled: true + sku: Premium + tags: + Release: beta1 + Environment: Production +- name: Create an azure container registry2 + azure.azcollection.azure_rm_containerregistry: + name: myRegistry + location: eastus + resource_group: myResourceGroup + admin_user_enabled: "true" + sku: Premium + tags: + Release: beta1 + Environment: Production + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/aks_monitoring_logging_disabled.md b/documentation/rules/ansible/azure/aks_monitoring_logging_disabled.md new file mode 100644 index 00000000..15b843e4 --- /dev/null +++ b/documentation/rules/ansible/azure/aks_monitoring_logging_disabled.md @@ -0,0 +1,174 @@ +--- +title: "AKS monitoring logging disabled" +group_id: "Ansible / Azure" +meta: + name: "azure/aks_monitoring_logging_disabled" + id: "d5e83b32-56dd-4247-8c2e-074f43b38a5e" + display_name: "AKS monitoring logging disabled" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `d5e83b32-56dd-4247-8c2e-074f43b38a5e` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_aks_module.html) + +### Description + +AKS clusters must have the monitoring addon enabled and configured to send logs and metrics to an Azure Log Analytics workspace. This ensures that cluster activity, security events, and configuration changes are visible for detection, alerting, and incident investigation. + +For Ansible tasks using `azure_rm_aks` or `azure.azcollection.azure_rm_aks`, the `addon.monitoring` block must be present with `enabled` set to an Ansible-`true` value and `log_analytics_workspace_resource_id` set to the workspace resource ID. Tasks missing the `addon` or `addon.monitoring` blocks, missing `enabled` or the workspace ID, or with `enabled` not set to an Ansible-`true` value (for example `yes`, `true`, `on`, or `1`) are flagged. + +Secure configuration example: + +```yaml +- name: Create AKS cluster with monitoring enabled + azure_rm_aks: + name: myAKS + resource_group: myRg + addon: + monitoring: + enabled: yes + log_analytics_workspace_resource_id: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRg/providers/Microsoft.OperationalInsights/workspaces/myWorkspace +``` + +## Compliant Code Examples +```yaml +- name: Create an AKS instance v4 + azure_rm_aks: + name: myAKS + resource_group: myResourceGroup + location: eastus + dns_prefix: akstest + kubernetes_version: 1.14.6 + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: cf72ca99-f6b9-4004-b0e0-bee10c521948 + client_secret: Password1234! + agent_pool_profiles: + - name: default + count: 1 + vm_size: Standard_DS1_v2 + type: VirtualMachineScaleSets + max_count: 3 + min_count: 1 + enable_rbac: yes + addon: + monitoring: + log_analytics_workspace_resource_id: qwqeqe + enabled: yes + +``` +## Non-Compliant Code Examples +```yaml +- name: Create an AKS instance v0 + azure_rm_aks: + name: myAKS + resource_group: myResourceGroup + location: eastus + dns_prefix: akstest + kubernetes_version: 1.14.6 + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: "cf72ca99-f6b9-4004-b0e0-bee10c521948" + client_secret: "Password1234!" + agent_pool_profiles: + - name: default + count: 1 + vm_size: Standard_DS1_v2 + type: VirtualMachineScaleSets + max_count: 3 + min_count: 1 + enable_rbac: yes +- name: Create an AKS instance + azure_rm_aks: + name: myAKS + resource_group: myResourceGroup + location: eastus + dns_prefix: akstest + kubernetes_version: 1.14.6 + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: "cf72ca99-f6b9-4004-b0e0-bee10c521948" + client_secret: "Password1234!" + agent_pool_profiles: + - name: default + count: 1 + vm_size: Standard_DS1_v2 + type: VirtualMachineScaleSets + max_count: 3 + min_count: 1 + enable_rbac: yes + addon: + http_application_routing: + enabled: yes +- name: Create an AKS instance v3 + azure_rm_aks: + name: myAKS + resource_group: myResourceGroup + location: eastus + dns_prefix: akstest + kubernetes_version: 1.14.6 + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: "cf72ca99-f6b9-4004-b0e0-bee10c521948" + client_secret: "Password1234!" + agent_pool_profiles: + - name: default + count: 1 + vm_size: Standard_DS1_v2 + type: VirtualMachineScaleSets + max_count: 3 + min_count: 1 + enable_rbac: yes + addon: + monitoring: + log_analytics_workspace_resource_id: "qwqeqe" +- name: Create an AKS instance v9 + azure_rm_aks: + name: myAKS + resource_group: myResourceGroup + location: eastus + dns_prefix: akstest + kubernetes_version: 1.14.6 + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: "cf72ca99-f6b9-4004-b0e0-bee10c521948" + client_secret: "Password1234!" + agent_pool_profiles: + - name: default + count: 1 + vm_size: Standard_DS1_v2 + type: VirtualMachineScaleSets + max_count: 3 + min_count: 1 + enable_rbac: yes + addon: + monitoring: + log_analytics_workspace_resource_id: "qwqeqe" + enabled: no + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/aks_network_policy_misconfigured.md b/documentation/rules/ansible/azure/aks_network_policy_misconfigured.md new file mode 100644 index 00000000..fb3b8880 --- /dev/null +++ b/documentation/rules/ansible/azure/aks_network_policy_misconfigured.md @@ -0,0 +1,137 @@ +--- +title: "AKS network policy misconfigured" +group_id: "Ansible / Azure" +meta: + name: "azure/aks_network_policy_misconfigured" + id: "8c3bedf1-c570-4c3b-b414-d068cd39a00c" + display_name: "AKS network policy misconfigured" + cloud_provider: "Azure" + platform: "Ansible" + severity: "LOW" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `8c3bedf1-c570-4c3b-b414-d068cd39a00c` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_aks_module.html#parameter-network_profile/network_policy) + +### Description + +AKS clusters must have a network policy configured to enforce pod-to-pod network isolation and the principle of least privilege. Without a network policy, pods can communicate freely, increasing the risk of lateral movement and unintended access to services. + +For Ansible resources using `azure.azcollection.azure_rm_aks` or `azure_rm_aks`, the `network_profile.network_policy` property must be defined and set to either `calico` or `azure`. Tasks that omit `network_profile` or `network_profile.network_policy`, or that set the property to any value other than `calico` or `azure`, are flagged. + +Secure example Ansible task: + +```yaml +- name: Create AKS cluster with network policy + azure.azcollection.azure_rm_aks: + name: my-aks-cluster + resource_group: my-rg + dns_prefix: myaks + network_profile: + network_policy: calico +``` + +## Compliant Code Examples +```yaml +- name: Create a managed Azure Container Services (AKS) instance01 + azure_rm_aks: + name: myAKS + location: eastus + resource_group: myResourceGroup + dns_prefix: akstest + kubernetes_version: 1.14.6 + network_profile: + network_policy: calico + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: cf72ca99-f6b9-4004-b0e0-bee10c521948 + client_secret: Password123! + agent_pool_profiles: + - name: default + count: 5 + vm_size: Standard_D2_v2 + tags: + Environment: Production +- name: Create a managed Azure Container Services (AKS) instance02 + azure_rm_aks: + name: myAKS + location: eastus + resource_group: myResourceGroup + dns_prefix: akstest + kubernetes_version: 1.14.6 + network_profile: + network_policy: azure + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: cf72ca99-f6b9-4004-b0e0-bee10c521948 + client_secret: Password123! + agent_pool_profiles: + - name: default + count: 5 + vm_size: Standard_D2_v2 + tags: + Environment: Production + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create a managed Azure Container Services (AKS) instance03 + azure_rm_aks: + name: myAKS + location: eastus + resource_group: myResourceGroup + dns_prefix: akstest + kubernetes_version: 1.14.6 + network_profile: + network_policy: istio + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: "cf72ca99-f6b9-4004-b0e0-bee10c521948" + client_secret: "Password123!" + agent_pool_profiles: + - name: default + count: 5 + vm_size: Standard_D2_v2 + tags: + Environment: Production +- name: Create a managed Azure Container Services (AKS) instance04 + azure_rm_aks: + name: myAKS + location: eastus + resource_group: myResourceGroup + dns_prefix: akstest + kubernetes_version: 1.14.6 + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: "cf72ca99-f6b9-4004-b0e0-bee10c521948" + client_secret: "Password123!" + agent_pool_profiles: + - name: default + count: 5 + vm_size: Standard_D2_v2 + tags: + Environment: Production + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/aks_rbac_disabled.md b/documentation/rules/ansible/azure/aks_rbac_disabled.md new file mode 100644 index 00000000..c31c942b --- /dev/null +++ b/documentation/rules/ansible/azure/aks_rbac_disabled.md @@ -0,0 +1,114 @@ +--- +title: "AKS RBAC disabled" +group_id: "Ansible / Azure" +meta: + name: "azure/aks_rbac_disabled" + id: "149fa56c-4404-4f90-9e25-d34b676d5b39" + display_name: "AKS RBAC disabled" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `149fa56c-4404-4f90-9e25-d34b676d5b39` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_aks_module.html) + +### Description + +AKS clusters must have role-based access control (RBAC) enabled to restrict Kubernetes API operations to authorized principals and prevent privilege escalation or unauthorized cluster modifications. + +In Ansible playbooks, tasks using the `azure.azcollection.azure_rm_aks` or `azure_rm_aks` modules must define the `enable_rbac` property and set it to a truthy value (for example `yes`/`true` or YAML true). Resources with `enable_rbac` missing or not set to a truthy value are flagged as insecure. + +Secure Ansible example: + +```yaml +- name: Create AKS cluster with RBAC enabled + azure.azcollection.azure_rm_aks: + name: myAKS + resource_group: myRG + enable_rbac: yes +``` + +## Compliant Code Examples +```yaml +- name: Create an AKS instance v3 + azure_rm_aks: + name: myAKS + resource_group: myResourceGroup + location: eastus + dns_prefix: akstest + kubernetes_version: 1.14.6 + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: cf72ca99-f6b9-4004-b0e0-bee10c521948 + client_secret: Password1234! + agent_pool_profiles: + - name: default + count: 1 + vm_size: Standard_DS1_v2 + type: VirtualMachineScaleSets + max_count: 3 + min_count: 1 + enable_rbac: yes + +``` +## Non-Compliant Code Examples +```yaml +- name: Create an AKS instance + azure_rm_aks: + name: myAKS + resource_group: myResourceGroup + location: eastus + dns_prefix: akstest + kubernetes_version: 1.14.6 + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: "cf72ca99-f6b9-4004-b0e0-bee10c521948" + client_secret: "Password1234!" + agent_pool_profiles: + - name: default + count: 1 + vm_size: Standard_DS1_v2 + type: VirtualMachineScaleSets + max_count: 3 + min_count: 1 + enable_rbac: no +- name: Create an AKS instance v2 + azure_rm_aks: + name: myAKS + resource_group: myResourceGroup + location: eastus + dns_prefix: akstest + kubernetes_version: 1.14.6 + linux_profile: + admin_username: azureuser + ssh_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAA... + service_principal: + client_id: "cf72ca99-f6b9-4004-b0e0-bee10c521948" + client_secret: "Password1234!" + agent_pool_profiles: + - name: default + count: 1 + vm_size: Standard_DS1_v2 + type: VirtualMachineScaleSets + max_count: 3 + min_count: 1 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/azure_container_registry_with_no_locks.md b/documentation/rules/ansible/azure/azure_container_registry_with_no_locks.md new file mode 100644 index 00000000..0aaeaf44 --- /dev/null +++ b/documentation/rules/ansible/azure/azure_container_registry_with_no_locks.md @@ -0,0 +1,120 @@ +--- +title: "Azure Container Registry with no locks" +group_id: "Ansible / Azure" +meta: + name: "azure/azure_container_registry_with_no_locks" + id: "581dae78-307d-45d5-aae4-fe2b0db267a5" + display_name: "Azure container registry with no locks" + cloud_provider: "Azure" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `581dae78-307d-45d5-aae4-fe2b0db267a5` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_lock_module.html) + +### Description + +Azure Container Registries must be protected by Azure resource locks to prevent accidental or unauthorized deletion or modification of container images and registry configuration. + +In Ansible playbooks, tasks that create or manage ACRs using the `azure.azcollection.azure_rm_containerregistry` or `azure_rm_containerregistry` modules must be accompanied by a lock task using `azure.azcollection.azure_rm_lock` or `azure_rm_lock`. The lock should either target the specific registry—by having `managed_resource_id` contain the registry's `.id`—or be scoped to the same `resource_group` as the registry (lock `resource_group` equals registry `resource_group`). Tasks without a corresponding lock task, or with locks that do not reference the registry by `managed_resource_id` nor share the same `resource_group`, are flagged. + +## Compliant Code Examples +```yaml +- name: Create an azure container registry + azure_rm_containerregistry: + name: myRegistry + location: eastus + resource_group: myResourceGroup + admin_user_enabled: true + sku: Premium + tags: + Release: beta1 + Environment: Production +- name: Create a lock for a resource group + azure_rm_lock: + resource_group: myResourceGroup + name: myLock + level: read_only + +``` + +```yaml +- name: Create an azure container registry11 + azure.azcollection.azure_rm_containerregistry: + name: myRegistry + location: eastus + admin_user_enabled: "true" + sku: Premium + tags: + Release: beta1 + Environment: Production + register: acr2 +- name: "Create lock for ACR11" + azure.azcollection.azure_rm_lock: + managed_resource_id: "{{ acr2.id }}" + name: "acr_lock" + level: can_not_delete + +``` +## Non-Compliant Code Examples +```yaml +- name: Create an azure container registryy1 + azure.azcollection.azure_rm_containerregistry: + name: myRegistry + location: eastus + admin_user_enabled: "true" + sku: Premium + tags: + Release: beta1 + Environment: Production + register: acr +- name: "Create lock for ACR1" + azure.azcollection.azure_rm_lock: + managed_resource_id: "{{ acr3.id }}" + name: "acr_lock" + level: can_not_delete + +``` + +```yaml +- name: Create an azure container registry + azure_rm_containerregistry: + name: myRegistry + location: eastus + resource_group: myResourceGroupFake + admin_user_enabled: true + sku: Premium + tags: + Release: beta1 + Environment: Production +- name: Create a lock for a resource group + azure_rm_lock: + resource_group: myResourceGroup32 + name: myLock + level: read_only +- name: Create an azure container registry2 + azure.azcollection.azure_rm_containerregistry: + name: myRegistry + location: eastus + resource_group: someResourceGroup + admin_user_enabled: "true" + sku: Premium + tags: + Release: beta1 + Environment: Production + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/azure_instance_using_basic_authentication.md b/documentation/rules/ansible/azure/azure_instance_using_basic_authentication.md new file mode 100644 index 00000000..7d269950 --- /dev/null +++ b/documentation/rules/ansible/azure/azure_instance_using_basic_authentication.md @@ -0,0 +1,80 @@ +--- +title: "Azure instance using basic authentication" +group_id: "Ansible / Azure" +meta: + name: "azure/azure_instance_using_basic_authentication" + id: "e2d834b7-8b25-4935-af53-4a60668dcbe0" + display_name: "Azure instance using basic authentication" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Best Practices" +--- +## Metadata + +**Id:** `e2d834b7-8b25-4935-af53-4a60668dcbe0` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_virtualmachine_module.html#parameter-linux_config/disable_password_authentication) + +### Description + +Linux virtual machines must require SSH key authentication instead of username/password. Password-based login is susceptible to brute-force attacks and credential compromise, which can lead to unauthorized access and lateral movement. + +For Ansible `azure_rm_virtualmachine` resources, ensure `ssh_password_enabled` is set to `false` and `linux_config.disable_password_authentication` is set to `true` so only SSH key authentication is allowed. This rule applies to resources intended to be Linux VMs (where `os_type` is `"linux"` or unspecified). Resources missing these properties or that allow password authentication are flagged. + +Secure example configuration: + +```yaml +- name: Create Linux VM with SSH keys only + azure_rm_virtualmachine: + name: my-linux-vm + resource_group: my-rg + os_type: Linux + ssh_password_enabled: false + linux_config: + disable_password_authentication: true + ssh_public_keys: + - path: /home/azureuser/.ssh/authorized_keys + key_data: "{{ lookup('file','~/.ssh/id_rsa.pub') }}" +``` + +## Compliant Code Examples +```yaml +--- +- name: Create a VM with a custom image + azure_rm_virtualmachine: + resource_group: myResourceGroup + name: testvm001 + vm_size: Standard_DS1_v2 + ssh_password_enabled: false + ssh_public_keys: + - path: ~/.ssh/id_rsa.pub + key_data: somegeneratedkeydata + image: customimage001 + os_type: Linux + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create a VM with a custom image + azure_rm_virtualmachine: + resource_group: myResourceGroup + name: testvm001 + vm_size: Standard_DS1_v2 + admin_username: adminUser + admin_password: password01 + image: customimage001 + os_type: Linux + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/cosmosdb_account_ip_range_filter_not_set.md b/documentation/rules/ansible/azure/cosmosdb_account_ip_range_filter_not_set.md new file mode 100644 index 00000000..82fd5216 --- /dev/null +++ b/documentation/rules/ansible/azure/cosmosdb_account_ip_range_filter_not_set.md @@ -0,0 +1,91 @@ +--- +title: "CosmosDB account IP range filter not set" +group_id: "Ansible / Azure" +meta: + name: "azure/cosmosdb_account_ip_range_filter_not_set" + id: "e8c80448-31d8-4755-85fc-6dbab69c2717" + display_name: "CosmosDB account IP range filter not set" + cloud_provider: "Azure" + platform: "Ansible" + severity: "CRITICAL" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `e8c80448-31d8-4755-85fc-6dbab69c2717` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_cosmosdbaccount_module.html#parameter-ip_range_filter) + +### Description + +Cosmos DB accounts should have an IP range filter configured to restrict which client IP addresses can connect. Without one, the account may accept connections from unintended networks, increasing the risk of unauthorized data access. + +In Ansible, the `azure.azcollection.azure_rm_cosmosdbaccount` (and legacy `azure_rm_cosmosdbaccount`) resource must include the `ip_range_filter` property set to the allowed IP addresses or CIDR ranges. Resources missing `ip_range_filter` or with it empty are flagged, as they indicate no network-level IP restrictions. Provide a comma-separated list of IPs/CIDRs to enforce access control. + +Secure example with IP restrictions: + +```yaml +- name: Create Cosmos DB account with IP restrictions + azure.azcollection.azure_rm_cosmosdbaccount: + resource_group: my-rg + name: my-cosmosdb + location: eastus + offer_type: Standard + ip_range_filter: "10.0.0.0/24,203.0.113.5" +``` + +## Compliant Code Examples +```yaml +- name: Create Cosmos DB Account - max + azure_rm_cosmosdbaccount: + resource_group: myResourceGroup + name: myDatabaseAccount + location: westus + kind: mongo_db + geo_rep_locations: + - name: southcentralus + failover_priority: 0 + database_account_offer_type: Standard + ip_range_filter: 10.10.10.10 + enable_multiple_write_locations: yes + virtual_network_rules: + - subnet: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVi + rtualNetwork/subnets/mySubnet + consistency_policy: + default_consistency_level: bounded_staleness + max_staleness_prefix: 10 + max_interval_in_seconds: 1000 + +``` +## Non-Compliant Code Examples +```yaml +- name: Create Cosmos DB Account - max + azure_rm_cosmosdbaccount: + resource_group: myResourceGroup + name: myDatabaseAccount + location: westus + kind: mongo_db + geo_rep_locations: + - name: southcentralus + failover_priority: 0 + database_account_offer_type: Standard + enable_multiple_write_locations: yes + virtual_network_rules: + - subnet: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVi + rtualNetwork/subnets/mySubnet" + consistency_policy: + default_consistency_level: bounded_staleness + max_staleness_prefix: 10 + max_interval_in_seconds: 1000 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/cosmosdb_account_without_tags.md b/documentation/rules/ansible/azure/cosmosdb_account_without_tags.md new file mode 100644 index 00000000..2b0b11ed --- /dev/null +++ b/documentation/rules/ansible/azure/cosmosdb_account_without_tags.md @@ -0,0 +1,80 @@ +--- +title: "Cosmos DB account without tags" +group_id: "Ansible / Azure" +meta: + name: "azure/cosmosdb_account_without_tags" + id: "23a4dc83-4959-4d99-8056-8e051a82bc1e" + display_name: "Cosmos DB account without tags" + cloud_provider: "Azure" + platform: "Ansible" + severity: "LOW" + category: "Build Process" +--- +## Metadata + +**Id:** `23a4dc83-4959-4d99-8056-8e051a82bc1e` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Build Process + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_cosmosdbaccount_module.html) + +### Description + +Cosmos DB account resources must include tags to support asset identification, ownership, and automated security or incident response processes. Without tags, inventory, cost allocation, and security triage become more difficult. + +For Ansible, tasks using the `azure.azcollection.azure_rm_cosmosdbaccount` or `azure_rm_cosmosdbaccount` modules must define the `tags` property as a mapping of key-value pairs. Resources missing the `tags` property or with it undefined are flagged. Include keys such as Owner and Environment to enable governance and automation. + +Secure example: + +```yaml +- name: create cosmosdb account + azure.azcollection.azure_rm_cosmosdbaccount: + name: my-cosmosdb + resource_group: my-rg + location: eastus + kind: GlobalDocumentDB + offer_type: Standard + tags: + Owner: team-abc + Environment: production + Project: billing-service +``` + +## Compliant Code Examples +```yaml +- name: Create Cosmos DB Account - min + azure_rm_cosmosdbaccount: + resource_group: myResourceGroup + name: myDatabaseAccount + location: westus + geo_rep_locations: + - name: southcentralus + failover_priority: 0 + database_account_offer_type: Standard + tags: + t1: t1 + t2: t2 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create Cosmos DB Account - min + azure_rm_cosmosdbaccount: + resource_group: myResourceGroup + name: myDatabaseAccount + location: westus + geo_rep_locations: + - name: southcentralus + failover_priority: 0 + database_account_offer_type: Standard + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/default_azure_storage_account_network_access_is_too_permissive.md b/documentation/rules/ansible/azure/default_azure_storage_account_network_access_is_too_permissive.md new file mode 100644 index 00000000..cbd5f829 --- /dev/null +++ b/documentation/rules/ansible/azure/default_azure_storage_account_network_access_is_too_permissive.md @@ -0,0 +1,118 @@ +--- +title: "Default Azure storage account network access is too permissive" +group_id: "Ansible / Azure" +meta: + name: "azure/default_azure_storage_account_network_access_is_too_permissive" + id: "ca4df748-613a-4fbf-9c76-f02cbd580307" + display_name: "Default Azure storage account network access is too permissive" + cloud_provider: "Azure" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `ca4df748-613a-4fbf-9c76-f02cbd580307` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_storageaccount_module.html#parameter-public_network_access) + +### Description + +Storage accounts must not permit broad public access or use a permissive default ACL. Public network access or a default-allow policy can expose blobs, queues, and file storage to unauthorized users, increasing the risk of data exfiltration. + +For Ansible resources using `azure.azcollection.azure_rm_storageaccount` or `azure_rm_storageaccount`, explicitly set `public_network_access` to `Disabled` and set `network_acls.default_action` to `Deny`. Resources that omit `public_network_access` (the default is `Enabled`), that set `public_network_access: Enabled`, or that set `network_acls.default_action: Allow` are flagged. + +Secure configuration example: + +```yaml +- name: Create secure Azure Storage Account + azure_rm_storageaccount: + resource_group: my-rg + name: mystorageacct + location: eastus + public_network_access: Disabled + network_acls: + default_action: Deny +``` + +## Compliant Code Examples +```yaml +--- +- name: create an account + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + tags: + testing: testing + delete: on-exit + network_acls: + default_action: Deny + +``` + +```yaml +--- +- name: create an account + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + tags: + testing: testing + delete: on-exit + public_network_access: Disabled + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create an account + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + tags: + testing: testing + delete: on-exit + +``` + +```yaml +--- +- name: create an account + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + tags: + testing: testing + delete: on-exit + network_acls: + default_action: Allow + +``` + +```yaml +--- +- name: create an account + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + tags: + testing: testing + delete: on-exit + public_network_access: Enabled + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache.md b/documentation/rules/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache.md new file mode 100644 index 00000000..e6293d8d --- /dev/null +++ b/documentation/rules/ansible/azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache.md @@ -0,0 +1,69 @@ +--- +title: "Firewall rule allows too many hosts to access Redis Cache" +group_id: "Ansible / Azure" +meta: + name: "azure/firewall_rule_allows_too_many_hosts_to_access_redis_cache" + id: "69f72007-502e-457b-bd2d-5012e31ac049" + display_name: "Firewall rule allows too many hosts to access Redis Cache" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `69f72007-502e-457b-bd2d-5012e31ac049` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_rediscachefirewallrule_module.html) + +### Description + +Redis Cache firewall rules should restrict the IP address range to minimize attack surface and prevent broad network access that could allow unauthorized access or lateral movement. + +In Ansible, tasks using `azure.azcollection.azure_rm_rediscachefirewallrule` or `azure_rm_rediscachefirewallrule` must set `start_ip_address` and `end_ip_address` so the numeric range covers at most 255 hosts. Any rule where the computed range (`abs(end - start)`) is greater than 255 is flagged. + +Resources missing these properties or defining overly large ranges should be tightened to a single IP or a narrow range. Alternatively, replace them with network-level controls such as private endpoints or service endpoints to limit access. + +Secure example with a small allowed range: + +```yaml +- name: Allow small Redis access range + azure.azcollection.azure_rm_rediscachefirewallrule: + resource_group: my-rg + name: my-redis + start_ip_address: 10.0.0.10 + end_ip_address: 10.0.0.20 +``` + +## Compliant Code Examples +```yaml +- name: reduced_hosts + azure_rm_rediscachefirewallrule: + resource_group: myResourceGroup + cache_name: myRedisCache + name: myRule + start_ip_address: 192.168.1.1 + end_ip_address: 192.168.1.4 + +``` +## Non-Compliant Code Examples +```yaml +- name: too_many_hosts + azure_rm_rediscachefirewallrule: + resource_group: myResourceGroup + cache_name: myRedisCache + name: myRule + start_ip_address: 192.168.1.1 + end_ip_address: 192.169.1.4 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/key_vault_soft_delete_is_disabled.md b/documentation/rules/ansible/azure/key_vault_soft_delete_is_disabled.md new file mode 100644 index 00000000..388d008e --- /dev/null +++ b/documentation/rules/ansible/azure/key_vault_soft_delete_is_disabled.md @@ -0,0 +1,99 @@ +--- +title: "Key Vault soft delete is disabled" +group_id: "Ansible / Azure" +meta: + name: "azure/key_vault_soft_delete_is_disabled" + id: "881696a8-68c5-4073-85bc-7c38a3deb854" + display_name: "Key Vault soft delete is disabled" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Backup" +--- +## Metadata + +**Id:** `881696a8-68c5-4073-85bc-7c38a3deb854` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Backup + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_keyvault_module.html#parameter-enable_soft_delete) + +### Description + +Key Vaults must have soft delete enabled to prevent permanent loss of keys, secrets, and certificates. This ensures deleted items can be recovered after accidental or malicious deletion. + +This rule checks Ansible tasks using the `azure.azcollection.azure_rm_keyvault` or `azure_rm_keyvault` modules and requires the `enable_soft_delete` property to be defined and set to `true`. Resources missing `enable_soft_delete` or with `enable_soft_delete: false` are flagged as insecure. Consider enabling purge protection for additional safeguards against permanent deletion. + +Secure configuration example: + +```yaml +- name: Create Key Vault with soft delete enabled + azure.azcollection.azure_rm_keyvault: + name: myKeyVault + resource_group: myResourceGroup + location: eastus + sku: standard + enable_soft_delete: true +``` + +## Compliant Code Examples +```yaml +- name: Create instance of Key Vault + azure_rm_keyvault: + resource_group: myResourceGroup + vault_name: samplekeyvault + enabled_for_deployment: yes + enable_soft_delete: yes + vault_tenant: 72f98888-8666-4144-9199-2d7cd0111111 + sku: + name: standard + access_policies: + - tenant_id: 72f98888-8666-4144-9199-2d7cd0111111 + object_id: 99998888-8666-4144-9199-2d7cd0111111 + keys: + - get + - list + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create instance of Key Vault + azure_rm_keyvault: + resource_group: myResourceGroup + vault_name: samplekeyvault + enabled_for_deployment: yes + enable_soft_delete: no + vault_tenant: 72f98888-8666-4144-9199-2d7cd0111111 + sku: + name: standard + access_policies: + - tenant_id: 72f98888-8666-4144-9199-2d7cd0111111 + object_id: 99998888-8666-4144-9199-2d7cd0111111 + keys: + - get + - list +- name: Create instance of Key Vault 02 + azure_rm_keyvault: + resource_group: myResourceGroup 02 + vault_name: samplekeyvault + enabled_for_deployment: yes + vault_tenant: 72f98888-8666-4144-9199-2d7cd0111111 + sku: + name: standard + access_policies: + - tenant_id: 72f98888-8666-4144-9199-2d7cd0111111 + object_id: 99998888-8666-4144-9199-2d7cd0111111 + keys: + - get + - list + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/log_retention_is_not_set.md b/documentation/rules/ansible/azure/log_retention_is_not_set.md new file mode 100644 index 00000000..3f63c1d8 --- /dev/null +++ b/documentation/rules/ansible/azure/log_retention_is_not_set.md @@ -0,0 +1,66 @@ +--- +title: "Log retention is not set" +group_id: "Ansible / Azure" +meta: + name: "azure/log_retention_is_not_set" + id: "0461b4fd-21ef-4687-929e-484ee4796785" + display_name: "Log retention is not set" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `0461b4fd-21ef-4687-929e-484ee4796785` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlconfiguration_module.html) + +### Description + +PostgreSQL servers must retain logs to support security incident investigation and satisfy audit and compliance requirements. Without log retention, attackers or misconfigurations may go undetected and forensic analysis is impeded. + +In Ansible playbooks using the `azure.azcollection.azure_rm_postgresqlconfiguration` or `azure_rm_postgresqlconfiguration` modules, the configuration entry with `name: log_retention` must have `value: on` (case-insensitive). Tasks missing the `log_retention` configuration or with `value` not equal to `on` are flagged as insecure. + +Secure Ansible example: + +```yaml +- name: Ensure PostgreSQL log_retention is enabled + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: my-resource-group + server_name: my-postgres-server + name: log_retention + value: on +``` + +## Compliant Code Examples +```yaml +- name: Update PostgreSQL Server setting + azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_retention + value: on + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Update PostgreSQL Server setting + azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_retention + value: off + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/monitoring_log_profile_without_all_activities.md b/documentation/rules/ansible/azure/monitoring_log_profile_without_all_activities.md new file mode 100644 index 00000000..2ad335b5 --- /dev/null +++ b/documentation/rules/ansible/azure/monitoring_log_profile_without_all_activities.md @@ -0,0 +1,109 @@ +--- +title: "Monitoring log profile without all activities" +group_id: "Ansible / Azure" +meta: + name: "azure/monitoring_log_profile_without_all_activities" + id: "89f84a1e-75f8-47c5-83b5-bee8e2de4168" + display_name: "Monitoring log profile without all activities" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `89f84a1e-75f8-47c5-83b5-bee8e2de4168` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_monitorlogprofile_module.html) + +### Description + +Monitor log profiles must include the Write, Action, and Delete categories so Azure records operations, configuration changes, and deletions. These records support detection, auditing, and forensic investigations. + +In Ansible tasks using `azure.azcollection.azure_rm_monitorlogprofile` (or `azure_rm_monitorlogprofile`), the `categories` property must be defined as a list and include the values `Write`, `Action`, and `Delete` (case-insensitive). Tasks missing the `categories` property or omitting any of these categories are flagged. + +Secure configuration example: + +```yaml +- name: Create monitor log profile + azure_rm_monitorlogprofile: + name: myLogProfile + categories: + - Write + - Action + - Delete + locations: + - eastus + retention_policy: + enabled: false +``` + +## Compliant Code Examples +```yaml +- name: Create a log profile + azure_rm_monitorlogprofile: + name: myProfile + location: eastus + locations: + - eastus + - westus + categories: + - Write + - Action + - Delete + retention_policy: + enabled: false + days: 1 + storage_account: + resource_group: myResourceGroup + name: myStorageAccount + register: output + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create a log profile + azure_rm_monitorlogprofile: + name: myProfile + location: eastus + locations: + - eastus + - westus + categories: + - Write + - Action + retention_policy: + enabled: False + days: 1 + storage_account: + resource_group: myResourceGroup + name: myStorageAccount + register: output + +- name: Create a log profile2 + azure_rm_monitorlogprofile: + name: myProfile + location: eastus + locations: + - eastus + - westus + retention_policy: + enabled: False + days: 1 + storage_account: + resource_group: myResourceGroup + name: myStorageAccount + register: output + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/mysql_ssl_connection_disabled.md b/documentation/rules/ansible/azure/mysql_ssl_connection_disabled.md new file mode 100644 index 00000000..b6ccae7a --- /dev/null +++ b/documentation/rules/ansible/azure/mysql_ssl_connection_disabled.md @@ -0,0 +1,94 @@ +--- +title: "MySQL SSL connection disabled" +group_id: "Ansible / Azure" +meta: + name: "azure/mysql_ssl_connection_disabled" + id: "2a901825-0f3b-4655-a0fe-e0470e50f8e6" + display_name: "MySQL SSL connection disabled" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `2a901825-0f3b-4655-a0fe-e0470e50f8e6` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_mysqlserver_module.html) + +### Description + +MySQL servers must enforce SSL/TLS connections to protect data in transit and prevent interception or man-in-the-middle attacks. For Ansible tasks using the `azure.azcollection.azure_rm_mysqlserver` or `azure_rm_mysqlserver` modules, the `enforce_ssl` property must be defined and set to `true` so the server requires TLS for client connections. + +Resources missing this property or with `enforce_ssl: false` (the default) are flagged. Use Ansible boolean values such as `true` or `yes` to enable this setting. The rule treats Ansible truthy values as valid. + +```yaml +- name: Create Azure MySQL server with SSL enforced + azure.azcollection.azure_rm_mysqlserver: + name: my-mysql-server + resource_group: my-rg + location: eastus + sku: B_Gen5_1 + version: "5.7" + administrator_login: adminuser + administrator_login_password: "{{ mysql_password }}" + enforce_ssl: true +``` + +## Compliant Code Examples +```yaml +- name: Create (or update) MySQL Server + azure.azcollection.azure_rm_mysqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: true + version: 5.6 + admin_username: cloudsa + admin_password: password + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create (or update) MySQL Server + azure.azcollection.azure_rm_mysqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + version: 5.6 + admin_username: cloudsa + admin_password: password +- name: Create (or update) MySQL Server2 + azure.azcollection.azure_rm_mysqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: false + version: 5.6 + admin_username: cloudsa + admin_password: password + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/postgresql_log_checkpoints_disabled.md b/documentation/rules/ansible/azure/postgresql_log_checkpoints_disabled.md new file mode 100644 index 00000000..302a7d1b --- /dev/null +++ b/documentation/rules/ansible/azure/postgresql_log_checkpoints_disabled.md @@ -0,0 +1,127 @@ +--- +title: "PostgreSQL log checkpoints disabled" +group_id: "Ansible / Azure" +meta: + name: "azure/postgresql_log_checkpoints_disabled" + id: "7ab33ac0-e4a3-418f-a673-50da4e34df21" + display_name: "PostgreSQL log checkpoints disabled" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `7ab33ac0-e4a3-418f-a673-50da4e34df21` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlconfiguration_module.html) + +### Description + +PostgreSQL's `log_checkpoints` should be enabled to record checkpoint activity. This improves visibility into I/O behavior and aids detection and troubleshooting of performance or recovery issues. + +In Ansible Azure PostgreSQL configuration resources (`azure.azcollection.azure_rm_postgresqlconfiguration` or `azure_rm_postgresqlconfiguration`), when the `name` property is `log_checkpoints`, the `value` property must be set to `ON` (case-insensitive). Resources missing this setting or with `value` not equal to `ON` are flagged as misconfigured. + +Secure configuration example: + +```yaml +- name: Ensure log_checkpoints is enabled + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: my-rg + server_name: my-pg-server + name: log_checkpoints + value: "ON" + state: present +``` + +## Compliant Code Examples +```yaml +- name: Update PostgreSQL Server setting + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: on +- name: Update PostgreSQL Server setting2 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: On +- name: Update PostgreSQL Server setting3 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: ON +- name: Update PostgreSQL Server setting4 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: on +- name: Update PostgreSQL Server setting5 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: On +- name: Update PostgreSQL Server setting6 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: ON + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Update PostgreSQL Server setting + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: off +- name: Update PostgreSQL Server setting2 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: Off +- name: Update PostgreSQL Server setting3 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: OFF +- name: Update PostgreSQL Server setting4 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: "off" +- name: Update PostgreSQL Server setting5 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: "Off" +- name: Update PostgreSQL Server setting6 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_checkpoints + value: "OFF" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/postgresql_log_connections_not_set.md b/documentation/rules/ansible/azure/postgresql_log_connections_not_set.md new file mode 100644 index 00000000..2a5b007b --- /dev/null +++ b/documentation/rules/ansible/azure/postgresql_log_connections_not_set.md @@ -0,0 +1,124 @@ +--- +title: "PostgreSQL log connections not set" +group_id: "Ansible / Azure" +meta: + name: "azure/postgresql_log_connections_not_set" + id: "7b47138f-ec0e-47dc-8516-e7728fe3cc17" + display_name: "PostgreSQL log connections not set" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `7b47138f-ec0e-47dc-8516-e7728fe3cc17` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlconfiguration_module.html) + +### Description + +PostgreSQL servers must have the server parameter `log_connections` set to `ON` so connection events are recorded for auditing and intrusion detection. Without this logging, connection attempts and session activity can go unnoticed, hampering incident investigation and compliance. + +In Ansible, tasks using the `azure.azcollection.azure_rm_postgresqlconfiguration` or `azure_rm_postgresqlconfiguration` modules must set the `name` property to `log_connections` and the `value` property to `ON`. This rule flags tasks where `name` equals `log_connections` (case-insensitive) and `value` is missing or not `ON` (case-insensitive). Secure configuration example: + +```yaml +- name: Enable PostgreSQL connection logging + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: my-rg + server_name: my-pg-server + name: log_connections + value: "ON" +``` + +## Compliant Code Examples +```yaml +- name: Update PostgreSQL Server setting + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: on +- name: Update PostgreSQL Server setting2 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: On +- name: Update PostgreSQL Server setting3 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: ON +- name: Update PostgreSQL Server setting4 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: on +- name: Update PostgreSQL Server setting5 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: On +- name: Update PostgreSQL Server setting6 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: ON + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Update PostgreSQL Server setting + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: off +- name: Update PostgreSQL Server setting2 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: Off +- name: Update PostgreSQL Server setting3 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: OFF +- name: Update PostgreSQL Server setting4 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: "off" +- name: Update PostgreSQL Server setting5 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: "Off" +- name: Update PostgreSQL Server setting6 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_connections + value: "OFF" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/postgresql_log_disconnections_not_set.md b/documentation/rules/ansible/azure/postgresql_log_disconnections_not_set.md new file mode 100644 index 00000000..f7526e32 --- /dev/null +++ b/documentation/rules/ansible/azure/postgresql_log_disconnections_not_set.md @@ -0,0 +1,126 @@ +--- +title: "PostgreSQL log disconnections not set" +group_id: "Ansible / Azure" +meta: + name: "azure/postgresql_log_disconnections_not_set" + id: "054d07b5-941b-4c28-8eef-18989dc62323" + display_name: "PostgreSQL log disconnections not set" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `054d07b5-941b-4c28-8eef-18989dc62323` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlconfiguration_module.html) + +### Description + +Enabling the PostgreSQL server parameter `log_disconnections` ensures the server records client connection termination events. This is important for detecting abnormal connection patterns, troubleshooting connectivity issues, and supporting forensic investigations. + +For Ansible, the `azure.azcollection.azure_rm_postgresqlconfiguration` (or legacy `azure_rm_postgresqlconfiguration`) resource must have `name: log_disconnections` and `value: ON` (value compared case-insensitively). Resources where `name` is `log_disconnections` but `value` is missing, not a string, or not set to `ON` are flagged as insecure. + +Secure Ansible configuration example: + +```yaml +- name: Enable PostgreSQL log_disconnections + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: my-rg + server_name: my-pg-server + name: log_disconnections + value: ON +``` + +## Compliant Code Examples +```yaml +- name: Update PostgreSQL Server setting + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: on +- name: Update PostgreSQL Server setting2 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: On +- name: Update PostgreSQL Server setting3 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: ON +- name: Update PostgreSQL Server setting4 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: on +- name: Update PostgreSQL Server setting5 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: On +- name: Update PostgreSQL Server setting6 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: ON + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Update PostgreSQL Server setting + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: off +- name: Update PostgreSQL Server setting2 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: Off +- name: Update PostgreSQL Server setting3 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: OFF +- name: Update PostgreSQL Server setting4 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: "off" +- name: Update PostgreSQL Server setting5 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: "Off" +- name: Update PostgreSQL Server setting6 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_disconnections + value: "OFF" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/postgresql_log_duration_not_set.md b/documentation/rules/ansible/azure/postgresql_log_duration_not_set.md new file mode 100644 index 00000000..ae0953c9 --- /dev/null +++ b/documentation/rules/ansible/azure/postgresql_log_duration_not_set.md @@ -0,0 +1,125 @@ +--- +title: "PostgreSQL log duration not set" +group_id: "Ansible / Azure" +meta: + name: "azure/postgresql_log_duration_not_set" + id: "729ebb15-8060-40f7-9017-cb72676a5487" + display_name: "PostgreSQL log duration not set" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `729ebb15-8060-40f7-9017-cb72676a5487` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlconfiguration_module.html) + +### Description + +Enable the PostgreSQL server parameter `log_duration` to record statement execution durations. Without duration logging, slow queries and malicious long-running activity can go undetected, hindering timely detection and forensic investigation. + +In Ansible tasks using the `azure.azcollection.azure_rm_postgresqlconfiguration` or `azure_rm_postgresqlconfiguration` module, the parameter entry with `name: log_duration` must have `value: 'ON'`. Tasks missing the `value` property or with a value other than `ON` (case-insensitive) are flagged. + +Secure Ansible task example: + +```yaml +- name: Enable log_duration for PostgreSQL server + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myPostgresServer + name: log_duration + value: "ON" +``` + +## Compliant Code Examples +```yaml +- name: example1 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: on +- name: example2 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: On +- name: example3 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: ON +- name: example4 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: on +- name: example5 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: On +- name: example6 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: ON + +``` +## Non-Compliant Code Examples +```yaml +- name: example1 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: off +- name: example2 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: Off +- name: example3 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: OFF +- name: example4 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: "off" +- name: example5 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: "Off" +- name: example6 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: log_duration + value: "OFF" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/postgresql_server_without_connection_throttling.md b/documentation/rules/ansible/azure/postgresql_server_without_connection_throttling.md new file mode 100644 index 00000000..9971783a --- /dev/null +++ b/documentation/rules/ansible/azure/postgresql_server_without_connection_throttling.md @@ -0,0 +1,126 @@ +--- +title: "PostgreSQL server without connection throttling" +group_id: "Ansible / Azure" +meta: + name: "azure/postgresql_server_without_connection_throttling" + id: "a9becca7-892a-4af7-b9e1-44bf20a4cd9a" + display_name: "PostgreSQL server without connection throttling" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `a9becca7-892a-4af7-b9e1-44bf20a4cd9a` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlconfiguration_module.html) + +### Description + +Connection throttling must be enabled on PostgreSQL servers to limit concurrent connection attempts and prevent resource exhaustion or availability degradation from runaway clients or connection storms. + +This rule checks Ansible tasks using the `azure.azcollection.azure_rm_postgresqlconfiguration` or `azure_rm_postgresqlconfiguration` module where `name` equals `connection_throttling`. The `value` property must be set to `ON` (case-insensitive). Resources missing this setting or with `value` set to `OFF` (or any value other than `ON`) are flagged as an incorrect configuration. + +Secure Ansible task example: + +```yaml +- name: Enable connection throttling on PostgreSQL server + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myPostgresServer + name: connection_throttling + value: ON +``` + +## Compliant Code Examples +```yaml +- name: Update PostgreSQL Server setting + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: on +- name: Update PostgreSQL Server setting2 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: On +- name: Update PostgreSQL Server setting3 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: ON +- name: Update PostgreSQL Server setting4 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: on +- name: Update PostgreSQL Server setting5 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: On +- name: Update PostgreSQL Server setting6 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: ON + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Update PostgreSQL Server setting + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: off +- name: Update PostgreSQL Server setting2 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: Off +- name: Update PostgreSQL Server setting3 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: OFF +- name: Update PostgreSQL Server setting4 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: "off" +- name: Update PostgreSQL Server setting5 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: "Off" +- name: Update PostgreSQL Server setting6 + azure.azcollection.azure_rm_postgresqlconfiguration: + resource_group: myResourceGroup + server_name: myServer + name: connection_throttling + value: "OFF" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/public_storage_account.md b/documentation/rules/ansible/azure/public_storage_account.md new file mode 100644 index 00000000..a3249a5c --- /dev/null +++ b/documentation/rules/ansible/azure/public_storage_account.md @@ -0,0 +1,86 @@ +--- +title: "Public storage account" +group_id: "Ansible / Azure" +meta: + name: "azure/public_storage_account" + id: "35e2f133-a395-40de-a79d-b260d973d1bd" + display_name: "Public storage account" + cloud_provider: "Azure" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `35e2f133-a395-40de-a79d-b260d973d1bd` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_storageaccount_module.html#parameter-network_acls) + +### Description + +Storage accounts must not allow public network access. Broad network access or open IP ranges expose account endpoints and data to unauthorized access and exfiltration. + +For Ansible `azure_rm_storageaccount` and `azure.azcollection.azure_rm_storageaccount` tasks, ensure `network_acls.default_action` is not set to `"Allow"` (use `"Deny"`). When `default_action` is `"Deny"`, the `network_acls.ip_rules` list must not contain the catch-all `"0.0.0.0/0"`. Resources missing these properties, with `default_action='Allow'`, or with `ip_rules` containing `0.0.0.0/0` are flagged. + +Secure example for an Ansible task: + +```yaml +- name: Create storage account with restricted network access + azure.azcollection.azure_rm_storageaccount: + resource_group: my-rg + name: mystorageacct + location: eastus + network_acls: + default_action: Deny + ip_rules: + - value: 203.0.113.5/32 +``` + +## Compliant Code Examples +```yaml +- name: configure firewall and virtual networks + azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + network_acls: + bypass: AzureServices,Metrics + default_action: Deny + ip_rules: + - value: 1.2.3.4 + action: Allow + +``` +## Non-Compliant Code Examples +```yaml +- name: configure firewall and virtual networks + azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + network_acls: + bypass: AzureServices,Metrics + default_action: Deny + ip_rules: + - value: 0.0.0.0/0 + action: Allow +- name: configure firewall and more virtual networks + azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0003 + type: Standard_RAGRS + network_acls: + bypass: AzureServices,Metrics + default_action: Allow + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/redis_cache_allows_non_ssl_connections.md b/documentation/rules/ansible/azure/redis_cache_allows_non_ssl_connections.md new file mode 100644 index 00000000..04abc736 --- /dev/null +++ b/documentation/rules/ansible/azure/redis_cache_allows_non_ssl_connections.md @@ -0,0 +1,68 @@ +--- +title: "Redis cache allows non-SSL connections" +group_id: "Ansible / Azure" +meta: + name: "azure/redis_cache_allows_non_ssl_connections" + id: "869e7fb4-30f0-4bdb-b360-ad548f337f2f" + display_name: "Redis cache allows non-SSL connections" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `869e7fb4-30f0-4bdb-b360-ad548f337f2f` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_rediscache_module.html) + +### Description + +Allowing non-SSL (plaintext) connections to Azure Cache for Redis exposes data in transit to interception and tampering. This can leak credentials and sensitive cached data or enable man-in-the-middle attacks. + +For Ansible tasks using the `azure.azcollection.azure_rm_rediscache` or `azure_rm_rediscache` modules, the `enable_non_ssl_port` property must be set to `false` or omitted so only SSL/TLS connections are permitted. Resources with `enable_non_ssl_port: true` are flagged. Ensure clients connect over the TLS/SSL port (typically 6380) and validate certificates. + +Secure Ansible configuration example: + +```yaml +- name: Create Redis Cache with TLS-only access + azure.azcollection.azure_rm_rediscache: + resource_group: my-rg + name: my-redis + location: eastus + sku: name=Standard + enable_non_ssl_port: false +``` + +## Compliant Code Examples +```yaml +- name: Non SSl Disallowed + azure_rm_rediscache: + resource_group: myResourceGroup + name: myRedis + enable_non_ssl_port: no +- name: Non SSl Undefined + azure_rm_rediscache: + resource_group: myResourceGroup + name: myRedis + +``` +## Non-Compliant Code Examples +```yaml +- name: Non SSl Allowed + azure_rm_rediscache: + resource_group: myResourceGroup + name: myRedis + enable_non_ssl_port: yes + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/redis_entirely_accessible.md b/documentation/rules/ansible/azure/redis_entirely_accessible.md new file mode 100644 index 00000000..2880ab89 --- /dev/null +++ b/documentation/rules/ansible/azure/redis_entirely_accessible.md @@ -0,0 +1,68 @@ +--- +title: "Redis entirely accessible" +group_id: "Ansible / Azure" +meta: + name: "azure/redis_entirely_accessible" + id: "0d0c12b9-edce-4510-9065-13f6a758750c" + display_name: "Redis entirely accessible" + cloud_provider: "Azure" + platform: "Ansible" + severity: "CRITICAL" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `0d0c12b9-edce-4510-9065-13f6a758750c` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_rediscachefirewallrule_module.html#parameter-start_ip_address) + +### Description + +Allowing a Redis cache firewall rule to use `0.0.0.0` for both start and end addresses grants unrestricted internet access to the cache, exposing it to unauthorized access, data exposure, and potential remote exploitation. + +For Ansible tasks using `azure.azcollection.azure_rm_rediscachefirewallrule` or `azure_rm_rediscachefirewallrule`, the `start_ip_address` and `end_ip_address` properties must be defined and must not be set to `"0.0.0.0"`. Specify a limited IP range or a single trusted IP address (set both start and end to the same IP for a single host). Resources where both `start_ip_address` and `end_ip_address` equal `"0.0.0.0"` are flagged. Restrict access to known management IPs, use VNet integration, or Azure service endpoints to avoid exposing Redis to the public internet. + +Secure example limiting access to a single admin IP: + +```yaml +- name: Allow Redis access from admin IP + azure.azcollection.azure_rm_rediscachefirewallrule: + resource_group: my-resource-group + name: my-redis-cache + start_ip_address: 203.0.113.5 + end_ip_address: 203.0.113.5 +``` + +## Compliant Code Examples +```yaml +- name: Create a Firewall rule for Azure Cache for Redis + azure_rm_rediscachefirewallrule: + resource_group: myResourceGroup + cache_name: myRedisCache + name: myRule + start_ip_address: 192.168.1.1 + end_ip_address: 192.168.1.4 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create a Firewall rule for Azure Cache for Redis + azure_rm_rediscachefirewallrule: + resource_group: myResourceGroup + cache_name: myRedisCache + name: myRule + start_ip_address: 0.0.0.0 + end_ip_address: 0.0.0.0 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/redis_publicly_accessible.md b/documentation/rules/ansible/azure/redis_publicly_accessible.md new file mode 100644 index 00000000..99ec0aed --- /dev/null +++ b/documentation/rules/ansible/azure/redis_publicly_accessible.md @@ -0,0 +1,71 @@ +--- +title: "Redis publicly accessible" +group_id: "Ansible / Azure" +meta: + name: "azure/redis_publicly_accessible" + id: "0632d0db-9190-450a-8bb3-c283bffea445" + display_name: "Redis publicly accessible" + cloud_provider: "Azure" + platform: "Ansible" + severity: "CRITICAL" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `0632d0db-9190-450a-8bb3-c283bffea445` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_rediscachefirewallrule_module.html#parameter-start_ip_address) + +### Description + +Allowing public IP ranges in Azure Redis Cache firewall rules exposes the cache to unauthorized internet access, increasing the risk of data exfiltration and lateral movement. + +The Ansible modules `azure.azcollection.azure_rm_rediscachefirewallrule` and `azure_rm_rediscachefirewallrule` must set `start_ip_address` and `end_ip_address` to private IP ranges (RFC1918). Tasks missing these properties or specifying non-private or public IPs are flagged. + +If access should be limited to Azure resources, prefer virtual network rules or service endpoints instead of broad IP ranges, and ensure any IP range only includes trusted internal addresses. + +Secure configuration example: + +```yaml +- name: allow internal subnet to access redis + azure.azcollection.azure_rm_rediscachefirewallrule: + name: allow-internal + resource_group: my-rg + redis_name: my-redis + start_ip_address: 10.0.0.1 + end_ip_address: 10.0.0.255 +``` + +## Compliant Code Examples +```yaml +- name: Create a Firewall rule for Azure Cache for Redis + azure_rm_rediscachefirewallrule: + resource_group: myResourceGroup + cache_name: myRedisCache + name: myRule + start_ip_address: 192.168.1.1 + end_ip_address: 192.168.1.4 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create a Firewall rule for Azure Cache for Redis + azure_rm_rediscachefirewallrule: + resource_group: myResourceGroup + cache_name: myRedisCache + name: myRule + start_ip_address: 1.2.3.4 + end_ip_address: 2.3.4.5 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/role_definition_allows_custom_role_creation.md b/documentation/rules/ansible/azure/role_definition_allows_custom_role_creation.md new file mode 100644 index 00000000..bd10fbf7 --- /dev/null +++ b/documentation/rules/ansible/azure/role_definition_allows_custom_role_creation.md @@ -0,0 +1,91 @@ +--- +title: "Role definition allows custom role creation" +group_id: "Ansible / Azure" +meta: + name: "azure/role_definition_allows_custom_role_creation" + id: "5c80db8e-03f5-43a2-b4af-1f3f87018157" + display_name: "Role definition allows custom role creation" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `5c80db8e-03f5-43a2-b4af-1f3f87018157` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_roledefinition_module.html#parameter-permissions/actions) + +### Description + +Role definitions must not grant the ability to create or modify other role definitions (`Microsoft.Authorization/roleDefinitions/write`). This capability enables privilege escalation and persistent unauthorized access by allowing creation of custom roles with elevated permissions. + +In Ansible playbooks using the `azure.azcollection.azure_rm_roledefinition` or `azure_rm_roledefinition` modules, the `permissions[].actions` array must not include the literal action `Microsoft.Authorization/roleDefinitions/write` and must not be a wildcard (`*`). This rule flags tasks where `permissions.actions` is `["*"]` or contains `Microsoft.Authorization/roleDefinitions/write`. Ensure the actions list contains only the specific, least-privilege actions required for the role. + +Secure example with no role-definition write permission: + +```yaml +- name: example role + azure.azcollection.azure_rm_roledefinition: + name: customReadOnlyRole + scope: /subscriptions/00000000-0000-0000-0000-000000000000 + permissions: + - actions: + - "Microsoft.Storage/storageAccounts/read" + - "Microsoft.Compute/virtualMachines/read" +``` + +## Compliant Code Examples +```yaml +--- +- name: Create a role definition3 + azure_rm_roledefinition: + name: myTestRole3 + scope: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myresourceGroup + permissions: + - actions: + - "Microsoft.Compute/virtualMachines/read" + data_actions: + - "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write" + assignable_scopes: + - "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create a role definition2 + azure_rm_roledefinition: + name: myTestRole2 + scope: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myresourceGroup + permissions: + - actions: + - "*" + assignable_scopes: + - "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + +``` + +```yaml +--- +- name: Create a role definition + azure_rm_roledefinition: + name: myTestRole + scope: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myresourceGroup + permissions: + - actions: + - "Microsoft.Authorization/roleDefinitions/write" + assignable_scopes: + - "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/security_group_is_not_configured.md b/documentation/rules/ansible/azure/security_group_is_not_configured.md new file mode 100644 index 00000000..7b7e2ab7 --- /dev/null +++ b/documentation/rules/ansible/azure/security_group_is_not_configured.md @@ -0,0 +1,97 @@ +--- +title: "Security group is not configured" +group_id: "Ansible / Azure" +meta: + name: "azure/security_group_is_not_configured" + id: "da4f2739-174f-4cdd-b9ef-dc3f14b5931f" + display_name: "Security group is not configured" + cloud_provider: "Azure" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `da4f2739-174f-4cdd-b9ef-dc3f14b5931f` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_subnet_module.html) + +### Description + +A subnet without an associated Network Security Group (NSG) lacks network-level access controls, increasing exposure to unauthorized access and enabling lateral movement between resources. + +For Ansible `azure_rm_subnet` resources (modules `azure.azcollection.azure_rm_subnet` and `azure_rm_subnet`), the `security_group` or `security_group_name` property must be defined and set to a non-empty value. Resources that omit these properties or set them to null/empty strings are flagged. Ensure the value references the appropriate NSG (name or ID) for your environment. + +Secure configuration example: + +```yaml +- name: Create subnet with NSG + azure.azcollection.azure_rm_subnet: + resource_group: my-rg + virtual_network: my-vnet + name: my-subnet + address_prefix: 10.0.1.0/24 + security_group: my-nsg +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: Create a subnet + azure_rm_subnet: + resource_group: myResourceGroup + virtual_network_name: myVirtualNetwork + name: mySubnet + address_prefix_cidr: 10.1.0.0/24 + security_group: mySecurityGroup + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: Create a subnet1 + azure_rm_subnet: + resource_group: myResourceGroup1 + virtual_network_name: myVirtualNetwork1 + name: mySubnet1 + address_prefix_cidr: "10.1.0.0/24" +- name: Create a subnet2 + azure_rm_subnet: + resource_group: myResourceGroup2 + virtual_network_name: myVirtualNetwork2 + name: mySubnet2 + address_prefix_cidr: "10.1.0.0/24" + security_group: +- name: Create a subnet3 + azure_rm_subnet: + resource_group: myResourceGroup3 + virtual_network_name: myVirtualNetwork3 + name: mySubnet3 + address_prefix_cidr: "10.1.0.0/24" + security_group_name: +- name: Create a subnet4 + azure_rm_subnet: + resource_group: myResourceGroup4 + virtual_network_name: myVirtualNetwork4 + name: mySubnet4 + address_prefix_cidr: "10.1.0.0/24" + security_group: "" +- name: Create a subnet5 + azure_rm_subnet: + resource_group: myResourceGroup5 + virtual_network_name: myVirtualNetwork5 + name: mySubnet5 + address_prefix_cidr: "10.1.0.0/24" + security_group_name: "" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/sensitive_port_is_exposed_to_entire_network.md b/documentation/rules/ansible/azure/sensitive_port_is_exposed_to_entire_network.md new file mode 100644 index 00000000..5343fe1a --- /dev/null +++ b/documentation/rules/ansible/azure/sensitive_port_is_exposed_to_entire_network.md @@ -0,0 +1,358 @@ +--- +title: "Sensitive port is exposed to entire network" +group_id: "Ansible / Azure" +meta: + name: "azure/sensitive_port_is_exposed_to_entire_network" + id: "0ac9abbc-6d7a-41cf-af23-2e57ddb3dbfc" + display_name: "Sensitive port is exposed to entire network" + cloud_provider: "Azure" + platform: "Ansible" + severity: "HIGH" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `0ac9abbc-6d7a-41cf-af23-2e57ddb3dbfc` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** High + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_securitygroup_module.html#parameter-rules) + +### Description + +Inbound network security group rules that allow TCP or UDP access to sensitive service ports from anywhere (for example, 0.0.0.0/0 or ::/0) expose services such as Telnet or POP3 to the public internet, increasing the risk of unauthorized access and exploitation. + +In Ansible tasks using `azure.azcollection.azure_rm_securitygroup` or `azure_rm_securitygroup`, inspect each entry in `rules[]`. A rule is flagged when `access` is `"Allow"`, `direction` is `"Inbound"` (or absent), `source_address_prefix` ends with `"/0"`, `protocol` is TCP/UDP (or `"*"`, which expands to include TCP/UDP), and `destination_port_range` contains a sensitive TCP port. + +The check handles `destination_port_range` as either a string or an array and supports single ports, comma-separated lists, and ranges. Resources missing the `direction` property are treated as inbound and are evaluated. + +Remediate by restricting `source_address_prefix` to specific CIDR ranges or internal/service endpoints, or by removing or denying public Allow rules for those ports. For example, allow only from a trusted management CIDR: + +```yaml +- name: Create NSG with restricted rule + azure_rm_securitygroup: + name: myNSG + resource_group: myRG + rules: + - name: AllowSSHFromMgmt + protocol: Tcp + destination_port_range: 22 + source_address_prefix: 10.0.0.0/24 + access: Allow + direction: Inbound + priority: 1000 +``` + +## Compliant Code Examples +```yaml +- name: foo1 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example1 + priority: 100 + direction: Inbound + access: Deny + protocol: TCP + source_port_range: '*' + destination_port_range: 23 + source_address_prefix: '*' + destination_address_prefix: '*' +- name: foo2 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example2 + priority: 100 + direction: Inbound + access: Allow + protocol: Icmp + source_port_range: '*' + destination_port_range: 23-24 + source_address_prefix: '*' + destination_address_prefix: '*' +- name: foo3 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example3 + priority: 100 + direction: Inbound + access: Allow + protocol: TCP + source_port_range: '*' + destination_port_range: 8-174 + source_address_prefix: 0.0.0.0 + destination_address_prefix: '*' +- name: foo4 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example4 + priority: 100 + direction: Inbound + access: Allow + protocol: TCP + source_port_range: '*' + destination_port_range: 23-196 + source_address_prefix: 192.168.0.0 + destination_address_prefix: '*' +- name: foo5 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example5 + priority: 100 + direction: Inbound + access: Allow + protocol: TCP + source_port_range: '*' + destination_port_range: 23 + source_address_prefix: /1 + destination_address_prefix: '*' +- name: foo6 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example6 + priority: 100 + direction: Inbound + access: Allow + protocol: '*' + source_port_range: '*' + destination_port_range: 43 + source_address_prefix: /0 + destination_address_prefix: '*' +- name: foo7 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example7 + priority: 100 + direction: Inbound + access: Allow + protocol: Icmp + source_port_range: '*' + destination_port_range: 23 + source_address_prefix: internet + destination_address_prefix: '*' +- name: foo8 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example8 + priority: 100 + direction: Inbound + access: Allow + protocol: '*' + source_port_range: '*' + destination_port_range: 22, 24,49-67 + source_address_prefix: any + destination_address_prefix: '*' +- name: foo9 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example9 + priority: 100 + direction: Inbound + access: Allow + protocol: Icmp + source_port_range: '*' + destination_port_range: 23 + source_address_prefix: /0 + destination_address_prefix: '*' +- name: foo10 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example10 + priority: 100 + direction: Inbound + access: Allow + protocol: TCP + source_port_range: '*' + destination_port_range: + - 23 + - 69 + source_address_prefix: 0.0.1.0 + destination_address_prefix: '*' + - name: example11 + priority: 100 + direction: Inbound + access: Allow + protocol: TCP + source_port_range: '*' + destination_port_range: + - 2 + - 310 + source_address_prefix: 0.0.0.0 + destination_address_prefix: '*' + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: foo1 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example1 + priority: 100 + direction: Inbound + access: Allow + protocol: UDP + source_port_range: "*" + destination_port_range: "61621" + source_address_prefix: "/0" + destination_address_prefix: "*" +- name: foo2 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example2 + priority: 100 + direction: Inbound + access: Allow + protocol: TCP + source_port_range: "*" + destination_port_range: "23-34" + source_address_prefix: "1.1.1.1/0" + destination_address_prefix: "*" +- name: foo3 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example3 + priority: 100 + direction: Inbound + access: Allow + protocol: "*" + source_port_range: "*" + destination_port_range: "21-23" + source_address_prefix: "/0" + destination_address_prefix: "*" +- name: foo4 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example4 + priority: 100 + direction: Inbound + access: Allow + protocol: "*" + source_port_range: "*" + destination_port_range: "23" + source_address_prefix: "0.0.0.0/0" + destination_address_prefix: "*" +- name: foo5 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example5 + priority: 100 + direction: Inbound + access: Allow + protocol: "UDP" + source_port_range: "*" + destination_port_range: + - "23" + - "245" + source_address_prefix: "34.15.11.3/0" + destination_address_prefix: "*" +- name: foo6 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example6 + priority: 100 + direction: Inbound + access: Allow + protocol: "TCP" + source_port_range: "*" + destination_port_range: "23" + source_address_prefix: "/0" + destination_address_prefix: "*" +- name: foo7 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example7 + priority: 100 + direction: Inbound + access: Allow + protocol: "UDP" + source_port_range: "*" + destination_port_range: "22-64, 94" + source_address_prefix: "10.0.0.0/0" + destination_address_prefix: "*" +- name: foo8 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example8 + priority: 100 + direction: Inbound + access: Allow + protocol: "TCP" + source_port_range: "*" + destination_port_range: + - "14" + - "23" + - "48" + source_address_prefix: "12.12.12.12/0" + destination_address_prefix: "*" +- name: foo9 + azure_rm_securitygroup: + resource_group: myResourceGroup + name: mysecgroup + rules: + - name: example9 + priority: 100 + direction: Inbound + access: Allow + protocol: "*" + source_port_range: "*" + destination_port_range: + - "12" + - "23-24" + - "46" + source_address_prefix: "/0" + destination_address_prefix: "*" + - name: example10 + priority: 100 + direction: Inbound + access: Allow + protocol: "*" + source_port_range: "*" + destination_port_range: 46-146, 18-36, 1-2, 3 + source_address_prefix: "1.2.3.4/0" + destination_address_prefix: "*" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/small_activity_log_retention_period.md b/documentation/rules/ansible/azure/small_activity_log_retention_period.md new file mode 100644 index 00000000..0d141eac --- /dev/null +++ b/documentation/rules/ansible/azure/small_activity_log_retention_period.md @@ -0,0 +1,126 @@ +--- +title: "Small activity log retention period" +group_id: "Ansible / Azure" +meta: + name: "azure/small_activity_log_retention_period" + id: "37fafbea-dedb-4e0d-852e-d16ee0589326" + display_name: "Small activity log retention period" + cloud_provider: "Azure" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `37fafbea-dedb-4e0d-852e-d16ee0589326` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_monitorlogprofile_module.html) + +### Description + +Activity Log retention must be configured to retain logs for at least 365 days (or indefinitely). Short retention windows hinder incident response, forensic investigations, and regulatory compliance. + +For Ansible `azure.azcollection.azure_rm_monitorlogprofile` / `azure_rm_monitorlogprofile` resources, the `retention_policy.enabled` property must be `true` and `retention_policy.days` must be set to `365` or greater, or to `0` to retain logs indefinitely. Tasks that omit `retention_policy`, set `retention_policy.enabled` to `false` (or `no`), or set `retention_policy.days` to a value between 1 and 364 are flagged. + +Secure configuration example: + +```yaml +- name: Configure Activity Log retention + azure.azcollection.azure_rm_monitorlogprofile: + name: my-log-profile + locations: + - global + categories: + - Write + - Delete + - Action + retention_policy: + enabled: yes + days: 365 +``` + +## Compliant Code Examples +```yaml +- name: Create a log profile + azure_rm_monitorlogprofile: + name: myProfile + location: eastus + locations: + - eastus + - westus + categories: + - Write + - Action + retention_policy: + enabled: true + days: 380 + storage_account: + resource_group: myResourceGroup + name: myStorageAccount + register: output + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create a log profile + azure_rm_monitorlogprofile: + name: myProfile + location: eastus + locations: + - eastus + - westus + categories: + - Write + - Action + retention_policy: + enabled: False + storage_account: + resource_group: myResourceGroup + name: myStorageAccount + register: output + +- name: Create a log profile2 + azure_rm_monitorlogprofile: + name: myProfile + location: eastus + locations: + - eastus + - westus + categories: + - Write + - Action + storage_account: + resource_group: myResourceGroup + name: myStorageAccount + register: output + +- name: Create a log profile3 + azure_rm_monitorlogprofile: + name: myProfile + location: eastus + locations: + - eastus + - westus + categories: + - Write + - Action + retention_policy: + enabled: True + days: 50 + storage_account: + resource_group: myResourceGroup + name: myStorageAccount + register: output + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/sql_server_ingress_from_any_ip.md b/documentation/rules/ansible/azure/sql_server_ingress_from_any_ip.md new file mode 100644 index 00000000..9b1e91c7 --- /dev/null +++ b/documentation/rules/ansible/azure/sql_server_ingress_from_any_ip.md @@ -0,0 +1,84 @@ +--- +title: "SQLServer ingress from any IP" +group_id: "Ansible / Azure" +meta: + name: "azure/sql_server_ingress_from_any_ip" + id: "f4e9ff70-0f3b-4c50-a713-26cbe7ec4039" + display_name: "SQLServer ingress from any IP" + cloud_provider: "Azure" + platform: "Ansible" + severity: "CRITICAL" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `f4e9ff70-0f3b-4c50-a713-26cbe7ec4039` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_sqlfirewallrule_module.html) + +### Description + +Allowing an Azure SQL firewall rule to accept connections from the entire internet (`start_ip_address` set to `0.0.0.0` and `end_ip_address` set to `255.255.255.255`) exposes database servers to unauthorized access and credential brute-force attacks. + +This rule checks Ansible resources using the `azure.azcollection.azure_rm_sqlfirewallrule` (or `azure_rm_sqlfirewallrule`) module. Resources with `start_ip_address` set to `0.0.0.0` and `end_ip_address` set to `255.255.255.255` are flagged. Restrict firewall rules to specific client IPs or CIDR ranges, or use virtual network-based rules to limit access. + +Secure example with a single allowed IP: + +```yaml +- name: Add SQL firewall rule for a specific IP + azure.azcollection.azure_rm_sqlfirewallrule: + resource_group: myResourceGroup + server_name: my-sql-server + name: allow-office-ip + start_ip_address: 203.0.113.5 + end_ip_address: 203.0.113.5 + state: present +``` + +## Compliant Code Examples +```yaml +- name: Create (or update) Firewall Rule + azure.azcollection.azure_rm_sqlfirewallrule: + resource_group: myResourceGroup + server_name: firewallrulecrudtest-6285 + name: firewallrulecrudtest-5370 + start_ip_address: 172.28.10.136 + end_ip_address: 172.28.10.138 +- name: Create (or update) Firewall Rule2 + azure.azcollection.azure_rm_sqlfirewallrule: + resource_group: myResourceGroup + server_name: firewallrulecrudtest-6285 + name: firewallrulecrudtest-5370 + start_ip_address: 0.0.0.0 + end_ip_address: 0.0.0.3 +- name: Create (or update) Firewall Rule3 + azure.azcollection.azure_rm_sqlfirewallrule: + resource_group: myResourceGroup + server_name: firewallrulecrudtest-6285 + name: firewallrulecrudtest-5370 + start_ip_address: 255.255.255.250 + end_ip_address: 255.255.255.255 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create (or update) Firewall Rule + azure.azcollection.azure_rm_sqlfirewallrule: + resource_group: myResourceGroup + server_name: firewallrulecrudtest-6285 + name: firewallrulecrudtest-5370 + start_ip_address: 0.0.0.0 + end_ip_address: 255.255.255.255 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/sql_server_predictable_active_directory_admin_account_name.md b/documentation/rules/ansible/azure/sql_server_predictable_active_directory_admin_account_name.md new file mode 100644 index 00000000..59cc7487 --- /dev/null +++ b/documentation/rules/ansible/azure/sql_server_predictable_active_directory_admin_account_name.md @@ -0,0 +1,80 @@ +--- +title: "SQL Server predictable Active Directory account name" +group_id: "Ansible / Azure" +meta: + name: "azure/sql_server_predictable_active_directory_admin_account_name" + id: "530e8291-2f22-4bab-b7ea-306f1bc2a308" + display_name: "SQL Server predictable Active Directory account name" + cloud_provider: "Azure" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `530e8291-2f22-4bab-b7ea-306f1bc2a308` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_adserviceprincipal_module.html) + +### Description + +Active Directory administrator accounts for Azure SQL Server must not use predictable or common names such as "admin" or "administrator." Predictable account names make privileged accounts easy to discover and enable targeted brute-force and credential-stuffing attacks. + +In Ansible, verify the `azure.azcollection.azure_rm_adserviceprincipal` (or `azure_rm_adserviceprincipal`) task's `ad_user` property is defined, non-empty, and set to a non-predictable, unique name. This rule flags tasks where `ad_user` is missing or `null`, or where the value matches common predictable names (case-insensitive) such as `admin`, `administrator`, `sqladmin`, `root`, `user`, `azure_admin`, `azure_administrator`, or `guest`. Use a clear, non-guessable name for `ad_user`. For example: + +```yaml +- name: Create AD service principal for Azure SQL admin + azure.azcollection.azure_rm_adserviceprincipal: + ad_user: "sqlsvc-prod-01" + password: "{{ lookup('password', '/dev/null length=32') }}" + state: present +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create ad sp + azure_rm_adserviceprincipal: + display_name: my-sp + app_id: '{{ app_id }}' + state: present + tenant: '{{ tenant_id }}' + ad_user: unpredictableName + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create ad sp + azure_rm_adserviceprincipal: + display_name: my-sp + app_id: "{{ app_id }}" + state: present + tenant: "{{ tenant_id }}" + ad_user: admin +- name: create ad sp2 + azure_rm_adserviceprincipal: + display_name: my-sp2 + app_id: "{{ app_id2 }}" + state: present + tenant: "{{ tenant_id2 }}" + ad_user: "" +- name: create ad sp3 + azure_rm_adserviceprincipal: + display_name: my-sp3 + app_id: "{{ app_id3 }}" + state: present + tenant: "{{ tenant_id3 }}" + ad_user: + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/sql_server_predictable_admin_account_name.md b/documentation/rules/ansible/azure/sql_server_predictable_admin_account_name.md new file mode 100644 index 00000000..cc0dd425 --- /dev/null +++ b/documentation/rules/ansible/azure/sql_server_predictable_admin_account_name.md @@ -0,0 +1,86 @@ +--- +title: "SQL Server predictable admin account name" +group_id: "Ansible / Azure" +meta: + name: "azure/sql_server_predictable_admin_account_name" + id: "663062e9-473d-4e87-99bc-6f3684b3df40" + display_name: "SQL Server predictable admin account name" + cloud_provider: "Azure" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `663062e9-473d-4e87-99bc-6f3684b3df40` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_sqlserver_module.html) + +### Description + +Admin usernames for Azure SQL Server must not be empty or use predictable names. Predictable account names (for example, "admin" or "administrator") make it significantly easier for attackers to perform brute-force, credential-stuffing, and targeted authentication attacks. + +For Ansible resources using `azure.azcollection.azure_rm_sqlserver` or `azure_rm_sqlserver`, the `admin_username` property must be defined as a non-empty string. It must not be one of the following predictable names: `admin`, `administrator`, `root`, `user`, `azure_admin`, `azure_administrator`, or `guest`. + +Tasks that omit `admin_username`, set it to an empty value, or use any of the predictable names (checked case-insensitively) are flagged as insecure. + +Secure example: + +```yaml +- name: Create Azure SQL Server + azure.azcollection.azure_rm_sqlserver: + name: my-sql-server + resource_group: my-rg + location: eastus + admin_username: dbadmin01 + admin_password: "{{ sql_admin_password }}" +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: Create (or update) SQL Server + azure_rm_sqlserver: + resource_group: myResourceGroup + name: server_name + location: westus + admin_username: mylogin + admin_password: Testpasswordxyz12! + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: Create (or update) SQL Server1 + azure_rm_sqlserver: + resource_group: myResourceGroup + name: server_name1 + location: westus + admin_username: "" + admin_password: Testpasswordxyz12! +- name: Create (or update) SQL Server2 + azure_rm_sqlserver: + resource_group: myResourceGroup + name: server_name2 + location: westus + admin_username: + admin_password: Testpasswordxyz12! +- name: Create (or update) SQL Server3 + azure_rm_sqlserver: + resource_group: myResourceGroup + name: server_name3 + location: westus + admin_username: admin + admin_password: Testpasswordxyz12! + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/ssl_enforce_is_disabled.md b/documentation/rules/ansible/azure/ssl_enforce_is_disabled.md new file mode 100644 index 00000000..dd79bec7 --- /dev/null +++ b/documentation/rules/ansible/azure/ssl_enforce_is_disabled.md @@ -0,0 +1,170 @@ +--- +title: "SSL enforce disabled" +group_id: "Ansible / Azure" +meta: + name: "azure/ssl_enforce_is_disabled" + id: "961ce567-a16d-4d7d-9027-f0ec2628a555" + display_name: "SSL enforce disabled" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `961ce567-a16d-4d7d-9027-f0ec2628a555` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_postgresqlserver_module.html#parameter-enforce_ssl) + +### Description + +PostgreSQL servers must enforce SSL connections to ensure client‑server traffic is encrypted and prevent credential exposure in transit. For Ansible playbooks using the `azure.azcollection.azure_rm_postgresqlserver` or `azure_rm_postgresqlserver` modules, the `enforce_ssl` parameter must be set to `true` (Ansible `yes`/true). Tasks that omit `enforce_ssl` (it defaults to `false`) or set it to `false` are flagged as insecure. + +Secure configuration example: + +```yaml +- name: Create PostgreSQL server with SSL enforced + azure.azcollection.azure_rm_postgresqlserver: + name: mypgserver + resource_group: my-rg + location: eastus + enforce_ssl: yes +``` + +## Compliant Code Examples +```yaml +- name: Create (or update) PostgreSQL Server + azure.azcollection.azure_rm_postgresqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: yes + admin_username: cloudsa + admin_password: password +- name: Create (or update) PostgreSQL Server2 + azure.azcollection.azure_rm_postgresqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: Yes + admin_username: cloudsa + admin_password: password +- name: Create (or update) PostgreSQL Server3 + azure.azcollection.azure_rm_postgresqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: true + admin_username: cloudsa + admin_password: password +- name: Create (or update) PostgreSQL Server4 + azure.azcollection.azure_rm_postgresqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: true + admin_username: cloudsa + admin_password: password +- name: Create (or update) PostgreSQL Server5 + azure.azcollection.azure_rm_postgresqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: yes + admin_username: cloudsa + admin_password: password +- name: Create (or update) PostgreSQL Server6 + azure.azcollection.azure_rm_postgresqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: Yes + admin_username: cloudsa + admin_password: password +- name: Create (or update) PostgreSQL Server7 + azure.azcollection.azure_rm_postgresqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: 'true' + admin_username: cloudsa + admin_password: password +- name: Create (or update) PostgreSQL Server8 + azure.azcollection.azure_rm_postgresqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: 'True' + admin_username: cloudsa + admin_password: password + +``` +## Non-Compliant Code Examples +```yaml +- name: Create (or update) PostgreSQL Server + azure.azcollection.azure_rm_postgresqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + admin_username: cloudsa + admin_password: password +- name: Create (or update) PostgreSQL Server2 + azure.azcollection.azure_rm_postgresqlserver: + resource_group: myResourceGroup + name: testserver + sku: + name: B_Gen5_1 + tier: Basic + location: eastus + storage_mb: 1024 + enforce_ssl: no + admin_username: cloudsa + admin_password: password + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/storage_account_not_forcing_https.md b/documentation/rules/ansible/azure/storage_account_not_forcing_https.md new file mode 100644 index 00000000..13cc1794 --- /dev/null +++ b/documentation/rules/ansible/azure/storage_account_not_forcing_https.md @@ -0,0 +1,205 @@ +--- +title: "Storage account not forcing HTTPS" +group_id: "Ansible / Azure" +meta: + name: "azure/storage_account_not_forcing_https" + id: "2c99a474-2a3c-4c17-8294-53ffa5ed0522" + display_name: "Storage account not forcing HTTPS" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `2c99a474-2a3c-4c17-8294-53ffa5ed0522` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_storageaccount_module.html#parameter-https_only) + +### Description + +Storage Accounts must enforce HTTPS-only connections to prevent sensitive data from being transmitted in cleartext and reduce the risk of man-in-the-middle interception. For Ansible tasks using `azure.azcollection.azure_rm_storageaccount` or `azure_rm_storageaccount`, the `https_only` property must be set to `true`. Resources where `https_only` is missing (it defaults to `false`) or explicitly set to `false` are flagged. + +Secure example: + +```yaml +- name: Create storage account with HTTPS enforced + azure.azcollection.azure_rm_storageaccount: + name: myStorageAccount + resource_group: myResourceGroup + location: eastus + account_type: Standard_LRS + https_only: true +``` + +## Compliant Code Examples +```yaml +- name: create an account + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: yes + tags: + testing: testing + delete: on-exit +- name: create an account2 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: true + tags: + testing: testing + delete: on-exit +- name: create an account3 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: true + tags: + testing: testing + delete: on-exit +- name: create an account4 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: 'true' + tags: + testing: testing + delete: on-exit +- name: create an account5 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: 'True' + tags: + testing: testing + delete: on-exit +- name: create an account6 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: yes + tags: + testing: testing + delete: on-exit +- name: create an account7 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: Yes + tags: + testing: testing + delete: on-exit +- name: create an account8 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: Yes + tags: + testing: testing + delete: on-exit + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create an account + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + tags: + testing: testing + delete: on-exit +- name: create an account2 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: false + tags: + testing: testing + delete: on-exit +- name: create an account3 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: False + tags: + testing: testing + delete: on-exit +- name: create an account4 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: no + tags: + testing: testing + delete: on-exit +- name: create an account5 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: No + tags: + testing: testing + delete: on-exit +- name: create an account6 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: "false" + tags: + testing: testing + delete: on-exit +- name: create an account7 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: "False" + tags: + testing: testing + delete: on-exit +- name: create an account8 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: "no" + tags: + testing: testing + delete: on-exit +- name: create an account9 + azure.azcollection.azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + https_only: "No" + tags: + testing: testing + delete: on-exit + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/storage_account_not_using_latest_tls_encryption_version.md b/documentation/rules/ansible/azure/storage_account_not_using_latest_tls_encryption_version.md new file mode 100644 index 00000000..794fe0d6 --- /dev/null +++ b/documentation/rules/ansible/azure/storage_account_not_using_latest_tls_encryption_version.md @@ -0,0 +1,65 @@ +--- +title: "Storage account not using latest TLS encryption version" +group_id: "Ansible / Azure" +meta: + name: "azure/storage_account_not_using_latest_tls_encryption_version" + id: "c62746cf-92d5-4649-9acf-7d48d086f2ee" + display_name: "Storage account not using latest TLS encryption version" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `c62746cf-92d5-4649-9acf-7d48d086f2ee` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_storageaccount_module.html#parameter-minimum_tls_version) + +### Description + +Storage accounts must enforce TLS 1.2 to protect data in transit and prevent downgrade attacks using older, vulnerable TLS versions. For Ansible, the `azure_rm_storageaccount` or `azure.azcollection.azure_rm_storageaccount` resource must include the `minimum_tls_version` property set to `"TLS1_2"`. Resources missing `minimum_tls_version` or configured with any value other than `"TLS1_2"` (for example `"TLS1_0"` or `"TLS1_1"`) are flagged. + +## Compliant Code Examples +```yaml +- name: Create an account with kind of FileStorage + azure_rm_storageaccount: + resource_group: myResourceGroup + name: c1h0002 + type: Premium_LRS + kind: FileStorage + minimum_tls_version: TLS1_2 + tags: + testing: testing + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create an account with kind of FileStorage + azure_rm_storageaccount: + resource_group: myResourceGroup + name: c1h0002 + type: Premium_LRS + kind: FileStorage + minimum_tls_version: TLS1_0 + tags: + testing: testing +- name: Create a second account with kind of FileStorage + azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0003 + type: Premium_LRS + kind: FileStorage + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/storage_container_is_publicly_accessible.md b/documentation/rules/ansible/azure/storage_container_is_publicly_accessible.md new file mode 100644 index 00000000..793ddcc6 --- /dev/null +++ b/documentation/rules/ansible/azure/storage_container_is_publicly_accessible.md @@ -0,0 +1,80 @@ +--- +title: "Storage container is publicly accessible" +group_id: "Ansible / Azure" +meta: + name: "azure/storage_container_is_publicly_accessible" + id: "4d3817db-dd35-4de4-a80d-3867157e7f7f" + display_name: "Storage container is publicly accessible" + cloud_provider: "Azure" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `4d3817db-dd35-4de4-a80d-3867157e7f7f` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_storageblob_module.html#parameter-public_access) + +### Description + +Allowing anonymous public read access to Azure Blob Storage containers or their blobs exposes stored data to anyone on the internet, increasing the risk of data exfiltration and compliance violations. In Ansible tasks using `azure.azcollection.azure_rm_storageblob` or `azure_rm_storageblob`, the `public_access` property must not be set to `"blob"` or `"container"`. + +The rule flags tasks where `public_access` (case-insensitive) equals `blob` or `container`. Setting it to `blob` permits anonymous read of individual blobs, while `container` also allows listing container contents. To remediate, omit the `public_access` property or set it to `private`. Use SAS tokens, Azure RBAC, private endpoints, or signed URLs for controlled sharing. + +Secure example: + +```yaml +- name: Create storage blob container (private) + azure.azcollection.azure_rm_storageblob: + resource_group: my-rg + account_name: my-storage-account + container: my-container + public_access: private +``` + +## Compliant Code Examples +```yaml +- name: Create container foo and upload a file + azure_rm_storageblob: + resource_group: myResourceGroup + storage_account_name: clh0002 + container: foo + blob: graylog.png + src: ./files/graylog.png + content_type: application/image +# access mode defaults to private + +``` +## Non-Compliant Code Examples +```yaml +- name: Create container foo and upload a file + azure_rm_storageblob: + resource_group: myResourceGroup + storage_account_name: clh0002 + container: foo + blob: graylog.png + src: ./files/graylog.png + content_type: 'application/image' + public_access: blob +- name: Create container foo2 and upload a file + azure_rm_storageblob: + resource_group: myResourceGroup + storage_account_name: clh0002 + container: foo2 + blob: graylog.png + src: ./files/graylog.png + public_access: container + content_type: 'application/image' + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/trusted_microsoft_services_not_enabled.md b/documentation/rules/ansible/azure/trusted_microsoft_services_not_enabled.md new file mode 100644 index 00000000..38c78c45 --- /dev/null +++ b/documentation/rules/ansible/azure/trusted_microsoft_services_not_enabled.md @@ -0,0 +1,151 @@ +--- +title: "Trusted Microsoft services not enabled" +group_id: "Ansible / Azure" +meta: + name: "azure/trusted_microsoft_services_not_enabled" + id: "1bc398a8-d274-47de-a4c8-6ac867b353de" + display_name: "Trusted Microsoft services not enabled" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `1bc398a8-d274-47de-a4c8-6ac867b353de` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_storageaccount_module.html#parameter-network_acls/bypass) + +### Description + +When a Storage Account's network access is restricted (`network_acls.default_action` set to `Deny`), Trusted Microsoft Services must be allowed to bypass the network rules. This ensures platform features such as Azure Backup, diagnostics/monitoring, and replication can access the account. Without this bypass, backups, telemetry, and other managed operations can fail, impacting data protection and operational visibility. + +In Ansible `azure_rm_storageaccount` or `azure.azcollection.azure_rm_storageaccount` resources, ensure the `network_acls.bypass` property includes the value `AzureServices` (it may be a comma-separated list, for example, `AzureServices,Logging`) whenever `network_acls.default_action` is `Deny`. Resources that omit `network_acls.bypass` or whose `bypass` value does not contain `AzureServices` are flagged. + +Secure configuration example: + +```yaml +- name: Create storage account with AzureServices bypass + azure_rm_storageaccount: + resource_group: my-rg + name: mystorageacct + location: eastus + account_type: Standard_LRS + network_acls: + default_action: Deny + bypass: AzureServices +``` + +## Compliant Code Examples +```yaml +- name: configure firewall and virtual networks + azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + network_acls: + bypass: AzureServices,Metrics + default_action: Deny + virtual_network_rules: + - id: /subscriptions/mySubscriptionId/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet + action: Allow + ip_rules: + - value: 1.2.3.4 + action: Allow + - value: 123.234.123.0/24 + action: Allow +- name: configure firewall and virtual networks2 + azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0003 + type: Standard_RAGRS + network_acls: + default_action: Deny + virtual_network_rules: + - id: /subscriptions/mySubscriptionId/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet + action: Allow + ip_rules: + - value: 1.2.3.4 + action: Allow + - value: 123.234.123.0/24 + action: Allow +- name: configure firewall and virtual networks3 + azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0004 + type: Standard_RAGRS + network_acls: + default_action: Deny + bypass: AzureServices + virtual_network_rules: + - id: /subscriptions/mySubscriptionId/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet + action: Allow + ip_rules: + - value: 1.2.3.4 + action: Allow + - value: 123.234.123.0/24 + action: Allow + +``` +## Non-Compliant Code Examples +```yaml +- name: configure firewall and virtual networks + azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0002 + type: Standard_RAGRS + network_acls: + bypass: Metrics + default_action: Deny + virtual_network_rules: + - id: /subscriptions/mySubscriptionId/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet + action: Allow + ip_rules: + - value: 1.2.3.4 + action: Allow + - value: 123.234.123.0/24 + action: Allow +- name: configure firewall and virtual networks2 + azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0003 + type: Standard_RAGRS + network_acls: + default_action: Deny + bypass: Metrics,Logging + virtual_network_rules: + - id: /subscriptions/mySubscriptionId/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet + action: Allow + ip_rules: + - value: 1.2.3.4 + action: Allow + - value: 123.234.123.0/24 + action: Allow +- name: configure firewall and virtual networks3 + azure_rm_storageaccount: + resource_group: myResourceGroup + name: clh0004 + type: Standard_RAGRS + network_acls: + default_action: Deny + bypass: "" + virtual_network_rules: + - id: /subscriptions/mySubscriptionId/resourceGroups/myResourceGroup/providers/Microsoft.Network/virtualNetworks/myVnet/subnets/mySubnet + action: Allow + ip_rules: + - value: 1.2.3.4 + action: Allow + - value: 123.234.123.0/24 + action: Allow + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/unrestricted_sql_server_acess.md b/documentation/rules/ansible/azure/unrestricted_sql_server_acess.md new file mode 100644 index 00000000..e1786b56 --- /dev/null +++ b/documentation/rules/ansible/azure/unrestricted_sql_server_acess.md @@ -0,0 +1,77 @@ +--- +title: "Unrestricted SQL Server access" +group_id: "Ansible / Azure" +meta: + name: "azure/unrestricted_sql_server_acess" + id: "3f23c96c-f9f5-488d-9b17-605b8da5842f" + display_name: "Unrestricted SQL Server access" + cloud_provider: "Azure" + platform: "Ansible" + severity: "CRITICAL" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `3f23c96c-f9f5-488d-9b17-605b8da5842f` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_sqlfirewallrule_module.html) + +### Description + +Allowing large IP ranges in Azure SQL firewall rules broadens the database attack surface and increases the risk of unauthorized access, brute-force attempts, and data exposure. Firewall rules should grant the minimal address range required. + +For Ansible tasks using `azure_rm_sqlfirewallrule` or `azure.azcollection.azure_rm_sqlfirewallrule`, ensure the `start_ip_address` and `end_ip_address` properties are defined and that the numeric difference between them is less than 256 (that is, a single IP or up to 255 addresses). Tasks that omit these properties, set either address to `0.0.0.0`, or specify a range with difference >= 256 are flagged as insecure. + +Secure configuration example: + +```yaml +- name: Allow single client IP to Azure SQL firewall + azure.azcollection.azure_rm_sqlfirewallrule: + resource_group: my-rg + server_name: my-sql-server + name: allow-client + start_ip_address: 203.0.113.45 + end_ip_address: 203.0.113.45 +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: Create (or update) Firewall Rule + azure_rm_sqlfirewallrule: + resource_group: myResourceGroup + server_name: firewallrulecrudtest-6285 + name: firewallrulecrudtest-5370 + start_ip_address: 172.28.10.136 + end_ip_address: 172.28.10.138 + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: Create (or update) Firewall Rule1 + azure_rm_sqlfirewallrule: + resource_group: myResourceGroup1 + server_name: firewallrulecrudtest-6285 + name: firewallrulecrudtest-5370 + start_ip_address: 0.0.0.0 + end_ip_address: 172.28.11.138 +- name: Create (or update) Firewall Rule2 + azure_rm_sqlfirewallrule: + resource_group: myResourceGroup2 + server_name: firewallrulecrudtest-6285 + name: firewallrulecrudtest-5370 + start_ip_address: 172.28.10.136 + end_ip_address: 172.28.11.138 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/vm_not_attached_to_network.md b/documentation/rules/ansible/azure/vm_not_attached_to_network.md new file mode 100644 index 00000000..15ae2f10 --- /dev/null +++ b/documentation/rules/ansible/azure/vm_not_attached_to_network.md @@ -0,0 +1,79 @@ +--- +title: "VM not attached to network" +group_id: "Ansible / Azure" +meta: + name: "azure/vm_not_attached_to_network" + id: "1e5f5307-3e01-438d-8da6-985307ed25ce" + display_name: "VM not attached to network" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `1e5f5307-3e01-438d-8da6-985307ed25ce` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_virtualmachine_module.html#parameter-network_interface_names) + +### Description + +Virtual machines should reference explicit network interfaces so network security controls (for example, Network Security Groups) can be applied and network exposure is predictable. Without explicit NIC configuration, instances may be created without NSGs or with default networking that exposes them to unintended access. + +For Ansible VM tasks using `azure.azcollection.azure_rm_virtualmachine` or `azure_rm_virtualmachine`, either the `network_interface_names` property (a list of existing NIC names) or the `network_interfaces` property (a list of interface definitions) must be defined. Tasks missing both `network_interface_names` and `network_interfaces` are flagged. This rule verifies the presence of NIC references only and does not validate whether the referenced NICs themselves have NSGs attached. + +Secure configuration examples: + +```yaml +- name: Create VM with NIC name + azure.azcollection.azure_rm_virtualmachine: + name: myVM + resource_group: myRG + network_interface_names: + - myNic + +- name: Create VM with inline NIC definition + azure.azcollection.azure_rm_virtualmachine: + name: myVM2 + resource_group: myRG + network_interfaces: + - name: myNic2 + primary: true +``` + +## Compliant Code Examples +```yaml +- name: Create a VM with a custom image + azure_rm_virtualmachine: + resource_group: myResourceGroup + name: testvm001 + vm_size: Standard_DS1_v2 + admin_username: adminUser + admin_password: password01 + image: customimage001 + network_interfaces: testvm001 + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Create a VM with a custom image + azure_rm_virtualmachine: + resource_group: myResourceGroup + name: testvm001 + vm_size: Standard_DS1_v2 + admin_username: adminUser + admin_password: password01 + image: customimage001 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/waf_is_disabled_for_azure_application_gateway.md b/documentation/rules/ansible/azure/waf_is_disabled_for_azure_application_gateway.md new file mode 100644 index 00000000..b10561f5 --- /dev/null +++ b/documentation/rules/ansible/azure/waf_is_disabled_for_azure_application_gateway.md @@ -0,0 +1,69 @@ +--- +title: "WAF is disabled for Azure Application Gateway" +group_id: "Ansible / Azure" +meta: + name: "azure/waf_is_disabled_for_azure_application_gateway" + id: "2fc5ab5a-c5eb-4ae4-b687-0f16fe77c255" + display_name: "WAF is disabled for Azure Application Gateway" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `2fc5ab5a-c5eb-4ae4-b687-0f16fe77c255` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_appgateway_module.html) + +### Description + +Application Gateway instances must have the Web Application Firewall (WAF) SKU enabled to protect web traffic from application-layer threats like SQL injection, cross-site scripting, and automated attacks. + +For Ansible resources using `azure.azcollection.azure_rm_appgateway` or `azure_rm_appgateway`, the `sku.tier` property must be set to `WAF` or `WAF_v2` (case-insensitive) to enable WAF capabilities. Resources missing `sku.tier` or configured with non-WAF tiers (for example `Standard` or `Standard_v2`) are flagged as insecure. + +Secure configuration example: + +```yaml +- name: Create Application Gateway with WAF_v2 + azure.azcollection.azure_rm_appgateway: + resource_group: myResourceGroup + name: myAppGateway + sku: + tier: WAF_v2 +``` + +## Compliant Code Examples +```yaml +- name: Create instance of Application Gateway + azure_rm_appgateway: + resource_group: myResourceGroup + name: myAppGateway + sku: + name: waf_medium + tier: waf + capacity: 2 + +``` +## Non-Compliant Code Examples +```yaml +- name: Create instance of Application Gateway + azure_rm_appgateway: + resource_group: myResourceGroup + name: myAppGateway + sku: + name: standard_small + tier: standard + capacity: 2 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/azure/web_app_accepting_traffic_other_than_https.md b/documentation/rules/ansible/azure/web_app_accepting_traffic_other_than_https.md new file mode 100644 index 00000000..d90426ff --- /dev/null +++ b/documentation/rules/ansible/azure/web_app_accepting_traffic_other_than_https.md @@ -0,0 +1,80 @@ +--- +title: "Web app accepting traffic other than HTTPS" +group_id: "Ansible / Azure" +meta: + name: "azure/web_app_accepting_traffic_other_than_https" + id: "eb8c2560-8bee-4248-9d0d-e80c8641dd91" + display_name: "Web app accepting traffic other than HTTPS" + cloud_provider: "Azure" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `eb8c2560-8bee-4248-9d0d-e80c8641dd91` + +**Cloud Provider:** Azure + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_webapp_module.html#parameter-https_only) + +### Description + +Azure Web Apps must accept only HTTPS traffic to protect data in transit from interception, tampering, and credential or session-token exposure. For Ansible deployments using the `azure_rm_webapp` or `azure.azcollection.azure_rm_webapp` module, the `https_only` property must be defined and set to `true` (or `yes`). Tasks that omit `https_only` or set it to a `false` value are flagged. + +Secure configuration example: + +```yaml +- name: Create web app with HTTPS only + azure.azcollection.azure_rm_webapp: + name: my-webapp + resource_group: my-rg + plan: my-plan + https_only: yes +``` + +## Compliant Code Examples +```yaml +- name: Create a windows web app with non-exist app service plan + azure_rm_webapp: + resource_group: myResourceGroup + name: myWinWebapp + https_only: true + plan: + resource_group: myAppServicePlan_rg + name: myAppServicePlan + is_linux: false + sku: S1 + +``` +## Non-Compliant Code Examples +```yaml +- name: Create a windows web app with non-exist app service plan + azure_rm_webapp: + resource_group: myResourceGroup + name: myWinWebapp + https_only: false + plan: + resource_group: myAppServicePlan_rg + name: myAppServicePlan + is_linux: false + sku: S1 +- name: Create another windows web app + azure_rm_webapp: + resource_group: myResourceGroup + name: myWinWebapp + plan: + resource_group: myAppServicePlan_rg + name: myAppServicePlan + is_linux: false + sku: S1 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/config/allow_unsafe_lookups_enabled_in_defaults.md b/documentation/rules/ansible/config/allow_unsafe_lookups_enabled_in_defaults.md new file mode 100644 index 00000000..d6d90f7c --- /dev/null +++ b/documentation/rules/ansible/config/allow_unsafe_lookups_enabled_in_defaults.md @@ -0,0 +1,101 @@ +--- +title: "Allow unsafe lookups enabled in defaults" +group_id: "Ansible / Ansible Config" +meta: + name: "config/allow_unsafe_lookups_enabled_in_defaults" + id: "86b97bb4-85c9-462d-8635-cbc057c5c8c5" + display_name: "Allow unsafe lookups enabled in defaults" + cloud_provider: "Ansible Config" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `86b97bb4-85c9-462d-8635-cbc057c5c8c5` + +**Cloud Provider:** Ansible Config + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-allow-unsafe-lookups) + +### Description + +The Ansible `allow_unsafe_lookups` option must be disabled. When enabled, lookup plugins can return values that bypass safety markers, which can expose sensitive data or cause playbooks to process untrusted input. Check the `defaults.allow_unsafe_lookups` property in your Ansible configuration and ensure it is defined and set to `False`. Configurations with this property set to `True` are flagged. Set this option in your `ansible.cfg` under the `[defaults]` section as shown in the following example: + +```ini +[defaults] +allow_unsafe_lookups = False +``` + +## Compliant Code Examples +```ini +[defaults] +action_warnings=True +cowsay_enabled_stencils=bud-frogs, bunny, cheese, daemon, default, dragon, elephant-in-snake, elephant, eyes, hellokitty, kitty, luke-koala, meow, milk, moofasa, moose, ren, sheep, small, stegosaurus, stimpy, supermilker, three-eyes, turkey, turtle, tux, udder, vader-koala, vader, www +cow_selection=default +force_color=False +nocolor=False +nocows=False +any_errors_fatal=False +become_plugins=~/.ansible/plugins/become:/usr/share/ansible/plugins/become +fact_caching=memory +fact_caching_prefix=ansible_facts +fact_caching_timeout=86400 +collections_on_ansible_version_mismatch=warning +collections_path=~/.ansible/collections:/usr/share/ansible/collections +collections_scan_sys_path=True +command_warnings=False +action_plugins=~/.ansible/plugins/action:/usr/share/ansible/plugins/action + +allow_unsafe_lookups=False +``` + +```ini +[defaults] +action_warnings=True +cowsay_enabled_stencils=bud-frogs, bunny, cheese, daemon, default, dragon, elephant-in-snake, elephant, eyes, hellokitty, kitty, luke-koala, meow, milk, moofasa, moose, ren, sheep, small, stegosaurus, stimpy, supermilker, three-eyes, turkey, turtle, tux, udder, vader-koala, vader, www +cow_selection=default +force_color=False +nocolor=False +nocows=False +any_errors_fatal=False +become_plugins=~/.ansible/plugins/become:/usr/share/ansible/plugins/become +fact_caching=memory +fact_caching_prefix=ansible_facts +fact_caching_timeout=86400 +collections_on_ansible_version_mismatch=warning +collections_path=~/.ansible/collections:/usr/share/ansible/collections +collections_scan_sys_path=True +command_warnings=False +action_plugins=~/.ansible/plugins/action:/usr/share/ansible/plugins/action +``` +## Non-Compliant Code Examples +```ini +[defaults] +action_warnings=True +cowsay_enabled_stencils=bud-frogs, bunny, cheese, daemon, default, dragon, elephant-in-snake, elephant, eyes, hellokitty, kitty, luke-koala, meow, milk, moofasa, moose, ren, sheep, small, stegosaurus, stimpy, supermilker, three-eyes, turkey, turtle, tux, udder, vader-koala, vader, www +cow_selection=default +force_color=False +nocolor=False +nocows=False +any_errors_fatal=False +become_plugins=~/.ansible/plugins/become:/usr/share/ansible/plugins/become +fact_caching=memory +fact_caching_prefix=ansible_facts +fact_caching_timeout=86400 +collections_on_ansible_version_mismatch=warning +collections_path=~/.ansible/collections:/usr/share/ansible/collections +collections_scan_sys_path=True +command_warnings=False +action_plugins=~/.ansible/plugins/action:/usr/share/ansible/plugins/action + +allow_unsafe_lookups=True +``` \ No newline at end of file diff --git a/documentation/rules/ansible/config/communication_over_http_in_defaults.md b/documentation/rules/ansible/config/communication_over_http_in_defaults.md new file mode 100644 index 00000000..74c72d13 --- /dev/null +++ b/documentation/rules/ansible/config/communication_over_http_in_defaults.md @@ -0,0 +1,58 @@ +--- +title: "Communication over HTTP in defaults" +group_id: "Ansible / Ansible Config" +meta: + name: "config/communication_over_http_in_defaults" + id: "d7dc9350-74bc-485b-8c85-fed22d276c43" + display_name: "Communication over HTTP in defaults" + cloud_provider: "Ansible Config" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `d7dc9350-74bc-485b-8c85-fed22d276c43` + +**Cloud Provider:** Ansible Config + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/plugins/httpapi.html) + +### Description + +Galaxy `server` URLs must use HTTPS to protect the confidentiality and integrity of downloaded roles and any credentials exchanged. Using plain HTTP exposes downloads and authentication data to interception or tampering. + +In Ansible configuration documents, this is the `groups.galaxy.server` property, which must begin with `https://` instead of `http://`. Resources with a missing `server` property or a value that starts with `http://` are flagged. Ensure the HTTPS endpoint presents a valid TLS certificate and do not disable certificate verification. + +Secure configuration example: + +```yaml +groups: + galaxy: + server: "https://galaxy.example.com" +``` + +## Compliant Code Examples +```ini +[galaxy] +cache_dir=~/.ansible/galaxy_cache +ignore_certs=False +role_skeleton_ignore=^.git$, ^.*/.git_keep$ +server=https://galaxy.ansible.com +``` +## Non-Compliant Code Examples +```ini +[galaxy] +cache_dir=~/.ansible/galaxy_cache +ignore_certs=False +role_skeleton_ignore=^.git$, ^.*/.git_keep$ +server=http://galaxy.ansible.com +``` \ No newline at end of file diff --git a/documentation/rules/ansible/config/logging_of_sensitive_data_in_defaults.md b/documentation/rules/ansible/config/logging_of_sensitive_data_in_defaults.md new file mode 100644 index 00000000..6ec5be9f --- /dev/null +++ b/documentation/rules/ansible/config/logging_of_sensitive_data_in_defaults.md @@ -0,0 +1,164 @@ +--- +title: "Logging of sensitive data in defaults" +group_id: "Ansible / Ansible Config" +meta: + name: "config/logging_of_sensitive_data_in_defaults" + id: "c6473dae-8477-4119-88b7-b909b435ce7b" + display_name: "Logging of sensitive data in defaults" + cloud_provider: "Ansible Config" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `c6473dae-8477-4119-88b7-b909b435ce7b` + +**Cloud Provider:** Ansible Config + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/reference_appendices/logging.html#protecting-sensitive-data-with-no-log) + +### Description + +The Ansible `no_log` setting must be enabled to prevent sensitive data such as passwords, tokens, or PII from being written to logs. Exposed log data can be accessed by unauthorized users or retained in build artifacts. This rule applies to resources of type `ansible_config` in the `defaults` group. The `no_log` property must be defined and set to boolean `true`. Resources missing the `no_log` property or with `no_log` set to `false` are flagged as insecure. + +Secure configuration example for ansible.cfg: + +```ini +[defaults] +no_log = True +``` + +## Compliant Code Examples +```ini +[defaults] +action_warnings=True +cowsay_enabled_stencils=bud-frogs, bunny, cheese, daemon, default, dragon, elephant-in-snake, elephant, eyes, hellokitty, kitty, luke-koala, meow, milk, moofasa, moose, ren, sheep, small, stegosaurus, stimpy, supermilker, three-eyes, turkey, turtle, tux, udder, vader-koala, vader, www +cow_selection=default +force_color=False +nocolor=False +nocows=False +any_errors_fatal=False +become_plugins=~/.ansible/plugins/become:/usr/share/ansible/plugins/become +fact_caching=memory +fact_caching_prefix=ansible_facts +fact_caching_timeout=86400 +collections_on_ansible_version_mismatch=warning +collections_path=~/.ansible/collections:/usr/share/ansible/collections +collections_scan_sys_path=True +command_warnings=False +action_plugins=~/.ansible/plugins/action:/usr/share/ansible/plugins/action +allow_unsafe_lookups=False +ask_pass=False +ask_vault_pass=False +cache_plugins=~/.ansible/plugins/cache:/usr/share/ansible/plugins/cache +callback_plugins=~/.ansible/plugins/callback:/usr/share/ansible/plugins/callback +cliconf_plugins=~/.ansible/plugins/cliconf:/usr/share/ansible/plugins/cliconf +connection_plugins=~/.ansible/plugins/connection:/usr/share/ansible/plugins/connection +debug=False +executable=/bin/sh +filter_plugins=~/.ansible/plugins/filter:/usr/share/ansible/plugins/filter +force_handlers=False +forks=5 +gathering=implicit +gather_subset=all +lookup_plugins=~/.ansible/plugins/lookup:/usr/share/ansible/plugins/lookup +ansible_managed=Ansible managed +module_compression=ZIP_DEFLATED +module_name=command +library=~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules +module_utils=~/.ansible/plugins/module_utils:/usr/share/ansible/plugins/module_utils +netconf_plugins=~/.ansible/plugins/netconf:/usr/share/ansible/plugins/netconf +no_log=True +``` +## Non-Compliant Code Examples +```ini +[defaults] +action_warnings=True +cowsay_enabled_stencils=bud-frogs, bunny, cheese, daemon, default, dragon, elephant-in-snake, elephant, eyes, hellokitty, kitty, luke-koala, meow, milk, moofasa, moose, ren, sheep, small, stegosaurus, stimpy, supermilker, three-eyes, turkey, turtle, tux, udder, vader-koala, vader, www +cow_selection=default +force_color=False +nocolor=False +nocows=False +any_errors_fatal=False +become_plugins=~/.ansible/plugins/become:/usr/share/ansible/plugins/become +fact_caching=memory +fact_caching_prefix=ansible_facts +fact_caching_timeout=86400 +collections_on_ansible_version_mismatch=warning +collections_path=~/.ansible/collections:/usr/share/ansible/collections +collections_scan_sys_path=True +command_warnings=False +action_plugins=~/.ansible/plugins/action:/usr/share/ansible/plugins/action +allow_unsafe_lookups=False +ask_pass=False +ask_vault_pass=False +cache_plugins=~/.ansible/plugins/cache:/usr/share/ansible/plugins/cache +callback_plugins=~/.ansible/plugins/callback:/usr/share/ansible/plugins/callback +cliconf_plugins=~/.ansible/plugins/cliconf:/usr/share/ansible/plugins/cliconf +connection_plugins=~/.ansible/plugins/connection:/usr/share/ansible/plugins/connection +debug=False +executable=/bin/sh +filter_plugins=~/.ansible/plugins/filter:/usr/share/ansible/plugins/filter +force_handlers=False +forks=5 +gathering=implicit +gather_subset=all +lookup_plugins=~/.ansible/plugins/lookup:/usr/share/ansible/plugins/lookup +ansible_managed=Ansible managed +module_compression=ZIP_DEFLATED +module_name=command +library=~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules +module_utils=~/.ansible/plugins/module_utils:/usr/share/ansible/plugins/module_utils +netconf_plugins=~/.ansible/plugins/netconf:/usr/share/ansible/plugins/netconf +no_log=False +``` + +```ini +[defaults] +action_warnings=True +cowsay_enabled_stencils=bud-frogs, bunny, cheese, daemon, default, dragon, elephant-in-snake, elephant, eyes, hellokitty, kitty, luke-koala, meow, milk, moofasa, moose, ren, sheep, small, stegosaurus, stimpy, supermilker, three-eyes, turkey, turtle, tux, udder, vader-koala, vader, www +cow_selection=default +force_color=False +nocolor=False +nocows=False +any_errors_fatal=False +become_plugins=~/.ansible/plugins/become:/usr/share/ansible/plugins/become +fact_caching=memory +fact_caching_prefix=ansible_facts +fact_caching_timeout=86400 +collections_on_ansible_version_mismatch=warning +collections_path=~/.ansible/collections:/usr/share/ansible/collections +collections_scan_sys_path=True +command_warnings=False +action_plugins=~/.ansible/plugins/action:/usr/share/ansible/plugins/action +allow_unsafe_lookups=False +ask_pass=False +ask_vault_pass=False +cache_plugins=~/.ansible/plugins/cache:/usr/share/ansible/plugins/cache +callback_plugins=~/.ansible/plugins/callback:/usr/share/ansible/plugins/callback +cliconf_plugins=~/.ansible/plugins/cliconf:/usr/share/ansible/plugins/cliconf +connection_plugins=~/.ansible/plugins/connection:/usr/share/ansible/plugins/connection +debug=False +executable=/bin/sh +filter_plugins=~/.ansible/plugins/filter:/usr/share/ansible/plugins/filter +force_handlers=False +forks=5 +gathering=implicit +gather_subset=all +lookup_plugins=~/.ansible/plugins/lookup:/usr/share/ansible/plugins/lookup +ansible_managed=Ansible managed +module_compression=ZIP_DEFLATED +module_name=command +library=~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules +module_utils=~/.ansible/plugins/module_utils:/usr/share/ansible/plugins/module_utils +netconf_plugins=~/.ansible/plugins/netconf:/usr/share/ansible/plugins/netconf +``` \ No newline at end of file diff --git a/documentation/rules/ansible/config/privilege_escalation_using_become_plugin_in_defaults.md b/documentation/rules/ansible/config/privilege_escalation_using_become_plugin_in_defaults.md new file mode 100644 index 00000000..476a2106 --- /dev/null +++ b/documentation/rules/ansible/config/privilege_escalation_using_become_plugin_in_defaults.md @@ -0,0 +1,100 @@ +--- +title: "Privilege escalation using become plugin in defaults" +group_id: "Ansible / Ansible Config" +meta: + name: "config/privilege_escalation_using_become_plugin_in_defaults" + id: "404908b6-4954-4611-98f0-e8ceacdabcb1" + display_name: "Privilege escalation using become plugin in defaults" + cloud_provider: "Ansible Config" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `404908b6-4954-4611-98f0-e8ceacdabcb1` + +**Cloud Provider:** Ansible Config + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/plugins/become.html) + +### Description + +Specifying a `become_user` without enabling privilege escalation prevents Ansible from elevating privileges. Tasks intended to run as that user will execute as the invoking user instead, which can cause configuration changes to be applied with incorrect permissions or fail entirely, leading to insecure or inconsistent system state. In the Ansible defaults group, when `defaults.become_user` is defined, the `defaults.become` property must be present and set to `true`. This rule flags defaults entries where `become_user` exists but `become` is missing or set to `false`. + +Secure configuration example: + +```yaml +defaults: + become: true + become_user: root +``` + +## Compliant Code Examples +```ini +[defaults] +action_warnings=True +cowsay_enabled_stencils=bud-frogs, bunny, cheese, daemon, default, dragon, elephant-in-snake, elephant, eyes, hellokitty, kitty, luke-koala, meow, milk, moofasa, moose, ren, sheep, small, stegosaurus, stimpy, supermilker, three-eyes, turkey, turtle, tux, udder, vader-koala, vader, www +cow_selection=default +force_color=False +nocolor=False +nocows=False +any_errors_fatal=False +become_plugins=~/.ansible/plugins/become:/usr/share/ansible/plugins/become +fact_caching=memory +``` + +```ini +[defaults] +action_warnings=True +cowsay_enabled_stencils=bud-frogs, bunny, cheese, daemon, default, dragon, elephant-in-snake, elephant, eyes, hellokitty, kitty, luke-koala, meow, milk, moofasa, moose, ren, sheep, small, stegosaurus, stimpy, supermilker, three-eyes, turkey, turtle, tux, udder, vader-koala, vader, www +cow_selection=default +force_color=False +nocolor=False +nocows=False +any_errors_fatal=False +fact_caching=memory +become_ask_pass=False +become_method=sudo +become=True +become_user=root +``` +## Non-Compliant Code Examples +```ini +[defaults] +action_warnings=True +cowsay_enabled_stencils=bud-frogs, bunny, cheese, daemon, default, dragon, elephant-in-snake, elephant, eyes, hellokitty, kitty, luke-koala, meow, milk, moofasa, moose, ren, sheep, small, stegosaurus, stimpy, supermilker, three-eyes, turkey, turtle, tux, udder, vader-koala, vader, www +cow_selection=default +force_color=False +nocolor=False +nocows=False +any_errors_fatal=False +fact_caching=memory +become_ask_pass=False +become_method=sudo +become_user=root +``` + +```ini +[defaults] +action_warnings=True +cowsay_enabled_stencils=bud-frogs, bunny, cheese, daemon, default, dragon, elephant-in-snake, elephant, eyes, hellokitty, kitty, luke-koala, meow, milk, moofasa, moose, ren, sheep, small, stegosaurus, stimpy, supermilker, three-eyes, turkey, turtle, tux, udder, vader-koala, vader, www +cow_selection=default +force_color=False +nocolor=False +nocows=False +any_errors_fatal=False +fact_caching=memory +become=False +become_ask_pass=False +become_method=sudo +become_user=root +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/bigquery_dataset_is_public.md b/documentation/rules/ansible/gcp/bigquery_dataset_is_public.md new file mode 100644 index 00000000..ab09bbb6 --- /dev/null +++ b/documentation/rules/ansible/gcp/bigquery_dataset_is_public.md @@ -0,0 +1,76 @@ +--- +title: "BigQuery dataset is public" +group_id: "Ansible / GCP" +meta: + name: "gcp/bigquery_dataset_is_public" + id: "2263b286-2fe9-4747-a0ae-8b4768a2bbd2" + display_name: "BigQuery dataset is public" + cloud_provider: "GCP" + platform: "Ansible" + severity: "HIGH" + category: "Access Control" +--- +## Metadata + +**Id:** `2263b286-2fe9-4747-a0ae-8b4768a2bbd2` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** High + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_bigquery_dataset_module.html#parameter-access/special_group) + +### Description + +BigQuery datasets must not grant access to the special group `allAuthenticatedUsers`. This allows any Google account to access the dataset, increasing the risk of sensitive data exposure and regulatory non-compliance. + +For Ansible tasks using the `google.cloud.gcp_bigquery_dataset` (or `gcp_bigquery_dataset`) module, validate the `access` entries and ensure no entry has `special_group` set to `"allAuthenticatedUsers"` (checked case-insensitively). Resources with `access` entries where `special_group` equals `allAuthenticatedUsers` are flagged. Restrict dataset access to specific users, groups, domains, or predefined roles instead. + +Secure Ansible task example (do not include `special_group: allAuthenticatedUsers`): + +```yaml +- name: Create BigQuery dataset with restricted access + google.cloud.gcp_bigquery_dataset: + dataset_id: my_dataset + access: + - role: READER + userByEmail: alice@example.com + - role: OWNER + groupByEmail: admins@example.com +``` + +## Compliant Code Examples +```yaml +- name: create a dataset + google.cloud.gcp_bigquery_dataset: + name: my_example_dataset + dataset_reference: + dataset_id: my_example_dataset + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a dataset + google.cloud.gcp_bigquery_dataset: + name: my_example_dataset + access: + - special_group: allAuthenticatedUsers + dataset_reference: + dataset_id: my_example_dataset + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/client_certificate_disabled.md b/documentation/rules/ansible/gcp/client_certificate_disabled.md new file mode 100644 index 00000000..0d77b593 --- /dev/null +++ b/documentation/rules/ansible/gcp/client_certificate_disabled.md @@ -0,0 +1,116 @@ +--- +title: "Client certificate disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/client_certificate_disabled" + id: "20180133-a0d0-4745-bfe0-94049fbb12a9" + display_name: "Client certificate disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `20180133-a0d0-4745-bfe0-94049fbb12a9` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html) + +### Description + +Client certificate authentication for the Kubernetes control plane ensures administrators authenticate with strong cryptographic credentials, reducing reliance on weaker or shared credentials that can lead to unauthorized control-plane access. + +For Ansible GCP Container Cluster resources (`google.cloud.gcp_container_cluster` and `gcp_container_cluster`), the `master_auth` object must include `client_certificate_config` with `issue_client_certificate: true`. Resources that omit `master_auth`, omit `client_certificate_config`, or set `issue_client_certificate` to `false` are flagged. + +Secure configuration example for an Ansible task: + +```yaml +- name: Create GKE cluster with client certificate enabled + google.cloud.gcp_container_cluster: + name: my-cluster + master_auth: + client_certificate_config: + issue_client_certificate: true +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + client_certificate_config: + issue_client_certificate: yes + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a cluster1 + google.cloud.gcp_container_cluster: + name: my-cluster1 + initial_node_count: 2 + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster2 + google.cloud.gcp_container_cluster: + name: my-cluster2 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster3 + google.cloud.gcp_container_cluster: + name: my-cluster3 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + client_certificate_config: + issue_client_certificate: no + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/cloud_dns_without_dnnsec.md b/documentation/rules/ansible/gcp/cloud_dns_without_dnnsec.md new file mode 100644 index 00000000..6644c1ce --- /dev/null +++ b/documentation/rules/ansible/gcp/cloud_dns_without_dnnsec.md @@ -0,0 +1,98 @@ +--- +title: "Cloud DNS without DNSSEC" +group_id: "Ansible / GCP" +meta: + name: "gcp/cloud_dns_without_dnnsec" + id: "80b15fb1-6207-40f4-a803-6915ae619a03" + display_name: "Cloud DNS without DNSSEC" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `80b15fb1-6207-40f4-a803-6915ae619a03` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_dns_managed_zone_module.html#return-dnssecConfig/state) + +### Description + +DNS zones must have DNSSEC enabled to protect DNS responses from tampering, spoofing, and cache poisoning and to ensure the authenticity and integrity of name resolution. + +For Ansible-managed Google Cloud DNS zones using `google.cloud.gcp_dns_managed_zone` or `gcp_dns_managed_zone`, the `dnssec_config.state` property must be defined and set to `"on"`. Resources missing `dnssec_config`, missing `dnssec_config.state`, or with `dnssec_config.state` not equal to `"on"` are flagged. + +Secure configuration example: + +```yaml +- name: Create DNS managed zone with DNSSEC enabled + google.cloud.gcp_dns_managed_zone: + name: my-managed-zone + dns_name: example.com. + dnssec_config: + state: "on" +``` + +## Compliant Code Examples +```yaml +- name: create a managed zone + google.cloud.gcp_dns_managed_zone: + name: test_object + dns_name: test.somewild2.example.com. + description: test zone + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + dnssec_config: + kind: some_kind + state: on + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a managed zone + google.cloud.gcp_dns_managed_zone: + name: test_object + dns_name: test.somewild2.example.com. + description: test zone + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a second managed zone + google.cloud.gcp_dns_managed_zone: + name: test_object + dns_name: test.somewild2.example.com. + description: test zone + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + dnssec_config: + kind: some_kind +- name: create a third managed zone + google.cloud.gcp_dns_managed_zone: + name: test_object + dns_name: test.somewild2.example.com. + description: test zone + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + dnssec_config: + kind: some_kind + state: off + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on.md b/documentation/rules/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on.md new file mode 100644 index 00000000..8d91c571 --- /dev/null +++ b/documentation/rules/ansible/gcp/cloud_sql_instance_with_contained_database_authentication_on.md @@ -0,0 +1,81 @@ +--- +title: "Cloud SQL instance with contained database authentication on" +group_id: "Ansible / GCP" +meta: + name: "gcp/cloud_sql_instance_with_contained_database_authentication_on" + id: "6d34aff3-fdd2-460c-8190-756a3b4969e8" + display_name: "Cloud SQL instance with contained database authentication on" + cloud_provider: "GCP" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `6d34aff3-fdd2-460c-8190-756a3b4969e8` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html#parameter-settings/database_flags) + +### Description + +Cloud SQL for SQL Server instances must have Contained Database Authentication disabled. Contained database users authenticate at the database level, bypassing server-level authentication and centralized IAM controls. This increases the risk of unauthorized access and unmanaged credentials. + +For Ansible `google.cloud.gcp_sql_instance` or `gcp_sql_instance` resources, ensure `settings.database_flags` includes an entry with `name: "contained database authentication"` and `value: "off"`. Resources that omit this flag or set it to any value other than `"off"` are flagged. The check evaluates the `settings.database_flags` entries. + +Secure configuration example: + +- name: Create Cloud SQL SQL Server instance + google.cloud.gcp_sql_instance: + name: my-sqlserver-instance + database_version: SQLSERVER_2019_STANDARD + settings: + database_flags: + - name: contained database authentication + value: "off" + +## Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: '{{ resource_name }}-2' + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: name1 + value: value1 + tier: db-n1-standard-1 + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: "{{ resource_name }}-2" + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: contained database authentication + value: on + tier: db-n1-standard-1 + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on.md b/documentation/rules/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on.md new file mode 100644 index 00000000..51eeb029 --- /dev/null +++ b/documentation/rules/ansible/gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on.md @@ -0,0 +1,84 @@ +--- +title: "Cloud SQL instance with cross DB ownership chaining on" +group_id: "Ansible / GCP" +meta: + name: "gcp/cloud_sql_instance_with_cross_db_ownership_chaining_on" + id: "9e0c33ed-97f3-4ed6-8be9-bcbf3f65439f" + display_name: "Cloud SQL instance with cross DB ownership chaining on" + cloud_provider: "GCP" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `9e0c33ed-97f3-4ed6-8be9-bcbf3f65439f` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html#parameter-settings/database_flags) + +### Description + +SQL Server instances must have Cross DB Ownership Chaining disabled to prevent cross-database privilege escalation and lateral access between databases. + +For Ansible-managed Google Cloud SQL resources (`google.cloud.gcp_sql_instance` or `gcp_sql_instance`), ensure the `settings.database_flags` entry with name `cross db ownership chaining` is present and its `value` is set to `off`. This check applies only when `database_version` indicates SQL Server. Instances missing the flag or with a value other than `off` are flagged. + +Secure Ansible configuration example: + +```yaml +- name: Create secure Cloud SQL SQLServer instance + google.cloud.gcp_sql_instance: + name: my-sqlserver-instance + database_version: SQLSERVER_2019_STANDARD + settings: + tier: db-custom-1-3840 + database_flags: + - name: "cross db ownership chaining" + value: "off" +``` + +## Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: '{{ resource_name }}-2' + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: name1 + value: value1 + tier: db-n1-standard-1 + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: "{{ resource_name }}-2" + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: cross db ownership chaining + value: on + tier: db-n1-standard-1 + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible.md b/documentation/rules/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible.md new file mode 100644 index 00000000..212ebe35 --- /dev/null +++ b/documentation/rules/ansible/gcp/cloud_storage_anonymous_or_publicly_accessible.md @@ -0,0 +1,86 @@ +--- +title: "Cloud storage anonymous or publicly accessible" +group_id: "Ansible / GCP" +meta: + name: "gcp/cloud_storage_anonymous_or_publicly_accessible" + id: "086031e1-9d4a-4249-acb3-5bfe4c363db2" + display_name: "Cloud storage anonymous or publicly accessible" + cloud_provider: "GCP" + platform: "Ansible" + severity: "CRITICAL" + category: "Access Control" +--- +## Metadata + +**Id:** `086031e1-9d4a-4249-acb3-5bfe4c363db2` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_storage_bucket_module.html) + +### Description + +Cloud Storage buckets must not be anonymously or publicly accessible. Setting an ACL entity to `allUsers` or `allAuthenticatedUsers` grants broad read or write access to anyone on the internet or to any authenticated Google account, risking data exposure or unauthorized modification. + +For Ansible `gcp_storage_bucket` resources (modules `google.cloud.gcp_storage_bucket` and `gcp_storage_bucket`), ensure neither the `acl.entity` nor the `default_object_acl.entity` property is set to `allUsers` or `allAuthenticatedUsers`. If a bucket does not define `acl`, `default_object_acl` must be explicitly defined and must not contain those public entities. Tasks missing `default_object_acl` or with either entity set to `allUsers`/`allAuthenticatedUsers` are flagged. + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a bucket + google.cloud.gcp_storage_bucket: + name: ansible-storage-module + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + acl: + bucket: bucketName + entity: group-example@googlegroups.com + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a bucket1 + google.cloud.gcp_storage_bucket: + name: ansible-storage-module1 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + default_object_acl: + bucket: bucketName1 + entity: allUsers + role: READER +- name: create a bucket2 + google.cloud.gcp_storage_bucket: + name: ansible-storage-module2 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + acl: + bucket: bucketName2 + entity: allAuthenticatedUsers + default_object_acl: + bucket: bucketName2 + entity: allUsers + role: READER +- name: create a bucket3 + google.cloud.gcp_storage_bucket: + name: ansible-storage-module3 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/cloud_storage_bucket_logging_not_enabled.md b/documentation/rules/ansible/gcp/cloud_storage_bucket_logging_not_enabled.md new file mode 100644 index 00000000..fe88adae --- /dev/null +++ b/documentation/rules/ansible/gcp/cloud_storage_bucket_logging_not_enabled.md @@ -0,0 +1,73 @@ +--- +title: "Cloud storage bucket logging not enabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/cloud_storage_bucket_logging_not_enabled" + id: "507df964-ad97-4035-ab14-94a82eabdfdd" + display_name: "Cloud storage bucket logging not enabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `507df964-ad97-4035-ab14-94a82eabdfdd` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_storage_bucket_module.html#parameter-logging) + +### Description + +Cloud Storage buckets must have access logging enabled to provide audit trails for object access and modifications. This is critical for detecting and investigating unauthorized access, data exfiltration, and operational incidents. + +For Ansible tasks using the `google.cloud.gcp_storage_bucket` or `gcp_storage_bucket` modules, the `logging` property must be defined. It should specify a `logBucket` (the destination bucket for logs) and may include `logObjectPrefix` to organize log objects. + +Resources missing the `logging` property are flagged. Ensure the designated log bucket exists and has the necessary IAM permissions so logs can be written and retained according to your retention and compliance requirements. + +Secure example (Ansible task): + +```yaml +- name: Create GCS bucket with access logging enabled + google.cloud.gcp_storage_bucket: + name: my-data-bucket + logging: + logBucket: my-logs-bucket + logObjectPrefix: access-logs/ +``` + +## Compliant Code Examples +```yaml +- name: create a bucket + google.cloud.gcp_storage_bucket: + name: ansible-storage-module + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + logging: + log_bucket: a_bucket_for_logs + log_object_prefix: log + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a bucket + google.cloud.gcp_storage_bucket: + name: ansible-storage-module + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/cloud_storage_bucket_versioning_disabled.md b/documentation/rules/ansible/gcp/cloud_storage_bucket_versioning_disabled.md new file mode 100644 index 00000000..97f6c78e --- /dev/null +++ b/documentation/rules/ansible/gcp/cloud_storage_bucket_versioning_disabled.md @@ -0,0 +1,76 @@ +--- +title: "Cloud storage bucket versioning disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/cloud_storage_bucket_versioning_disabled" + id: "7814ddda-e758-4a56-8be3-289a81ded929" + display_name: "Cloud storage bucket versioning disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `7814ddda-e758-4a56-8be3-289a81ded929` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_storage_bucket_module.html#parameter-versioning) + +### Description + +Cloud Storage buckets should have object versioning enabled to protect against accidental or malicious deletion and allow recovery of prior object states. In Ansible, tasks using the `google.cloud.gcp_storage_bucket` or `gcp_storage_bucket` modules must define the `versioning` parameter and set `versioning.enabled` to `true`. Resources missing the `versioning` parameter or with `versioning.enabled` set to `false` are flagged. + +Secure configuration example: + +```yaml +- name: Create GCS bucket with versioning + google.cloud.gcp_storage_bucket: + name: my-bucket + versioning: + enabled: true +``` + +## Compliant Code Examples +```yaml +- name: create a bucket + google.cloud.gcp_storage_bucket: + name: ansible-storage-module + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + versioning: + enabled: yes + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a bucket + google.cloud.gcp_storage_bucket: + name: ansible-storage-module + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a second bucket + google.cloud.gcp_storage_bucket: + name: ansible-storage-module + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + versioning: + enabled: no + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/cluster_labels_disabled.md b/documentation/rules/ansible/gcp/cluster_labels_disabled.md new file mode 100644 index 00000000..542fb567 --- /dev/null +++ b/documentation/rules/ansible/gcp/cluster_labels_disabled.md @@ -0,0 +1,114 @@ +--- +title: "Cluster labels disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/cluster_labels_disabled" + id: "fbe9b2d0-a2b7-47a1-a534-03775f3013f7" + display_name: "Cluster labels disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "LOW" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `fbe9b2d0-a2b7-47a1-a534-03775f3013f7` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html) + +### Description + +Kubernetes clusters should include resource labels to ensure assets are identifiable and support inventory, policy targeting, and incident response. For Ansible-managed GKE clusters using the `google.cloud.gcp_container_cluster` or `gcp_container_cluster` modules, the `resource_labels` property must be defined and contain at least one key/value pair. Tasks missing the `resource_labels` property or with it set to an empty value (for example, an empty string) are flagged. + +Secure configuration example: + +```yaml +- name: Create GKE cluster with labels + google.cloud.gcp_container_cluster: + name: my-cluster + resource_labels: + env: prod + owner: team-a +``` + +## Compliant Code Examples +```yaml +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + resource_labels: label1 + +``` +## Non-Compliant Code Examples +```yaml +- name: create a cluster1 + google.cloud.gcp_container_cluster: + name: my-cluster1 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster2 + google.cloud.gcp_container_cluster: + name: my-cluster3 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + resource_labels: +- name: create a cluster3 + google.cloud.gcp_container_cluster: + name: my-cluster3 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + resource_labels: "" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/cluster_master_authentication_disabled.md b/documentation/rules/ansible/gcp/cluster_master_authentication_disabled.md new file mode 100644 index 00000000..aecc20d2 --- /dev/null +++ b/documentation/rules/ansible/gcp/cluster_master_authentication_disabled.md @@ -0,0 +1,141 @@ +--- +title: "Cluster master authentication disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/cluster_master_authentication_disabled" + id: "9df7f78f-ebe3-432e-ac3b-b67189c15518" + display_name: "Cluster master authentication disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `9df7f78f-ebe3-432e-ac3b-b67189c15518` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html) + +### Description + +Kubernetes Engine clusters must have master authentication credentials defined so control plane access is not left unauthenticated or ambiguous. This ensures administrative access is explicit and auditable. + +For Ansible GKE cluster resources using the `google.cloud.gcp_container_cluster` or `gcp_container_cluster` modules, the `master_auth` property must be present and its `username` and `password` subproperties must be defined and non-empty strings. This rule flags resources where `master_auth` is undefined or null, or where `master_auth.username` or `master_auth.password` are undefined, null, or empty. If you use alternative authentication mechanisms, ensure they are explicitly configured. Otherwise, provide non-empty credentials so the cluster admin account is not left unspecified. + +Secure configuration example for an Ansible task: + +```yaml +- name: Create GKE cluster with master auth + google.cloud.gcp_container_cluster: + name: my-cluster + zone: us-central1 + master_auth: + username: admin + password: "{{ gke_admin_password }}" +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a cluster1 + google.cloud.gcp_container_cluster: + name: my-cluster1 + initial_node_count: 2 + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster2 + google.cloud.gcp_container_cluster: + name: my-cluster2 + initial_node_count: 2 + master_auth: + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster3 + google.cloud.gcp_container_cluster: + name: my-cluster3 + initial_node_count: 2 + master_auth: + username: cluster_admin + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster4 + google.cloud.gcp_container_cluster: + name: my-cluster4 + initial_node_count: 2 + master_auth: + username: + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster5 + google.cloud.gcp_container_cluster: + name: my-cluster5 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/compute_instance_is_publicly_accessible.md b/documentation/rules/ansible/gcp/compute_instance_is_publicly_accessible.md new file mode 100644 index 00000000..abe4a143 --- /dev/null +++ b/documentation/rules/ansible/gcp/compute_instance_is_publicly_accessible.md @@ -0,0 +1,78 @@ +--- +title: "Compute instance is publicly accessible" +group_id: "Ansible / GCP" +meta: + name: "gcp/compute_instance_is_publicly_accessible" + id: "829f1c60-2bab-44c6-8a21-5cd9d39a2c82" + display_name: "Compute instance is publicly accessible" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `829f1c60-2bab-44c6-8a21-5cd9d39a2c82` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_instance_module.html#parameter-network_interfaces/access_configs) + +### Description + +Compute instances must not be assigned external (public) IP addresses. Public IPs expose instances directly to the internet, increasing the risk of unauthorized access, brute-force attacks, and data exfiltration. + +For Ansible Google Cloud compute instance resources (modules `google.cloud.gcp_compute_instance` and `gcp_compute_instance`), ensure the `network_interfaces[].access_configs` property is not defined. Any `network_interfaces` entry containing `access_configs` indicates an external IP is being assigned and is flagged. Remove `access_configs` to prevent automatic external IP allocation and use Cloud NAT, internal load balancers, or bastion hosts for controlled outbound/inbound access instead. + +Secure configuration example (no external IP): + +```yaml +- name: Create instance without external IP + google.cloud.gcp_compute_instance: + name: my-instance + machine_type: e2-medium + zone: us-central1-a + network_interfaces: + - network: default + subnetwork: default + # no access_configs defined -> no external IP assigned +``` + +## Compliant Code Examples +```yaml +- name: create a instance + google.cloud.gcp_compute_instance: + name: test_object + network_interfaces: + - network: '{{ network }}' + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: create a instance + google.cloud.gcp_compute_instance: + name: test_object + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/cos_node_image_not_used.md b/documentation/rules/ansible/gcp/cos_node_image_not_used.md new file mode 100644 index 00000000..60390464 --- /dev/null +++ b/documentation/rules/ansible/gcp/cos_node_image_not_used.md @@ -0,0 +1,95 @@ +--- +title: "COS node image not used" +group_id: "Ansible / GCP" +meta: + name: "gcp/cos_node_image_not_used" + id: "be41f891-96b1-4b9d-b74f-b922a918c778" + display_name: "COS node image not used" + cloud_provider: "GCP" + platform: "Ansible" + severity: "LOW" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `be41f891-96b1-4b9d-b74f-b922a918c778` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_node_pool_module.html#parameter-config/image_type) + +### Description + +GKE node pools should use Container-Optimized OS (COS) images. COS is a Google-managed, hardened OS with automatic security updates and tighter integration with GKE, reducing exposure to unpatched vulnerabilities and kernel-level attack surface. + +In Ansible, check tasks using the `google.cloud.gcp_container_node_pool` or `gcp_container_node_pool` modules and ensure the `config.image_type` property is defined and starts with `COS` (case-insensitive). Tasks missing `config.image_type` or with values that do not start with `COS` are flagged. + +Secure configuration example: + +```yaml +- name: Create GKE node pool with COS image + google.cloud.gcp_container_node_pool: + name: my-node-pool + initial_node_count: 3 + config: + machine_type: e2-medium + image_type: COS_CONTAINERD +``` + +## Compliant Code Examples +```yaml +- name: create a node pool + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: '{{ cluster }}' + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + config: + image_type: COS + +``` + +```yaml +- name: create a node pool + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: "{{ cluster }}" + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + config: + image_type: COS_CONTAINERD + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a node pool + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: "{{ cluster }}" + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + config: + image_type: WINDOWS_LTSC + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/disk_encryption_disabled.md b/documentation/rules/ansible/gcp/disk_encryption_disabled.md new file mode 100644 index 00000000..924395c8 --- /dev/null +++ b/documentation/rules/ansible/gcp/disk_encryption_disabled.md @@ -0,0 +1,152 @@ +--- +title: "Disk encryption disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/disk_encryption_disabled" + id: "092bae86-6105-4802-99d2-99cd7e7431f3" + display_name: "Disk encryption disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `092bae86-6105-4802-99d2-99cd7e7431f3` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_disk_module.html) + +### Description + +VM disks must be encrypted using customer-supplied (CSEK) or customer-managed (CMEK) keys. This ensures you retain control over key lifecycle and reduces the risk of cloud-managed keys being used to decrypt sensitive data without your authorization. + +For Ansible resources using `google.cloud.gcp_compute_disk` (or `gcp_compute_disk`), the `disk_encryption_key` property must be defined and contain either a non-empty `kms_key_name` (CMEK) or a non-empty `raw_key` (CSEK). This rule flags disks where `disk_encryption_key` is missing or `null`, where both `raw_key` and `kms_key_name` are absent, or where either subproperty is an empty string. + +Prefer using `kms_key_name` (a full KMS crypto key resource name, for example, `projects/.../locations/.../keyRings/.../cryptoKeys/...`) and avoid hardcoding `raw_key` in source code—store secrets in a secure secret manager. + +Secure configuration examples: + +```yaml +- name: create disk with CMEK + google.cloud.gcp_compute_disk: + name: my-disk + zone: us-central1-a + size_gb: 100 + disk_encryption_key: + kms_key_name: projects/my-project/locations/global/keyRings/my-kr/cryptoKeys/my-key +``` + +```yaml +- name: create disk with CSEK (raw key stored securely, not in plaintext) + google.cloud.gcp_compute_disk: + name: my-disk + zone: us-central1-a + size_gb: 100 + disk_encryption_key: + raw_key: REDACTED_BASE64_KEY +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a disk + google.cloud.gcp_compute_disk: + name: test_object + size_gb: 50 + disk_encryption_key: + raw_key: SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0= + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` + +```yaml +#this code is a correct code for which the query should not find any result +- name: create a disk + google.cloud.gcp_compute_disk: + name: test_object + size_gb: 50 + disk_encryption_key: + kms_key_name: disk-crypto-key + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: create a disk3 + google.cloud.gcp_compute_disk: + name: test_object3 + size_gb: 50 + disk_encryption_key: + kms_key_name: + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a disk4 + google.cloud.gcp_compute_disk: + name: test_object4 + size_gb: 50 + disk_encryption_key: + kms_key_name: "" + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` + +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a disk1 + google.cloud.gcp_compute_disk: + name: test_object1 + size_gb: 50 + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a disk3 + google.cloud.gcp_compute_disk: + name: test_object3 + size_gb: 50 + disk_encryption_key: + raw_key: + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a disk4 + google.cloud.gcp_compute_disk: + name: test_object4 + size_gb: 50 + disk_encryption_key: + raw_key: "" + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/dnssec_using_rsasha1.md b/documentation/rules/ansible/gcp/dnssec_using_rsasha1.md new file mode 100644 index 00000000..969127cd --- /dev/null +++ b/documentation/rules/ansible/gcp/dnssec_using_rsasha1.md @@ -0,0 +1,80 @@ +--- +title: "DNSSEC using RSASHA1" +group_id: "Ansible / GCP" +meta: + name: "gcp/dnssec_using_rsasha1" + id: "6cf4c3a7-ceb0-4475-8892-3745b84be24a" + display_name: "DNSSEC using RSASHA1" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `6cf4c3a7-ceb0-4475-8892-3745b84be24a` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_dns_managed_zone_module.html#return-dnssecConfig/defaultKeySpecs/algorithm) + +### Description + +Using the RSASHA1 algorithm for DNSSEC weakens DNS integrity because SHA-1 is deprecated and vulnerable to collision attacks, increasing the risk of forged or tampered DNS responses. + +For Ansible-managed Google Cloud DNS zones (modules `google.cloud.gcp_dns_managed_zone` and `gcp_dns_managed_zone`), the `dnssec_config.defaultKeySpecs.algorithm` property must not be set to `rsasha1` (checked case-insensitively). Resources with `dnssec_config.defaultKeySpecs.algorithm` set to `rsasha1` are flagged. Update the property to a stronger algorithm such as `RSASHA256`, `RSASHA512`, or an ECDSA option like `ECDSAP256SHA256`. + +Secure configuration example: + +```yaml +- name: Create managed zone with secure DNSSEC algorithm + google.cloud.gcp_dns_managed_zone: + name: my-zone + dnssec_config: + defaultKeySpecs: + - algorithm: RSASHA256 +``` + +## Compliant Code Examples +```yaml +- name: create a managed zone + google.cloud.gcp_dns_managed_zone: + name: test_object + dns_name: test.somewild2.example.com. + description: test zone + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + dnssec_config: + defaultKeySpecs: + algorithm: RSASHA256 + state: off + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a managed zone + google.cloud.gcp_dns_managed_zone: + name: test_object + dns_name: test.somewild2.example.com. + description: test zone + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + dnssec_config: + defaultKeySpecs: + algorithm: RSASHA1 + state: off + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/gke_basic_authentication_enabled.md b/documentation/rules/ansible/gcp/gke_basic_authentication_enabled.md new file mode 100644 index 00000000..dd8c4e1c --- /dev/null +++ b/documentation/rules/ansible/gcp/gke_basic_authentication_enabled.md @@ -0,0 +1,140 @@ +--- +title: "GKE basic authentication enabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/gke_basic_authentication_enabled" + id: "344bf8ab-9308-462b-a6b2-697432e40ba1" + display_name: "GKE basic authentication enabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `344bf8ab-9308-462b-a6b2-697432e40ba1` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html) + +### Description + +Disabling GKE basic authentication is required because an embedded cluster username and password can be leaked or abused to gain direct admin access to the Kubernetes API, bypassing IAM and RBAC protections. + +The Ansible GKE resources `google.cloud.gcp_container_cluster` and `gcp_container_cluster` must include a `master_auth` block with both `username` and `password` set to empty strings to indicate basic auth is disabled. Resources that omit `master_auth`, omit either `username` or `password`, or provide non-empty values are flagged. + +Secure configuration example: + +```yaml +- name: Create GKE cluster with basic auth disabled + google.cloud.gcp_container_cluster: + name: my-cluster + master_auth: + username: "" + password: "" +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: '' + password: '' + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a cluster1 + google.cloud.gcp_container_cluster: + name: my-cluster1 + initial_node_count: 2 + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster2 + google.cloud.gcp_container_cluster: + name: my-cluster2 + initial_node_count: 2 + master_auth: + password: "" + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster3 + google.cloud.gcp_container_cluster: + name: my-cluster3 + initial_node_count: 2 + master_auth: + username: "" + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster4 + google.cloud.gcp_container_cluster: + name: my-cluster4 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: "" + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster5 + google.cloud.gcp_container_cluster: + name: my-cluster5 + initial_node_count: 2 + master_auth: + username: "" + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/gke_legacy_authorization_enabled.md b/documentation/rules/ansible/gcp/gke_legacy_authorization_enabled.md new file mode 100644 index 00000000..80e0d1fc --- /dev/null +++ b/documentation/rules/ansible/gcp/gke_legacy_authorization_enabled.md @@ -0,0 +1,90 @@ +--- +title: "GKE legacy authorization enabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/gke_legacy_authorization_enabled" + id: "300a9964-b086-41f7-9378-b6de3ba1c32b" + display_name: "GKE legacy authorization enabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `300a9964-b086-41f7-9378-b6de3ba1c32b` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html) + +### Description + +Legacy Authorization (ABAC) must be disabled on Kubernetes Engine (GKE) clusters. ABAC grants access based on user attributes instead of fine-grained RBAC rules, which can result in overly broad permissions and increase the risk of unauthorized access or privilege escalation. + +For Ansible-managed clusters using the `google.cloud.gcp_container_cluster` or `gcp_container_cluster` modules, the `legacy_abac.enabled` property must be set to `false`. This rule flags cluster resources where `legacy_abac.enabled` is `true`. Ensure the property is explicitly defined as `false` in your cluster declaration to enforce RBAC. + +Secure configuration example: + +```yaml +- name: Create GKE cluster with legacy ABAC disabled + google.cloud.gcp_container_cluster: + name: my-cluster + location: us-central1 + legacy_abac: + enabled: false + initial_node_count: 3 +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + legacy_abac: + enabled: no + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + legacy_abac: + enabled: yes + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/gke_master_authorized_networks_disabled.md b/documentation/rules/ansible/gcp/gke_master_authorized_networks_disabled.md new file mode 100644 index 00000000..70d227a7 --- /dev/null +++ b/documentation/rules/ansible/gcp/gke_master_authorized_networks_disabled.md @@ -0,0 +1,93 @@ +--- +title: "GKE master authorized networks disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/gke_master_authorized_networks_disabled" + id: "d43366c5-80b0-45de-bbe8-2338f4ab0a83" + display_name: "GKE master authorized networks disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `d43366c5-80b0-45de-bbe8-2338f4ab0a83` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html#parameter-master_authorized_networks_config/enabled) + +### Description + +GKE clusters must enable master authorized networks to restrict access to the Kubernetes control plane to trusted network ranges. Without this restriction, unauthorized or network-based access could lead to cluster compromise. + +For Ansible resources using `google.cloud.gcp_container_cluster` or `gcp_container_cluster`, the `master_authorized_networks_config` property must be defined and its `enabled` field set to `true`. Resources missing `master_authorized_networks_config` or with `master_authorized_networks_config.enabled` set to `false` are flagged as insecure. Optionally, include CIDR entries to specify allowed client networks via `master_authorized_networks_config.cidr_blocks`. + +Secure Ansible example: + +```yaml +- name: Create secure GKE cluster with master authorized networks + google.cloud.gcp_container_cluster: + name: my-cluster + location: us-central1 + master_authorized_networks_config: + enabled: true + cidr_blocks: + - cidr_block: 203.0.113.0/24 + display_name: office-network +``` + +## Compliant Code Examples +```yaml +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + location: us-central1-a + auth_kind: serviceaccount + master_authorized_networks_config: + cidr_blocks: + - cidr_block: 192.0.2.0/24 + enabled: yes + state: present + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + location: us-central1-a + auth_kind: serviceaccount + master_authorized_networks_config: + cidr_blocks: + - cidr_block: 192.0.2.0/24 + enabled: no + state: present +- name: create a second cluster + google.cloud.gcp_container_cluster: + name: my-second-cluster + location: us-central1-a + auth_kind: serviceaccount + master_authorized_networks_config: + cidr_blocks: + - cidr_block: 192.0.2.0/24 + state: present +- name: create a third cluster + google.cloud.gcp_container_cluster: + name: my-third-cluster + location: us-central1-a + auth_kind: serviceaccount + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/gke_using_default_service_account.md b/documentation/rules/ansible/gcp/gke_using_default_service_account.md new file mode 100644 index 00000000..53c3b5a7 --- /dev/null +++ b/documentation/rules/ansible/gcp/gke_using_default_service_account.md @@ -0,0 +1,122 @@ +--- +title: "GKE using default service account" +group_id: "Ansible / GCP" +meta: + name: "gcp/gke_using_default_service_account" + id: "dc126833-125a-40fb-905a-ce5f2afde240" + display_name: "GKE using default service account" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Defaults" +--- +## Metadata + +**Id:** `dc126833-125a-40fb-905a-ce5f2afde240` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Defaults + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html#parameter-node_config/service_account) + +### Description + +Kubernetes Engine clusters should not use the default node service account. The default account typically has broad permissions, increasing the blast radius if a node is compromised. + +For Ansible resources using `google.cloud.gcp_container_cluster` or `gcp_container_cluster`, the `node_config.service_account` property must be defined and set to a dedicated, least-privilege IAM service account (full email address). Resources missing `node_config.service_account` or with a value containing `"default"` are flagged. Use a distinct service account with narrowly scoped IAM roles, for example, `my-sa@PROJECT_ID.iam.gserviceaccount.com`. + +Secure configuration example: + +```yaml +- name: Create GKE cluster with custom node service account + google.cloud.gcp_container_cluster: + name: my-cluster + location: us-central1 + node_config: + service_account: my-sa@my-project.iam.gserviceaccount.com +``` + +## Compliant Code Examples +```yaml +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + service_account: "{{ myaccount }}" + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a service account + google.cloud.gcp_iam_service_account: + name: sa-{{ resource_name.split("-")[-1] }}@graphite-playground.google.com.iam.gserviceaccount.com + display_name: My Ansible test key + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + register: myaccount + +``` +## Non-Compliant Code Examples +```yaml +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + service_account: "{{ default }}" + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a service account + google.cloud.gcp_iam_service_account: + name: sa-{{ resource_name.split("-")[-1] }}@graphite-playground.google.com.iam.gserviceaccount.com + display_name: My Ansible test key + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + register: default + +``` + +```yaml +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/google_compute_network_using_default_firewall_rule.md b/documentation/rules/ansible/gcp/google_compute_network_using_default_firewall_rule.md new file mode 100644 index 00000000..4c4a4e99 --- /dev/null +++ b/documentation/rules/ansible/gcp/google_compute_network_using_default_firewall_rule.md @@ -0,0 +1,88 @@ +--- +title: "Google Compute network using default firewall rule" +group_id: "Ansible / GCP" +meta: + name: "gcp/google_compute_network_using_default_firewall_rule" + id: "29b8224a-60e9-4011-8ac2-7916a659841f" + display_name: "Google Compute network using default firewall rule" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `29b8224a-60e9-4011-8ac2-7916a659841f` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_firewall_module.html#parameter-name) + +### Description + +Using a default firewall rule named "default" can expose a Compute Network to overly permissive ingress or egress, violating least-privilege network segmentation and increasing the risk of unauthorized access and lateral movement. + +This rule flags Ansible tasks using the `google.cloud.gcp_compute_firewall` or `gcp_compute_firewall` module where the firewall `name` contains "default" and the `network` property attaches to a network created or registered by a prior `google.cloud.gcp_compute_network` or `gcp_compute_network` task. Specifically, firewall tasks with `name` including "default" and `network` set to the registered network value (for example, `network: "{{ }}"`) are flagged. + +Replace default rules with explicit, least-privilege firewall rules that specify precise allowed ports and source ranges, or reference the intended network and rule names explicitly rather than reusing the default. + +## Compliant Code Examples +```yaml +- name: create a firewall + google.cloud.gcp_compute_firewall: + name: test_object + allowed: + - ip_protocol: tcp + ports: + - '22' + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + network: "{{ my_network }}" +- name: create a network + google.cloud.gcp_compute_network: + name: test_object + auto_create_subnetworks: 'true' + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + register: my_network + +``` +## Non-Compliant Code Examples +```yaml +- name: create a firewall2 + google.cloud.gcp_compute_firewall: + name: default + allowed: + - ip_protocol: tcp + ports: + - '22' + state: present + network: "{{ my_network2 }}" +- name: create a network2 + google.cloud.gcp_compute_network: + name: test_object2 + auto_create_subnetworks: 'true' + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + register: my_network2 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/google_compute_network_using_firewall_allows_port_range.md b/documentation/rules/ansible/gcp/google_compute_network_using_firewall_allows_port_range.md new file mode 100644 index 00000000..bf4251a0 --- /dev/null +++ b/documentation/rules/ansible/gcp/google_compute_network_using_firewall_allows_port_range.md @@ -0,0 +1,112 @@ +--- +title: "Google Compute network using firewall rule that allows port range" +group_id: "Ansible / GCP" +meta: + name: "gcp/google_compute_network_using_firewall_allows_port_range" + id: "7289eebd-a477-4064-8ad4-3c044bd70b00" + display_name: "Google Compute network using firewall rule that allows port range" + cloud_provider: "GCP" + platform: "Ansible" + severity: "LOW" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `7289eebd-a477-4064-8ad4-3c044bd70b00` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_firewall_module.html#parameter-allowed) + +### Description + +Compute network firewall rules must not permit ingress using broad port ranges because ranges increase attack surface, make it harder to apply least privilege, and can unintentionally expose multiple services. + +This check inspects Ansible tasks using the `google.cloud.gcp_compute_firewall` or `gcp_compute_firewall` modules and flags ingress rules where `allowed[].ports[]` entries are numeric ranges matching the `start-end` pattern (for example, `"8000-9000"`). The rule does not match the literal `"0-65535"`. + +The check applies when the firewall's `network` references a compute network task, meaning the firewall `network` equals the compute network's registered name. To resolve, specify explicit single ports or a minimal list of ports and scope ingress with specific source ranges or target tags. + +Secure example with explicit single ports: + +```yaml +- name: Create restricted firewall rule + google.cloud.gcp_compute_firewall: + name: allow-ssh + network: "{{ my_network.registered_name }}" + direction: INGRESS + allowed: + - IPProtocol: tcp + ports: + - "22" + sourceRanges: + - "203.0.113.0/24" +``` + +## Compliant Code Examples +```yaml +- name: create a firewall + google.cloud.gcp_compute_firewall: + name: test_object + allowed: + - ip_protocol: tcp + ports: + - '22' + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + network: "{{ my_network }}" +- name: create a network + google.cloud.gcp_compute_network: + name: test_object + auto_create_subnetworks: 'true' + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + register: my_network + +``` +## Non-Compliant Code Examples +```yaml +- name: create a firewall2 + google.cloud.gcp_compute_firewall: + name: test_object + allowed: + - ip_protocol: tcp + ports: + - '20-1000' + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + network: "{{ my_network2 }}" +- name: create a network2 + google.cloud.gcp_compute_network: + name: test_object + auto_create_subnetworks: 'true' + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + register: my_network2 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports.md b/documentation/rules/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports.md new file mode 100644 index 00000000..6dec1e83 --- /dev/null +++ b/documentation/rules/ansible/gcp/google_compute_network_using_firewall_rule_allows_all_ports.md @@ -0,0 +1,110 @@ +--- +title: "Google Compute network using firewall rule that allows all ports" +group_id: "Ansible / GCP" +meta: + name: "gcp/google_compute_network_using_firewall_rule_allows_all_ports" + id: "3602d273-3290-47b2-80fa-720162b1a8af" + display_name: "Google Compute network using firewall rule that allows all ports" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `3602d273-3290-47b2-80fa-720162b1a8af` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_firewall_module.html#parameter-allowed) + +### Description + +Allowing ingress on all ports (0-65535) greatly increases attack surface by exposing every service port to network scanning and exploitation. This can lead to unauthorized access, lateral movement, and easier compromise of instances. + +This rule flags Ansible tasks using the `google.cloud.gcp_compute_firewall` or `gcp_compute_firewall` module where the rule is ingress and the `allowed` entry contains `ports: ["0-65535"]` for a firewall associated with a compute network referenced by a preceding `google.cloud.gcp_compute_network`/`gcp_compute_network` task. + +The `allowed.ports` property must not include `"0-65535"`. Instead, specify explicit ports or narrow ranges (for example `"80"`, `"443"`, or `"1024-2048"`) and restrict access with appropriate `sourceRanges` or other selectors. + +Secure example (allow only HTTP/HTTPS from a limited source range): + +```yaml +- name: Allow HTTP and HTTPS from internal range + google.cloud.gcp_compute_firewall: + name: allow-web + network: "{{ my_network }}" + direction: INGRESS + allowed: + - IPProtocol: tcp + ports: ["80", "443"] + sourceRanges: ["10.0.0.0/8"] +``` + +## Compliant Code Examples +```yaml +- name: create a firewall + google.cloud.gcp_compute_firewall: + name: test_object + allowed: + - ip_protocol: tcp + ports: + - '22' + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + network: "{{ my_network }}" +- name: create a network + google.cloud.gcp_compute_network: + name: test_object + auto_create_subnetworks: 'true' + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + register: my_network + +``` +## Non-Compliant Code Examples +```yaml +- name: create a firewall2 + google.cloud.gcp_compute_firewall: + name: test_object + allowed: + - ip_protocol: tcp + ports: + - '0-65535' + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + network: "{{ my_network2 }}" +- name: create a network2 + google.cloud.gcp_compute_network: + name: test_object + auto_create_subnetworks: 'true' + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + register: my_network2 + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use.md b/documentation/rules/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use.md new file mode 100644 index 00000000..165f0aa2 --- /dev/null +++ b/documentation/rules/ansible/gcp/google_compute_ssl_policy_weak_cipher_in_use.md @@ -0,0 +1,83 @@ +--- +title: "Google Compute SSL policy weak cipher in use" +group_id: "Ansible / GCP" +meta: + name: "gcp/google_compute_ssl_policy_weak_cipher_in_use" + id: "b28bcd2f-c309-490e-ab7c-35fc4023eb26" + display_name: "Google Compute SSL policy weak cipher in use" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Encryption" +--- +## Metadata + +**Id:** `b28bcd2f-c309-490e-ab7c-35fc4023eb26` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_ssl_policy_module.html) + +### Description + +Compute SSL policies must enforce a minimum TLS version of `TLS_1_2` to prevent use of older, vulnerable protocol versions and weak cipher suites. The `min_tls_version` property on `google.cloud.gcp_compute_ssl_policy` (or `gcp_compute_ssl_policy`) resources must be defined and set to `TLS_1_2`. Resources that omit `min_tls_version` or set it to any other value are flagged. + +```yaml +- name: Create SSL policy with TLS 1.2 minimum + google.cloud.gcp_compute_ssl_policy: + name: my-ssl-policy + profile: MODERN + min_tls_version: TLS_1_2 +``` + +## Compliant Code Examples +```yaml +- name: create a SSL policy + google.cloud.gcp_compute_ssl_policy: + name: test_object + profile: CUSTOM + min_tls_version: TLS_1_2 + custom_features: + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: create a SSL policy + google.cloud.gcp_compute_ssl_policy: + name: test_object + profile: CUSTOM + custom_features: + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a SSL policy2 + google.cloud.gcp_compute_ssl_policy: + name: test_object2 + profile: CUSTOM + min_tls_version: TLS_1_1 + custom_features: + - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled.md b/documentation/rules/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled.md new file mode 100644 index 00000000..b3285c4c --- /dev/null +++ b/documentation/rules/ansible/gcp/google_compute_subnetwork_with_private_google_access_disabled.md @@ -0,0 +1,90 @@ +--- +title: "Google Compute subnetwork with Private Google Access disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/google_compute_subnetwork_with_private_google_access_disabled" + id: "6a4080ae-79bd-42f6-a924-8f534c1c018b" + display_name: "Google Compute subnetwork with Private Google Access disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "LOW" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `6a4080ae-79bd-42f6-a924-8f534c1c018b` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_subnetwork_module.html#parameter-private_ip_google_access) + +### Description + +Subnetworks must have Private Google Access enabled so VM instances with only internal IPs can reach Google APIs and services over Google's internal network. Without Private Google Access, operators may assign external IPs or route traffic over the public internet, increasing attack surface and the risk of data exposure or network-based attacks. + +For Ansible resources using the google.cloud.gcp_compute_subnetwork or gcp_compute_subnetwork modules, the `private_ip_google_access` property must be defined and set to `yes`. Tasks missing this property or with `private_ip_google_access` not equal to `yes` are flagged. + +Secure Ansible example: + +```yaml +- name: Create subnetwork with Private Google Access enabled + google.cloud.gcp_compute_subnetwork: + name: my-subnet + region: us-central1 + ip_cidr_range: 10.0.0.0/24 + network: my-vpc + private_ip_google_access: yes +``` + +## Compliant Code Examples +```yaml +- name: create a subnetwork3 + google.cloud.gcp_compute_subnetwork: + name: ansiblenet + region: us-west1 + network: "{{ network }}" + ip_cidr_range: 172.16.0.0/16 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + private_ip_google_access: yes + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: create a subnetwork2 + google.cloud.gcp_compute_subnetwork: + name: ansiblenet + region: us-west1 + network: "{{ network }}" + ip_cidr_range: 172.16.0.0/16 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + private_ip_google_access: no + state: present + +``` + +```yaml +- name: create a subnetwork + google.cloud.gcp_compute_subnetwork: + name: ansiblenet + region: us-west1 + network: "{{ network }}" + ip_cidr_range: 172.16.0.0/16 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/google_container_node_pool_auto_repair_disabled.md b/documentation/rules/ansible/gcp/google_container_node_pool_auto_repair_disabled.md new file mode 100644 index 00000000..7eff2fce --- /dev/null +++ b/documentation/rules/ansible/gcp/google_container_node_pool_auto_repair_disabled.md @@ -0,0 +1,117 @@ +--- +title: "Google container node pool auto repair disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/google_container_node_pool_auto_repair_disabled" + id: "d58c6f24-3763-4269-9f5b-86b2569a003b" + display_name: "Google container node pool auto repair disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `d58c6f24-3763-4269-9f5b-86b2569a003b` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_node_pool_module.html) + +### Description + +Node pools must have automatic node repair enabled so unhealthy or failing nodes are remediated automatically, reducing the risk of prolonged downtime and inconsistent cluster state. + +For Ansible GKE node pool resources (modules `google.cloud.gcp_container_node_pool` and `gcp_container_node_pool`), the `management` block must be defined and its `auto_repair` property set to `true`. Tasks missing the `management` block or with `management.auto_repair` set to `false` are flagged. + +Secure configuration example: + +```yaml +- name: Create GKE node pool with auto repair enabled + google.cloud.gcp_container_node_pool: + name: my-node-pool + cluster: my-cluster + location: us-central1 + initial_node_count: 3 + management: + auto_repair: true +``` + +## Compliant Code Examples +```yaml +- name: create a node pool + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: '{{ cluster }}' + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + management: + auto_repair: yes + +- name: create a node pool + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: '{{ cluster }}' + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + management: + auto_repair: true + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a node pool + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: "{{ cluster }}" + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + management: + auto_repair: no + +- name: create a node pool2 + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: "{{ cluster }}" + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + management: + auto_repair: false + +- name: create a node pool3 + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: "{{ cluster }}" + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/high_google_kms_crypto_key_rotation_period.md b/documentation/rules/ansible/gcp/high_google_kms_crypto_key_rotation_period.md new file mode 100644 index 00000000..1e96f405 --- /dev/null +++ b/documentation/rules/ansible/gcp/high_google_kms_crypto_key_rotation_period.md @@ -0,0 +1,102 @@ +--- +title: "High Google KMS crypto key rotation period" +group_id: "Ansible / GCP" +meta: + name: "gcp/high_google_kms_crypto_key_rotation_period" + id: "f9b7086b-deb8-4034-9330-d7fd38f1b8de" + display_name: "High Google KMS crypto key rotation period" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Secret Management" +--- +## Metadata + +**Id:** `f9b7086b-deb8-4034-9330-d7fd38f1b8de` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Secret Management + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_kms_crypto_key_module.html) + +### Description + +KMS crypto keys must have a `rotation_period` of 90 days or less to limit key lifetime and reduce the blast radius if a key is compromised. + +For Ansible resources using `google.cloud.gcp_kms_crypto_key` or `gcp_kms_crypto_key`, the `rotation_period` property must be a duration string in seconds ending with an `s`. The numeric value must be less than or equal to `7776000` (90 days). Resources missing `rotation_period`, lacking the `s` suffix, or with a value greater than `7776000` are flagged. + +Secure configuration example: + +```yaml +- name: Create KMS crypto key with 90-day rotation + google.cloud.gcp_kms_crypto_key: + name: my-key + key_ring: projects/my-project/locations/global/keyRings/my-keyring + purpose: ENCRYPT_DECRYPT + rotation_period: "7776000s" + state: present +``` + +## Compliant Code Examples +```yaml +- name: create a key ring + google.cloud.gcp_kms_key_ring: + name: key-key-ring + location: us-central1 + project: '{{ gcp_project }}' + auth_kind: '{{ gcp_cred_kind }}' + service_account_file: '{{ gcp_cred_file }}' + state: present + register: keyring + +- name: create a crypto key + google.cloud.gcp_kms_crypto_key: + name: test_object + key_ring: projects/{{ gcp_project }}/locations/us-central1/keyRings/key-key-ring + project: test_project + auth_kind: serviceaccount + rotation_period: 7776000s + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a key ring + google.cloud.gcp_kms_key_ring: + name: key-key-ring + location: us-central1 + project: "{{ gcp_project }}" + auth_kind: "{{ gcp_cred_kind }}" + service_account_file: "{{ gcp_cred_file }}" + state: present + register: keyring + +- name: create a crypto key + google.cloud.gcp_kms_crypto_key: + name: test_object + key_ring: projects/{{ gcp_project }}/locations/us-central1/keyRings/key-key-ring + project: test_project + auth_kind: serviceaccount + rotation_period: "315356000s" + service_account_file: "/tmp/auth.pem" + state: present + +- name: create a crypto key2 + google.cloud.gcp_kms_crypto_key: + name: test_object + key_ring: projects/{{ gcp_project }}/locations/us-central1/keyRings/key-key-ring + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/ip_aliasing_disabled.md b/documentation/rules/ansible/gcp/ip_aliasing_disabled.md new file mode 100644 index 00000000..facc013a --- /dev/null +++ b/documentation/rules/ansible/gcp/ip_aliasing_disabled.md @@ -0,0 +1,119 @@ +--- +title: "IP aliasing disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/ip_aliasing_disabled" + id: "ed672a9f-fbf0-44d8-a47d-779501b0db05" + display_name: "IP aliasing disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `ed672a9f-fbf0-44d8-a47d-779501b0db05` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html) + +### Description + +Kubernetes clusters must enable Alias IP ranges so pods use VPC-native networking. This prevents pod IP address conflicts and enables VPC features such as network policy enforcement and private IP addressing. + +For Ansible-managed GKE clusters using the `google.cloud.gcp_container_cluster` (or `gcp_container_cluster`) module, the `ip_allocation_policy` property must be defined and its `use_ip_aliases` subproperty must be set to `true` (Ansible: `yes`). Resources missing `ip_allocation_policy`, missing `use_ip_aliases`, or with `use_ip_aliases` set to `false` are flagged. Secure configuration example: + +```yaml +- name: create gke cluster with alias IPs + google.cloud.gcp_container_cluster: + name: my-cluster + location: us-central1 + ip_allocation_policy: + use_ip_aliases: yes +``` + +## Compliant Code Examples +```yaml +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + ip_allocation_policy: + create_subnetwork: no + use_ip_aliases: yes + +``` +## Non-Compliant Code Examples +```yaml +- name: create a cluster1 + google.cloud.gcp_container_cluster: + name: my-cluster1 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster2 + google.cloud.gcp_container_cluster: + name: my-cluster2 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + ip_allocation_policy: + create_subnetwork: no +- name: create a cluster3 + google.cloud.gcp_container_cluster: + name: my-cluster3 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + ip_allocation_policy: + create_subnetwork: no + use_ip_aliases: no + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/ip_forwarding_enabled.md b/documentation/rules/ansible/gcp/ip_forwarding_enabled.md new file mode 100644 index 00000000..a50499fa --- /dev/null +++ b/documentation/rules/ansible/gcp/ip_forwarding_enabled.md @@ -0,0 +1,86 @@ +--- +title: "IP forwarding enabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/ip_forwarding_enabled" + id: "11bd3554-cd56-4257-8e25-7aaf30cf8f5f" + display_name: "IP forwarding enabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `11bd3554-cd56-4257-8e25-7aaf30cf8f5f` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_instance_module.html) + +### Description + +Compute instances must not have IP forwarding enabled. Allowing an instance to forward packets can be used to intercept, relay, or spoof network traffic. This enables lateral movement or bypassing of network security controls. For Google Cloud Compute instances managed with the Ansible modules `google.cloud.gcp_compute_instance` or `gcp_compute_instance`, the `can_ip_forward` property must be defined and set to `false` (not true/yes). + +Instances with `can_ip_forward` set to `true` or where the property is omitted are flagged. Only enable IP forwarding when strictly necessary, and document justification and compensating controls such as restrictive firewall rules and isolated network segments. + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a instance + google.cloud.gcp_compute_instance: + name: test_object + machine_type: n1-standard-1 + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: '{{ network }}' + access_configs: + - name: External NAT + nat_ip: '{{ address }}' + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + can_ip_forward: no + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a instance + google.cloud.gcp_compute_instance: + name: test_object + machine_type: n1-standard-1 + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + can_ip_forward: yes + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/mysql_instance_with_local_infile_on.md b/documentation/rules/ansible/gcp/mysql_instance_with_local_infile_on.md new file mode 100644 index 00000000..6395f795 --- /dev/null +++ b/documentation/rules/ansible/gcp/mysql_instance_with_local_infile_on.md @@ -0,0 +1,81 @@ +--- +title: "MySQL instance with local infile on" +group_id: "Ansible / GCP" +meta: + name: "gcp/mysql_instance_with_local_infile_on" + id: "a7b520bb-2509-4fb0-be05-bc38f54c7a4c" + display_name: "MySQL instance with local infile on" + cloud_provider: "GCP" + platform: "Ansible" + severity: "HIGH" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `a7b520bb-2509-4fb0-be05-bc38f54c7a4c` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** High + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html#parameter-settings/database_flags) + +### Description + +MySQL instances must have the `local_infile` flag disabled. Enabling `LOAD DATA LOCAL INFILE` can be abused to read or exfiltrate files via SQL operations or by malicious servers and clients, exposing sensitive data. For Ansible tasks using `google.cloud.gcp_sql_instance` or `gcp_sql_instance`, ensure the `settings.database_flags` list contains an entry with `name: local_infile` and `value: "off"` for instances whose `database_version` contains "MYSQL". Resources missing this flag or with `local_infile` set to any value other than `"off"` are flagged as insecure. + +Secure configuration example: + +```yaml +- name: create MySQL Cloud SQL instance with local_infile disabled + google.cloud.gcp_sql_instance: + name: my-instance + database_version: MYSQL_5_7 + settings: + database_flags: + - name: local_infile + value: "off" +``` + +## Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: '{{ resource_name }}-2' + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: name1 + value: value1 + tier: db-n1-standard-1 + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: MYSQL_5_6 + name: "{{ resource_name }}-2" + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: local_infile + value: on + tier: db-n1-standard-1 + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/network_policy_disabled.md b/documentation/rules/ansible/gcp/network_policy_disabled.md new file mode 100644 index 00000000..93c046b0 --- /dev/null +++ b/documentation/rules/ansible/gcp/network_policy_disabled.md @@ -0,0 +1,173 @@ +--- +title: "Network policy disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/network_policy_disabled" + id: "98e04ca0-34f5-4c74-8fec-d2e611ce2790" + display_name: "Network policy disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `98e04ca0-34f5-4c74-8fec-d2e611ce2790` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html) + +### Description + +Kubernetes Engine clusters must have network policy enabled to enforce pod-to-pod network segmentation and limit lateral movement. Without it, workloads can communicate unrestricted and a compromised pod could access other services or sensitive data. + +For Ansible-managed GKE clusters using `google.cloud.gcp_container_cluster` or `gcp_container_cluster`, the `network_policy.enabled` property must be `true` and `addons_config.network_policy_config.disabled` must be `false`. Resources missing the `network_policy` or `addons_config.network_policy_config` blocks, or with `network_policy.enabled` set to `false` or `addons_config.network_policy_config.disabled` set to `true`, are flagged as misconfigured. + +Secure Ansible configuration example: + +```yaml +- name: Create GKE cluster with Network Policy enabled + google.cloud.gcp_container_cluster: + name: my-cluster + location: us-central1 + network_policy: + enabled: true + addons_config: + network_policy_config: + disabled: false +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + network_policy: + enabled: yes + addons_config: + network_policy_config: + disabled: no + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a cluster1 + google.cloud.gcp_container_cluster: + name: my-cluster1 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + addons_config: + network_policy_config: + disabled: false +- name: create a cluster2 + google.cloud.gcp_container_cluster: + name: my-cluster2 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + network_policy: + enabled: yes +- name: create a cluster3 + google.cloud.gcp_container_cluster: + name: my-cluster3 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + network_policy: + enabled: yes + addons_config: + horizontal_pod_autoscaling: + disabled: yes +- name: create a cluster4 + google.cloud.gcp_container_cluster: + name: my-cluster4 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + network_policy: + enabled: no + addons_config: + network_policy_config: + disabled: no +- name: create a cluster5 + google.cloud.gcp_container_cluster: + name: my-cluster5 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + network_policy: + enabled: yes + addons_config: + network_policy_config: + disabled: yes + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/node_auto_upgrade_disabled.md b/documentation/rules/ansible/gcp/node_auto_upgrade_disabled.md new file mode 100644 index 00000000..aa14f77f --- /dev/null +++ b/documentation/rules/ansible/gcp/node_auto_upgrade_disabled.md @@ -0,0 +1,102 @@ +--- +title: "Node auto-upgrade disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/node_auto_upgrade_disabled" + id: "d6e10477-2e19-4bcd-b8a8-19c65b89ccdf" + display_name: "Node auto-upgrade disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Resource Management" +--- +## Metadata + +**Id:** `d6e10477-2e19-4bcd-b8a8-19c65b89ccdf` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Resource Management + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_node_pool_module.html#parameter-management/auto_upgrade) + +### Description + +Kubernetes node pools must have automatic node upgrades enabled so nodes receive security patches and Kubernetes version updates promptly. This reduces exposure to known vulnerabilities and version drift. + +For Ansible tasks using the `google.cloud.gcp_container_node_pool` or `gcp_container_node_pool` modules, the `management.auto_upgrade` property must be defined and set to `true`. Tasks missing the `management` block, missing `management.auto_upgrade`, or with `auto_upgrade` set to `false` are flagged as insecure. Secure configuration example: + +```yaml +- name: Create GKE node pool with auto-upgrade + google.cloud.gcp_container_node_pool: + name: my-node-pool + cluster: my-cluster + zone: us-central1-a + management: + auto_upgrade: true + initial_node_count: 3 +``` + +## Compliant Code Examples +```yaml +- name: create a node pool + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: '{{ cluster }}' + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + management: + auto-repair: yes + auto_upgrade: yes + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a node pool + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: "{{ cluster }}" + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a second node pool + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: "{{ cluster }}" + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + management: + auto_repair: yes +- name: create a third node pool + google.cloud.gcp_container_node_pool: + name: my-pool + initial_node_count: 4 + cluster: "{{ cluster }}" + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + management: + auto_repair: yes + auto_upgrade: no + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/oslogin_is_disabled_for_vm_instance.md b/documentation/rules/ansible/gcp/oslogin_is_disabled_for_vm_instance.md new file mode 100644 index 00000000..091e7b29 --- /dev/null +++ b/documentation/rules/ansible/gcp/oslogin_is_disabled_for_vm_instance.md @@ -0,0 +1,70 @@ +--- +title: "OSLogin is disabled in VM instance" +group_id: "Ansible / GCP" +meta: + name: "gcp/oslogin_is_disabled_for_vm_instance" + id: "66dae697-507b-4aef-be18-eec5bd707f33" + display_name: "OSLogin is disabled in VM instance" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `66dae697-507b-4aef-be18-eec5bd707f33` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_instance_module.html) + +### Description + +OS Login should be enabled on Google Compute VM instances to centralize SSH access control via IAM and avoid unmanaged, long-lived SSH keys on individual instances. For Ansible-managed instances using the `google.cloud.gcp_compute_instance` or `gcp_compute_instance` modules, set the `metadata.enable-oslogin` property to `true`. Resources missing the `enable-oslogin` metadata key or with a value that does not evaluate to Ansible true are flagged. + +Secure configuration example: + +```yaml +- name: create instance with OS Login enabled + google.cloud.gcp_compute_instance: + name: my-instance + zone: us-central1-a + metadata: + enable-oslogin: true +``` + +## Compliant Code Examples +```yaml +- name: oslogin-enabled + google.cloud.gcp_compute_instance: + metadata: + enable-oslogin: yes + zone: us-central1-a + auth_kind: serviceaccount +- name: oslogin-missing + google.cloud.gcp_compute_instance: + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + zone: us-central1-a + auth_kind: serviceaccount + +``` +## Non-Compliant Code Examples +```yaml +- name: oslogin-disabled + google.cloud.gcp_compute_instance: + metadata: + enable-oslogin: no + zone: us-central1-a + auth_kind: serviceaccount + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on.md b/documentation/rules/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on.md new file mode 100644 index 00000000..968f3213 --- /dev/null +++ b/documentation/rules/ansible/gcp/postgresql_log_checkpoints_flag_not_set_to_on.md @@ -0,0 +1,94 @@ +--- +title: "PostgreSQL log_checkpoints flag not set to on" +group_id: "Ansible / GCP" +meta: + name: "gcp/postgresql_log_checkpoints_flag_not_set_to_on" + id: "89afe3f0-4681-4ce3-89ed-896cebd4277c" + display_name: "PostgreSQL log_checkpoints flag not set to on" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `89afe3f0-4681-4ce3-89ed-896cebd4277c` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html#parameter-settings/database_flags) + +### Description + +PostgreSQL Cloud SQL instances must have the `log_checkpoints` flag enabled so checkpoint events are recorded. Without these logs, crash recovery and forensic analysis are hindered, making it harder to detect or investigate anomalous or destructive activity. + +For Ansible tasks using `google.cloud.gcp_sql_instance` or `gcp_sql_instance`, the `settings.databaseFlags` list must include an entry with `name: log_checkpoints` and `value: on`. Tasks that omit the `settings` block, omit `databaseFlags`, or have `log_checkpoints` set to any value other than `on` are flagged. + +Secure example configuration in an Ansible task: + +```yaml +- name: Create Cloud SQL PostgreSQL instance with checkpoint logging + google.cloud.gcp_sql_instance: + name: my-postgres-instance + database_version: POSTGRES_13 + settings: + databaseFlags: + - name: log_checkpoints + value: on +``` + +## Compliant Code Examples +```yaml +- name: create a instance + google.cloud.gcp_sql_instance: + name: GCP instance + settings: + databaseFlags: + - name: log_checkpoints + value: on + tier: db-n1-standard-1 + region: us-central1 + project: test_project + database_version: POSTGRES_9_6 + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: create instance + google.cloud.gcp_sql_instance: + name: GCP instance + settings: + databaseFlags: + - name: log_checkpoints + value: off + tier: db-n1-standard-1 + region: us-central1 + project: test_project + database_version: POSTGRES_9_6 + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create another instance + google.cloud.gcp_sql_instance: + name: GCP instance 2 + settings: + tier: db-n1-standard-1 + region: us-central1 + project: test_project + database_version: POSTGRES_9_6 + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/postgresql_log_connections_disabled.md b/documentation/rules/ansible/gcp/postgresql_log_connections_disabled.md new file mode 100644 index 00000000..073558c0 --- /dev/null +++ b/documentation/rules/ansible/gcp/postgresql_log_connections_disabled.md @@ -0,0 +1,93 @@ +--- +title: "PostgreSQL log connections disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/postgresql_log_connections_disabled" + id: "d7a5616f-0a3f-4d43-bc2b-29d1a183e317" + display_name: "PostgreSQL log connections disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `d7a5616f-0a3f-4d43-bc2b-29d1a183e317` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html#parameter-settings/database_flags) + +### Description + +PostgreSQL Cloud SQL instances must have the `log_connections` flag set to `on` so connection events are recorded for auditing and to help detect suspicious or unauthorized access. For Ansible resources using `google.cloud.gcp_sql_instance` or `gcp_sql_instance`, the `settings.databaseFlags` property must include an entry with `name: log_connections` and `value: on`. Resources missing `settings` or `settings.databaseFlags`, or where `log_connections` is absent or set to `off`, are flagged. + +Secure Ansible example: + +```yaml +- name: Create PostgreSQL Cloud SQL instance with connection logging enabled + google.cloud.gcp_sql_instance: + name: my-postgres-instance + database_version: POSTGRES_13 + settings: + tier: db-custom-1-3840 + databaseFlags: + - name: log_connections + value: "on" +``` + +## Compliant Code Examples +```yaml +- name: create a instance + google.cloud.gcp_sql_instance: + name: GCP instance + settings: + databaseFlags: + - name: log_connections + value: on + tier: db-n1-standard-1 + region: us-central1 + project: test_project + database_version: POSTGRES_9_6 + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: create instance + google.cloud.gcp_sql_instance: + name: GCP instance + settings: + databaseFlags: + - name: log_connections + value: off + tier: db-n1-standard-1 + region: us-central1 + project: test_project + database_version: POSTGRES_9_6 + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create another instance + google.cloud.gcp_sql_instance: + name: GCP instance 2 + settings: + tier: db-n1-standard-1 + region: us-central1 + project: test_project + database_version: POSTGRES_9_6 + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/postgresql_logging_of_temporary_files_disabled.md b/documentation/rules/ansible/gcp/postgresql_logging_of_temporary_files_disabled.md new file mode 100644 index 00000000..e8fda033 --- /dev/null +++ b/documentation/rules/ansible/gcp/postgresql_logging_of_temporary_files_disabled.md @@ -0,0 +1,81 @@ +--- +title: "PostgreSQL logging of temporary files disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/postgresql_logging_of_temporary_files_disabled" + id: "d6fae5b6-ada9-46c0-8b36-3108a2a2f77b" + display_name: "PostgreSQL logging of temporary files disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `d6fae5b6-ada9-46c0-8b36-3108a2a2f77b` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html#parameter-settings/database_flags) + +### Description + +The PostgreSQL `log_temp_files` flag should be set to `0` so that all temporary file creation is logged. This provides visibility into queries that spill to disk and helps detect potential data exposure or performance issues. + +Check Ansible Cloud SQL instance resources using the `google.cloud.gcp_sql_instance` or `gcp_sql_instance` modules. The `settings.database_flags` entry with `name: log_temp_files` must have `value: "0"`. Resources missing this flag or with a different value are flagged. In Ansible, `database_flags` is a list of name/value pairs, so specify the flag explicitly as shown below. + +```yaml +- name: Create Cloud SQL instance + google.cloud.gcp_sql_instance: + name: my-postgres + database_version: POSTGRES_13 + settings: + database_flags: + - name: log_temp_files + value: "0" +``` + +## Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: '{{ resource_name }}-2' + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: log_temp_files + value: 0 + tier: db-n1-standard-1 + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: "{{ resource_name }}-2" + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: log_temp_files + value: 1 + tier: db-n1-standard-1 + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/postgresql_misconfigured_log_messages_flag.md b/documentation/rules/ansible/gcp/postgresql_misconfigured_log_messages_flag.md new file mode 100644 index 00000000..fd5eab64 --- /dev/null +++ b/documentation/rules/ansible/gcp/postgresql_misconfigured_log_messages_flag.md @@ -0,0 +1,82 @@ +--- +title: "PostgreSQL misconfigured log messages flag" +group_id: "Ansible / GCP" +meta: + name: "gcp/postgresql_misconfigured_log_messages_flag" + id: "28a757fc-3d8f-424a-90c0-4233363b2711" + display_name: "PostgreSQL misconfigured log messages flag" + cloud_provider: "GCP" + platform: "Ansible" + severity: "LOW" + category: "Observability" +--- +## Metadata + +**Id:** `28a757fc-3d8f-424a-90c0-4233363b2711` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html#parameter-settings/database_flags) + +### Description + +PostgreSQL instances must have the `log_min_messages` flag set to a valid verbosity level. This ensures critical database events are recorded for detection and forensic analysis, while avoiding overly verbose debug logs that can expose sensitive information. + +For Ansible Google Cloud SQL resources using the `google.cloud.gcp_sql_instance` (or `gcp_sql_instance`) module, ensure `settings.database_flags` contains an entry with `name: "log_min_messages"` and `value` set to one of the following: `fatal`, `panic`, `log`, `error`, `warning`, `notice`, `info`, `debug1`, `debug2`, `debug3`, `debug4`, or `debug5`. Resources missing this entry or using a value outside the allowed set are flagged. + +Secure configuration example: + +```yaml +- name: Create Cloud SQL instance with secure logging + google.cloud.gcp_sql_instance: + name: my-sql-instance + settings: + database_flags: + - name: log_min_messages + value: warning +``` + +## Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: '{{ resource_name }}-2' + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: log_min_messages + value: log + tier: db-n1-standard-1 + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: "{{ resource_name }}-2" + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: log_min_messages + value: debug6 + tier: db-n1-standard-1 + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/postgresql_misconfigured_logging_duration_flag.md b/documentation/rules/ansible/gcp/postgresql_misconfigured_logging_duration_flag.md new file mode 100644 index 00000000..b577561e --- /dev/null +++ b/documentation/rules/ansible/gcp/postgresql_misconfigured_logging_duration_flag.md @@ -0,0 +1,84 @@ +--- +title: "PostgreSQL misconfigured logging duration flag" +group_id: "Ansible / GCP" +meta: + name: "gcp/postgresql_misconfigured_logging_duration_flag" + id: "aed98a2a-e680-497a-8886-277cea0f4514" + display_name: "PostgreSQL misconfigured logging duration flag" + cloud_provider: "GCP" + platform: "Ansible" + severity: "LOW" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `aed98a2a-e680-497a-8886-277cea0f4514` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html#parameter-settings/database_flags) + +### Description + +The PostgreSQL `log_min_duration_statement` flag controls whether SQL statements are recorded for slow queries. If it is not set to `-1`, statement text may be written to logs, increasing the risk of exposing sensitive data and creating additional compliance and log-management burden. + +For Ansible-managed Cloud SQL PostgreSQL instances, ensure the `settings.database_flags` entry for `log_min_duration_statement` is present and set to `-1` in `google.cloud.gcp_sql_instance` or `gcp_sql_instance` tasks. Resources missing this flag or with a different value are flagged. Use `-1` (integer) to disable duration-based statement logging. + +Secure configuration example: + +```yaml +- name: Create Cloud SQL PostgreSQL instance + google.cloud.gcp_sql_instance: + name: my-pg-instance + database_version: POSTGRES_13 + region: us-central1 + settings: + database_flags: + - name: log_min_duration_statement + value: -1 +``` + +## Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: '{{ resource_name }}-2' + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: log_min_duration_statement + value: -1 + tier: db-n1-standard-1 + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + database_version: SQLSERVER_13_1 + name: "{{ resource_name }}-2" + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + database_flags: + - name: log_min_duration_statement + value: 0 + tier: db-n1-standard-1 + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/private_cluster_disabled.md b/documentation/rules/ansible/gcp/private_cluster_disabled.md new file mode 100644 index 00000000..94fc2fe1 --- /dev/null +++ b/documentation/rules/ansible/gcp/private_cluster_disabled.md @@ -0,0 +1,157 @@ +--- +title: "Private cluster disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/private_cluster_disabled" + id: "3b30e3d6-c99b-4318-b38f-b99db74578b5" + display_name: "Private cluster disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `3b30e3d6-c99b-4318-b38f-b99db74578b5` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html) + +### Description + +GKE clusters must be configured as private to avoid exposing the control plane endpoint and worker nodes to the public internet. Public exposure increases the risk of unauthorized access and lateral movement. + +For Ansible resources using `google.cloud.gcp_container_cluster` or `gcp_container_cluster`, the `private_cluster_config` property must be defined with `enable_private_endpoint` and `enable_private_nodes` set to `true`. Resources missing `private_cluster_config`, missing either attribute, or with either attribute set to `false` are flagged. + +Secure Ansible configuration example: + +```yaml +- name: Create private GKE cluster + google.cloud.gcp_container_cluster: + name: my-cluster + location: us-central1 + private_cluster_config: + enable_private_nodes: true + enable_private_endpoint: true +``` + +## Compliant Code Examples +```yaml +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + private_cluster_config: + enable_private_endpoint: yes + enable_private_nodes: yes + +``` +## Non-Compliant Code Examples +```yaml +- name: create a cluster1 + google.cloud.gcp_container_cluster: + name: my-cluster1 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster2 + google.cloud.gcp_container_cluster: + name: my-cluster2 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + private_cluster_config: + enable_private_endpoint: yes +- name: create a cluster3 + google.cloud.gcp_container_cluster: + name: my-cluster3 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + private_cluster_config: + enable_private_nodes: yes +- name: create a cluster4 + google.cloud.gcp_container_cluster: + name: my-cluster4 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + private_cluster_config: + enable_private_endpoint: no + enable_private_nodes: yes +- name: create a cluster5 + google.cloud.gcp_container_cluster: + name: my-cluster5 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + private_cluster_config: + enable_private_endpoint: yes + enable_private_nodes: no + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances.md b/documentation/rules/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances.md new file mode 100644 index 00000000..13a7c2b2 --- /dev/null +++ b/documentation/rules/ansible/gcp/project_wide_ssh_keys_are_enabled_in_vm_instances.md @@ -0,0 +1,76 @@ +--- +title: "Project-wide SSH keys are enabled in VM instances" +group_id: "Ansible / GCP" +meta: + name: "gcp/project_wide_ssh_keys_are_enabled_in_vm_instances" + id: "099b4411-d11e-4537-a0fc-146b19762a79" + display_name: "Project-wide SSH keys are enabled in VM instances" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Secret Management" +--- +## Metadata + +**Id:** `099b4411-d11e-4537-a0fc-146b19762a79` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Secret Management + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_instance_module.html) + +### Description + +VM instances should block project-wide SSH keys. This prevents SSH keys defined at the project level from granting access to individual instances, reducing the risk of unintended or persistent SSH access and lateral movement if project metadata or keys are compromised. + +For Ansible resources using `google.cloud.gcp_compute_instance` or `gcp_compute_instance`, ensure the `metadata.block-project-ssh-keys` property is defined and set to `true`. Resources that omit the `metadata` map, omit the `block-project-ssh-keys` key, or set it to `false` are flagged. + +Secure configuration example for an Ansible task: + +```yaml +- name: Create VM with project-wide SSH keys blocked + google.cloud.gcp_compute_instance: + name: my-instance + machine_type: e2-medium + metadata: + block-project-ssh-keys: true +``` + +## Compliant Code Examples +```yaml +- name: ssh_keys_blocked + google.cloud.gcp_compute_instance: + metadata: + block-project-ssh-keys: yes + zone: us-central1-a + auth_kind: serviceaccount + +``` +## Non-Compliant Code Examples +```yaml +- name: ssh_keys_unblocked + google.cloud.gcp_compute_instance: + metadata: + block-project-ssh-keys: no + zone: us-central1-a + auth_kind: serviceaccount +- name: ssh_keys_missing + google.cloud.gcp_compute_instance: + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + zone: us-central1-a + auth_kind: serviceaccount +- name: no_metadata + google.cloud.gcp_compute_instance: + zone: us-central1-a + auth_kind: serviceaccount + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/rdp_access_is_not_restricted.md b/documentation/rules/ansible/gcp/rdp_access_is_not_restricted.md new file mode 100644 index 00000000..0aee606a --- /dev/null +++ b/documentation/rules/ansible/gcp/rdp_access_is_not_restricted.md @@ -0,0 +1,115 @@ +--- +title: "RDP access is not restricted" +group_id: "Ansible / GCP" +meta: + name: "gcp/rdp_access_is_not_restricted" + id: "75418eb9-39ec-465f-913c-6f2b6a80dc77" + display_name: "RDP access is not restricted" + cloud_provider: "GCP" + platform: "Ansible" + severity: "HIGH" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `75418eb9-39ec-465f-913c-6f2b6a80dc77` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** High + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_firewall_module.html) + +### Description + +Allowing unrestricted RDP (TCP port 3389) ingress exposes hosts to automated brute-force attacks and unauthorized remote access. This rule inspects Ansible `google.cloud.gcp_compute_firewall` and `gcp_compute_firewall` tasks and flags ingress rules whose `source_ranges` include unrestricted CIDRs (for example `0.0.0.0/0` or `::/0`) and whose `allowed` entries include port `3389` (typically `ip_protocol: tcp`). + +The `allowed` property must not include port `3389` for rules that permit unrestricted source ranges. Either remove or disable RDP on the firewall, or restrict `source_ranges` to trusted CIDRs. Consider using a bastion host, VPN, or identity-based access (IAP/SSM) instead of direct RDP. Resources where `direction` is ingress, `source_ranges` contains an unrestricted CIDR, and `allowed[].ports` contains `"3389"` are flagged. + +Secure example that restricts RDP to a corporate CIDR: + +```yaml +- name: allow-rdp-from-corporate + google.cloud.gcp_compute_firewall: + name: allow-rdp-corp + network: default + direction: INGRESS + source_ranges: + - 10.0.0.0/8 + allowed: + - ip_protocol: tcp + ports: + - "3389" +``` + +## Compliant Code Examples +```yaml +- name: create a firewall + google.cloud.gcp_compute_firewall: + name: test_object + allowed: + - ip_protocol: tcp + ports: + - '80' + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: rdp_in_range + google.cloud.gcp_compute_firewall: + name: test_object + source_ranges: + - "0.0.0.0/0" + allowed: + - ip_protocol: tcp + ports: + - "22" + - "80" + - "8080" + - "2000-4000" + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: rdp_in_port + google.cloud.gcp_compute_firewall: + name: test_object + source_ranges: + - "0.0.0.0/0" + allowed: + - ip_protocol: tcp + ports: + - "22" + - "80" + - "3389" + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/serial_ports_enabled_for_vm_instances.md b/documentation/rules/ansible/gcp/serial_ports_enabled_for_vm_instances.md new file mode 100644 index 00000000..e907a162 --- /dev/null +++ b/documentation/rules/ansible/gcp/serial_ports_enabled_for_vm_instances.md @@ -0,0 +1,72 @@ +--- +title: "Serial ports are enabled for VM instances" +group_id: "Ansible / GCP" +meta: + name: "gcp/serial_ports_enabled_for_vm_instances" + id: "c6fc6f29-dc04-46b6-99ba-683c01aff350" + display_name: "Serial ports are enabled for VM instances" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `c6fc6f29-dc04-46b6-99ba-683c01aff350` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_instance_module.html) + +### Description + +Enabling the serial console on Google Compute Engine VMs grants low-level interactive access to the instance console. This can bypass network and SSH controls, allowing actors who know project or instance details to interact with or tamper with the VM. + +In Ansible, check tasks using `google.cloud.gcp_compute_instance` or `gcp_compute_instance` and ensure the `metadata.serial-port-enable` property is either undefined or explicitly set to `false`. Tasks with `metadata.serial-port-enable: true` are flagged. Remediate by removing the metadata key or setting it to `false`. + +Secure Ansible example: + +```yaml +- name: Create GCE instance with serial port disabled + google.cloud.gcp_compute_instance: + name: my-vm + machine_type: e2-medium + metadata: + "serial-port-enable": false +``` + +## Compliant Code Examples +```yaml +- name: serial_disabled + google.cloud.gcp_compute_instance: + metadata: + serial-port-enabled: no + zone: us-central1-a + auth_kind: serviceaccount +- name: serial_undefined + google.cloud.gcp_compute_instance: + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + zone: us-central1-a + auth_kind: serviceaccount + +``` +## Non-Compliant Code Examples +```yaml +- name: serial_enabled + google.cloud.gcp_compute_instance: + metadata: + serial-port-enable: yes + zone: us-central1-a + auth_kind: serviceaccount + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/shielded_vm_disabled.md b/documentation/rules/ansible/gcp/shielded_vm_disabled.md new file mode 100644 index 00000000..dded2ab4 --- /dev/null +++ b/documentation/rules/ansible/gcp/shielded_vm_disabled.md @@ -0,0 +1,252 @@ +--- +title: "Shielded VM disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/shielded_vm_disabled" + id: "18d3a83d-4414-49dc-90ea-f0387b2856cc" + display_name: "Shielded VM disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `18d3a83d-4414-49dc-90ea-f0387b2856cc` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_instance_module.html) + +### Description + +Compute instances must have Shielded VM features enabled to protect boot integrity and prevent or detect kernel and firmware tampering. + +For Ansible resources using `google.cloud.gcp_compute_instance` or `gcp_compute_instance`, the `shielded_instance_config` property must be defined with `enable_secure_boot`, `enable_vtpm`, and `enable_integrity_monitoring` set to `true`. Resources missing `shielded_instance_config` or with any of these attributes undefined or set to `false` are flagged. + +Secure configuration example: + +```yaml +- name: Create GCP compute instance with Shielded VM enabled + google.cloud.gcp_compute_instance: + name: my-instance + machine_type: e2-medium + zone: us-central1-a + shielded_instance_config: + enable_secure_boot: true + enable_vtpm: true + enable_integrity_monitoring: true +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a instance + google.cloud.gcp_compute_instance: + name: test_object + machine_type: n1-standard-1 + disks: + - auto_delete: 'true' + boot: 'true' + source: '{{ disk }}' + - auto_delete: 'true' + interface: NVME + type: SCRATCH + initialize_params: + disk_type: local-ssd + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: '{{ network }}' + access_configs: + - name: External NAT + nat_ip: '{{ address }}' + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + shielded_instance_config: + enable_integrity_monitoring: yes + enable_secure_boot: yes + enable_vtpm: yes + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a instance1 + google.cloud.gcp_compute_instance: + name: test_object1 + machine_type: n1-standard-1 + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a instance2 + google.cloud.gcp_compute_instance: + name: test_object2 + machine_type: n1-standard-1 + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + shielded_instance_config: + enable_secure_boot: yes + enable_vtpm: yes +- name: create a instance3 + google.cloud.gcp_compute_instance: + name: test_object3 + machine_type: n1-standard-1 + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + shielded_instance_config: + enable_integrity_monitoring: yes + enable_vtpm: yes +- name: create a instance4 + google.cloud.gcp_compute_instance: + name: test_object4 + machine_type: n1-standard-1 + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + shielded_instance_config: + enable_integrity_monitoring: yes + enable_secure_boot: yes +- name: create a instance5 + google.cloud.gcp_compute_instance: + name: test_object5 + machine_type: n1-standard-1 + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + shielded_instance_config: + enable_integrity_monitoring: no + enable_secure_boot: yes + enable_vtpm: yes +- name: create a instance6 + google.cloud.gcp_compute_instance: + name: test_object6 + machine_type: n1-standard-1 + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + shielded_instance_config: + enable_integrity_monitoring: yes + enable_secure_boot: no + enable_vtpm: yes +- name: create a instance7 + google.cloud.gcp_compute_instance: + name: test_object7 + machine_type: n1-standard-1 + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + shielded_instance_config: + enable_integrity_monitoring: yes + enable_secure_boot: yes + enable_vtpm: no + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/sql_db_instance_backup_disabled.md b/documentation/rules/ansible/gcp/sql_db_instance_backup_disabled.md new file mode 100644 index 00000000..03314cdb --- /dev/null +++ b/documentation/rules/ansible/gcp/sql_db_instance_backup_disabled.md @@ -0,0 +1,112 @@ +--- +title: "SQL DB instance backup disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/sql_db_instance_backup_disabled" + id: "0c82eae2-aca0-401f-93e4-fb37a0f9e5e8" + display_name: "SQL DB instance backup disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Backup" +--- +## Metadata + +**Id:** `0c82eae2-aca0-401f-93e4-fb37a0f9e5e8` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Backup + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html#parameter-settings/backup_configuration/enabled) + +### Description + +Cloud SQL instances must have backups enabled so you can recover from accidental deletion, data corruption, or ransomware. Without backups, data loss can be permanent and service restoration time increases. + +For Ansible resources using `google.cloud.gcp_sql_instance` or `gcp_sql_instance`, ensure the `settings.backup_configuration.enabled` property is present and set to `true`. Resources missing `settings`, `settings.backup_configuration`, or `settings.backup_configuration.enabled`, or where `enabled` is `false`, are flagged. + +Secure configuration example: + +```yaml +- name: Create Cloud SQL instance with backups enabled + google.cloud.gcp_sql_instance: + name: my-instance + settings: + tier: db-f1-micro + backup_configuration: + enabled: true + start_time: "03:00" +``` + +## Compliant Code Examples +```yaml +- name: create a instance + google.cloud.gcp_sql_instance: + name: '{{ resource_name }}-2' + settings: + backup_configuration: + binary_log_enabled: yes + enabled: yes + tier: db-n1-standard-1 + region: us-central1 + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a instance + google.cloud.gcp_sql_instance: + name: "{{ resource_name }}-2" + region: us-central1 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a second instance + google.cloud.gcp_sql_instance: + name: "{{ resource_name }}-2" + settings: + tier: db-n1-standard-1 + region: us-central1 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a third instance + google.cloud.gcp_sql_instance: + name: "{{ resource_name }}-2" + settings: + backup_configuration: + binary_log_enabled: yes + tier: db-n1-standard-1 + region: us-central1 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a forth instance + google.cloud.gcp_sql_instance: + name: "{{ resource_name }}-2" + settings: + backup_configuration: + binary_log_enabled: yes + enabled: no + tier: db-n1-standard-1 + region: us-central1 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/sql_db_instance_is_publicly_accessible.md b/documentation/rules/ansible/gcp/sql_db_instance_is_publicly_accessible.md new file mode 100644 index 00000000..3dc1d516 --- /dev/null +++ b/documentation/rules/ansible/gcp/sql_db_instance_is_publicly_accessible.md @@ -0,0 +1,115 @@ +--- +title: "SQL DB instance publicly accessible" +group_id: "Ansible / GCP" +meta: + name: "gcp/sql_db_instance_is_publicly_accessible" + id: "7d7054c0-3a52-4e9b-b9ff-cbfe16a2378b" + display_name: "SQL DB instance publicly accessible" + cloud_provider: "GCP" + platform: "Ansible" + severity: "CRITICAL" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `7d7054c0-3a52-4e9b-b9ff-cbfe16a2378b` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Critical + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html) + +### Description + +Cloud SQL instances must not be publicly accessible. Allowing access from `0.0.0.0/0` or enabling public IPv4 without restricted networks exposes databases to unauthorized access and data exfiltration. + +For Ansible tasks using the `google.cloud.gcp_sql_instance` or `gcp_sql_instance` module, ensure `settings.ip_configuration.authorized_networks` does not contain an entry with `value: "0.0.0.0"`. Authorized networks should be explicit trusted CIDRs. If no `authorized_networks` are defined, `settings.ip_configuration.ipv4_enabled` must be set to `false` (or omitted/disabled) to prevent public IPv4 access. Resources missing `settings.ip_configuration` should be defined with a restricted `authorized_networks` list or have `ipv4_enabled: false`. Instances with `value` set to `"0.0.0.0"` or with IPv4 enabled and no authorized networks are flagged. + +Secure configuration examples: + +```yaml +- name: create Cloud SQL instance with restricted authorized networks + google.cloud.gcp_sql_instance: + name: my-sql + settings: + ip_configuration: + authorized_networks: + - name: office + value: 203.0.113.0/24 + ipv4_enabled: true +``` + +```yaml +- name: create Cloud SQL instance without public IPv4 + google.cloud.gcp_sql_instance: + name: my-sql + settings: + ip_configuration: + ipv4_enabled: false +``` + +## Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + name: '{{ resource_name }}-2' + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + ip_configuration: + authorized_networks: + - name: google dns server + value: 8.8.8.8/32 + tier: db-n1-standard-1 + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: sql_instance + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + name: "{{ resource_name }}-2" + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + ip_configuration: + authorized_networks: + - name: "google dns server" + value: "0.0.0.0" + tier: db-n1-standard-1 + state: present +- name: sql_instance2 + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + name: "{{ resource_name }}-2" + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + ip_configuration: + ipv4_enabled: yes + tier: db-n1-standard-1 + state: present +- name: sql_instance3 + google.cloud.gcp_sql_instance: + auth_kind: serviceaccount + name: "{{ resource_name }}-2" + project: test_project + region: us-central1 + service_account_file: /tmp/auth.pem + settings: + tier: db-n1-standard-1 + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/sql_db_instance_with_ssl_disabled.md b/documentation/rules/ansible/gcp/sql_db_instance_with_ssl_disabled.md new file mode 100644 index 00000000..d1050f50 --- /dev/null +++ b/documentation/rules/ansible/gcp/sql_db_instance_with_ssl_disabled.md @@ -0,0 +1,116 @@ +--- +title: "SQL DB instance with SSL disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/sql_db_instance_with_ssl_disabled" + id: "d0f7da39-a2d5-4c78-bb85-4b7f338b3cbb" + display_name: "SQL DB instance with SSL disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "HIGH" + category: "Encryption" +--- +## Metadata + +**Id:** `d0f7da39-a2d5-4c78-bb85-4b7f338b3cbb` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** High + +**Category:** Encryption + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_sql_instance_module.html#parameter-settings/ip_configuration/require_ssl) + +### Description + +Cloud SQL instances must require SSL for client connections to protect data in transit and prevent unauthorized or unencrypted access to the database. In Ansible tasks using the `google.cloud.gcp_sql_instance` or `gcp_sql_instance` module, the `settings.ip_configuration.require_ssl` property must be set to `true`. Resources that omit `settings.ip_configuration.require_ssl` or set it to `false` are flagged as a misconfiguration. + +Secure Ansible task example: + +```yaml +- name: Create Cloud SQL instance with SSL required + google.cloud.gcp_sql_instance: + project: my-project + name: my-sql-instance + settings: + tier: db-f1-micro + ip_configuration: + require_ssl: true +``` + +## Compliant Code Examples +```yaml +- name: create a instance + google.cloud.gcp_sql_instance: + name: '{{ resource_name }}-2' + settings: + ip_configuration: + require_ssl: yes + authorized_networks: + - name: google dns server + value: 8.8.8.8/32 + tier: db-n1-standard-1 + region: us-central1 + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: create a instance + google.cloud.gcp_sql_instance: + name: "{{ resource_name }}-2" + region: us-central1 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a second instance + google.cloud.gcp_sql_instance: + name: "{{ resource_name }}-2" + settings: + tier: db-n1-standard-1 + region: us-central1 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a third instance + google.cloud.gcp_sql_instance: + name: "{{ resource_name }}-2" + settings: + ip_configuration: + authorized_networks: + - name: google dns server + value: 8.8.8.8/32 + tier: db-n1-standard-1 + region: us-central1 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a forth instance + google.cloud.gcp_sql_instance: + name: "{{ resource_name }}-2" + settings: + ip_configuration: + require_ssl: no + authorized_networks: + - name: google dns server + value: 8.8.8.8/32 + tier: db-n1-standard-1 + region: us-central1 + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/ssh_access_is_not_restricted.md b/documentation/rules/ansible/gcp/ssh_access_is_not_restricted.md new file mode 100644 index 00000000..9b938f13 --- /dev/null +++ b/documentation/rules/ansible/gcp/ssh_access_is_not_restricted.md @@ -0,0 +1,95 @@ +--- +title: "SSH access is not restricted" +group_id: "Ansible / GCP" +meta: + name: "gcp/ssh_access_is_not_restricted" + id: "b2fbf1df-76dd-4d78-a6c0-e538f4a9b016" + display_name: "SSH access is not restricted" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Networking and Firewall" +--- +## Metadata + +**Id:** `b2fbf1df-76dd-4d78-a6c0-e538f4a9b016` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Networking and Firewall + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_firewall_module.html) + +### Description + +Allowing SSH (port 22) from the public Internet exposes instances to brute-force attacks and unauthorized access. This can lead to credential compromise and lateral movement across your network. + +In Ansible tasks using the `google.cloud.gcp_compute_firewall` or `gcp_compute_firewall` modules, this rule flags ingress rules where `source_ranges` includes `0.0.0.0/0` or `::/0` and an `allowed` entry specifies port `22` (for example, `allowed[].ip_protocol='tcp'` and `allowed[].ports` contains `22`). + +Restrict SSH access to specific trusted CIDR ranges, place SSH behind a bastion host or VPN, or use identity-aware access methods instead of allowing unrestricted Internet access. + +Secure example restricting SSH to a single admin IP: + +```yaml +- name: allow-ssh-from-admin + google.cloud.gcp_compute_firewall: + name: allow-ssh-from-admin + network: default + direction: INGRESS + source_ranges: + - 203.0.113.5/32 + allowed: + - ip_protocol: tcp + ports: ['22'] +``` + +## Compliant Code Examples +```yaml +- name: ssh_restricted + google.cloud.gcp_compute_firewall: + name: test_object + denied: + - ip_protocol: tcp + ports: + - '22' + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + source_ranges: + - 0.0.0.0 + +``` +## Non-Compliant Code Examples +```yaml +- name: ssh_unrestricted + google.cloud.gcp_compute_firewall: + name: test_object + allowed: + - ip_protocol: tcp + ports: + - '22' + target_tags: + - test-ssh-server + - staging-ssh-server + source_tags: + - test-ssh-clients + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + source_ranges: + - "0.0.0.0/0" + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/stackdriver_logging_disabled.md b/documentation/rules/ansible/gcp/stackdriver_logging_disabled.md new file mode 100644 index 00000000..713038c8 --- /dev/null +++ b/documentation/rules/ansible/gcp/stackdriver_logging_disabled.md @@ -0,0 +1,101 @@ +--- +title: "Stackdriver logging disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/stackdriver_logging_disabled" + id: "19c9e2a0-fc33-4264-bba1-e3682661e8f7" + display_name: "Stackdriver logging disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `19c9e2a0-fc33-4264-bba1-e3682661e8f7` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html) + +### Description + +GKE clusters must have Cloud Logging (Stackdriver) enabled so cluster control plane and node logs are centrally collected for monitoring, alerting, incident response, and forensic analysis. Without central logging, audit trails and operational diagnostics can be lost or unavailable during security investigations. + +For the Ansible GCP modules `google.cloud.gcp_container_cluster` and `gcp_container_cluster`, the `logging_service` property must be defined and must not be set to `"none"` (case-insensitive), since `"none"` disables Cloud Logging. Resources missing `logging_service` or with `logging_service: "none"` are flagged. + +Secure example configuration: + +```yaml +- name: Create GKE cluster with logging enabled + google.cloud.gcp_container_cluster: + name: my-cluster + zone: us-central1-a + logging_service: logging.googleapis.com +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + logging_service: logging.googleapis.com + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a cluster1 + google.cloud.gcp_container_cluster: + name: my-cluster1 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster2 + google.cloud.gcp_container_cluster: + name: my-cluster2 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + logging_service: none + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/stackdriver_monitoring_disabled.md b/documentation/rules/ansible/gcp/stackdriver_monitoring_disabled.md new file mode 100644 index 00000000..3d18cfee --- /dev/null +++ b/documentation/rules/ansible/gcp/stackdriver_monitoring_disabled.md @@ -0,0 +1,100 @@ +--- +title: "Stackdriver monitoring disabled" +group_id: "Ansible / GCP" +meta: + name: "gcp/stackdriver_monitoring_disabled" + id: "20dcd953-a8b8-4892-9026-9afa6d05a525" + display_name: "Stackdriver monitoring disabled" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Observability" +--- +## Metadata + +**Id:** `20dcd953-a8b8-4892-9026-9afa6d05a525` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Observability + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_container_cluster_module.html) + +### Description + +GKE clusters must have Cloud Monitoring (Stackdriver) enabled to provide observability and support timely incident detection and response. Disabling monitoring removes metrics and logs needed for alerting, troubleshooting, and forensic analysis. + +For Ansible resources using the `google.cloud.gcp_container_cluster` or `gcp_container_cluster` modules, the `monitoring_service` property must be defined and must not be set to `'none'`. Resources that omit `monitoring_service` or explicitly set `monitoring_service: 'none'` are flagged. + +Secure configuration example: + +```yaml +- name: Create GKE cluster with monitoring enabled + google.cloud.gcp_container_cluster: + name: my-cluster + monitoring_service: monitoring.googleapis.com/kubernetes +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a cluster + google.cloud.gcp_container_cluster: + name: my-cluster + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: /tmp/auth.pem + state: present + monitoring_service: monitoring.googleapis.com + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a cluster1 + google.cloud.gcp_container_cluster: + name: my-cluster1 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present +- name: create a cluster2 + google.cloud.gcp_container_cluster: + name: my-cluster2 + initial_node_count: 2 + master_auth: + username: cluster_admin + password: my-secret-password + node_config: + machine_type: n1-standard-4 + disk_size_gb: 500 + location: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_file: "/tmp/auth.pem" + state: present + monitoring_service: none + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/using_default_service_account.md b/documentation/rules/ansible/gcp/using_default_service_account.md new file mode 100644 index 00000000..df6bbb45 --- /dev/null +++ b/documentation/rules/ansible/gcp/using_default_service_account.md @@ -0,0 +1,196 @@ +--- +title: "Using default service account" +group_id: "Ansible / GCP" +meta: + name: "gcp/using_default_service_account" + id: "2775e169-e708-42a9-9305-b58aadd2c4dd" + display_name: "Using default service account" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `2775e169-e708-42a9-9305-b58aadd2c4dd` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_instance_module.html) + +### Description + +Compute instances must not use the default Google Compute Engine service account. That account often has broad Cloud API privileges, which can lead to unintended privilege escalation or overly permissive access. For Ansible tasks using the `google.cloud.gcp_compute_instance` or `gcp_compute_instance` module with `auth_kind: serviceaccount`, the `service_account_email` property must be defined, must be a non-empty string containing an `@`, and must not reference a default Compute Engine service account (values containing `@developer.gserviceaccount.com`). Resources missing `service_account_email`, with an empty value, lacking an `@` character, or using a default developer service account are flagged. + +Secure example: + +```yaml +- name: Create instance with explicit service account + google.cloud.gcp_compute_instance: + name: my-instance + auth_kind: serviceaccount + service_account_email: my-sa@my-project.iam.gserviceaccount.com +``` + +## Compliant Code Examples +```yaml +#this code is a correct code for which the query should not find any result +- name: create a instance + google.cloud.gcp_compute_instance: + name: test_object + machine_type: n1-standard-1 + disks: + - auto_delete: 'true' + boot: 'true' + source: '{{ disk }}' + - auto_delete: 'true' + interface: NVME + type: SCRATCH + initialize_params: + disk_type: local-ssd + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: '{{ network }}' + access_configs: + - name: External NAT + nat_ip: '{{ address }}' + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_email: admin@admin.com + state: present + +``` +## Non-Compliant Code Examples +```yaml +#this is a problematic code where the query should report a result(s) +- name: create a instance1 + google.cloud.gcp_compute_instance: + name: test_object1 + machine_type: n1-standard-1 + disks: + - auto_delete: 'true' + boot: 'true' + source: "{{ disk }}" + - auto_delete: 'true' + interface: NVME + type: SCRATCH + initialize_params: + disk_type: local-ssd + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + state: present +- name: create a instance2 + google.cloud.gcp_compute_instance: + name: test_object2 + machine_type: n1-standard-1 + disks: + - auto_delete: 'true' + boot: 'true' + source: "{{ disk }}" + - auto_delete: 'true' + interface: NVME + type: SCRATCH + initialize_params: + disk_type: local-ssd + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_email: "" + state: present +- name: create a instance3 + google.cloud.gcp_compute_instance: + name: test_object3 + machine_type: n1-standard-1 + disks: + - auto_delete: 'true' + boot: 'true' + source: "{{ disk }}" + - auto_delete: 'true' + interface: NVME + type: SCRATCH + initialize_params: + disk_type: local-ssd + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_email: "admin" + state: present +- name: create a instance4 + google.cloud.gcp_compute_instance: + name: test_object4 + machine_type: n1-standard-1 + disks: + - auto_delete: 'true' + boot: 'true' + source: "{{ disk }}" + - auto_delete: 'true' + interface: NVME + type: SCRATCH + initialize_params: + disk_type: local-ssd + metadata: + startup-script-url: gs:://graphite-playground/bootstrap.sh + cost-center: '12345' + labels: + environment: production + network_interfaces: + - network: "{{ network }}" + access_configs: + - name: External NAT + nat_ip: "{{ address }}" + type: ONE_TO_ONE_NAT + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_account_email: "admin@developer.gserviceaccount.com" + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/gcp/vm_with_full_cloud_access.md b/documentation/rules/ansible/gcp/vm_with_full_cloud_access.md new file mode 100644 index 00000000..1342df5b --- /dev/null +++ b/documentation/rules/ansible/gcp/vm_with_full_cloud_access.md @@ -0,0 +1,74 @@ +--- +title: "VM with full cloud access" +group_id: "Ansible / GCP" +meta: + name: "gcp/vm_with_full_cloud_access" + id: "bc20bbc6-0697-4568-9a73-85af1dd97bdd" + display_name: "VM with full cloud access" + cloud_provider: "GCP" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `bc20bbc6-0697-4568-9a73-85af1dd97bdd` + +**Cloud Provider:** GCP + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_instance_module.html#parameter-service_accounts/scopes) + +### Description + +Granting the `cloud-platform` OAuth scope to a VM's service account gives that instance full access to all Google Cloud APIs. This increases the blast radius if the VM or its credentials are compromised and enables unintended lateral movement or data access. + +In Ansible tasks using `google.cloud.gcp_compute_instance` or `gcp_compute_instance`, inspect the `service_accounts` property's `scopes` list and ensure it does not contain the `cloud-platform` scope (for example, `cloud-platform` or `https://www.googleapis.com/auth/cloud-platform`). Resources with `service_accounts.scopes` containing the `cloud-platform` scope are flagged. + +Specify only the minimal OAuth scopes required for the workload, or avoid broad instance-level scopes by assigning appropriate IAM roles to the service account or using Workload Identity. + +Secure configuration example with a limited scope: + +```yaml +- name: Create VM with minimal OAuth scopes + google.cloud.gcp_compute_instance: + name: my-instance + machine_type: n1-standard-1 + service_accounts: + - email: my-service-account@project.iam.gserviceaccount.com + scopes: + - https://www.googleapis.com/auth/compute.readonly +``` + +## Compliant Code Examples +```yaml +- name: create a instance + google.cloud.gcp_compute_instance: + name: test_object + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + state: present + +``` +## Non-Compliant Code Examples +```yaml +- name: create a instance + google.cloud.gcp_compute_instance: + name: test_object + zone: us-central1-a + project: test_project + auth_kind: serviceaccount + service_accounts: + - scopes: + - cloud-platform + state: present + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/general/communication_over_http.md b/documentation/rules/ansible/general/communication_over_http.md new file mode 100644 index 00000000..30cddb27 --- /dev/null +++ b/documentation/rules/ansible/general/communication_over_http.md @@ -0,0 +1,73 @@ +--- +title: "Communication over HTTP" +group_id: "Ansible / Common" +meta: + name: "general/communication_over_http" + id: "2e8d4922-8362-4606-8c14-aa10466a1ce3" + display_name: "Communication over HTTP" + cloud_provider: "Common" + platform: "Ansible" + severity: "MEDIUM" + category: "Insecure Configurations" +--- +## Metadata + +**Id:** `2e8d4922-8362-4606-8c14-aa10466a1ce3` + +**Cloud Provider:** Common + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Insecure Configurations + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/uri_module.html#parameter-url) + +### Description + +Using HTTP URLs in Ansible uri tasks exposes requests and any sensitive data (tokens, credentials, or cookies) to interception and tampering because traffic is sent in plaintext. Tasks that use the `ansible.builtin.uri` module should have a `url` property that begins with `https://`. Tasks whose `url` starts with `http://` are flagged and should be updated to use `https://` endpoints or other secure transport. + +Secure example: + +```yaml +- name: Call API over HTTPS + ansible.builtin.uri: + url: "https://api.example.com/endpoint" + method: GET +``` + +## Compliant Code Examples +```yaml +- name: Verificar o status de um site usando o módulo uri + hosts: localhost + tasks: + - name: Verificar o status do site + ansible.builtin.uri: + url: "https://www.example.com" + method: GET + register: site_response + + - name: Exibir resposta do site + debug: + var: site_response + +``` +## Non-Compliant Code Examples +```yaml +- name: Verificar o status de um site usando o módulo uri + hosts: localhost + tasks: + - name: Verificar o status do site + ansible.builtin.uri: + url: "http://www.example.com" + method: GET + register: site_response + + - name: Exibir resposta do site + debug: + var: site_response + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/general/insecure_relative_path_resolution.md b/documentation/rules/ansible/general/insecure_relative_path_resolution.md new file mode 100644 index 00000000..5e65b3a4 --- /dev/null +++ b/documentation/rules/ansible/general/insecure_relative_path_resolution.md @@ -0,0 +1,104 @@ +--- +title: "Insecure relative path resolution" +group_id: "Ansible / Common" +meta: + name: "general/insecure_relative_path_resolution" + id: "8d22ae91-6ac1-459f-95be-d37bd373f244" + display_name: "Insecure relative path resolution" + cloud_provider: "Common" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `8d22ae91-6ac1-459f-95be-d37bd373f244` + +**Cloud Provider:** Common + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://ansible.readthedocs.io/projects/lint/rules/no-relative-paths/) + +### Description + +Using upward-relative src paths in Ansible copy or template tasks (for example, `../templates` or `../files`) can cause unpredictable file selection and accidental inclusion of sensitive files. The path is resolved against the current working directory, which may differ across control hosts or CI runs. + +This rule examines tasks that use the modules `copy`, `win_copy`, `template`, `win_template`, `ansible.builtin.copy`, and `ansible.builtin.template`. Any task whose `src` property contains a `../` segment referencing role folders (for example, `../files`, `../templates`, `../win_templates`) is flagged. + +Fix by placing assets in the role's `files`/`templates` directories and referencing them by name, or use absolute paths or `{{ role_path }}` when necessary so `src` does not include upward-traversal segments. + +Secure examples: + +```yaml +- name: Deploy config file + copy: + src: myapp.conf + dest: /etc/myapp/myapp.conf + +- name: Deploy template + template: + src: myapp.conf.j2 + dest: /etc/myapp/config.conf +``` + +## Compliant Code Examples +```yaml +--- +- name: Negative Example + hosts: localhost + tasks: + - name: One + ansible.builtin.copy: + content: + dest: /etc/mine.conf + mode: "0644" + - name: Two + ansible.builtin.copy: + src: /home/example/files/foo.conf + dest: /etc/foo.conf + mode: "0644" + +--- +- name: Negative Example 2 + hosts: localhost + tasks: + - name: One + ansible.builtin.template: + src: ../example/foo.j2 + dest: /etc/file.conf + mode: "0644" + - name: Two + ansible.builtin.copy: + src: ../example/foo.conf + dest: /etc/foo.conf + mode: "0644" + - name: Three + win_template: + src: ../example/foo2.j2 + dest: /etc/file.conf + mode: "0644" +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Positive Example + hosts: localhost + tasks: + - name: One + ansible.builtin.template: + src: ../templates/foo.j2 + dest: /etc/file.conf + mode: "0644" + - name: Two + ansible.builtin.copy: + src: ../files/foo.conf + dest: /etc/foo.conf + mode: "0644" +``` \ No newline at end of file diff --git a/documentation/rules/ansible/general/logging_of_sensitive_data.md b/documentation/rules/ansible/general/logging_of_sensitive_data.md new file mode 100644 index 00000000..592f03d4 --- /dev/null +++ b/documentation/rules/ansible/general/logging_of_sensitive_data.md @@ -0,0 +1,117 @@ +--- +title: "Logging of sensitive data" +group_id: "Ansible / Common" +meta: + name: "general/logging_of_sensitive_data" + id: "59029ddf-e651-412b-ae7b-ff6d403184bc" + display_name: "Logging of sensitive data" + cloud_provider: "Common" + platform: "Ansible" + severity: "LOW" + category: "Best Practices" +--- +## Metadata + +**Id:** `59029ddf-e651-412b-ae7b-ff6d403184bc` + +**Cloud Provider:** Common + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://ansible.readthedocs.io/projects/lint/rules/no-log-password/) + +### Description + +Tasks that create or modify users and set a `password` can emit plaintext credentials in playbook output and logs, risking credential leakage. For `ansible.builtin.user` tasks that include the `password` property, the task-level `no_log` attribute must be set to `true`. Tasks missing `no_log` or with `no_log: false` are flagged by this rule. Apply `no_log: true` to any task that handles plaintext secrets or templated variables that resolve to secrets. + +```yaml +- name: Create application user without exposing password + ansible.builtin.user: + name: appuser + password: "{{ appuser_password }}" + no_log: true +``` + +## Compliant Code Examples +```yaml +--- +- name: Negative playbook + hosts: localhost + tasks: + - name: foo + ansible.builtin.user: + name: john_doe + comment: John Doe + uid: 1040 + group: admin + password: "{{ item }}" + with_items: + - wow + no_log: true + +--- +- name: Negative Playbook 2 + hosts: localhost + tasks: + - name: bar + ansible.builtin.user: + name: john_doe + comment: John Doe + uid: 1040 + group: admin + with_items: + - wow + no_log: false + +--- +- name: Negative Playbook 3 + hosts: localhost + tasks: + - name: bar + ansible.builtin.user: + name: john_doe + comment: John Doe + uid: 1040 + group: admin + with_items: + - wow +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Positive Playbook + hosts: localhost + tasks: + - name: bar + ansible.builtin.user: + name: john_doe + comment: John Doe + uid: 1040 + group: admin + password: "{{ item }}" + with_items: + - wow +``` + +```yaml +--- +- name: Positive Playbook + hosts: localhost + tasks: + - name: bar + ansible.builtin.user: + name: john_doe + comment: John Doe + uid: 1040 + group: admin + password: "{{ item }}" + with_items: + - wow + no_log: false +``` \ No newline at end of file diff --git a/documentation/rules/ansible/general/privilege_escalation_using_become_plugin.md b/documentation/rules/ansible/general/privilege_escalation_using_become_plugin.md new file mode 100644 index 00000000..c85b9858 --- /dev/null +++ b/documentation/rules/ansible/general/privilege_escalation_using_become_plugin.md @@ -0,0 +1,137 @@ +--- +title: "Privilege escalation using become plugin" +group_id: "Ansible / Common" +meta: + name: "general/privilege_escalation_using_become_plugin" + id: "0e75052f-cc02-41b8-ac39-a78017527e95" + display_name: "Privilege escalation using become plugin" + cloud_provider: "Common" + platform: "Ansible" + severity: "MEDIUM" + category: "Access Control" +--- +## Metadata + +**Id:** `0e75052f-cc02-41b8-ac39-a78017527e95` + +**Cloud Provider:** Common + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Access Control + +#### Learn More + + - [Provider Reference](https://ansible.readthedocs.io/projects/lint/rules/partial-become/#problematic-code) + +### Description + +Playbooks and tasks that specify a target user with `become_user` must also enable privilege escalation so actions execute with the intended elevated privileges. Without `become: true`, commands run as the unprivileged connection user or fail. This can lead to misconfiguration, failed security controls, or unintended access to sensitive resources. Verify the `become` property is defined and set to `true` on `ansible_playbook` and `ansible_task` resources whenever `become_user` is present. Resources where `become_user` is defined but `become` is missing or `false` are flagged for correction. + +Secure examples: + +```yaml +- hosts: servers + become: true + become_user: root + tasks: + - name: Perform privileged action + command: /usr/bin/some-command +``` + +```yaml +- name: Install package + become: true + become_user: root + apt: + name: nginx + state: present +``` + +## Compliant Code Examples +```yaml +--- +- hosts: localhost + become_user: postgres + become: true + tasks: + - name: some task + ansible.builtin.command: whoamyou + changed_when: false + +--- +- hosts: localhost + tasks: + - name: become from the same scope + ansible.builtin.command: whoami + become: true + become_user: postgres + changed_when: false +``` +## Non-Compliant Code Examples +```yaml +--- +- hosts: localhost + name: become_user without become + become_user: bar + + tasks: + - name: Simple hello + ansible.builtin.debug: + msg: hello + +--- +- hosts: localhost + name: become_user with become false + become_user: root + become: false + + tasks: + - name: Simple hello + ansible.builtin.debug: + msg: hello + +--- +- hosts: localhost + tasks: + - name: become and become_user on different tasks + block: + - name: Sample become + become: true + ansible.builtin.command: ls . + - name: Sample become_user + become_user: foo + ansible.builtin.command: ls . + +--- +- hosts: localhost + tasks: + - name: become false + block: + - name: Sample become + become: true + ansible.builtin.command: ls . + - name: Sample become_user + become_user: postgres + become: false + ansible.builtin.command: ls . + +--- +- hosts: localhost + tasks: + - name: become_user with become task as false + ansible.builtin.command: whoami + become_user: mongodb + become: false + changed_when: false + +--- +- hosts: localhost + tasks: + - name: become_user without become + ansible.builtin.command: whoami + become_user: mysql + changed_when: false +``` \ No newline at end of file diff --git a/documentation/rules/ansible/general/risky_file_permissions.md b/documentation/rules/ansible/general/risky_file_permissions.md new file mode 100644 index 00000000..94f09d8b --- /dev/null +++ b/documentation/rules/ansible/general/risky_file_permissions.md @@ -0,0 +1,247 @@ +--- +title: "Risky file permissions" +group_id: "Ansible / Common" +meta: + name: "general/risky_file_permissions" + id: "88841d5c-d22d-4b7e-a6a0-89ca50e44b9f" + display_name: "Risky file permissions" + cloud_provider: "Common" + platform: "Ansible" + severity: "LOW" + category: "Supply-Chain" +--- +## Metadata + +**Id:** `88841d5c-d22d-4b7e-a6a0-89ca50e44b9f` + +**Cloud Provider:** Common + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Supply-Chain + +#### Learn More + + - [Provider Reference](https://ansible.readthedocs.io/projects/lint/rules/risky-file-permissions/) + +### Description + +Files and directories created or modified by Ansible tasks must have explicit, least-privilege file modes. Omitting the `mode` or relying on preserved/default permissions can leave artifacts world-readable or writable, increasing the risk of data exposure and privilege escalation. + +This rule checks file-related modules—`archive`, `assemble`, `copy`, `file`, `get_url`, `template` (including their FQCNs) and content-creation modules like `htpasswd` and `ini_file`. Tasks that create files (task `state` not `absent`/`link`) without defining the `mode` property are flagged. + +For modules that provide a `create` boolean (for example `htpasswd` and `ini_file`), tasks where `create` is true (or defaults to true) and `mode` is not defined are also flagged. `mode: preserve` is only allowed for `copy` and `template` modules. Any other module using `mode: preserve` is reported as invalid. + +To remediate, add an explicit `mode` with an appropriate octal value, or set `create: false` when creation is not desired. + +```yaml +- name: Create config file with restrictive permissions + ansible.builtin.file: + path: /etc/myapp/config.yml + state: file + mode: '0640' + +- name: Create ini file with explicit mode + community.general.ini_file: + path: /etc/myapp/settings.ini + create: true + mode: '0640' +``` + +## Compliant Code Examples +```yaml +--- +- name: SUCCESS_PERMISSIONS_PRESENT + hosts: all + tasks: + - name: Permissions not missing and numeric + ansible.builtin.file: + path: foo + mode: "0600" + +--- +- name: SUCCESS_PERMISSIONS_PRESENT_GET_URL + hosts: all + tasks: + - name: Permissions not missing and numeric + ansible.builtin.get_url: + url: http://foo + dest: foo + mode: "0600" + +--- +- name: SUCCESS_ABSENT_STATE + hosts: all + tasks: + - name: Permissions missing while state is absent is fine + ansible.builtin.file: + path: foo + state: absent + +--- +- name: SUCCESS_DEFAULT_STATE + hosts: all + tasks: + - name: Permissions missing while state is file (default) is fine + ansible.builtin.file: + path: foo + +--- +- name: SUCCESS_LINK_STATE + hosts: all + tasks: + - name: Permissions missing while state is link is fine + ansible.builtin.file: + path: foo2 + src: foo + state: link + +--- +- name: SUCCESS_CREATE_FALSE + hosts: all + tasks: + - name: File edit when create is false + ansible.builtin.lineinfile: + path: foo + create: false + line: some content here + +--- +- name: SUCCESS_REPLACE + hosts: all + tasks: + - name: Replace should not require mode + ansible.builtin.replace: + path: foo + regexp: foo + +--- +- name: SUCCESS_RECURSE + hosts: all + tasks: + - name: File with recursive does not require mode + ansible.builtin.file: + state: directory + recurse: true + path: foo + - name: Permissions not missing and numeric (fqcn) + ansible.builtin.file: + path: bar + mode: "755" + - name: File edit when create is false (fqcn) + ansible.builtin.lineinfile: + path: foo + create: false + line: some content here + +--- +- name: LINIINFILE_CREATE + tasks: + - name: create is true 2x + lineinfile: + path: foo + line: some content here + mode: "0600" + +--- +- name: PRESERVE_MODE + tasks: + - name: not preserve value + copy: + path: foo + mode: preserve + +--- +- name: LINEINFILE_CREATE2 + tasks: + - name: create_false + ansible.builtin.lineinfile: + path: foo + create: true + line: some content here + mode: "644" + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: PRESERVE_MODE + tasks: + - name: not preserve value + ansible.builtin.file: + path: foo + mode: preserve + +--- +- name: MISSING_PERMISSIONS_TOUCH + tasks: + - name: Permissions missing + file: + path: foo + state: touch + - name: Permissions missing 2x + ansible.builtin.file: + path: foo + state: touch + +--- +- name: MISSING_PERMISSIONS_DIRECTORY + tasks: + - name: Permissions missing 3x + file: + path: foo + state: directory + - name: create is true + ansible.builtin.lineinfile: + path: foo + create: true + line: some content here + +--- +- name: MISSING_PERMISSIONS_GET_URL + tasks: + - name: Permissions missing 4x + get_url: + url: http://foo + dest: foo + +--- +- name: LINEINFILE_CREATE + tasks: + - name: create is true 2x + ansible.builtin.lineinfile: + path: foo + create: true + line: some content here + +--- +- name: REPLACE_PRESERVE + tasks: + - name: not preserve mode 2x + replace: + path: foo + mode: preserve + regexp: foo + +--- +- name: NOT_PERMISSION + tasks: + - name: Not Permissions + file: + path: foo + owner: root + group: root + state: directory + +--- +- name: LINEINFILE_CREATE2 + tasks: + - name: create_false + ansible.builtin.lineinfile: + path: foo + create: true + line: some content here + mode: preserve +``` \ No newline at end of file diff --git a/documentation/rules/ansible/general/unpinned_package_version.md b/documentation/rules/ansible/general/unpinned_package_version.md new file mode 100644 index 00000000..0ff68a7a --- /dev/null +++ b/documentation/rules/ansible/general/unpinned_package_version.md @@ -0,0 +1,360 @@ +--- +title: "Unpinned package version" +group_id: "Ansible / Common" +meta: + name: "general/unpinned_package_version" + id: "c05e2c20-0a2c-4686-b1f8-5f0a5612d4e8" + display_name: "Unpinned package version" + cloud_provider: "Common" + platform: "Ansible" + severity: "LOW" + category: "Supply-Chain" +--- +## Metadata + +**Id:** `c05e2c20-0a2c-4686-b1f8-5f0a5612d4e8` + +**Cloud Provider:** Common + +**Platform:** Ansible + +**Severity:** Low + +**Category:** Supply-Chain + +#### Learn More + + - [Provider Reference](https://ansible.readthedocs.io/projects/lint/rules/package-latest/) + +### Description + +Package installer tasks that set `state: latest` without pinning a `version` or enabling `update_only` can cause unintended upgrades. This may introduce breaking changes, regressions, or service disruptions and make deployments non-reproducible. + +Ansible package installer modules (for example `apt`, `yum`, `dnf`, `pip`) are checked for the following task properties: `state` must not be `latest` unless a `version` is specified or `update_only` is set to `true`. Tasks with `state: latest` and no `version` and missing or `false` `update_only` are flagged. + +Remediate by pinning packages to explicit versions for deterministic installs, or set `update_only: true` when you only want to upgrade already-installed packages. + +Secure example — pin a version: + +```yaml +- name: Install mypkg at a specific version + apt: + name: mypkg=1.2.3 + state: present +```Secure example — allow only updates to already-installed packages: + +```yaml +- name: Update installed packages only + yum: + name: mypkg + state: latest + update_only: true +``` + +## Compliant Code Examples +```yaml +--- +- name: Example playbook + hosts: localhost + tasks: + - name: Install Ansible + ansible.builtin.yum: + name: ansible-2.12.7.0 + state: present + + - name: Install Ansible-lint + ansible.builtin.pip: + name: ansible-lint + state: present + version: 5.4.0 + + - name: Update Ansible with update_only to true + ansible.builtin.yum: + name: sudo + state: latest + update_only: true + + - name: Install nmap + community.general.zypper: + name: nmap + state: present + + - name: Install package without using cache + community.general.apk: + name: foo + state: present + no_cache: true + + - name: Install apache httpd + ansible.builtin.apt: + name: apache2 + state: present + + - name: Update Gemfile in another directory + community.general.bundler: + state: present + chdir: ~/rails_project + + - name: Install a modularity appstream with defined profile + ansible.builtin.dnf: + name: "@postgresql/client" + state: present + + - name: Install rake + community.general.gem: + name: rake + state: present + + - name: Install formula foo with 'brew' from cask + community.general.homebrew: + name: homebrew/cask/foo + state: present + + - name: Install Green Balls plugin + community.general.jenkins_plugin: + name: greenballs + version: present + state: present + url: http://host_jenkins:8080 + username: user_jenkins + password: userpass_jenkins + register: result + + - name: Install packages based on package.json + community.general.npm: + path: /app/location + state: present + + - name: Install nmap + community.general.openbsd_pkg: + name: nmap + state: present + + - name: Install ntpdate + ansible.builtin.package: + name: ntpdate + state: present + + - name: Install package bar from file + community.general.pacman: + name: ~/bar-1.0-1-any.pkg.tar.xz + state: present + + - name: Install package bar from file + community.general.pacman: + name: ~/bar-1.0-1-any.pkg.tar.xz + state: present + + - name: Install finger daemon + community.general.pkg5: + name: service/network/finger + state: present + + - name: Install several packages + community.general.pkgutil: + name: + - CSWsudo + - CSWtop + state: present + + - name: Install package foo + community.general.portage: + package: foo + state: present + + - name: Make sure that it is the most updated package + community.general.slackpkg: + name: foo + state: present + + - name: Make sure spell foo is installed + community.general.sorcery: + spell: foo + state: present + + - name: Install package unzip + community.general.swdepot: + name: unzip + state: present + depot: "repository:/path" + + - name: Install multiple packages + win_chocolatey: + name: + - procexp + - putty + - windirstat + state: present + + - name: Install "imagemin" node.js package globally. + community.general.yarn: + name: imagemin + global: true + + - name: Install a list of packages (suitable replacement for 2.11 loop deprecation warning) + ansible.builtin.yum: + name: + - nginx + - postgresql + - postgresql-server + state: present + + - name: Install local rpm file + community.general.zypper: + name: /tmp/fancy-software.rpm + state: present + +``` +## Non-Compliant Code Examples +```yaml +--- +- name: Example playbook + hosts: localhost + tasks: + - name: Install Ansible + ansible.builtin.yum: + name: ansible + state: latest + + - name: Install Ansible-lint + ansible.builtin.pip: + name: ansible-lint + state: latest + + - name: Install some-package + ansible.builtin.package: + name: some-package + state: latest + + - name: Install Ansible with update_only to false + ansible.builtin.yum: + name: sudo + state: latest + update_only: false + + - name: Install nmap + community.general.zypper: + name: nmap + state: latest + + - name: Install package without using cache + community.general.apk: + name: foo + state: latest + no_cache: true + + - name: Install apache httpd + ansible.builtin.apt: + name: apache2 + state: latest + + - name: Update Gemfile in another directory + community.general.bundler: + state: latest + chdir: ~/rails_project + + - name: Install a modularity appstream with defined profile + ansible.builtin.dnf: + name: "@postgresql/client" + state: latest + + - name: Install rake + community.general.gem: + name: rake + state: latest + + - name: Install formula foo with 'brew' from cask + community.general.homebrew: + name: homebrew/cask/foo + state: latest + + - name: Install Green Balls plugin + community.general.jenkins_plugin: + name: greenballs + state: latest + url: http://host_jenkins:8080 + username: user_jenkins + password: userpass_jenkins + register: result + + - name: Install packages based on package.json + community.general.npm: + path: /app/location + state: latest + + - name: Install nmap + community.general.openbsd_pkg: + name: nmap + state: latest + + - name: Install ntpdate + ansible.builtin.package: + name: ntpdate + state: latest + + - name: Install package bar from file + community.general.pacman: + name: ~/bar-1.0-1-any.pkg.tar.xz + state: latest + + - name: Install finger daemon + community.general.pkg5: + name: service/network/finger + state: latest + + - name: Install several packages + community.general.pkgutil: + name: + - CSWsudo + - CSWtop + state: latest + + - name: Install package foo + community.general.portage: + package: foo + state: latest + + - name: Make sure that it is the most updated package + community.general.slackpkg: + name: foo + state: latest + + - name: Make sure spell foo is installed + community.general.sorcery: + spell: foo + state: latest + + - name: Install package unzip + community.general.swdepot: + name: unzip + state: latest + depot: "repository:/path" + + - name: Install multiple packages + win_chocolatey: + name: + - procexp + - putty + - windirstat + state: latest + + - name: Install "imagemin" node.js package globally. + community.general.yarn: + name: imagemin + global: true + state: latest + + - name: Install a list of packages (suitable replacement for 2.11 loop deprecation warning) + ansible.builtin.yum: + name: + - nginx + - postgresql + - postgresql-server + state: latest + + - name: Install local rpm file + community.general.zypper: + name: /tmp/fancy-software.rpm + state: latest + +``` \ No newline at end of file diff --git a/documentation/rules/ansible/hosts/ansible_tower_exposed_to_internet.md b/documentation/rules/ansible/hosts/ansible_tower_exposed_to_internet.md new file mode 100644 index 00000000..9ea95848 --- /dev/null +++ b/documentation/rules/ansible/hosts/ansible_tower_exposed_to_internet.md @@ -0,0 +1,143 @@ +--- +title: "Ansible Tower exposed to the internet" +group_id: "Ansible / Ansible Inventory" +meta: + name: "hosts/ansible_tower_exposed_to_internet" + id: "1b2bf3ff-31e9-460e-bbfb-45e48f4f20cc" + display_name: "Ansible Tower exposed to the internet" + cloud_provider: "Ansible Inventory" + platform: "Ansible" + severity: "MEDIUM" + category: "Best Practices" +--- +## Metadata + +**Id:** `1b2bf3ff-31e9-460e-bbfb-45e48f4f20cc` + +**Cloud Provider:** Ansible Inventory + +**Platform:** Ansible + +**Severity:** Medium + +**Category:** Best Practices + +#### Learn More + + - [Provider Reference](https://docs.ansible.com/ansible-tower/latest/html/administration/security_best_practices.html#understand-the-architecture-of-ansible-and-tower) + +### Description + +Ansible Tower hosts must not be assigned public IP addresses. Exposing Tower to the public internet increases the risk of unauthorized access and credential compromise of your automation infrastructure. Check the Ansible inventory resource (`ansible_inventory`) for entries under `all.children.tower.hosts` and ensure each host value is a private IP address (RFC1918) or an internal DNS name rather than a public IP. Resources with hosts set to public IPs are flagged. + +Use private IP ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`) or internal hostnames, and place Tower behind a VPN, bastion host, or firewall/security-group restrictions to limit exposure. + +Secure inventory example with a private IP: + +```yaml +all: + children: + tower: + hosts: + tower.internal.example.com: + ansible_host: 10.0.1.5 +``` + +## Compliant Code Examples +```yaml +all: + children: + automationhub: + hosts: + automationhub.acme.org: + admin_password: + pg_database: awx + pg_host: database-01.acme.org + pg_password: + pg_port: '5432' + pg_sslmode: prefer + pg_username: awx + database: + hosts: + database-01.acme.org: + admin_password: + pg_database: awx + pg_host: database-01.acme.org + pg_password: + pg_port: '5432' + pg_sslmode: prefer + pg_username: awx + tower: + hosts: + 172.27.0.5: + admin_password: + pg_database: awx + pg_host: database-01.acme.org + pg_password: + pg_port: '5432' + pg_sslmode: prefer + pg_username: awx + ungrouped: {} + +``` + +```ini +[tower] +172.27.0.2 +172.27.0.3 +172.27.0.4 +``` +## Non-Compliant Code Examples +```yaml +all: + children: + automationhub: + hosts: + automationhub.acme.org: + admin_password: + pg_database: awx + pg_host: database-01.acme.org + pg_password: + pg_port: '5432' + pg_sslmode: prefer + pg_username: awx + database: + hosts: + database-01.acme.org: + admin_password: + pg_database: awx + pg_host: database-01.acme.org + pg_password: + pg_port: '5432' + pg_sslmode: prefer + pg_username: awx + tower: + hosts: + 139.50.1.1: + admin_password: + pg_database: awx + pg_host: database-01.acme.org + pg_password: + pg_port: '5432' + pg_sslmode: prefer + pg_username: awx + ungrouped: {} + +``` + +```ini +[tower] +150.50.1.1 +[automationhub] +automationhub.acme.org +[database] +database-01.acme.org +[all:vars] +admin_password='' +pg_host='database-01.acme.org' +pg_port='5432' +pg_database='awx' +pg_username='awx' +pg_password='' +pg_sslmode='prefer' +``` \ No newline at end of file