Automating YugabyteDB & YBA Security Group Rules with Ansible + AWS CLI

When deploying YugabyteDB and YugabyteDB Anywhere (YBA) in AWS, one of the first hurdles is networking.

The YugabyteDB official documentation lays out a long list of ports that need to be opened for node-to-node traffic, YBA-to-node communication, app connections, and admin access.

Doing this manually through the AWS console is painful. Even with aws ec2 authorize-security-group-ingress, it’s error-prone if you try to keep the rules straight across environments. That’s where Ansible comes in.

The Port Matrix

Here’s a quick view of the key ports YugabyteDB requires:

  • • Node ↔ Node (intra-cluster): 7000, 7100, 9000, 9100, 18018

  • • YBA ↔ DB Nodes: 5433, 9042, 9070, 9300, 12000, 13000 (and optionally SSH/22 for legacy provisioning)

  • • Applications ↔ DB Nodes: 5433 (YSQL), 9042 (YCQL)

  • • YBA UI / Agents: 443 (YBA UI + agent callbacks), 9090 (Prometheus)

For xCluster replication, make sure 7100 and 9100 are open both ways between universes (9000 optional but recommended for lag metrics).

Why Split Security Groups?

It’s cleaner and safer to use two distinct security groups:

  • • DB-node SG: Opens only what DB nodes need for replication, YBA provisioning, and application access.

  • • YBA SG: Exposes the UI and metrics (443/9090) to operators, and allows DB-node agents to call back to YBA on 443.

This separation limits the blast radius and makes audits much easier.

The Automation Approach

Rather than hand-crafting rules, we can write an Ansible playbook that loops through the ports and applies them consistently. The playbook uses the AWS CLI’s authorize-security-group-ingress command, and can work in two modes:

  1. CIDR-based rules – simple, works across VPCs.

  2. Source-SG rules – tighter, allows DB SG ↔ YBA SG directly without exposing wide CIDRs.

We also make it idempotent by ignoring duplicate-rule errors, so the play can be run repeatedly.

The Full Playbook
				
					---
# open-yugabyte-ports-split-sg.yml
#
# Usage example:
#   ansible-playbook open-yugabyte-ports-split-sg.yml \
#     -e aws_region=us-east-1 \
#     -e aws_profile=default \
#     -e sg_db_id=sg-0db123456789 \
#     -e sg_yba_id=sg-0ab987654321 \
#     -e use_source_groups=true \
#     -e db_cidrs='["10.0.0.0/16"]' \
#     -e yba_cidrs='["10.1.0.0/16"]' \
#     -e app_cidrs='["10.2.0.0/16"]' \
#     -e admin_cidrs='["203.0.113.0/24"]'

- name: Open YBA/YugabyteDB ports via AWS CLI (split: DB SG vs YBA SG)
  hosts: localhost
  connection: local
  gather_facts: false

  vars:
    sg_db_id: ""
    sg_yba_id: ""
    sg_db_name: ""
    sg_yba_name: ""
    vpc_id: ""

    aws_region: us-east-1
    aws_profile: default
    use_source_groups: false

    db_cidrs:   ["10.0.0.0/16"]
    yba_cidrs:  ["10.1.0.0/16"]
    app_cidrs:  ["10.2.0.0/16"]
    admin_cidrs: ["203.0.113.0/24"]

    node_to_node_ports: [7000, 7100, 9000, 9100, 18018]
    yba_to_db_ports: [22, 5433, 7000, 7100, 9000, 9100, 9042, 9070, 9300, 12000, 13000, 18018]
    app_to_db_ports: [5433, 9042]
    yba_ui_ports_for_admins: [443, 9090]
    yba_agent_port: 443

  tasks:
    - name: Ensure AWS CLI is available
      command: aws --version
      register: awscli_check
      failed_when: awscli_check.rc != 0
      changed_when: false

    - name: Resolve SG IDs by name if needed (DB SG)
      when: sg_db_id | length == 0
      command: >
        aws ec2 describe-security-groups
        --region {{ aws_region }} --profile {{ aws_profile }}
        --filters Name=group-name,Values={{ sg_db_name }} Name=vpc-id,Values={{ vpc_id }}
      register: db_sg_lookup
      changed_when: false

    - set_fact:
        sg_db_id: "{{ (db_sg_lookup.stdout | from_json).SecurityGroups[0].GroupId }}"
      when: sg_db_id | length == 0

    - name: Resolve SG IDs by name if needed (YBA SG)
      when: sg_yba_id | length == 0
      command: >
        aws ec2 describe-security-groups
        --region {{ aws_region }} --profile {{ aws_profile }}
        --filters Name=group-name,Values={{ sg_yba_name }} Name=vpc-id,Values={{ vpc_id }}
      register: yba_sg_lookup
      changed_when: false

    - set_fact:
        sg_yba_id: "{{ (yba_sg_lookup.stdout | from_json).SecurityGroups[0].GroupId }}"
      when: sg_yba_id | length == 0

    - debug: msg="DB SG={{ sg_db_id }}, YBA SG={{ sg_yba_id }}"

    # --- DB SG Rules ---
    - name: DB SG | Node↔Node (CIDRs)
      when: not use_source_groups
      vars:
        pairs: "{{ query('product', node_to_node_ports, db_cidrs) }}"
      loop: "{{ pairs }}"
      command: >
        aws ec2 authorize-security-group-ingress
        --region {{ aws_region }} --profile {{ aws_profile }}
        --group-id {{ sg_db_id }}
        --protocol tcp --port {{ item.0 }} --cidr {{ item.1 }}
      register: db_node_node
      failed_when: db_node_node.rc != 0 and
                   ("InvalidPermission.Duplicate" not in db_node_node.stderr)

    - name: DB SG | Node↔Node (SG reference)
      when: use_source_groups
      loop: "{{ node_to_node_ports }}"
      command: >
        aws ec2 authorize-security-group-ingress
        --region {{ aws_region }} --profile {{ aws_profile }}
        --group-id {{ sg_db_id }}
        --protocol tcp --port {{ item }}
        --source-group {{ sg_db_id }}
      register: db_node_node_sg
      failed_when: db_node_node_sg.rc != 0 and
                   ("InvalidPermission.Duplicate" not in db_node_node_sg.stderr)

    - name: DB SG | YBA→DB (CIDRs)
      when: not use_source_groups
      vars:
        pairs: "{{ query('product', yba_to_db_ports, yba_cidrs) }}"
      loop: "{{ pairs }}"
      command: >
        aws ec2 authorize-security-group-ingress
        --region {{ aws_region }} --profile {{ aws_profile }}
        --group-id {{ sg_db_id }}
        --protocol tcp --port {{ item.0 }} --cidr {{ item.1 }}
      register: db_yba_to_db
      failed_when: db_yba_to_db.rc != 0 and
                   ("InvalidPermission.Duplicate" not in db_yba_to_db.stderr)

    - name: DB SG | YBA→DB (SG reference)
      when: use_source_groups
      loop: "{{ yba_to_db_ports }}"
      command: >
        aws ec2 authorize-security-group-ingress
        --region {{ aws_region }} --profile {{ aws_profile }}
        --group-id {{ sg_db_id }}
        --protocol tcp --port {{ item }}
        --source-group {{ sg_yba_id }}
      register: db_yba_to_db_sg
      failed_when: db_yba_to_db_sg.rc != 0 and
                   ("InvalidPermission.Duplicate" not in db_yba_to_db_sg.stderr)

    - name: DB SG | Apps→DB (CIDRs only)
      vars:
        pairs: "{{ query('product', app_to_db_ports, app_cidrs) }}"
      loop: "{{ pairs }}"
      command: >
        aws ec2 authorize-security-group-ingress
        --region {{ aws_region }} --profile {{ aws_profile }}
        --group-id {{ sg_db_id }}
        --protocol tcp --port {{ item.0 }} --cidr {{ item.1 }}
      register: db_app_to_db
      failed_when: db_app_to_db.rc != 0 and
                   ("InvalidPermission.Duplicate" not in db_app_to_db.stderr)

    # --- YBA SG Rules ---
    - name: YBA SG | DB agents→YBA (CIDRs)
      when: not use_source_groups
      loop: "{{ db_cidrs }}"
      command: >
        aws ec2 authorize-security-group-ingress
        --region {{ aws_region }} --profile {{ aws_profile }}
        --group-id {{ sg_yba_id }}
        --protocol tcp --port {{ yba_agent_port }} --cidr {{ item }}
      register: yba_agents_cidr
      failed_when: yba_agents_cidr.rc != 0 and
                   ("InvalidPermission.Duplicate" not in yba_agents_cidr.stderr)

    - name: YBA SG | DB agents→YBA (SG reference)
      when: use_source_groups
      command: >
        aws ec2 authorize-security-group-ingress
        --region {{ aws_region }} --profile {{ aws_profile }}
        --group-id {{ sg_yba_id }}
        --protocol tcp --port {{ yba_agent_port }}
        --source-group {{ sg_db_id }}
      register: yba_agents_sg
      failed_when: yba_agents_sg.rc != 0 and
                   ("InvalidPermission.Duplicate" not in yba_agents_sg.stderr)

    - name: YBA SG | Admins→YBA UI/metrics
      vars:
        pairs: "{{ query('product', yba_ui_ports_for_admins, admin_cidrs) }}"
      loop: "{{ pairs }}"
      command: >
        aws ec2 authorize-security-group-ingress
        --region {{ aws_region }} --profile {{ aws_profile }}
        --group-id {{ sg_yba_id }}
        --protocol tcp --port {{ item.0 }} --cidr {{ item.1 }}
      register: yba_admins
      failed_when: yba_admins.rc != 0 and
                   ("InvalidPermission.Duplicate" not in yba_admins.stderr)

				
			
Must-set variables (pick one path for each)

1) Which Security Groups to modify

Choose one method:

  • A. Direct by IDs (simplest)

    • sg_db_id — Security Group ID for DB nodes (e.g., sg-0db123...)

    • sg_yba_id — Security Group ID for YBA VM (e.g., sg-0ab987...)

  • B. Lookup by names (if you don’t know IDs)

    • sg_db_name — name of DB SG (e.g., yb-db-sg)

    • sg_yba_name — name of YBA SG (e.g., yb-yba-sg)

    • vpc_id — VPC where those SGs live (e.g., vpc-0123...)

You only need A or B. If you set IDs, you can leave the names/VPC blank.

2) How to scope access

Pick one:

  • • CIDR mode (cross-VPC friendly)

    • ◦ Set use_source_groups: false

    • ◦ Set CIDR lists below

  • • SG-to-SG mode (tighter, same VPC)

    • ◦ Set use_source_groups: true

    • ◦ CIDRs are ignored for DB↔YBA flows; you’ll still use CIDRs for apps and admin access

3) Your networks / CIDRs

(Used when use_source_groups: false; also always used for apps/admins)

  • db_cidrs — CIDRs where DB nodes live (often the DB subnets)

  • yba_cidrs — CIDRs where YBA lives

  • app_cidrs — CIDRs where application clients live

  • admin_cidrs — CIDRs for operators who reach YBA UI/metrics

Examples:

				
					db_cidrs:   ["10.0.0.0/16"]
yba_cidrs:  ["10.1.0.0/16"]
app_cidrs:  ["10.2.0.0/16"]
admin_cidrs: ["203.0.113.0/24"]
				
			

4) AWS CLI context

  • aws_region — region of your SGs (e.g., us-east-1)

  • aws_profile — local AWS CLI profile to use (e.g., default)

    • ◦ Ensure the profile has permission for: ec2:AuthorizeSecurityGroupIngress, ec2:DescribeSecurityGroups

Optional variables (tweak if needed)

  • node_to_node_ports, yba_to_db_ports, app_to_db_ports, yba_ui_ports_for_admins, yba_agent_port
    Only change if you’ve customized YSQL/YCQL or other ports in your environment.

  • Keep 22 in yba_to_db_ports only if you still use legacy provisioning; otherwise remove it.

  • If you also want to revoke unknown rules, ask me for the “enforce exact state” companion play.

Two example command lines

1) IDs + SG-to-SG (tightest)

				
					ansible-playbook open-yugabyte-ports-split-sg.yml \
  -e aws_region=us-east-1 -e aws_profile=default \
  -e sg_db_id=sg-0db123456789 \
  -e sg_yba_id=sg-0ab987654321 \
  -e use_source_groups=true \
  -e admin_cidrs='["203.0.113.0/24"]' \
  -e app_cidrs='["10.2.0.0/16"]'

				
			

(DB↔DB and YBA↔DB are scoped SG→SG; admins/apps still use CIDRs.)

2) Names + VPC + CIDRs (cross-VPC)

				
					ansible-playbook open-yugabyte-ports-split-sg.yml \
  -e aws_region=us-east-1 -e aws_profile=default \
  -e sg_db_name=yb-db-sg -e sg_yba_name=yb-yba-sg -e vpc_id=vpc-0123456789abcdef0 \
  -e use_source_groups=false \
  -e db_cidrs='["10.0.0.0/16"]' \
  -e yba_cidrs='["10.1.0.0/16"]' \
  -e app_cidrs='["10.2.0.0/16"]' \
  -e admin_cidrs='["203.0.113.0/24"]'
				
			
Quick preflight checklist
  • ✅ AWS CLI installed and credentials/profile working (aws sts get-caller-identity)

  • ✅ The SGs exist in the specified region/VPC

  • ✅ CIDRs reflect real routes/peering between VPCs/regions

  • ✅ Remove 22 if you’re not using legacy provisioning

  • ✅ If you customized YSQL/YCQL ports, update the port lists accordingly

Benefits of Ansible + AWS CLI
  • • Repeatable: Rerun safely, rules won’t duplicate.

  • • Environment-aware: Plug in CIDRs or SG names per environment.

  • • More secure: Use SG-to-SG references instead of broad CIDRs when possible.

  • • Audit-friendly: Everything’s codified in one place.

Pro Tips
  • Don’t open SSH/22 unless you’re using legacy provisioning.

  • For hardened AMIs (e.g., CIS RHEL), mirror rules in firewalld if required.

  • For multi-region universes, add peered VPC CIDRs or use SG-to-SG rules.

Closing thoughts

With this automation in place, you’ll never again wonder “did I forget 9100 or 9300?” The playbook ensures that your YugabyteDB and YBA environments always have the correct networking configuration… consistent, secure, and easy to audit.

Perfect day for a walk at Avonworth Community Park 🌳☀️ Pittsburgh showing off its summer charm!