[Laravel5.4] Proxy環境下でSSL強制

5年ほどROM専だったけど気が向いたので投稿。
最近業務でLaravel使うこと多くなってきた。

前提

  • Laravel 5.4
  • PHP7.1
  • AWS + CloudFront(CDN)
  • Port443空いてる Port80閉じてる

解決したいこと

まーあるあるネタ

  • 普通にLaravel使ってると、リダイレクト時やblade でリンク生成時、
    最終的にUrlGeneratorでURL生成してることがほとんど
  • UrlGeneratorはアクセス元のスキームやホストからフルURLを生成する
  • Proxy環境下ではクライアントからのアクセスはHTTPSでも、エンドポイントから見るとHTTPなこと多し
  • よってUrlGeneratorがクライアント側がHTTPSでもhttpなURLを生成しちゃう

解決方法

ミドルウェアでリクエストを確認して、HTTPSなアクセスだったらスキーマを書き換える

ミドルウェア作成

app/Http/Middleware/SecureAccess.php
<?php
namespace AppHttpMiddleware;

use SymfonyComponentHttpFoundationRequest;

class SecureAccess
{
    public function handle($request, Closure $next, $guard = null)
    {
        $is_secure =  $request->server('HTTPS') === 'on'
            || $request->server('HTTPS') === '1'
            || $request->server('SSL') === 'on'
            || $request->server('HTTP_X_FORWARDED_PROTO') === 'https'
            || $request->server('HTTP_CLOUDFRONT_FORWARDED_PROTO') === 'https'
        ;   // ※後述1

        if (! $is_secure) {
            return $next($request);
        }

        URL::forceScheme('https'); // ※後述2

        Request::setTrustedProxies([
            '0.0.0.0/0'
        ]); // ※後述3

        return $next($request);
    }
}
  1. 大抵のProxy環境下ではHTTP_X_FORWARDED_PROTOでいけるはずだけど、
    CloudFront経由時は更にHTTP_CLOUDFRONT_FORWARDED_PROTOを見ないとダメだった。
    HTTPSかどうかの判定は他にもあるかも。
  2. UrlGenerator::forceScheme() のエイリアス。これでUrlGeneratorが返すURLが(ほぼ)httpsになる。
  3. 上記2の例外の対処で一部のURLを生成する関数は、
    forceScheme関係なくSymfonyのRequestオブジェクトからURLを生成している。
    そやつは $_SERVER['REMOTE_ADDR']とここでセットしたIPを判定しマッチしてればhttps、
    そうでなければ$_SERVER['HTTPS']のみを見てスキーマを返している。
    (正しくIP指定しないとローカル開発時の直HTTPSアクセスでProxy経由と判定されちゃうよ☆)

ミドルウェアの登録

あとは普通にミドルウェア登録。

app/Http/Kernel.php
<?php
namespace AppHttp;

use IlluminateFoundationHttpKernel as HttpKernel;

class Kernel extends HttpKernel
{
    protected $middleware = [
        /* ~~ その他ミドルウェア ~~ */
        AppHttpMiddlewareSecureAccess::class,
        /* ~~ その他ミドルウェア ~~ */
    ];
}

所感

  • forceScheme 強制スキーマ(強制とは言ってない)
  • SymfonyのRequestクラスはホストはX_ORIGINAL_URLやらX_REWRITE_URLetc…見て大分がんばってるのに、
    スキーマだけはこういう判定方法にしてるのはなんか理由あるんかな?

続きを読む

Elastic BeanStalkの環境別のタグ付を忘れたときにどうすんの?って話

1年ぐらい前からBeanStalk使ってます。
なんつうのか、この辺のAWSのマネージドサービスっていうの?ってほんとすごいですよね。
インフラエンジニアなんか必要なくなりますよね。
AWSの機能をガリガリ使って一昔前の3-tier systemなんて、ネットワークの知識もインフラ系のIeasの知識もなくアプリ屋さんがポチポチやってればいつでも復元できるシステム!が出来上がっちゃうんですもんね。
しかもはやりのECSつかったDockerコンテナの仕組みもつかって・・・
「あ〜〜Dockerとか最近ちゃってもう最近開発とか楽でさ〜〜、インフラ屋?いるの?仕事おせえよあいつら」とかほざけるわけです。

俺なんか無職まっしぐらです。

Beanstalkってなんぞや?

さて、まぁ今更ですがBeanStalkってなんぞやって人に簡単に説明しておきますと、
要は初見殺しとも言えるAWSの「CloudFomartion」を”比較的”わかりやすいUIで設定できるというマネージメントシステムです。
基本はネットワーク層+3tier+インスタンススケーリング何かをある程度コントロールしてくれます
更にアプリケーションのデプロイはソースをまとめておくとECR上にコンテナイメージとして作成してくれたり、そこまで必要ない人でもPHP用のAMIを使ってちょっとPHPで動作させたいコンテンツを作ってデプロイしてみたいな事ができます。
この辺、コンソールでポチポチやってると、たしかに時間的にそれなりのリソースを取られますしで開発したアプリケーションのバージョン管理もソレナリにしてくれるので何度も言いますがとても便利なサービスだと思っています。

BeanStalkのアレなところ

こんなに便利そうなBeanStalkなんですが、サーバサイド(アプリケーション開発者)にはなり針を振り気味なサービスなので、少しでも込み入ったことを始めるととたんに出来ないことが出てきます。
そこで、「.ebextentions」というファイルを作ってアプリ層のレポジトリに拡張設定をおいて設定を入れたりする訳です。
まぁ、デフォルトで設定されるCloudFormationの設定項目をアプリ層に置くようにするわけで、インフラ屋のいない会社なんかだと違和感ないというかとても理にかなっているようにみえるんですが、タスクをサーバサイド・インフラサイドと別けているチームなんかだと

インフラ屋がアプリ屋に土下座をしてネットワーク設定なんかを開発アプリレポジトリに入れてもらう

なんていう状態が発生してしまい、なんとなく自分の存在意義を感じられなくなってきたりもします。

そこで、インフラ屋さんは「EBとかつかってらんねぇよ、せめてCloudFormationでやろうぜ・・・(JSON書きたくないけど)」とか言い始めるわけです。

あ、個人的にはterraformに逃げましたw

本題の環境ごとのタグの調べ方について

なんか、閑話休題のボリュームがいつもまとまらず多めになってしまう訳ですが
本当に書きたかったEBで作った環境に対してのタグの後付とかに関してです。

EBで環境を作るとEBにおけるタグを設定しておけば、そのアプリケーションで起動したEC2インスタンスなどには一応Nameタグがついてくれてどの環境かわかりやすくなるのですが、SecurityGroup・ELB/ALBなどには最初にタグを付け忘れたりすると、どの環境で使っているSG・LBなのかを探すのが非常に困難になります。

具体的には例えばSecurityGroupなんかだと下記のようなタグが付くことになります
– awseb-a-3xxxxx2xxxx-stack-AWSEBSecurityGroup-HOGEFUGE1HOGE

まぁ訳わんないですよね。SGに関してはまだNameのタグを設定しておけば自分で任意に環境名をつけられますが
ELBとかだとName=「ユニークな文字列:DNS名」になっているのでAWSのコンソールやawscliコマンドの返り値だけでは一見どの環境用セット用の機材なのかが全くわからない状態になってしまっています。

また、タグ自体も起動段階で設定していないと、環境をすべてEB的に再構築しない限り(つまり環境内のすべてを捨ててもう一度構築し直す処理)を入れないと追加したNameタグなどは設定されない状況に陥ります。

これをあとでチクチクと手作業で入れることになるんですが、
– awseb-a-3xxxxx2xxxx-stack-AWSEBSecurityGroup-HOGEFUGE1HOGE

この文字列はEBの中身を見ているだけだと判別がつかない物なのです。
なぜか
この文字列自体がCloudformationで作られている物だからですwww。
なのでこのELBがどのEBアプリケーションで使われているのか?というのを調べるためには

  • まず、上記の文字列のうち、「awseb-a-3xxxxx2xxxx-stack」までの部分を抜き出し
  • CloudFormationのスタック一覧のFilterをその文字列に入れ
  • 説明欄にちゃんと記載されている「AWS Elastic Beanstalk environment (Name: ‘hogefuge-app’)」を確認するという作業が必要になります。

まぁ調べる方法がある分マシですがね・・・この辺もうデフォルトでNameダグとしてすべての環境に入れてしまってもいいと思うんですけどね

以上、ちょっとしたボヤキでした。
ちなみに、サーバサイドの方の「構築の手間が楽になるからEB使うことを強く推したい」という要望であるプロジェクトでEB使いましたがもう本サービス運用では二度と使いたくありません・・・・

続きを読む

AWSのApacheログにCannot allocate memoryと出た時の対処

はじめに

レンタルサーバーで運用していたサービス(Wordpress)をAWSに移行したら、管理画面の設定ページなどでHTTP 500エラーになることが度々あった。
以下のようにApacheのエラーログを見てみた

$ sudo less /etc/httpd/logs/error_log

するとCannot allocate memoryとかPHP Fatal error: Out of memoryとかのエラーが出ていて、メモリ不足だった。

とりあえず解決方法

PHPのメモリ使用料を無制限にしたら直った。

// php.iniを編集
$ sudo vi /etc/php.ini

memory.limit = 128Mってなってるところをmemory.limit = -1にしたら解決した。

テスト環境だったので、これでいいけど、本当はインスタンスのメモリを増強とかしないといけない気がする

続きを読む

開発用サーバーを作る on AWS(Ruby on Rails 5)

はじめに

まぁそのまんま。
こちらのRubyonRails編。

Ruby 2.4.2
Ruby on Rails 5.1.4
なり。

Amazon Linux AMI+Nginx+Unicorn
やで。

インストールなど

sudo yum update
sudo yum -y install git
sudo yum install nodejs --enablerepo=epel
git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source ~/.bash_profile
sudo yum install gcc openssl-devel readline-devel sqlite-devel
rbenv install 2.4.2
rbenv global 2.4.2
gem update --system
gem install --no-ri --no-rdoc rails
gem install bundler
gem install sqlite3
gem install unicorn
rbenv rehash
rails -v
rails new /var/www/html/sample
sudo mkdir /var/run/unicorn
sudo chmod 777 /var/run/unicorn
sudo chown -R nginx:ec2-user /var/www/html
sudo chmod 2777 /var/www -R

まぁ、だいぶ時間がかかりまっせ。

sample/config/unicorn.rbを作成

application = 'sample'

worker_processes 2
timeout 15

pid "/var/run/unicorn/unicorn_#{application}.pid"
listen "/var/run/unicorn/unicorn_#{application}.sock"

preload_app true

before_fork do |server, worker|
  Signal.trap 'TERM' do
  puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
  Process.kill 'QUIT', Process.pid
  end

  defined?(ActiveRecord::Base) and
  ActiveRecord::Base.connection.disconnect!end

after_fork do |server, worker|
  Signal.trap 'TERM' do
  puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and
  ActiveRecord::Base.establish_connection
end

stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])

起動

bundle exec unicorn_rails -c config/unicorn.rb -E development -D

OSの再起動対応

~/.bashrcに追記

sudo mkdir -p /var/run/unicorn && sudo chmod 777 /var/run/unicorn

/etc/nginx/conf.d/sample.conf作成

upstream unicorn {
  server unix:/var/run/unicorn/unicorn_sample.sock;
}
server {
    listen 8080;
    server_name localhost;
    root /var/www/html/sample;

    access_log /var/log/nginx/myapp_access.log;
    error_log /var/log/nginx/myapp_error.log;

    try_files $uri/index.html $uri @unicorn;
    location @unicorn {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_pass http://unicorn;
    }
}

PHPを80で動かしてる都合上8080でごわす。

Nginx再起動

sudo service nginx restart

いじょー
Security Groupで8080はあけておきましょうね。

by 株式会社Arrvis

続きを読む

開発用サーバーを作る on AWS(PHP7+Nginx)

そのまんま

3分くらいでサーバーが立つ便利な世の中。
EC2起動したらsshで接続して以下コピペすればおk。
Apacheでいい人はnginxは外してくだされ。
最後の行で止まるからEnterしたったらええよ。

sudo yum update
sudo rpm -Uvh ftp://ftp.scientificlinux.org/linux/scientific/6.4/x86_64/updates/fastbugs/scl-utils-20120927-8.el6.x86_64.rpm
sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
sudo yum install -y nginx php70 php70-php-fpm php70-mbstring php70-mcrypt php70-pdo php70-xml php70-fpm
sudo sed -i -e "s#/usr/share/nginx/html;#/var/www/html;#g" /etc/nginx/nginx.conf
sudo sed -i -e "s/user = apache/user = nginx/g" /etc/php-fpm.d/www.conf
sudo sed -i -e "s/group = apache/group = nginx/g" /etc/php-fpm.d/www.conf
sudo chmod 2777 /var/www -R
sudo chown -R nginx:ec2-user /var/www/html
echo "<?php phpinfo(); ?>" >> /var/www/html/index.php
sudo chkconfig php-fpm on
sudo chkconfig nginx on
sudo service php70-php-fpm start
sudo service nginx start

アクセスしてphpinfoが表示されるか確認しませう。

おまけ

EC2を日本時間に

$ sudo mv /etc/localtime /etc/_localtime
$ sudo cp /usr/share/zoneinfo/Japan /etc/localtime

さらにおまけ

AWS SDK導入

$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require aws/aws-sdk-php

もひとつおまけ

MySQLクライアントインストール

sudo yum install mysql57 php70-mysql php70-mysqlnd

by 株式会社Arrvis

続きを読む

ダイジェスト認証時のELBヘルスチェックではまった。。。ELBのヘルスチェックを認証から外す。

はじめに

ELB⇒EC2の構成で、ダイジェスト認証を行っているサーバーに対してヘルスチェックを行う際、
ずっとステータスがOutofServiceで、解決するのに時間がかかてつぃまった。。。

原因

ヘルスチェック対象のサーバーに、Digest認証やBasic認証がある場合、ヘルスチェックも妨害されてしまう。。。
Apacheのアクセスログに、401エラーが出ていました。
401エラーは認証を必要としているエラーです。
Apacheのステータスコードについてはこちら

解決策

ヘルスチェックの通信を除外するように、設定ファイルに追記しました!
基本はlocationタブ内の範囲を、設定ファイルに追記すれば大丈夫なはずです。

/etc/httpd/conf/httpd.confだけ使っていれば、httpd.confに追記します。
バーチャルホストを使用しているのであれば、各バーチャルホストの設定ファイルに書いていきましょう。
※バーチャルホストを使用している場合は、httpd.confに書くとすべてに適用される可能性があるので気を付けてください。

# vi /etc/httpd/conf.d/virtualhost.conf


●変更前

<VirtualHost *:80>
    ServerName {{ 任意のサーバー名 }}:80
    DocumentRoot /var/www/html
    DirectoryIndex index.php
    RewriteEngine on
    RewriteCond $1 !^/(index.php|img|js|fonts|css|robots.txt|favicon.ico|.well-known)
    <Directory /var/www/html>
        AuthType Digest
        AuthName "Digest Auth"
        AuthUserFile "/var/www/.htdigest"
        Require valid-user
    </Directory>
</VirtualHost>


●変更後

<VirtualHost *:80>
    ServerName {{ 任意のサーバー名 }}:80
    DocumentRoot /var/www/html
    DirectoryIndex index.php
    RewriteEngine on
    RewriteCond $1 !^/(index.php|img|js|fonts|css|robots.txt|favicon.ico|.well-known|health.html) ★リダイレクトの設定がある場合のみ。ヘルスチェックで使用するファイル名を追記!(「.」の前に「」が必要)
    <Directory /var/www/html>
        AuthType Digest ★認証方法(Basic認証ならBasic)
        AuthName "Digest Auth" ★認証設定時に設定した認証名
        AuthUserFile "/var/www/.htdigest" ★認証情報が記載されたファイルパス
        Require valid-user
    </Directory>

    <Location /> ★下記(<location>~</location>)を追記!
        AllowOverride all
        Satisfy Any
        SetEnvIf Request_URI "/health.html" healthcheck ★"ここはヘルスチェックで使用するファイル名"
        Order Deny,Allow
        Deny from all
        Allow from env=healthcheck
        AuthType Digest ★認証方法(Basic認証ならBasic)
        require valid-user
        AuthName "Digest Auth" ★認証設定時に設定した認証名
       AuthUserFile "/var/www/.htdigest" ★認証情報が記載されたファイルパス
    </Location>
</VirtualHost>


●文法確認
# httpd -t


●設定反映
# systemctl restart httpd

はまったところ

リダイレクト設定が入っていることで、解消に時間がかかってしまいました。。。
⇒単に気が付くのが遅かっただけです。。。

下記の設定で、任意のファイル名を追記することでアクセス許可することができます。
アプリの仕様上、静的ファイルへのアクセスを制限するために下記設定を入れていたようです。。。。
RewriteCond $1 !^/(index.php|img|js|fonts|css|robots.txt|favicon.ico|.well-known|health.html)
リダイレクトについて、まだ全然わかっていないので、別の機会にまとめます。

参考にしたURL

続きを読む

PHPでAWS SSMパラメータストアの値を参照する

AWS SSMパラメータストアとは

準備

  • パラメータの定義

    • AWSコンソールから設定する場合は [サービス] -> [EC2] -> [パラメータストア] -> [パラメータの作成]
  • IAMロールにSSMの権限を追加

    • 管理ポリシーのAmazonSSMReadOnlyAccessをアタッチしておく
  • EC2インスタンスにIAMロールを割り当て

単純な実装例

sample.php
<?php

require 'vendor/autoload.php';

function getSsmParameters(array $names)
{
    $config = [
        'version' => '2014-11-06',
        'region' => 'ap-northeast-1'
    ];

    $client = new AwsSsmSsmClient($config);
    $result = $client->getParameters(
        [
            'Names' => $names,
            'WithDecryption' => false
        ]
    );

    if ($result['InvalidParameters']) {
        // 存在しない値
        echo "InvalidParameters: " . implode(', ', $result['InvalidParameters']) . "n";
    }

    if (!is_array($result['Parameters'])) {
        return null;
    }

    $params = [];

    foreach ($result['Parameters'] as $v) {
        $name = $v['Name'];
        $params[$name] = $v['Value'];
    }

    return $params;
}

$params = getSsmParameters(['hoge', 'fuga']);

var_dump($params);
$ php sample.php
InvalidParameters: fuga
array(1) {
  ["hoge"]=>
  string(11) "hello,world"
}

参考: http://docs.aws.amazon.com/aws-sdk-php/v3/api/api-ssm-2014-11-06.html#getparameters

続きを読む

EC2サーバにPHP環境構築

インスタンス情報

  • インスタンスタイプ t2.micro
  • Amazon Linux AMI 2017.03.1.20170812 x86_64 HVM

無料枠で作れる一番しょぼいやつです

yumで色々インストール

開発系のツールを一括でインストール

$ sudo yum groupinstall 'Development tools' -y

PHPのバージョン確認(まだインストールされていないので怒られる)

$ php -v

php7.1系とモジュールを諸々インストール

$ sudo yum php71 php71-devel php71-mysqlnd php71-mbstring php71-pdo php71-gd -y

PHPが入ったはずなのでバージョン確認
$ php -v
PHP 7.1.7 (cli) (built: Sep 14 2017 15:47:38) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
以下コマンドでphp7.1系のモジュールが見れるので必要ならインストール

yum list | grep php71

動作確認

php -aで対話形式でPHPが使える
$ php -a
Interactive shell

php > echo 5 * 5;
25
php > exit

composerインストール

本体ダウンロード
$ curl -sS https://getcomposer.org/installer | php
All settings correct for using Composer
Downloading...

Composer (version 1.5.2) successfully installed to: /home/ec2-user/composer.phar
Use it: php composer.phar
ちゃんとダウンロードできてるか確認
$ ls
composer.phar
パスの通ってる場所へ移動
$ sudo mv composer.phar /usr/local/bin/composer
whichコマンドとか-Vで出てくれば成功
$ which composer
/usr/local/bin/composer
$ composer -V
Composer version 1.5.2 2017-09-11 16:59:25

続きを読む