【Ansible】アクセス制限用のモジュールを作成する【シェルスクリプト】

Ansible の 標準モジュールに良さげなモジュールがなかったんで、んじゃいっそ作ってみようかと。

ディレクトリ構造

├── README.md
├── ansible.cfg
├── block.yml
├── group_vars
│   └── main.yml
├── hosts
├── library
│   └── block_ip.sh
└── roles
    └── block_ip
        ├── README.md
        ├── tasks
        │   └── main.yml
        └── vars
            └── main.yml

モジュール

root@DESKTOP-MOGIJIA:/opt/playbook/ip_block# cat library/block_ip.sh
#!/bin/sh
source `dirname $0`/args

if [ $state == "absent" ] ; then
        route del -host $target reject
        if [ $? -eq 0 ] ; then
                echo '{ "rc": 0, "changed": true }'
        else
                echo '{ "rc": 0 }'
        fi
elif [ $state == "present" ] ; then
        route add -host $target reject
        if [ $? -eq 0 ] ; then
                echo '{ "rc": 0, "changed": true }'
        else
                echo '{ "rc": 0 }'
        fi
else
        echo '{ "failed": true, "rc": 0 }'
fi

library ディレクトリ以下にモジュールを作成します。
それと、playbook の中で target (対象IPアドレス)、 state (状態)を定義できるようにしておきます。

Role

root@DESKTOP-MOGIJIA:/opt/playbook/ip_block# cat roles/block_ip/tasks/main.yml
---
# tasks file for block_ip
- name: block_ip
  block_ip:
    target: "{{ item }}"
    state: absent
  with_items:
    - "{{ block_target }}"

- name: accept_ip
  block_ip:
    target: "{{ item }}"
    state: absent
  with_items:
    - "{{ accept_target }}"

「state」が「present」⇒アクセスを制限
「state」が「absent」⇒アクセス制限を解除
としておきます。
root@DESKTOP-MOGIJIA:/opt/playbook/ip_block# cat group_vars/main.yml
---
   block_target:
     - 192.168.99.99
     - 192.168.99.98
     - 192.168.99.97
     - 192.168.99.96
     - 192.168.99.95
     - 192.168.99.94
     - 192.168.99.93
     - 192.168.99.00

   accept_target:
     - 127.0.0.1

playbook

root@DESKTOP-MOGIJIA:/opt/playbook/ip_block# cat block.yml
---
# Main Playbook
- name: apply master configuration to master nodes
  hosts: all
  vars_files:
   - ./group_vars/main.yml
  remote_user: vagrant
#  remote_user: centos
  become: yes
  roles:
    - block_ip

実行

root@DESKTOP-MOGIJIA:/opt/playbook/ip_block# ansible-playbook block.yml --ask-pass
SSH password:

PLAY [apply master configuration to master nodes] **********************************************************************

TASK [Gathering Facts] *************************************************************************************************
ok: [192.168.33.10]

TASK [block_ip : block_ip] *********************************************************************************************
changed: [192.168.33.10] => (item=192.168.99.99)
changed: [192.168.33.10] => (item=192.168.99.98)
changed: [192.168.33.10] => (item=192.168.99.97)
changed: [192.168.33.10] => (item=192.168.99.96)
changed: [192.168.33.10] => (item=192.168.99.95)
changed: [192.168.33.10] => (item=192.168.99.94)
changed: [192.168.33.10] => (item=192.168.99.93)
changed: [192.168.33.10] => (item=192.168.99.00)

TASK [block_ip : accept_ip] ********************************************************************************************
ok: [192.168.33.10] => (item=127.0.0.1)

PLAY RECAP *************************************************************************************************************
192.168.33.10              : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

結果

[root@ansible-dev ~]# ip route show
default via 10.0.2.2 dev eth0 proto dhcp metric 100
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
192.168.33.0/24 dev eth1 proto kernel scope link src 192.168.33.10 metric 101
unreachable 192.168.99.0 scope host
unreachable 192.168.99.93 scope host
unreachable 192.168.99.94 scope host
unreachable 192.168.99.95 scope host
unreachable 192.168.99.96 scope host
unreachable 192.168.99.97 scope host
unreachable 192.168.99.98 scope host
unreachable 192.168.99.99 scope host
192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1

うん、大丈夫ですね。

【Ansible】AnsibleでAWSのネットワークを構築する【IaC】

Terraform に埋もれがちですが Ansible でも AWS のリソース構築が出来るんですよね。

作るもの

・VPC
・public subnet x 2
・private subnet x 2
・internet gateway
・route table

ディレクトリ構造

├── README.md
├── ansible.cfg
├── hosts
├── roles
│   └── aws_vpc
│       ├── tasks
│       │   └── main.yml
│       └── vars
│           └── main.yml
└── vpc_create.yml

インベントリファイル

root@DESKTOP-MOGIJIA:/opt/playbook/aws-vpc-2layer# cat hosts
[localhost]
127.0.0.1

サーバをプロビジョニングする訳ではないので、ローカルホストを指定

Role

root@DESKTOP-MOGIJIA:/opt/playbook/aws-vpc-2layer# cat roles/aws_vpc/tasks/main.yml
---
# tasks file for aws_vpc
- name: create_vpc
  ec2_vpc_net:
    name: "{{ vpc_name }}"
    cidr_block: "{{ vpc_cidr }}"
    region: "{{ region }}"
    dns_hostnames: yes
    dns_support: yes
  register: vpc_info

# PUBLIC_SUBNETの作成
- name: create_public_subnet
  ec2_vpc_subnet:
    vpc_id: "{{ vpc_info.vpc.id }}"
    cidr: "{{ item.pub_subnet_cidr }}"
    az: "{{ item.subnet_az }}"
    region: "{{ region }}"
    resource_tags: { "Name":"{{ item.pub_subnet_name }}" }
  register: pubsub_info
  with_items:
    - "{{ pub_subnet }}"

# PRIVATE_SUBNETの作成
- name: create_private_subnet
  ec2_vpc_subnet:
    vpc_id: "{{ vpc_info.vpc.id }}"
    cidr: "{{ item.pri_subnet_cidr }}"
    az: "{{ item.subnet_az }}"
    region: "{{ region }}"
    resource_tags: { "Name":"{{ item.pri_subnet_name }}" }
  register: prisub_info
  with_items:
    - "{{ pri_subnet }}"

# IGWの作成
- name: create_igw
  ec2_vpc_igw:
    vpc_id: "{{ vpc_info.vpc.id }}"
    region: "{{ region }}"
    tags: { "Name":"{{ igw_name }}" }
  register: igw_info

# ROUTETABLEの作成(IGW)
- name: create_route_table
  ec2_vpc_route_table:
    vpc_id: "{{ vpc_info.vpc.id }}"
    subnets: "{{ atache_igw_subnet }}"
    routes:
      - dest: 0.0.0.0/0
        gateway_id: "{{ igw_info.gateway_id }}"
    region: "{{ region }}"
    resource_tags: { "Name":"{{ rttable_pub_name }}" }

root@DESKTOP-MOGIJIA:/opt/playbook/aws-vpc-2layer# cat roles/aws_vpc/vars/main.yml
---
# vars file for aws_vpc

# REGION
  region: "ap-northeast-1"

# VPC
  vpc_name: "sanuki-wd-vpc"
  vpc_cidr: "10.0.0.0/16"

# IGW
  igw_name: "sanuki-igw"

# ROUTETABLE(PUBLIC)
  rttable_pub_name: "sanuki-pub-rt"

# PUBLIC_SUBNET
  pub_subnet:
    - { pub_subnet_cidr: "10.0.10.0/24" ,subnet_az: "ap-northeast-1a" ,pub_subnet_name: "sanuki-wd-public-subnet-a" }
    - { pub_subnet_cidr: "10.0.20.0/24" ,subnet_az: "ap-northeast-1c" ,pub_subnet_name: "sanuki-wd-public-subnet-c" }

# PRIVATE_SUBNET
  pri_subnet:
    - { pri_subnet_cidr: "10.0.50.0/24" ,subnet_az: "ap-northeast-1a" ,pri_subnet_name: "sanuki-wd-private-subnet-a" }
    - { pri_subnet_cidr: "10.0.60.0/24" ,subnet_az: "ap-northeast-1c" ,pri_subnet_name: "sanuki-wd-private-subnet-c" }

# IGWに紐付けるサブネット
  atache_igw_subnet:
    - "10.0.10.0/24"
    - "10.0.20.0/24"

playbook

root@DESKTOP-MOGIJIA:/opt/playbook/aws-vpc-2layer# cat vpc_create.yml
---
# VPC CREATE Playbook
- name: create vpc subnet igw routetable
  hosts: localhost
  connection: local
  gather_facts: False
  become: False
  roles:
    - aws_vpc

実行

root@DESKTOP-MOGIJIA:/opt/playbook/aws-vpc-2layer# ansible-playbook -i hosts vpc_create.yml

PLAY [create vpc subnet igw routetable] ********************************************************************************

TASK [aws_vpc : create_vpc] ********************************************************************************************
[DEPRECATION WARNING]: Distribution Ubuntu 18.04 on host 127.0.0.1 should use /usr/bin/python3, but is using
/usr/bin/python for backward compatibility with prior Ansible releases. A future Ansible release will default to using
the discovered platform python for this host. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information. This feature
 will be removed in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in
ansible.cfg.
changed: [127.0.0.1]

TASK [aws_vpc : create_public_subnet] **********************************************************************************
changed: [127.0.0.1] => (item={u'pub_subnet_name': u'sanuki-wd-public-subnet-a', u'subnet_az': u'ap-northeast-1a', u'pub_subnet_cidr': u'10.0.10.0/24'})
changed: [127.0.0.1] => (item={u'pub_subnet_name': u'sanuki-wd-public-subnet-c', u'subnet_az': u'ap-northeast-1c', u'pub_subnet_cidr': u'10.0.20.0/24'})

TASK [aws_vpc : create_private_subnet] *********************************************************************************
changed: [127.0.0.1] => (item={u'pri_subnet_cidr': u'10.0.50.0/24', u'pri_subnet_name': u'sanuki-wd-private-subnet-a', u'subnet_az': u'ap-northeast-1a'})
changed: [127.0.0.1] => (item={u'pri_subnet_cidr': u'10.0.60.0/24', u'pri_subnet_name': u'sanuki-wd-private-subnet-c', u'subnet_az': u'ap-northeast-1c'})

TASK [aws_vpc : create_igw] ********************************************************************************************
changed: [127.0.0.1]

TASK [aws_vpc : create_route_table] ************************************************************************************
changed: [127.0.0.1]

PLAY RECAP *************************************************************************************************************
127.0.0.1                  : ok=5    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

補足

boto3 が必要になります。
pip でインストールしておいて下さい。

pip install boto boto3

【Ansible】いい感じにデータを変数にいれる【シェルスクリプト】

スペース区切りだったり、カンマ区切りのデータをいい感じに Ansible の変数に入れたいことってありますよね。
そんな時によく使うスクリプトです。

シェルスクリプト

vi var_create.sh
===============================================
#!/bin/bash

while IFS=' ' read key val
do
  echo "     - { domain: '$val' ,owner: '$key' }"
done < $1

vi data.txt
============================
user1 aaa
user2 bbb
user3 ccc

実行

[root@keisuke-main tmp]# ./var_create.sh data.txt
     - { domain: 'aaa' ,owner: 'user1' }
     - { domain: 'bbb' ,owner: 'user2' }
     - { domain: 'ccc' ,owner: 'user3' }

【Ansible】Ansible と expect で MySQL 導入を自動化する【IaC】

こんにちは。
MySQL の導入が面倒だったので Ansible と expect で自動化にチャレンジします。

mysql_secure_installation の自動化

expect と awk でシェルスクリプトを作成します。

#!/bin/bash


# 初期パスワードを取得
IntPasswd=$(grep "A temporary password is generated for root@localhost:" /var/log/mysqld.log | awk '{ print $13}')

# パスワード指定
MysqlRootPasswd="{{ db_passwd }}"

expect -c '
    set timeout 10;
    spawn mysql_secure_installation;
    expect "Enter password for user root:";
    send -- "'"${IntPasswd}"'\n";
    expect "New password:";
    send -- "'"${MysqlRootPasswd}"'\n";
    expect "Re-enter new password:";
    send -- "'"${MysqlRootPasswd}"'\n";
    expect "Change the password for root ?";
    send "n\n";
    expect "Remove anonymous users?";
    send "y\n";
    expect "Disallow root login remotely?";
    send "y\n";
    expect "Remove test database and access to it?";
    send "y\n";
    expect "Reload privilege tables now?";
    send "y\n";
    interact;'

awk で初期パスワードを取得し、対話処理は expect で処理します。

Playbook

Ansible の playbook で処理します。

---
# tasks file for mysql-server80
- name: install mysql80 repository
  yum:
    name: https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
    state: present

- name: install mysql
  yum:
    name:
      - mysql-server
      - expect
    state: present

- name: put my.cnf
  template:
    src: ../templates/my.cnf.j2
    dest: /etc/my.cnf
    owner: root
    group: root
    mode: 0644
    backup: yes

- name: mkdir /var/log/mysql/
  file:
    path: /var/log/mysql
    state: directory
    owner: mysql
    group: mysql
    mode: 0755

- name: start mysql
  systemd:
    name: mysqld
    state: started
    enabled: yes

- name: confirm check_file
  stat: path=/usr/local/etc/mysql_stat.txt
  register: result_mysql_exit

- name: put mysql_secure_installation_script
  template:
    src: ../templates/mysql_secure_installation_script
    dest: /tmp/mysql_secure_installation_script
    owner: root
    group: root
    mode: 0755

- name: exec mysql_secure_installation_script
  shell: "/tmp/mysql_secure_installation_script"
  when: not result_mysql_exit.stat.exists

- name: create check_file
  file:
    path: /usr/local/etc/mysql_stat.txt
    state: touch
    mode: "u=rw,g=r,o=r"

- name: change error-log location
  lineinfile:
    dest: /etc/my.cnf
    state: present
    backrefs: yes
    regexp: '^log-error = /var/log/mysqld.log'
    line: 'log-error = /var/log/mysql/mysqld.log'
  notify: mysqld_restart

おまけ

こちらに CentOS7用の playbook を纏めていますので宜しければ。
https://github.com/keisukesanuki/default-CentOS7