Repository: ansible/ansible

Issue:
ec2_group rules_egress has an All Access outbound additional rule added to new security groups
##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report

##### COMPONENT NAME
<!---
Name of the module, plugin, task or feature
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path
-->

ec2_group

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.4.2.0
  config file = /mnt/c/git/ansible/ansible.cfg
  configured module search path = [u'/mnt/c/git/ansible/library', u'/usr/share/ansible']
  ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.6 (default, Nov 23 2017, 15:49:48) [GCC 4.8.4]
```

ec2_group from d877c146ab750c0f1505b4180e4170163d854fb5

##### CONFIGURATION
<!---
If using Ansible 2.4 or above, paste the results of "ansible-config dump --only-changed"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

I'm using custom modules to import newer stuff before it's fully released.

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->

Ubuntu on WSL (Windows 10/Bash on windows)

##### SUMMARY
<!--- Explain the problem briefly -->

On the creation of new security groups, the "All out" outbound rule is added despite specifying rules_egress. It feels related to #30038 but is a little different.

I don't remember it doing this before the end of 2017.

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- ec2_group:
      name: AllowAllWebTraffic
      description: Allow HTTP(S) traffic from the whole wide world!
      region: "{{region}}"
      purge_rules: yes
      purge_rules_egress: yes
      rules:
      - rule_desc: Allow HTTP(S) traffic from anywhere
        proto: tcp
        ports: [80, 443]
        cidr_ip: 0.0.0.0/0
        cidr_ipv6: ::/0
      rules_egress: 
      - rule_desc: Bogus rule
        proto: tcp
        ports: 1
        cidr_ip: 127.0.0.0/32
```

Results in a group with the following:

`aws ec2 describe-security-groups --group-ids sg-thisone`

```json
{
    "SecurityGroups": [
        {
            "IpPermissionsEgress": [
                {
                    "PrefixListIds": [],
                    "FromPort": 1,
                    "IpRanges": [
                        {
                            "Description": "Bogus rule",
                            "CidrIp": "127.0.0.0/32"
                        }
                    ],
                    "ToPort": 1,
                    "IpProtocol": "tcp",
                    "UserIdGroupPairs": [],
                    "Ipv6Ranges": []
                },
                {
                    "IpProtocol": "-1",
                    "PrefixListIds": [],
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ],
                    "UserIdGroupPairs": [],
                    "Ipv6Ranges": []
                }
            ],
            "Description": "Allow HTTP(S) traffic from the whole wide world!",
            "IpPermissions": [
                {
                    "PrefixListIds": [],
                    "FromPort": 80,
                    "IpRanges": [
                        {
                            "Description": "Allow HTTP(S) traffic from anywhere",
                            "CidrIp": "0.0.0.0/0"
                        }
                    ],
                    "ToPort": 80,
                    "IpProtocol": "tcp",
                    "UserIdGroupPairs": [],
                    "Ipv6Ranges": [
                        {
                            "CidrIpv6": "::/0",
                            "Description": "Allow HTTP(S) traffic from anywhere"
                        }
                    ]
                },
                {
                    "PrefixListIds": [],
                    "FromPort": 443,
                    "IpRanges": [
                        {
                            "Description": "Allow HTTP(S) traffic from anywhere",
                            "CidrIp": "0.0.0.0/0"
                        }
                    ],
                    "ToPort": 443,
                    "IpProtocol": "tcp",
                    "UserIdGroupPairs": [],
                    "Ipv6Ranges": [
                        {
                            "CidrIpv6": "::/0",
                            "Description": "Allow HTTP(S) traffic from anywhere"
                        }
                    ]
                }
            ],
            "GroupName": "AllowAllWebTraffic",
            "VpcId": "vpc-...",
            "OwnerId": "...",
            "GroupId": "sg-thisone"
        }
    ]
}
```

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->

No additional egress rules

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
{
  "changed": false,
  "description": "Allow HTTP(S) traffic from the whole wide world!",
  "group_id": "sg-thisOne",
  "group_name": "AllowAllWebTraffic",
  "invocation": {
    "module_args": {
      "aws_access_key": null,
      "aws_secret_key": null,
      "description": "Allow HTTP(S) traffic from the whole wide world!",
      "ec2_url": null,
      "group_id": null,
      "name": "AllowAllWebTraffic",
      "profile": null,
      "purge_rules": true,
      "purge_rules_egress": true,
      "purge_tags": true,
      "region": "us-west-2",
      "rules": [{
        "cidr_ip": "0.0.0.0/0",
        "cidr_ipv6": "::/0",
        "ports": [
          80,
          443
        ],
        "proto": "tcp",
        "rule_desc": "Allow HTTP(S) traffic from anywhere"
      }],
      "rules_egress": [{
        "cidr_ip": "127.0.0.0/32",
        "ports": 1,
        "proto": "tcp",
        "rule_desc": "Bogus rule"
      }],
      "security_token": null,
      "state": "present",
      "tags": null,
      "validate_certs": true,
      "vpc_id": null
    }
  },
  "ip_permissions": [{
      "from_port": 80,
      "ip_protocol": "tcp",
      "ip_ranges": [{
        "cidr_ip": "0.0.0.0/0",
        "description": "Allow HTTP(S) traffic from anywhere"
      }],
      "ipv6_ranges": [{
        "cidr_ipv6": "::/0",
        "description": "Allow HTTP(S) traffic from anywhere"
      }],
      "prefix_list_ids": [],
      "to_port": 80,
      "user_id_group_pairs": []
    },
    {
      "from_port": 443,
      "ip_protocol": "tcp",
      "ip_ranges": [{
        "cidr_ip": "0.0.0.0/0",
        "description": "Allow HTTP(S) traffic from anywhere"
      }],
      "ipv6_ranges": [{
        "cidr_ipv6": "::/0",
        "description": "Allow HTTP(S) traffic from anywhere"
      }],
      "prefix_list_ids": [],
      "to_port": 443,
      "user_id_group_pairs": []
    }
  ],
  "ip_permissions_egress": [{
      "from_port": 1,
      "ip_protocol": "tcp",
      "ip_ranges": [{
        "cidr_ip": "127.0.0.0/32",
        "description": "Bogus rule"
      }],
      "ipv6_ranges": [],
      "prefix_list_ids": [],
      "to_port": 1,
      "user_id_group_pairs": []
    },
    {
      "ip_protocol": "-1",
      "ip_ranges": [{
        "cidr_ip": "0.0.0.0/0"
      }],
      "ipv6_ranges": [],
      "prefix_list_ids": [],
      "user_id_group_pairs": []
    }
  ],
  "owner_id": "...",
  "tags": {},
  "vpc_id": "vpc-..."
}
```



Discussion:
Files identified in the description:
* [lib/ansible/modules/cloud/amazon/ec2_group.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/amazon/ec2_group.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @adq @willthames
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
It is definitely different to #30038, which I've just closed as expected behaviour - this one is *not* expected behaviour!

Will need to create some tests and isolate cause - I can't see the problem from a quick look at the code.
Cannot reproduce. 

```
ansible 2.4.2.0
  config file = /home/will/tmp/ansible/issue-34429/ansible.cfg
  configured module search path = [u'/home/will/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /home/will/src/ansible/bin/ansible
  python version = 2.7.14 (default, Dec 11 2017, 16:08:01) [GCC 7.2.1 20170915 (Red Hat 7.2.1-2)]
```

ansible.cfg:
```
[default]
module_utils = ./module_utils
```

I copied the necessary files from commit d877c14 into `library` and `module_utils`:
```
$ find . 
.
./ansible.cfg
./module_utils
./module_utils/network
./module_utils/network/common
./module_utils/network/common/__init__.py
./module_utils/network/common/utils.py
./module_utils/network/__init__.py
./library
./library/ec2_group.py
./play.yml
```

I then ran `ansible-playbook play.yml -vvv --profile mine` with the relevant part of the result below
```
ansible-playbook 2.4.2.0
  config file = /home/will/tmp/ansible/issue-34429/ansible.cfg
  configured module search path = [u'/home/will/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /home/will/src/ansible/bin/ansible-playbook
  python version = 2.7.14 (default, Dec 11 2017, 16:08:01) [GCC 7.2.1 20170915 (Red Hat 7.2.1-2)]
Using /home/will/tmp/ansible/issue-34429/ansible.cfg as config file
Parsed /etc/ansible/hosts inventory source with ini plugin

--- 8< -----------------------------------------------------------

TASK [ec2_group] *********************************************************************************************************************************************
task path: /home/will/tmp/ansible/issue-34429/play.yml:12
Using module file /home/will/tmp/ansible/issue-34429/library/ec2_group.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: will
<127.0.0.1> EXEC /bin/sh -c 'echo ~ && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/will/.ansible/tmp/ansible-tmp-1515392749.12-113651714846008 `" && echo ansible-tmp-1515392749.12-113651714846008="` echo /home/will/.ansible/tmp/ansible-tmp-1515392749.12-113651714846008 `" ) && sleep 0'
<127.0.0.1> PUT /tmp/tmpVEXeoW TO /home/will/.ansible/tmp/ansible-tmp-1515392749.12-113651714846008/ec2_group.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/will/.ansible/tmp/ansible-tmp-1515392749.12-113651714846008/ /home/will/.ansible/tmp/ansible-tmp-1515392749.12-113651714846008/ec2_group.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python /home/will/.ansible/tmp/ansible-tmp-1515392749.12-113651714846008/ec2_group.py; rm -rf "/home/will/.ansible/tmp/ansible-tmp-1515392749.12-113651714846008/" > /dev/null 2>&1 && sleep 0'
changed: [localhost] => {
    "changed": true, 
    "description": "Allow HTTP(S) traffic from the whole wide world!", 
    "group_id": "sg-7a87801c", 
    "group_name": "AllowAllWebTraffic", 
    "invocation": {
        "module_args": {
            "aws_access_key": null, 
            "aws_secret_key": null, 
            "description": "Allow HTTP(S) traffic from the whole wide world!", 
            "ec2_url": null, 
            "group_id": null, 
            "name": "AllowAllWebTraffic", 
            "profile": "mine", 
            "purge_rules": true, 
            "purge_rules_egress": true, 
            "purge_tags": true, 
            "region": "ap-southeast-2", 
            "rules": [
                {
                    "cidr_ip": "0.0.0.0/0", 
                    "cidr_ipv6": "::/0", 
                    "ports": [
                        80, 
                        443
                    ], 
                    "proto": "tcp"
                }
            ], 
            "rules_egress": [
                {
                    "cidr_ip": "127.0.0.0/32", 
                    "ports": 1, 
                    "proto": "tcp"
                }
            ], 
            "security_token": null, 
            "state": "present", 
            "tags": null, 
            "validate_certs": true, 
            "vpc_id": "vpc-aa867acd"
        }
    }, 
    "ip_permissions": [
        {
            "from_port": 80, 
            "ip_protocol": "tcp", 
            "ip_ranges": [
                {
                    "cidr_ip": "0.0.0.0/0"
                }
            ], 
            "ipv6_ranges": [
                {
                    "cidr_ipv6": "::/0"
                }
            ], 
            "prefix_list_ids": [], 
            "to_port": 80, 
            "user_id_group_pairs": []
        }, 
        {
            "from_port": 443, 
            "ip_protocol": "tcp", 
            "ip_ranges": [
                {
                    "cidr_ip": "0.0.0.0/0"
                }
            ], 
            "ipv6_ranges": [
                {
                    "cidr_ipv6": "::/0"
                }
            ], 
            "prefix_list_ids": [], 
            "to_port": 443, 
            "user_id_group_pairs": []
        }
    ], 
    "ip_permissions_egress": [
        {
            "from_port": 1, 
            "ip_protocol": "tcp", 
            "ip_ranges": [
                {
                    "cidr_ip": "127.0.0.0/32"
                }
            ], 
            "ipv6_ranges": [], 
            "prefix_list_ids": [], 
            "to_port": 1, 
            "user_id_group_pairs": []
        }
    ], 
    "owner_id": "864163117881", 
    "tags": {}, 
    "vpc_id": "vpc-aa867acd"
}
META: ran handlers
META: ran handlers

PLAY RECAP ***************************************************************************************************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0   
```

```
$ aws ec2 describe-security-groups --group-id sg-7a87801c 
{
    "SecurityGroups": [
        {
            "IpPermissionsEgress": [
                {
                    "PrefixListIds": [], 
                    "FromPort": 1, 
                    "IpRanges": [
                        {
                            "CidrIp": "127.0.0.0/32"
                        }
                    ], 
                    "ToPort": 1, 
                    "IpProtocol": "tcp", 
                    "UserIdGroupPairs": [], 
                    "Ipv6Ranges": []
                }
            ], 
            "Description": "Allow HTTP(S) traffic from the whole wide world!", 
            "IpPermissions": [
                {
                    "PrefixListIds": [], 
                    "FromPort": 80, 
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ], 
                    "ToPort": 80, 
                    "IpProtocol": "tcp", 
                    "UserIdGroupPairs": [], 
                    "Ipv6Ranges": [
                        {
                            "CidrIpv6": "::/0"
                        }
                    ]
                }, 
                {
                    "PrefixListIds": [], 
                    "FromPort": 443, 
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ], 
                    "ToPort": 443, 
                    "IpProtocol": "tcp", 
                    "UserIdGroupPairs": [], 
                    "Ipv6Ranges": [
                        {
                            "CidrIpv6": "::/0"
                        }
                    ]
                }
            ], 
            "GroupName": "AllowAllWebTraffic", 
            "VpcId": "vpc-aa867acd", 
            "OwnerId": "864163117881", 
            "GroupId": "sg-7a87801c"
        }
    ]
}
```

The above is all expected behaviour, and not what you've described. Can you let me know where my setup differs to yours?

```
- hosts: localhost
  connection: local
  gather_facts: no

  tasks:
    - ec2_vpc_net:
        name: issue-34429
        cidr_block: 10.90.0.0/16
        profile: "{{ profile|default(omit) }}"
        region: ap-southeast-2
      register: ec2_vpc_net

    - ec2_group:
        name: AllowAllWebTraffic
        description: Allow HTTP(S) traffic from the whole wide world!
        purge_rules: yes
        purge_rules_egress: yes
        rules:
        - proto: tcp
          ports: [80, 443]
          cidr_ip: 0.0.0.0/0
          cidr_ipv6: ::/0
        rules_egress:␠
        - proto: tcp
          ports: 1
          cidr_ip: 127.0.0.0/32
        vpc_id: "{{ ec2_vpc_net.vpc.id }}"
        profile: "{{ profile|default(omit) }}"
        region: ap-southeast-2
```
needs_info
I ran the snippet you provided and got expected results.

I then ran the snipped on us-west-2 (making a new vpc). This also produced expected results.

I then ran the snipped on us-west-2 without specifying the vpc (comment out vpc_id) and it made a rule with the extra egress.

I deleted the test vpcs and then ran it again and got expected results (no extra egress).

I don't know what was going on but that was odd (and it works now?). I had already modified the default vpc security group to not have an egress rule and I didn't touch anything else to my knowledge. 

Here's the logs so far https://gist.github.com/Everspace/e86acfb1ceffe302372a3e7bcffe1a0e
Yeah, @willthames solution works for me as well (no extra egress) and creating with no egress rule specified in the playbook correctly creates the default all-egress rule. 

Going to close this and chalk it up to a fluke, please reopen if you're able to trigger this behavior again. 
> I then ran the snipped on us-west-2 without specifying the vpc (comment out vpc_id) and it made a rule with the extra egress.

Right, that is indeed the difference. I shouldn't have added creating a test VPC into my play!

```
TASK [ec2_group] *********************************************************************************************************************************************
task path: /home/will/tmp/ansible/issue-34429/play.yml:6
Using module file /home/will/tmp/ansible/issue-34429/library/ec2_group.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: will
<127.0.0.1> EXEC /bin/sh -c 'echo ~ && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/will/.ansible/tmp/ansible-tmp-1515453558.12-201960326689028 `" && echo ansible-tmp-1515453558.12-201960326689028="` echo /home/will/.ansible/tmp/ansible-tmp-1515453558.12-201960326689028 `" ) && sleep 0'
<127.0.0.1> PUT /tmp/tmpyngD57 TO /home/will/.ansible/tmp/ansible-tmp-1515453558.12-201960326689028/ec2_group.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/will/.ansible/tmp/ansible-tmp-1515453558.12-201960326689028/ /home/will/.ansible/tmp/ansible-tmp-1515453558.12-201960326689028/ec2_group.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/home/will/venvs/ansible-2.4/bin/python2 /home/will/.ansible/tmp/ansible-tmp-1515453558.12-201960326689028/ec2_group.py; rm -rf "/home/will/.ansible/tmp/ansible-tmp-1515453558.12-201960326689028/" > /dev/null 2>&1 && sleep 0'
changed: [localhost] => {
    "changed": true, 
    "description": "Allow HTTP(S) traffic from the whole wide world!", 
    "group_id": "sg-8e898fe8", 
    "group_name": "AllowAllWebTraffic", 
    "invocation": {
        "module_args": {
            "aws_access_key": null, 
            "aws_secret_key": null, 
            "description": "Allow HTTP(S) traffic from the whole wide world!", 
            "ec2_url": null, 
            "group_id": null, 
            "name": "AllowAllWebTraffic", 
            "profile": "personal", 
            "purge_rules": true, 
            "purge_rules_egress": true, 
            "purge_tags": true, 
            "region": "ap-southeast-2", 
            "rules": [
                {
                    "cidr_ip": "0.0.0.0/0", 
                    "cidr_ipv6": "::/0", 
                    "ports": [
                        80, 
                        443
                    ], 
                    "proto": "tcp"
                }
            ], 
            "rules_egress": [
                {
                    "cidr_ip": "127.0.0.0/32", 
                    "ports": 1, 
                    "proto": "tcp"
                }
            ], 
            "security_token": null, 
            "state": "present", 
            "tags": null, 
            "validate_certs": true, 
            "vpc_id": null
        }
    }, 
    "ip_permissions": [
        {
            "from_port": 80, 
            "ip_protocol": "tcp", 
            "ip_ranges": [
                {
                    "cidr_ip": "0.0.0.0/0"
                }
            ], 
            "ipv6_ranges": [
                {
                    "cidr_ipv6": "::/0"
                }
            ], 
            "prefix_list_ids": [], 
            "to_port": 80, 
            "user_id_group_pairs": []
        }, 
        {
            "from_port": 443, 
            "ip_protocol": "tcp", 
            "ip_ranges": [
                {
                    "cidr_ip": "0.0.0.0/0"
                }
            ], 
            "ipv6_ranges": [
                {
                    "cidr_ipv6": "::/0"
                }
            ], 
            "prefix_list_ids": [], 
            "to_port": 443, 
            "user_id_group_pairs": []
        }
    ], 
    "ip_permissions_egress": [
        {
            "from_port": 1, 
            "ip_protocol": "tcp", 
            "ip_ranges": [
                {
                    "cidr_ip": "127.0.0.0/32"
                }
            ], 
            "ipv6_ranges": [], 
            "prefix_list_ids": [], 
            "to_port": 1, 
            "user_id_group_pairs": []
        }, 
        {
            "ip_protocol": "-1", 
            "ip_ranges": [
                {
                    "cidr_ip": "0.0.0.0/0"
                }
            ], 
            "ipv6_ranges": [], 
            "prefix_list_ids": [], 
            "user_id_group_pairs": []
        }
    ], 
    "owner_id": "864163117881", 
    "tags": {}, 
    "vpc_id": "vpc-a1dbd3c3"
}
```
Problem is https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/amazon/ec2_group.py#L960

The second half should likely be `if 'VpcId' not in group`.

Worth fixing this and adding a test - difficulty then is testing in non-VPC mode.
  

Resolving patch:
diff --git a/lib/ansible/modules/cloud/amazon/ec2_group.py b/lib/ansible/modules/cloud/amazon/ec2_group.py
--- a/lib/ansible/modules/cloud/amazon/ec2_group.py
+++ b/lib/ansible/modules/cloud/amazon/ec2_group.py
@@ -957,7 +957,7 @@ def main():
                 del groupRules[default_egress_rule]
 
         # Finally, remove anything left in the groupRules -- these will be defunct rules
-        if purge_rules_egress and vpc_id is not None:
+        if purge_rules_egress and 'VpcId' in group:
             for (rule, grant) in groupRules.values():
                 # we shouldn't be revoking 0.0.0.0 egress
                 if grant != '0.0.0.0/0':


Test patch:
diff --git a/test/integration/targets/ec2_group/tasks/main.yml b/test/integration/targets/ec2_group/tasks/main.yml
--- a/test/integration/targets/ec2_group/tasks/main.yml
+++ b/test/integration/targets/ec2_group/tasks/main.yml
@@ -161,15 +161,22 @@
            - 'result.failed'
            - '"validate the provided access credentials" in result.msg'
 
+    # ============================================================
+    - name: set up aws connection info
+      set_fact:
+        aws_connection_info: &aws_connection_info
+          aws_access_key: "{{ aws_access_key }}"
+          aws_secret_key: "{{ aws_secret_key }}"
+          security_token: "{{ security_token }}"
+          region: "{{ aws_region }}"
+      no_log: yes
+
     # ============================================================
     - name: test state=absent
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: absent
       register: result
 
@@ -178,10 +185,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
       register: result
 
@@ -196,10 +200,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}CHANGED'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
       ignore_errors: true
       register: result
@@ -215,10 +216,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
       register: result
 
@@ -233,10 +231,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
         rules:
         - proto: "tcp"
@@ -256,10 +251,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
         rules:
         - proto: "tcp"
@@ -285,10 +277,7 @@
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
         ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
-        state: present
+        <<: *aws_connection_info
         rules:
         - proto: "tcp"
           from_port: 8182
@@ -307,10 +296,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
         rules:
         - proto: "tcp"
@@ -329,10 +315,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
         purge_rules: no
         rules:
@@ -355,10 +338,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
         rules:
         - proto: "tcp"
@@ -383,10 +363,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
         rules:
         - proto: "tcp"
@@ -414,10 +391,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
         # set purge_rules to false so we don't get a false positive from previously added rules
         purge_rules: false
@@ -441,10 +415,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
         # set purge_rules to false so we don't get a false positive from previously added rules
         purge_rules: false
@@ -467,10 +438,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
         # set purge_rules to false so we don't get a false positive from previously added rules
         purge_rules: false
@@ -520,10 +488,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         state: present
         # set purge_rules to false so we don't get a false positive from previously added rules
         purge_rules: false
@@ -546,11 +511,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         state: absent
-      environment:
-        EC2_REGION: '{{ec2_region}}'
-        EC2_ACCESS_KEY: '{{ec2_access_key}}'
-        EC2_SECRET_KEY: '{{ec2_secret_key}}'
-        EC2_SECURITY_TOKEN: '{{security_token|default("")}}'
+        <<: *aws_connection_info
       register: result
 
     - name: assert state=absent (expected changed=true)
@@ -564,10 +525,7 @@
         name: "{{ resource_prefix }}-vpc"
         state: present
         cidr_block: "10.232.232.128/26"
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         tags:
           Name: "{{ resource_prefix }}-vpc"
           Description: "Created by ansible-test"
@@ -577,10 +535,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         vpc_id: '{{ vpc_result.vpc.id }}'
         state: present
         rules:
@@ -603,10 +558,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         vpc_id: '{{ vpc_result.vpc.id }}'
         state: present
         rules:
@@ -632,10 +584,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         vpc_id: '{{ vpc_result.vpc.id }}'
         state: present
         purge_rules_egress: false
@@ -661,10 +610,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         vpc_id: '{{ vpc_result.vpc.id }}'
         state: present
         rules:
@@ -688,10 +634,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         vpc_id: '{{ vpc_result.vpc.id }}'
         state: present
         rules:
@@ -713,10 +656,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         vpc_id: '{{ vpc_result.vpc.id }}'
         state: present
         rules:
@@ -739,10 +679,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         vpc_id: '{{ vpc_result.vpc.id }}'
         # purge the other rules so assertions work for the subsequent tests for rule descriptions
         purge_rules_egress: true
@@ -785,10 +722,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         vpc_id: '{{ vpc_result.vpc.id }}'
         purge_rules_egress: false
         purge_rules: false
@@ -826,14 +760,43 @@
 
     # ============================================================
 
+    - name: test creating rule in default vpc with egress rule (expected changed=true)
+      ec2_group:
+        name: '{{ec2_group_name}}-default-vpc'
+        description: '{{ec2_group_description}} default VPC'
+        <<: *aws_connection_info
+        purge_rules_egress: true
+        state: present
+        rules:
+        - proto: "tcp"
+          ports:
+          - 8281
+          cidr_ipv6: 1001:d00::/24
+          rule_desc: ipv6 rule desc 2
+        rules_egress:
+        - proto: "tcp"
+          ports:
+          - 8282
+          cidr_ip: 2.2.2.2/32
+          rule_desc: egress rule desc 2
+      register: result
+
+    - name: assert that rule descriptions were modified (expected changed=true)
+      # Only assert this if rule description is defined as the botocore version may < 1.7.2.
+      # It's still helpful to have these tests run on older versions since it verifies backwards
+      # compatibility with this feature.
+      assert:
+        that:
+          - 'result.changed'
+          - 'result.ip_permissions_egress|length == 1'
+
+    # ============================================================
+
     - name: test that keeping the same rule descriptions (expected changed=false)
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         vpc_id: '{{ vpc_result.vpc.id }}'
         purge_rules_egress: false
         purge_rules: false
@@ -875,10 +838,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         description: '{{ec2_group_description}}'
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
         vpc_id: '{{ vpc_result.vpc.id }}'
         purge_rules_egress: false
         purge_rules: false
@@ -920,11 +880,7 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         state: absent
-      environment:
-        EC2_REGION: '{{ec2_region}}'
-        EC2_ACCESS_KEY: '{{ec2_access_key}}'
-        EC2_SECRET_KEY: '{{ec2_secret_key}}'
-        EC2_SECURITY_TOKEN: '{{security_token|default("")}}'
+        <<: *aws_connection_info
       register: result
 
     - name: assert state=absent (expected changed=true)
@@ -940,27 +896,27 @@
       ec2_group:
         name: '{{ec2_group_name}}'
         state: absent
-      environment:
-        EC2_REGION: '{{ec2_region}}'
-        EC2_ACCESS_KEY: '{{ec2_access_key}}'
-        EC2_SECRET_KEY: '{{ec2_secret_key}}'
-        EC2_SECURITY_TOKEN: '{{security_token|default("")}}'
+        <<: *aws_connection_info
+      ignore_errors: yes
+
+    - name: tidy up default VPC security group
+      ec2_group:
+        name: '{{ec2_group_name}}-default-vpc'
+        state: absent
+        <<: *aws_connection_info
+      ignore_errors: yes
 
     - name: tidy up automatically created SG
       ec2_group:
         name: "{{ resource_prefix }} - Another security group"
         state: absent
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
+      ignore_errors: yes
 
     - name: tidy up VPC
       ec2_vpc_net:
         name: "{{ resource_prefix }}-vpc"
         state: absent
         cidr_block: "10.232.232.128/26"
-        ec2_region: '{{ec2_region}}'
-        ec2_access_key: '{{ec2_access_key}}'
-        ec2_secret_key: '{{ec2_secret_key}}'
-        security_token: '{{security_token}}'
+        <<: *aws_connection_info
+      ignore_errors: yes