Search code examples
open-policy-agentrego

Validating every element of a list against another list using Rego


I am new to rego and trying to write a policy to validate elements of a list against another list. Here is the problem I am trying to solve: I have an approved list of security groups against which I need to check the security groups from my input. If the SG's in my input are not from the approved list, I need to output a message saying "Invalid SG's are being used". I couldn't find any documentation which explains how I could compare one list with another. Here is the policy in rego playground: https://play.openpolicyagent.org/p/m09f2K1g0h

Policy:

check_list = ["sg-1001a1d199tx8866a","sg-2002b2e200in9966b", "sg-3003c3f201dc0011c","sg-4004d4e202nj1122d", "sg-5003c3f201dc0011e","sg-6004d4e202nj1122f"]

aws_valid_sgs := {i: Reason |
    doc = input[i]; i="aws_launch_configuration.ecs-cluster-lc"
    key_ids := [k | doc[k]; startswith(k, "security_groups.")]
    resource := {
        doc[k] : doc[replace(k, ".key", ".value")] | key_ids[_] == k
    }
    
    sg_ids := [m | resource[m]; startswith(m, "sg-")]
    list := {x| x:=check_list[_]}

    not(list[sg_ids[i]])

    Reason := sprintf("Resource %v is not using an approved SG ", [doc.name])
}

Input:

{
    "aws_launch_configuration.ecs-cluster-lc": {
        "associate_public_ip_address": "false",
        "destroy": false,
        "destroy_tainted": false,
        "ebs_block_device.#": "",
        "ebs_optimized": "",
        "enable_monitoring": "true",
        "id": "",
        "instance_type": "t3.large",
        "key_name": "",
        "name": "test_r1-us-east-1",
        "security_groups.1122334455": "sg-1001a1d199tx8866a",
        "security_groups.2233445566": "sg-2002b2e200in9966b",
        "security_groups.3344556677": "sg-3003c3f201dc0011c",
        "security_groups.4455667788": "sg-4004d4e202nj1122d"
    },
    "destroy": false
}

Any help is appreciated. Thanks.


Solution

  • TLDR; This is best done using set operations, e.g., {"foo", "bar", "baz"} - {"bar"} == {"foo", "baz"}). Here's a complete example:

    
    # Define a SET of valid SGs to compare against.
    valid_sgs := {"sg-1001a1d199tx8866a","sg-2002b2e200in9966b", "sg-3003c3f201dc0011c","sg-4004d4e202nj1122d", "sg-5003c3f201dc0011e","sg-6004d4e202nj1122f"}
    
    # Compute the SET of SGs from the launch configuration in the input.
    launch_config_sgs := {sg |
        some key
        launch_config := input["aws_launch_configuration.ecs-cluster-lc"]
        sg := launch_config[key]
        startswith(key, "security_groups.")
    }
    
    # Compute the SET of invalid SGs by taking the difference of the other two sets.
    invalid_launch_config_sgs := launch_config_sgs - valid_sgs
    
    # Generate a 'deny' message if there are any invalid SGs.
    deny[msg] {
        launch_config := input["aws_launch_configuration.ecs-cluster-lc"]
        count(invalid_launch_config_sgs) > 0
        msg := sprintf("Resource %v is not using an approved SG", [launch_config.name])
    }
    

    Playground link

    There were a few things the posters version that made the problem harder than necessary:

    • check_list is defined as an array (not a set). Arrays are indexed by position. To check if a value is contained in the list, you need to iterate. It's often easier to just construct sets and test for membership/intersection/difference/etc.

    • The aws_valid_sgs rule constructs a bunch of unnecessary intermediate values from the input (e.g., doc, key_ids, resource, etc.) All that's needed is the set of SGs from the input which can be computed in 2 statements: find fields in the launch config where the key starts with security_group. and construct a set of field values for those keys.