AWS ALB常時SSL(HTTPS)のLaravel 6.x Middleware書き方

つらつらと書いていますが、仕様としては、middlewareを作成後、「### 5.Middleware(ForceHttpProtocol.php)にformatSchemeをoverrideする」に飛んで頂いて構いません。

# 背景
ACMで証明書を取得後、ALBでattachし、挙動テストすると、「完全には保護されていません」と表示される
->PRDのソースを確認すると、プロトコルがhttpで出力されている

<link rel="stylesheet" href="http://${domain}/css/main.css">

ソースコードではasset()で記載

<link rel="stylesheet" href="{{ asset('css/main.css') }}">

原因は、assetが、httpsではなく、httpを読みにいっているから。

# 要件
- 商用環境でhttp通信のリクエストの場合は、https通信にリダイレクト
- ただし、ローカル環境はhttp通信
- 商用環境リンクをフォームを含めて全てhttpsに変更

## 前準備
middlewareの追加
$ php artisan make:middleware ForceHttpProtocol

app/Http/kernel.php

protected $middleware = [
        //省略
        \App\Http\Middleware\ForceHttpProtocol::class,
    ];

### 1. secure()に書き換えるやり方
app/Http/Middleware/ForceHttpProtocol.php

public function handle($request, Closure $next)
    {
        if(!$request->secure() && env('APP_ENV') === 'production'){
            return redirect()->secure($request->getRequestUri());
        }
        return $next($request);
    }

上記では永久リダイレクトとなり、タイムアウトになる。

### 2. ‘https://’.$_SERVER[‘HTTP_HOST’].$_SERVER[‘REQUEST_URI’]にリダイレクト

public function handle($request, Closure $next)
    {
        if(!$this->is_ssl() && config('app.env') === 'production'){
            return redirect('https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
        }
        return $next($request);
    }

    public function is_ssl()
    {
        if ( isset($_SERVER['HTTPS']) === true )
        {
            return ( $_SERVER['HTTPS'] === 'on' or $_SERVER['HTTPS'] === '1' );
        }
        elseif ( isset($_SERVER['SSL']) === true )
        {
            return ( $_SERVER['SSL'] === 'on' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) === true )
        {
            return ( strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PORT']) === true )
        {
            return ( $_SERVER['HTTP_X_FORWARDED_PORT'] === '443' );
        }
        elseif ( isset($_SERVER['SERVER_PORT']) === true )
        {
            return ( $_SERVER['SERVER_PORT'] === '443' );
        }

        return false;
    }

これでhttpリクエストの場合も、httpsにリダイレクトされるようになる。
ただし、asset、routeなどのリンクが、httpsではなく、httpを読みにいく事象は解消されないまま

->urlのhelperを確認
Illuminate/Foundation/helper.php

function url($path = null, $parameters = [], $secure = null)

URLやViewのasset, redirectを全て書き換える?
嘘だろ?

### 3. パスをassetからurlに書き換える
AppServiceProvicerで商用ならtrue, 商用以外ならfalseのメソッドを作成

app/Providers/AppServiceProvider.php

use View;
public function boot()
    {
       $is_production = env('APP_ENV') === 'production' ? true: false;
       View::share('is_production', $is_production);
    }

続いて、viewで、assetをurlに変更して、上記の変数を第3パラメータに追加
resources/view/auth/login.blad.php

<form method="POST" action="{{ url('login', null, $is_production) }}">

一箇所修正して挙動確認。
URLがhttpsに変わっていることを確認
3~4ページぐらい修正を繰り返していると、早くも絶望的な気分になってくる

### 4. Laravelコレクティブ&Controllerでのhttpsパラメータの渡し方を確認
laravel collective

{!! Form::open(['method'=>'POST', 'action'=>['HogeController@confirm',null,$is_production] ]) !!}

controller

protected $is_production;

    public function __construct(){
        $this->is_production = env('APP_ENV') === 'production' ? true: false;
    }

// 戻るボタン処理
        if($action == '戻る'){
            return redirect()->action('HogeController@create', 302, null, $this->is_production)->withInput($inputs);
        }

うまくいかない。

縷々調査すると、ssetやrouteのプロトコルはformatSchemeで生成しているとのこと。
Illuminate/Routing/UrlGenerator.php

public function formatScheme($secure = null)
    {
        if (! is_null($secure)) {
            return $secure ? 'https://' : 'http://';
        }

        if (is_null($this->cachedScheme)) {
            $this->cachedScheme = $this->forceScheme ?: $this->request->getScheme().'://';
        }

        return $this->cachedScheme;
    }

### 5.Middleware(ForceHttpProtocol.php)にformatSchemeをoverrideする

use URL;
public function handle($request, Closure $next)
    {
        if(!$this->is_ssl() && config('app.env') === 'production'){
            return redirect('https://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
        }
        if (env('APP_ENV') === 'production')
        {
            URL::forceScheme('https');
        }

        return $next($request);
    }

    public function is_ssl()
    {
        if ( isset($_SERVER['HTTPS']) === true )
        {
            return ( $_SERVER['HTTPS'] === 'on' or $_SERVER['HTTPS'] === '1' );
        }
        elseif ( isset($_SERVER['SSL']) === true )
        {
            return ( $_SERVER['SSL'] === 'on' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) === true )
        {
            return ( strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https' );
        }
        elseif ( isset($_SERVER['HTTP_X_FORWARDED_PORT']) === true )
        {
            return ( $_SERVER['HTTP_X_FORWARDED_PORT'] === '443' );
        }
        elseif ( isset($_SERVER['SERVER_PORT']) === true )
        {
            return ( $_SERVER['SERVER_PORT'] === '443' );
        }

        return false;
    }

このように書くことで、要件を満たすことができる
- 商用環境でhttp通信のリクエストの場合は、https通信にリダイレクト
- ただし、ローカル環境はhttp通信
- 商用環境リンクをフォームを含めて全てhttpsに変更

axiosとは?

– axiosは、HTTP通信を簡単に行うことができるJavascriptライブラリ
https://github.com/axios/axios
– 主にJsonの取得に利用される

### Features
– Make XMLHttpRequests from the browser
– Make http requests from node.js
– Supports the Promise API
– Intercept request and response
– Transform request and response data
– Cancel requests
– Automatic transforms for JSON data
– Client side support for protecting against XSRF

## サンプル
### npm install
$ npm install axios –save

### index.js
– axios.get(‘${url}’), axios.post(‘${url}’)と実装する
– get, post以外にも、request, delete, head, options, put, patch, getUriなどがある

var express = require('express');
var router = express.Router();

// axiosのインスタンス生成
const axiosBase = require('axios');
const axios = axiosBase.create({
	baseURL = '192.168.33.10:8000',
	headers: {
		'Content-Type' : 'application/json',
		'X-Requested-With': 'XMLHttpRequest'
	},
	responseType: 'json'
});

router.get('/', function(req, res, next){
	axios.get('/title')
	.then(function(response){
		res.render('index', response.data);
	})

	.catch(function(error){
		console.log('Error!! occurred in Backend.')
	});
});

module.exports = router;

### res, err, finally

<script>
		axios.get(${url})
		.then(res => console.log(res.data))
		.catch(err => console.error(err))
		.finally(res => console.log('finally'))
	</script>

XMLHttpRequestを生成せずに処理できるので、確かに使い勝手良さそうです。

ProxyPass、ProxyPassReverseを設定し、商用環境でexpressサーバに接続

## virtualhostで、ProxyPass、ProxyPassReverseを設定し、node appとすると、商用のドメインからでもexpressさーばに接続できるようになる。

vi /etc/httpd/conf.d/hoge.conf

<VirtualHost *:80>
DocumentRoot /var/www/html/hoge/express
ServerName www.target.com
ServerAlias target.com
CustomLog logs/target.com-access.log common
ErrorLog  logs/target.com-error.log
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
AddDefaultCharset UTF-8
<Directory "/var/www/html/hoge/express/">
AllowOverride All
</Directory>
</VirtualHost>

ProxyPassは転送
ProxyPassReverseはリダイレクト情報の書き換え

なるほど、expressサーバについて大分理解が深まった。expressサーバはapacheとは別ですね。
しかし、expressを使うところだけ、Jsonで取得するように、ユーザからのアクセスはProxyPassを使って転送させるって、アーキテクチャとしてどうなんだろうか。。。express単体のアプリケーションならいいが、フレームワークの中でexpressを使うってのは、仕組み的には可能だが、他の方法を探した方が建設的か。。。

Vagrant環境(amazon linux2)で、Expressを使ってhttpsサーバーを立てる

まず、sslモジュールをinstall
$ sudo yum install mod_ssl

続いてkeyとcertを作成して読み込む
# 手順
## certificate file作成
openssl req -newkey rsa:2048 -new -nodes -keyout key.pem -out csr.pem
openssl x509 -req -days 365 -in csr.pem -signkey key.pem -out server.crt

## package.json

{
	"name": "test-webrtc",
	"version": "0.0.1",
	"private": true,
	"dependencies": {
		"express": "4.x",
		"ejs": "3.0.1"
	}
}

$ npm install

## server.js

var express = require('express');
var app = express();

var fs = require("fs");
var https = require("https");
var options = {
	key: fs.readFileSync('key.pem'),
	cert: fs.readFileSync('server.crt')
}
var server = https.createServer(options, app);

console.log('server started');

app.get('/', function(req, res){
	res.render('index.ejs');
});

server.listen(3000);

$ node server.js

# 駄目な方法
## certificate file作成
$ openssl genrsa > server.key
$ openssl req -new -key server.key > server.csr
$ openssl x509 -req -signkey server.key < server.csr > server.crt

var express = require('express');
var app = express();

var fs = require("fs");
var https = require("https");
var options = {
	key: fs.readFileSync('server.key'),
	cert: fs.readFileSync('server.crt')
}
var server = https.createServer(options, app);

console.log('server started');

app.get('/', function(req, res){
	res.writeHead(200);
	res.render('index.ejs');
});

server.listen(3000);

## server.js
keyがpemファイルでないので、エラーが出ます
$ node server.js
_tls_common.js:88
c.context.setCert(options.cert);
^

Error: error:0906D06C:PEM routines:PEM_read_bio:no start line
at Object.createSecureContext (_tls_common.js:88:17)
at Server (_tls_wrap.js:819:25)
at new Server (https.js:60:14)
at Object.createServer (https.js:82:10)
at Object. (/home/vagrant/webrtc/server.js:10:20)
at Module._compile (module.js:653:30)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)

vagrantでhttpsの環境を作ろうとした時、opensslとphpのビルトインサーバーでhttps環境を作っていましたが、フロントエンドだけならexpressで十分だということがわかりました。
expressはhttpのみかと勘違いしていたが、よくよく考えたら、できないわけない😂😂😂

php build-in serverでhttpsサーバーを起動

ローカル環境で、httpsの挙動を確認したい時に使えるのが、hyper-builtinというライブラリ
https://github.com/mpyw/php-hyper-builtin-server

opensslでサーバー証明書を生成し、composerでhyper-builtinを入れて起動
※下はawslinuxだが、centosでも同様

### sslモジュールインストール(centOSの場合はmod_ssl)
$ sudo yum install mod24_ssl
$ httpd -M | grep ssl

### 秘密鍵作成
$ openssl genrsa > server.key

### CSR作成
$ openssl req -new -key server.key > server.csr

### サーバー証明書作成
$ openssl x509 -req -signkey server.key < server.csr > server.crt
$ rm server.csr

### 秘密鍵&サーバー証明書配置
$ sudo mkdir /etc/httpd/conf/ssl.key
$ sudo mkdir /etc/httpd/conf/ssl.crt
$ sudo mv server.key /etc/httpd/conf/ssl.key/
$ sudo mv server.crt /etc/httpd/conf/ssl.crt/

### ssl.conf編集
sudo vi /etc/httpd/conf.d/ssl.conf

# SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateFile /etc/httpd/conf/ssl.crt/server.crt
# SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
SSLCertificateKeyFile /etc/httpd/conf/ssl.key/server.key

### apache再起動
$ sudo service httpd restart

### composerでhttps用のphp buildin-server libraryインストール
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require –dev mpyw/php-hyper-builtin-server:^2.0

### httpsサーバー起動
$ vendor/bin/hyper-run -s 192.168.33.10:8000

うおおおおおおおおおおおおおお、めんどくせええええええええええええ
これ、playbook.ymlで一括管理してーーーーーーーーーーーーー

User Agent perspective

How to investigate user agent?
As an example, the user agent for IE9 is described as follows.

Mozilla / 5.0 (compatible;MSIE 9.0; Windows NT 6.1; Trident/5.0)

Application name
It corresponds to the “Mozilla” part. It is used in the sense that it has the function of the type of browser or its application.

Application Version
In the “5.0” part after Mozilla, it indicates the version of the application.

Platform token
It corresponds to the “Windows NT 6.1” part. It will be the display about OS.

Version Token
It corresponds to the part of “MISE 9.0”.Displaying browser version.

Compatibility Flags
It corresponds to the “Compatible” part. It shows the compatibility with Internet Explore.

Rendering engine
It corresponds to the part of “Trident/5.0”. Shows software that causes the browser to display the requested content.

Internet Explore
ver.11

Mozilla / 5.0 (Windows NT 6.3; ARM; Trident / 7.0; Touch; MALNJS; rv: 11.0) like Gecko

ver.10

Mozilla / 5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW 64; Trident / 6.0)

Chrome

Mozilla / 5.0 (Windows NT 5.1) AppleWebKit / 535.11 (KHTML, like Gecko) Chrome / 17.0.963.79 Safari / 535.11

Firefox

Mozilla / 5.0 (Windows NT 6.1; rv: 11.0) Gecko / 20100101 Firefox / 11.0

Safari

Mozilla / 5.0 (Macintosh; U; Intel Mac OS X; en-jp) AppleWebKit / 523.12 (KHTML, like Gecko) Version / 3.0.4 Safari / 523.12

Android

Mozilla / 5.0 (Linux; Android 4.4.2; 302KC Build / 101.0.2c00) AppleWebKit / 537.36 (KHTML, like Gecko) Version / 4.0 Chrome / 30.0.0.0 Mobile Safari / 537.36 YJApp-ANDROID jp.co.yahoo.android. ybrowser / 1.7.5

なるほど、内容よく見ると、どのブラウザか一発でわかりますね。まあ、一般的にユーザーエージェントは偽装されるのであまり意味ないかもしれませんが。そういえば、IPって偽装できるんでしょうか??? Chrome Extensionで、Hotspot Shieldを使えば偽装できるようですが。。

HTTP Trace method

In HTTP1.1 (RFC2616), eight kinds of methods are defined. GET, POST, HEAD, etc. are familiar, but there are five other types PUT, DELETE, OPTIONS, TRACE and CONNECT.
Of these, the TRACE method returns an HTTP request as “HTTP Parallel” as an HTTP response, and requests the Web server as TRACE instead of GET etc. as follows.

Apache setting file
/etc/httpd/conf/httpd.conf

あれ?TraceEnableないぞ。。

403 404 500 503

There are HTTP status codes from 100 series to 500 series. The 400th to 500th are the codes returned when there is an error with respect to the server or request.

403

403 is the code returned when access restriction etc is set.

It is displayed when IP restriction is applied and access is made from an IP address that is not permitted.
It is considered when there is access from other on the page to be displayed only in the company environment.

404

The code returned if the page does not exist. It is often displayed when deleting a page.

500
500 is a code returned in the case of CGI setting or program mistake. In case of this error, setting is wrong often, so it is necessary to modify permissions and code.

503
503 is the code returned when the number of accesses to the server has been exceeded and the server is under load. It is displayed when a large amount of access to the server gather at the same time.
In the case of a site where there are many instantaneous accesses, it is necessary to consider a server corresponding to that. Also, there is a possibility that may be attacked by a site.

Other representative HTTP status code
200 series
The 200 series means that the request to the server was successful. If you have successfully accessed the WEB, the status code “200” will be returned.

300 series
The 300 series is the code returned when doing redirect processing. Representative items such as “301” and “302” are listed.

Difference between GET and POST method

GET, POST are some of the HTTP methods negotiated by the specification.
Besides this there are also PUT, PATCH, HEAD, DELETE etc ..

GET method
GET adds it to the URL and makes a request

POST method
POST method is included in body of request.

GET adds directly to the URL so you can see the parameters with your eyes.
Since POST is included in body, it can not be seen with eyes.

There are different specifications when requesting with GET and POST, such as being able to send in binary, size restriction, etc.