DEV Community

nishikawaakira
nishikawaakira

Posted on • Edited on

Delete automatic assignment of Public IPv4 addresses to Amazon EC2 instances using the AWS Systems Manager Automation runbook.

I recently did something similar using AWS Config -> Amazon EventBridge -> AWS Lambda in another article. another article. In this case, I would like to use an Automation runbook in AWS Systems Manager to automatically delete the public IPv4 addresses of Amazon EC2 instances.

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

Create an AWS Systems Manager Automation runbook

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

Image description

Image description

Initially, the following screen is opened, with the design tab now selected.
Image description

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

Writing an Automation runbook

When you visually add parts to the Automation runbook, you will see YAML being described. You can also write Python within this YAML. Take, for example, AWS-BulkDeleteAssociation 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

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 AWS API to be used this time. Drag and drop it to the middle of the "ModifyNetworkInterfaceAttribute" between Start and End.

Image description

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

Image description

We want to put arguments here, as you can see from the AWS Lambda code in the previous article. It seems to be sufficient to enter NetworkInterfaceId.γ€€API Reference is here You can also see that you need to set AssociatePublicIpAddress to false to prevent the automatic assignment of Public IPv4 addresses.

Let's describe it as follows. The regular expression is written assuming that NetworkInterfaceId starts with "eni-" and that a combination of alphabets and numbers is entered. Also, entering (Required) in the parameter description will make it 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, 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
Enter fullscreen mode Exit fullscreen mode

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

Image description

Test Run Automation runbook

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

Image description

Open it and click on Execute automation.

Image description

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

Image description

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

Image description

Check that the public IP address in the network interface ID on the 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

Choose your own AWS Systems Manager Automation runbook.

Image description

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

Image description

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 IDs 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 to pass it on. There are two main methods. The first is to use DescribeInstances to get all EC2 information and extract only the necessary information. The second is to use DescribeNetworkInterfaces to get just the network interface information.

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

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

Image description

The code is as follows.

schemaVersion: '0.3'
description: Disable Public IPv4 on EC2.
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
  InstanceId:
    type: String
    description: The ID of 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
Enter fullscreen mode Exit fullscreen mode

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γ€€is written.

Filters:
- Name: attachment.instance-id
Values:
- '{{InstanceId}}'
Enter fullscreen mode Exit fullscreen mode

Another is the Selector in outputs.

Selector: $.NetworkInterfaces..NetworkInterfaceId
Enter fullscreen mode Exit fullscreen mode

This Selector allows you to specify which values are output and used in the next step. As an EC2 instance may have multiple network interfaces, 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

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 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

Enter fullscreen mode Exit fullscreen mode

"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

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

Image description

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

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.

Top comments (0)