【備忘録】nginx, Rails連携時にassets配下のファイルにアクセスできない場合の解決方法

# はじめに
nginx, Rails連携させている環境において、assets配下のファイルにアクセスできない場合があるので解決方法をメモしておきます。

**原因**
ケース１：productionモードでassets配下のアクセスをRailsへ転送している
ケース２：アクセス権が原因でnginxがassets配下のファイルにアクセスできない
ケース３：SELinuxが原因でnginxがassets配下のファイルにアクセスできない

**環境**
CentOS 7.3.1611
Rails 5.1.2
nginx 1.10.2

**事前準備**
Rails : 3000ポートでサーバ起動しておく
nginx : アクセスを3000ポートへ転送する

# 問題点
Railsへアクセスした際にcss, jsが読み込まれない

# ケース１：productionモードでassets配下のアクセスをRailsへ転送している

## 調査
このケースではアクセス時にnginxのエラーログは出力されない

```
$ sudo tail /var/log/nginx/hello-app_error.log
```

Railsのログでcss, jsへのアクセス時に`No route matches`と出力される

```
tail -f log/production.log 

F, [2017-07-30T05:39:40.310181 #4934] FATAL -- : [f8ec5376-7c17-4aa0-b46b-e3669b834c5e] ActionController::R
outingError (No route matches [GET] "/assets/application-35729bfbaf9967f119234595ed222f7ab14859f304ab0acc54
51afb387f637fa.css"):

F, [2017-07-30T05:39:40.439806 #4934] FATAL -- : [6bfdd3a1-fbae-4cf3-9566-c8865ee56988] ActionController::R
outingError (No route matches [GET] "/assets/application-a0c66820a1b25b7b91aab7fb553a37bc0728f9b1df860c3660
e590597bf9cafa.js"):
```

## 暫定対処/ローカル環境

`config/environments/production.rb`で`config.public_file_server.enabled`を有効にする

```ruby:config/environments/production.rb
config.public_file_server.enabled = true
```

## 解決方法
nginxからassets配下のファイルへアクセスさせる

nginxの設定ファイルを編集

```text:/etc/nginx/sites-available/hello-app
upstream hello-app {
  server unix:/home/xxx/hello-app/tmp/sockets/puma.sock;
}
server {
  listen 80;
  server_name hello-app;
  root /path/hello-app/public;
  access_log  /var/log/nginx/helllo-app_access.log;
  error_log   /var/log/nginx/hello-app_error.log;

  # ==追記ここから===
  location ~ ^/assets/ {
    root   /home/xxx/hello-app/public;
  }
  # ==追記ここまで===

  location / {
    client_max_body_size 0;
    gzip off;
    proxy_read_timeout      300;
    proxy_connect_timeout   300;
    proxy_redirect          off;
    proxy_http_version 1.1;
    proxy_set_header    Host                $http_host;
    proxy_set_header    X-Real-IP           $remote_addr;
    proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_set_header    X-Forwarded-Proto   $scheme;
    proxy_pass http://127.0.0.1:3000;
  }
}
```

nginxに設定ファイルを再読込させる

```
# nginxを手動起動していた場合
$ sudo nginx -s reload

# nginxをサービス起動していた場合
$ sudo service nginx reload
```

アクセス確認
http://xxx.xxx.xxx.xxx

→ css, jsが読み込めたらOK
→ css, jsが404エラーとなる場合はpublicディレクトリへのパスを再確認
→ css, jsが403エラーとなる場合はケース２〜３も確認する

# ケース２：アクセス権が原因でnginxがassets配下のファイルにアクセスできない

## 調査
このケースではアクセス時にnginxのエラーログに`open() "/home/xxx/hello-app/public/assets/xxx.css" failed (13: Permission denied)`と出力される

```
$ sudo tail /var/log/nginx/hello-app_error.log

2017/07/30 06:00:12 [error] 5175#0: *215 open() "/home/xxx/hello-app/public/assets/application-3572
9bfbaf9967f119234595ed222f7ab14859f304ab0acc5451afb387f637fa.css" failed (13: Permission denied), client: 1
xxx.xxx.xxx.xxx, server: hello-app, request: "GET /assets/application-35729bfbaf9967f119234595ed222f7ab14859
f304ab0acc5451afb387f637fa.css HTTP/1.1", host: "xxx.xxx.xxx.xxx", referrer: "http://xxx.xxx.xxx.xxx/"
```

## 解決方法
`/`から`/public`までのディレクトリにnginx実行ユーザへのアクセス権が付与されてるか確認する。付与されてないディレクトリにはアクセス権を付与する

```
# アクセス権を確認
$ ls -l /home/xxx

# アクセス権を付与
$ chmod 755 /home/xxx
```

アクセス確認
http://xxx.xxx.xxx.xxx

→ css, jsが読み込めたらOK
→ css, jsが403エラーとなる場合は３も確認する

# ケース３：SELinuxが原因でnginxがassets配下のファイルにアクセスできない


## 調査
このケースでもアクセス時にnginxのエラーログに`open() "/home/xxx/hello-app/public/assets/xxx.css" failed (13: Permission denied)`と出力される

```
$ sudo tail /var/log/nginx/hello-app_error.log

2017/07/30 06:00:12 [error] 5175#0: *215 open() "/home/xxx/hello-app/public/assets/application-3572
9bfbaf9967f119234595ed222f7ab14859f304ab0acc5451afb387f637fa.css" failed (13: Permission denied), client: 1
xxx.xxx.xxx.xxx, server: hello-app, request: "GET /assets/application-35729bfbaf9967f119234595ed222f7ab14859
f304ab0acc5451afb387f637fa.css HTTP/1.1", host: "xxx.xxx.xxx.xxx", referrer: "http://xxx.xxx.xxx.xxx/"
```

## 解決方法

SELinuxのモジュールを追加・更新する
（過去にモジュールを追加していた場合でも下記コマンドの結果と設定済みモジュールに差分がないか確認する）

```
# ポリシー確認
$ sudo cat /var/log/audit/audit.log | grep nginx | audit2allow -m nginx

module nginx 1.0;
require {
        type httpd_t;
        type user_home_t;
        type ntop_port_t;
        class tcp_socket name_connect;
        class file { open read };
}
#============= httpd_t ==============
#!!!! This avc is allowed in the current policy
allow httpd_t ntop_port_t:tcp_socket name_connect;
#!!!! This avc can be allowed using the boolean 'httpd_read_user_content'
allow httpd_t user_home_t:file read;

# モジュール作成
$ sudo cat /var/log/audit/audit.log | audit2allow -M nginx

# インストール
$ sudo semodule -i nginx.pp
```

アクセス確認
http://xxx.xxx.xxx.xxx

→ css, jsが読み込めたらOK
→ css, jsが403エラーとなる場合は再度ポリシー確認を実施する

補足：
作成されるポリシーの内容はaudit.logの内容によって変わる
SELinuxを無効にして解決する場合はモジュールの更新を実施する

今回は環境構築中に以下のように変化した

```
require {
        type httpd_t;
        type ntop_port_t;
        class tcp_socket name_connect;
}
```

　↓

```
require {
        type httpd_t;
        type user_home_t;
        type ntop_port_t;
        class tcp_socket name_connect;
        class file { open read };
}
```

補足：
SELinuxセキュリティコンテキストはlsコマンドの`-Z`オプションで確認できる

```
$ ls -Z
```


## 最終手段
SELinuxを一時的に無効にする

```
# SELinuxの無効化
$ sudo setenforce 0

# SELinuxの状態確認
$ sudo getenforce

Permissive
```

SELinuxの無効を永続化する

```
# 設定変更
$ sudo vi /etc/selinux/config
```

```text:/etc/selinux/config
SELINUX=disabled
```

```
# 再起動
$ sudo reboot
```