Delete automatic assignment of Public IPv4 addresses to Amazon EC2 instances using the AWS Systems Manager Automation runbook.
Tags: aws, automation
Published: 2024-07-11 07:49:47


I recently did something similar using AWS Config -> Amazon EventBridge -> AWS Lambda in [another article](https://dev.to/nishikawaakira/automatically-remove-automatic-ipv4-address-assignment-to-amazon-ec2-instances-12fl). In this case, I would like to use Automation runbook in AWS Systems Manager to delete the public IPv4 addresses of the EC2 instance automatically.

I would like to tell you that you don't need to have many programming skills to use Automation runbook to gain control.


## Create an AWS Systems Manager Automation runbook

Now, let's go and actually create AWS Systems Manager Automation runbook.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hv4dao7p0u23klm29ac4.png)



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/igqn4z2gyazad1jx33yq.png)


Initially, the following screen is opened. The Design tab is now selected.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79dkawy15gq1tmtm3hxd.png)

You can create a runbook using the graphical interface in this way, or you can select the {} Code tab and edit YAML or JSON directly.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/023d7wmhs2lquytl96jn.png)


## Writing Automation runbook
When you visually add parts to Automation runbook, you will see  YAML being described. You can also write Python within this YAML. Take, for example, [AWS-BulkDeleteAssociation](https://ap-northeast-1.console.aws.amazon.com/systems-manager/documents/AWS-BulkDeleteAssociation/content?region=ap-northeast-1) in the managed runbook.

The following is written in Python, but this is quite difficult without some programming skills. This is not smart, and for the purpose of this article, I want to show that it can be implemented by people with no programming knowledge, so I want to prove that we can do it without using services like AWS Lambda!
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/if3trflsmtzzy8tja9st.png)


First of all, I will create a runbook with only the minimum required elements.
Select the "AWS APIs" tab under the search box and enter "modifynetwork" in the search box to find the ModifyNetworkInterfaceAttribute of the AWS API to be used this time. Drag and drop it to the middle of the "ModifyNetworkInterfaceAttribute" between Start and End.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mifojlreaiu5m1tbi0xu.png)


{} Open the Code tab. You will see that "ModifyNetworkInterfaceAttribute" is being hit as an API, as shown below.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eog08tu6jpqry91n9j7x.png)


We want to put arguments here, as you can see from AWS Lambda code in the previous article,
As you can see from AWS Lambda code in the previous article, it seems to be sufficient to enter NetworkInterfaceId.　[The API Reference is here](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_ModifyNetworkInterfaceAttribute.html) You can also see that you need to set AssociatePublicIpAddress to false in order to prevent the automatic assignment of Public IPv4 addresses.


Let's describe it as follows. The regular expression is written on the assumption that NetworkInterfaceId starts with "eni-" and that a combination of alphabets and numbers is entered. Also, entering (Required) in the parameter description will be treated as a required parameter. It is not mandatory to pass assumeRole as a parameter, but it is always recommended to do so. This is because you may want to use this runbook by assuming it from another AWS account. For example, there are use cases where a runbook is distributed to each account in CloudFormation StackSets, etc., and the runbook can be assumed and executed from an administrative account.

```
schemaVersion: '0.3'
description: Disable Public IPv4 on EC2.
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
  AutomationAssumeRole:
    type: String
    description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
    default: ''
  NetworkInterfaceId:
    type: String
    description: (Required) Network Interface Id
    allowedPattern: ^eni-[a-z0-9]+$
mainSteps:
  - name: ModifyNetworkInterfaceAttribute
    action: aws:executeAwsApi
    isEnd: true
    inputs:
      Service: ec2
      Api: ModifyNetworkInterfaceAttribute
      NetworkInterfaceId: '{{ NetworkInterfaceId }}'
      AssociatePublicIpAddress: false
```

Now, enter a suitable name in NewRunbook and press "Create runbook".


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/covitff45kpch1p5qls1.png)


## Test runs Automation runbook.

Open the runbook you have created. You can find the runbook you created in "Owned by me" tab.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t2guz0eusmxahyanft4s.png)

Open it and click on Execute automation.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0i3gesva1jivupytioe0.png)


Before you do so, create an EC2 instance.
Check the network interface ID of the EC2 instance.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6ytvwlqn2twj1h4e5hs.png)


Try entering a Network Interface ID starting with "eni-" in "NetworkInterfaceId" field of the Input parameters and click Execute.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6em9to6wy24dlt0da7mb.png)


Check that the public IP address in the network interface ID on EC2 instance has been removed. You will see that it has been deleted. We will now see if this is really the end of the process.


## AWS Config settings

As before, I will use the "ec2-instance-no-public-ip" managed rule.
Open this Config rule and click on "Manage remediation"

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0gsh24r6v6k31jkrtfk.png)

Choose your own AWS Systems Manager Automation runbook.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/10zxtv3h4oyloon4pefl.png)


Notice that only InstanceId can be passed as Resource ID parameter.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdfltv551yq40gjn2ei7.png)


Oops, it seems that the network interface ID cannot be passed here, even though I really want to pass it here.
In other words, there seems to be no way to pass the network interface ID dynamically.


## Get a list of network interface ID from EC2 instance ID

We have no choice but to change course. You will find that you need to get the network interface ID from the EC2 instance in order to pass it on. There are two main methods. The first is to use DescribeInstances to get all the EC2 information and extract only the necessary information. The second is to use [DescribeNetworkInterfaces](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeNetworkInterfaces.html) to get just network interfaces information.


This time, the information on EC2 instance will hardly be used, so we will hit DescribeNetworkInterfaces API to get the information on network interfaces only.

So, as shown below, DescribeNetworkInterfaces API is used to create the part that obtains information on network interfaces.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9epmrzxb1fx814oa38c9.png)


The code is as follows.
```
schemaVersion: '0.3'
description: Disable Public IPv4 on EC2.
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
  InstanceId:
    type: String
    description: The ID of the EC2 instance
  AutomationAssumeRole:
    type: String
    description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
    default: ''
mainSteps:
  - name: DescribeNetworkInterfaces
    action: aws:executeAwsApi
    isEnd: true
    inputs:
      Service: ec2
      Api: DescribeNetworkInterfaces
      Filters:
        - Name: attachment.instance-id
          Values:
            - '{{InstanceId}}'
    outputs:
      - Name: NetworkInterfaceIds
        Selector: $.NetworkInterfaces..NetworkInterfaceId
        Type: StringList
```


Network interface Ids can now be retrieved as a StringList.
There is a caveat here: if you look at the API specification for DescribeNetworkInterfaces, the Request Parameter is written as Filter.N, but at first, it was not clear how to write it in YAML.
The answer I arrived at is below, which is closer to the way [CLI](https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-network-interfaces.html)　is written.

```
Filters:
- Name: attachment.instance-id
Values:
- '{{InstanceId}}'
```

Another is Selector in outputs.
``` 
Selector: $.NetworkInterfaces..NetworkInterfaceId
```

This Selector allows you to specify which values are output and used in the next step. As an EC2 instance may have multiple network interface, it is necessary to retrieve them all, but when using Selector: $.NetworkInterfaces, only a MapList (like an associative array) can be specified for Type. In this case, the loop in the runbook cannot be used. Therefore, you need to get the array as a StringList, which can be done by writing "..". By writing this, the list of specified items can be retrieved as a StringList.


## Delete the public IPv4 address of the relevant network interface ID

Now that you have reached this point, the rest is easy. Create a runbook like the following.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/692nutvvc6kyzioci1ae.png)


The final code is as follows.

```
schemaVersion: '0.3'
description: Disable Public IPv4 on EC2.
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
  InstanceId:
    type: String
    description: The ID of the EC2 instance
  AutomationAssumeRole:
    type: String
    description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
    default: ''
mainSteps:
  - name: DescribeNetworkInterfaces
    action: aws:executeAwsApi
    nextStep: Loop
    isEnd: false
    inputs:
      Service: ec2
      Api: DescribeNetworkInterfaces
      Filters:
        - Name: attachment.instance-id
          Values:
            - '{{InstanceId}}'
    outputs:
      - Name: NetworkInterfaceIds
        Selector: $.NetworkInterfaces..NetworkInterfaceId
        Type: StringList
  - name: Loop
    action: aws:loop
    isEnd: true
    inputs:
      Iterators: '{{ DescribeNetworkInterfaces.NetworkInterfaceIds }}'
      Steps:
        - name: ModifyNetworkInterfaceAttribute
          action: aws:executeAwsApi
          isEnd: true
          inputs:
            Service: ec2
            Api: ModifyNetworkInterfaceAttribute
            NetworkInterfaceId: '{{ Loop.CurrentIteratorValue }}'
            AssociatePublicIpAddress: false

```



"Create new version", then select "Create new default version" to create a new document.

Before you start testing, launch an EC2 instance and attach several Network Interfaces.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfmh3j2d41zkq86orw1y.png)

Then open the runbook, click on "Execute automation", enter the InstanceId and press Execute.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aly8h53ps2gg4xo9pwfy.png)


Check the previous screen to see if it has been disabled properly. If it is disabled as shown in the image below, you have succeeded.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ygzk1df8gb1126uccmgu.png)




## Conclusion

In this way, control can be exercised without programming knowledge. However, when you write it like this, you might think it's easy to do! And while that is the aim, there are some real quirks in the way YAML is written, and I had a lot of trouble with it. 
At first, I thought that to get the network interface IDs as a list, I would need to write some Python code. And indeed, the first version of this blog did. While researching, I realized that it could be retrieved using "..".

I'm going to accumulate this kind of runbook writing style and work towards a situation where anyone can control the AWS environment!

Thank you for reading to the end.