Ansibleでhttpd設定ファイルなどを配信したら謎のPermission denied

ちゃんと普通に考えたら当たり前なんですけど、普段SELinuxが無効な環境で作業していると、軽くはまるので備忘で残しておきます。

Apacheを設定する次のような素朴なタスクを定義しているとしましょう。

```:roles/web/tasks/main.yml
---
- yum: name=httpd state=installed
- service: name=httpd state=running enabled=yes
```

実行するとApacheがインストールされて、起動します。

    $ ansible-playbook -i hosts.ini vagrant.yml
    
    PLAY [web] ********************************************************************
    
    GATHERING FACTS ***************************************************************
    ok: [192.168.33.12]
    
    TASK: [yum name=httpd state=installed] ****************************************
    changed: [192.168.33.12]
    
    TASK: [service name=httpd state=running enabled=yes] **************************
    changed: [192.168.33.12]
    
    PLAY RECAP ********************************************************************
    192.168.33.12              : ok=3    changed=2    unreachable=0    failed=0

この後、設定ファイルを追加したくなったとします。

```yaml:roles/web/tasks/main.yml
---
- yum: name=httpd state=installed
- template: dest=/etc/httpd/conf.d/ltsv.conf src=etc/httpd/conf.d/ltsv.conf.j2 owner=root group=root mode=0644
  notify: restart httpd
- service: name=httpd state=running enabled=yes
```

これは失敗してしまいます。

    $ ansible-playbook -i hosts.ini vagrant.yml
    
    PLAY [web] ********************************************************************
    
    GATHERING FACTS ***************************************************************
    ok: [192.168.33.12]
    
    TASK: [yum name=httpd state=installed] ****************************************
    ok: [192.168.33.12]
    
    TASK: [template dest=/etc/httpd/conf.d/ltsv.conf src=etc/httpd/conf.d/ltsv.conf.j2] ***
    changed: [192.168.33.12]
    
    TASK: [service name=httpd state=running enabled=yes] **************************
    ok: [192.168.33.12]
    
    NOTIFIED: [restart httpd] *****************************************************
    failed: [192.168.33.12] => {"failed": true, "item": ""}
    msg: httpd: Syntax error on line 221 of /etc/httpd/conf/httpd.conf: Could not open configuration file /etc/httpd/conf.d/ltsv.conf: Permission denied
    
    
    PLAY RECAP ********************************************************************
               to retry, use: --limit @/var/tmp/ansible/vagrant.retry
    
    192.168.33.12              : ok=4    changed=1    unreachable=0    failed=1

「Permission denied」って言われて意味が分からなくなりますが、`ls -Z`としてみると分かります。

     $ vagrant ssh -- ls -Z /etc/httpd/conf.d
     -rw-r--r--. root root system_u:object_r:httpd_config_t:s0 README
     -rw-r--r--. root root unconfined_u:object_r:user_home_t:s0 ltsv.conf
     -rw-r--r--. root root system_u:object_r:httpd_config_t:s0 welcome.conf

セキュリティコンテクストがよろしくないですね。というわけで、ファイルを変更したり追加した時には`restorecon`コマンドを呼び出すようにします。

```yaml:roles/web/tasks/main.yml
---
- yum: name=httpd state=installed
- template: dest=/etc/httpd/conf.d/ltsv.conf src=etc/httpd/conf.d/ltsv.conf.j2 owner=root group=root mode=0644
  notify:
    - restorecon /etc/httpd/conf.d
    - restart httpd
- service: name=httpd state=running enabled=yes
```

```yaml:roles/web/handlers/main.yml
---
- name: restorecon /etc/httpd/conf.d
  command: /sbin/restorecon -R /etc/httpd/conf.d
- name: restart httpd
  service: name=httpd state=restarted
```

`hanlers/main.yml`の中身は書いた順番に実行されます、つまり、`restart httpd`より **先に** `restorecon`を書かなくてはいけません。`restorecon`を後から付け足す時なんか忘れがちなので注意。

これでうまくいきます。

    $ ansible-playbook -i hosts.ini vagrant.yml
    
    PLAY [web] ******************************************************************** 
    
    GATHERING FACTS *************************************************************** 
    ok: [192.168.33.12]
    
    TASK: [yum name=httpd state=installed] **************************************** 
    ok: [192.168.33.12]
    
    TASK: [template dest=/etc/httpd/conf.d/ltsv.conf src=etc/httpd/conf.d/ltsv.conf.j2 owner=root group=root mode=0644] *** 
    changed: [192.168.33.12]
    
    TASK: [service name=httpd state=running enabled=yes] ************************** 
    ok: [192.168.33.12]
    
    NOTIFIED: [restorecon /etc/httpd/conf.d] ************************************** 
    changed: [192.168.33.12]
    
    NOTIFIED: [restart httpd] ***************************************************** 
    changed: [192.168.33.12]
    
    PLAY RECAP ******************************************************************** 
    192.168.33.12              : ok=6    changed=3    unreachable=0    failed=0   

Ansibleの[fileモジュール][filemodule]には`selevel`、`serole`、`setype`、`seuser`というSELinux用のオプションがありますが、（普通）`/etc/httpd`全体としてセキュリティコンテクストの設定がされているので、それ以下であればわざわざ設定しなくても大丈夫です。

    $ vagrant ssh -- grep /etc/httpd /etc/selinux/targeted/contexts/files/file_contexts
    /etc/httpd(/.*)?	system_u:object_r:httpd_config_t:s0
    /etc/httpd/alias(/.*)?	system_u:object_r:cert_t:s0
    /etc/httpd/logs	system_u:object_r:httpd_log_t:s0
    /etc/httpd/modules	system_u:object_r:httpd_modules_t:s0
    /etc/httpd/conf/keytab	--	system_u:object_r:httpd_keytab_t:s0

[filemodule]: http://docs.ansible.com/file_module.html