まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:apache

CentOS 7 で運用する独自ドメインのウェブサーバーを Certbot(Let's encrypt) で無料の証明書を取得して SSL 化しました。その手順の備忘録です:
le-logo-standard



【環境】
OS: CentOS 7.6(64bit)
HTTP: Apache HTTPD 2.4.6※

※正確には WordPress 5.2.2(ja) を MariaDB 5.5.60 で運用中。Apache と MariaDB は yum で導入、WordPress は公式サイトから最新版 latest-ja.tar.gz を取得して展開

Document Root: /var/www/html/wordpress
ドメイン名(仮): mydomain.com (DNS 設定済み)

1サーバーで1ドメイン運用、のシンプルなケースを想定しています。http では既に稼働中、という状態だと思ってください。


【準備】

Certbot のインストール
$ sudo yum install -y epel-release
$ sudo yum install -y certbot python-certbot-apache

root で動作確認
$ sudo -i
# certbot --version
certbot 0.36.0

certbot コマンドを実行し、バージョン番号が表示される(=インストールされている)ことを確認します。

【作業】

以下のコマンドを実行するにあたり、証明書を導入するサーバーに HTTP(TCP/80) および HTTPS(TCP/443) でパブリック・アクセスできるようにポート設定できているものとします。またDocument Root に Basic 認証などがかかっている場合は外しておきます(必須)。

証明書作成
# certbot certonly --webroot -w /var/www/html/wordpress -d mydomain.com

-w オプションで DocumentRoot、-d オプションでドメイン名を指定します。E メールアドレスの入力を求められるので自分のメールアドレスを入力して Enter 、そして利用規約に A(Agree) を押して、最後に N(No) を押します:
# certbot certonly --webroot -w /var/www/html/wordpress -d mydomain.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): myname@xxx.co.jp
Starting new HTTPS connection (1): acme-v02.api.letsencrypt.org

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: A

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N

処理が成功すると以下のような "Conguratulations!" というメッセージが表示されます:
2019082900


ここまで完了していると、以下の4ファイルが作成されているはずです(mydomain.com 部分には指定したドメイン名が入ります):
  1. /etc/letsencrypt/live/mydomain.com/cert.pem (サーバー証明書(公開鍵))
  2. /etc/letsencrypt/live/mydomain.com/chain.pem (中間証明書)
  3. /etc/letsencrypt/live/mydomain.com/fullchain.pem (サーバー証明書と中間署名書の結合ファイル 今回は使いません
  4. /etc/letsencrypt/live/mydomain.com/privkey.pem (秘密鍵)

証明書を有効にする

/etc/httpd/conf.d/ssl.conf ファイルの以下3箇所を書き換えて&コメントを外すなどして有効にします:
  • SSLCertificateFile /etc/letsencrypt/live/mydomain.com/cert.pem
  • SSLCertificateKeyFile /etc/letsencrypt/live/mydomain.com/privkey.pem
  • SSLCertificateChainFile /etc/letsencrypt/live/mydomain.com/chain.pem
  • (fullchain.pem は使わない)

また、Basic 認証をかける場合はこのタイミングで元に戻して(有効にして)おきます。

HTTP サーバーを再起動
# systemctl restart httpd


以上です。僕が以前にウェブサーバーやそのアプリケーションを大量に作る仕事していた頃は無料で証明書作ってもらえるサービスはなくて、いわゆる「オレオレ証明書」に頼っていたのでした。改めていい時代になりました。


IBM Cloud から提供されている IBM CloudantApache CouchDB をベースとしたマネージドな NoSQL データベースのサービスです:
2018090300


ベース製品が同じなので、例えば REST API レベルでは互換性があります。注意が必要な点として自分が気づいた限りでは IBM Cloudant は標準で Apache Lucene ベースの検索機能が有効になっており、インデックスとなる Design Document を用意することでテキスト検索が可能になる、ということが挙げられますが、それ以外に大きな差はありません。 一方で IBM Cloud から提供されているライトアカウント(無料プラン)でも IBM Cloudant を利用することができるので、わざわざ Apache CouchDB を用意しなくても気軽に使うことができる DBaaS としてとても手軽で便利だと思っています:
2018090301


さて、自分は業務のプログラミングでは主に Node.js を使うのですが、Node.js のパッケージライブラリには IBM Cloudant 用のものと、Apache CouchDB 用のもの、両方が存在しています:
(IBM Cloudant)
2018090302

(Apache CouchDB)
2018090303


仮に対象となるデータベースが IBM Cloudant であれば前者の方が簡単に使えるという印象を持っています。ただし IBM Cloudant 用ライブラリは IBM Cloud 上の IBM Cloudant を想定していることもあり、例えばオンプレミス上の Apache CouchDB に対して使えるものではありません。

一方、Apache CouchDB 用ライブラリはローカルや社内サーバー、クラウド上にある Apache CouchDB データベース全般に対して利用することが可能です。この対象はクラウド上の IBM Cloudant であっても構いません。要するにこちらのライブラリを使えば Apache CouchDB だけでなく IBM Cloudant にも接続できる、ということです。


実際にこういった需要がどれだけあるのかわからないのですが、例えばあるシステムを作る際に、そのデータストアとして、
(1) 試しに動かす場合は IBM Cloud 上の IBM Cloudant を使って気軽に開発/テストを行い、
(2) 本番運用ではオンプレミスな Apache CouchDB を利用する(IBM Cloudant の独自機能は使わない想定)

といったことが接続先の切り替えだけでできると便利です。ただこれを実現するためには IBM Cloudant 用の便利なライブラリを使って開発しまうと (2) の本番の時に問題が起こってしまいます。以下では IBM Cloudant に対しても Apache CouchDB 用ライブラリ(以下 node-couchdb)を使ってアクセスするように実装してみたコードを紹介します。ベースが同じ製品なのでできることはできるんですが、そのための手順と注意点を含めて紹介します。


【準備】
まず Node.js のコードを記述する前に上述の node-couchdb を npm install しておきます:
$ npm install node-couchdb


【データベース接続】
node-couchdb を使って IBM Cloudant に接続します。こんな感じのコードを記述します:
var dblib = require( 'node-couchdb' );

var option = {
  auth: {
    user: 'username',
    pass: 'password'
  },
  'host': 'username.cloudant.com',
  'protocol': 'https',
  'port': 443
};
var db = dblib( option );

usernamepassword の部分にはそれぞれ IBM Cloudant の username と password を指定します(localhost の Apache CouchDB に接続する場合は option = {} で接続できます)。これで IBM Cloudant との接続ができました。ここで取得した db を使って、以下の主要な操作を行うことができます。


【主要な操作】

ドキュメント追加
insert() メソッドにデータベース名(以下の例では 'testdb')を指定して、ドキュメントを追加します。取得前に db.uniqid() でユニーク ID を取得し、_id に設定している点に注意してください:
var doc = { name: 'dotnsf', height: 170.0 };  //. 追加するドキュメント
db.uniqid().then( function( id ){
  doc._id = id[0];
  db.insert( 'testdb', doc ).then( function( body, headers, status ){
    console.log( body );
  }).catch( function( err ){
    console.log( err );
  });
});

ドキュメント読み取り
同様に get() メソッドにデータベース名と id を指定してドキュメントを取得します:
db.get( 'testdb', id ).then( function( doc, headers, status ){
  console.log( doc );
}).catch( function( err ){
  console.log( err );
});

ドキュメント削除
del() メソッドにデータベース名と id と rev を指定して、データベースからドキュメントを削除します。以下の例では一度 get() メソッドを実行して id 値から rev 値を取り出してから del() を実行しています :
db.get( 'testdb', id ).then( function( doc, headers, status ){
  db.del( 'testdb', doc.data._rev ).then( function( data, headers, status ){
    console.log( data );
  }).catch( function( err ){
    console.log( err );
  });
}).catch( function( err ){
  console.log( err );
});

ビューを指定してドキュメント一覧取得
あらかじめ作成したビューを指定して、そのビューに含まれるドキュメントの一覧を取得します。以下の例ではデザイン名 : library, ビュー名 : byname というビューを指定して文書一覧を取得しています :
db.get( 'testdb', '_design/library/_view/byname', {} ).then( function( data, headers, status ){
  if( data && data.data ){
    var docs = data.data.rows;
    console.log( docs );
  }
}).catch( function( err ){
  console.log( err );
});


IBM Cloudant の npm だと最初にデータベース名を指定してそのデータベースのオブジェクトを取得した上で各種操作を行う、、という流れなんですが、Apache CouchDB 版だと毎回データベース名と一緒に各種操作を行う、、という点が大きな違いだと思いました。ただその辺りさえ理解していればまあ大丈夫かな。。


 

Apache HTTP サーバーのリダイレクト機能を使って、ウェブアプリケーションに○ニータイマー的な機能を実装してみました。

※最近は「○ニータイマー」と言われても知らないエンジニアも増えてるんだろうなあ。。
(注 ○ニータイマーとは? 参考 - Wikipedia



【やりたいこと】
普通に動いているアプリケーションに対して、ある日時以降は自動的にエラーページへ転送する


【作るもの】
既存のウェブアプリケーション(/xonytimer/ 以下)に対して、以下のようなファイル構成を追加します:
|- xonytimer/ (目的のアプリケーション)
|   |- index.html
|   |-    :
|
|- error.html (エラーページ)
|- .htaccess (リダイレクト設定用)

/xonytimer/ 以下に目的のウェブアプリケーションが展開されているものとします(現在普通に動いているものとします)。で、ある特定の日時以降になったら /xonytimer/ 以下へのアクセスは全て /error.html に転送するよう .htaccess に設定します。

error.html の内容は適当にこんな感じにしました:
<html>
<head>
<title>Error</title>
</head>
<body>
<h1>○ニータイマー発動。。。</h1>
</body>
</html>

肝心の転送設定は以下のような内容にします。これを .htaccess に追加(または新規作成)します:
RewriteEngine On

RewriteCond %{TIME} >20170403033000
RewriteRule ^xonytimer/(.*) /error.html [R=302,L]
 ↑注 RewriteCond の ">" と "20170403033000" との間にスペースを入れてはいけません

↑の場合、システム時間が 2017年04月03日 03時30分01秒以降となる時間以降に /xonytimer/ 以下にアクセスがあった場合は /error.html に転送する、という内容にしています。日時はあくまでサーバー側のシステム時間なので、日本時間なのかそうでないのかはシステムに依存します。設定されているタイムゾーンに併せて指定する必要がある点に注意してください。

この設定であれば 2017/04/03 03:30:00 までに /xonytimer/ 以下にアクセスした場合は普通に使えます:
2017040301


が、上記時刻以降に /xonytimer/ 以下にアクセスすると転送設定が有効になり、/error.html に強制転送されます(/xonytimer/ 以下へはアクセスできなくなります):
2017040302


簡単なリライトルールだけで設定できました。


【こんなもん何に使うのか?】

実際に使うのは、この逆のケースが多いと思っています。例えば .htaccess に記述する内容を以下のようにします:
RewriteEngine On

RewriteCond %{TIME} <20170410000000
RewriteRule ^newapplication/(.*) /underconstruction.html [R=302,L]
  ↑RewriteCond の不等号が逆向きになっている点に注意


この指定であれば、2017/04/10 00:00:00 までの間に /newapplication/ 以下にアクセスがあった場合、そのリクエストは /underconstruction.html (準備中、みたいなページを想定)に転送され、2017/04/10 00:00:00 を過ぎると /newapplication/ 以下はアクセス可能になります。

新しいアプリケーションや(申し込みサイトなど)期日を決めた一時アプリケーションを運用する場合に、URL だけは事前にお知らせてしておくとして、実際の運用は指定期日になったらアクセスを許すがそれまでは URL を知っていてもアクセスさせない、という場合に便利な転送設定となります。


Java で(Web)アプリケーションから REST API を実行する時など、HTTP のクライアント機能を java.net.* から作るのは面倒です。現実的にはなんらかのライブラリを使うことになると思います。

そんな場合によく使われるのが Apache HTTP Client だと思ってます。2017/Mar/16 現在の最新バージョンは 4.5.3 でした。モジュールはこちらからダウンロードできます:
https://hc.apache.org/downloads.cgi

2017031501


上記サイトの HttpClient カテゴリから 4.5.3.zip と書かれたリンクをクリックすると 4.5.3 のバイナリが zip アーカイブとして取得できます(ファイル名は httpcomponents-client-4.5.3-bin.zip)。ダウンロードした zip ファイルを展開し、lib フォルダから jar ファイル群を取り出します(今回のサンプルで最低限必要なのは以下の6ファイルです):
  • commons-codec-1.4.jar
  • commons-logging-1.2.jar
  • httpclient-4.5.3.jar
  • httpclient-cache-4.5.3.jar
  • httpcore-4.4.6.jar
  • httpmime-4.5.3.jar

Eclipse 等で Java のウェブアプリケーションプロジェクトを作成し、lib フォルダ(Webcontent/WEB-INF/lib など)に上記作業で取り出した jar ファイル群をまとめてコピーしておきます。これで準備完了:
2017031502


では実際にこれらのモジュールを使って HTTP アクセスを実現するプログラムを書いて実行してみます。今回はスタンドアロンに HTTP GET を実行する、こんなプログラムにしてみます:
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClient1 {
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    String url = "https://www.ibm.com/developerworks/jp/"; //. HTTP GET する URL

    try{
      CloseableHttpClient client = HttpClients.createDefault();
      HttpGet get = new HttpGet( url );
      CloseableHttpResponse response = client.execute( get );
      int sc = response.getStatusLine().getStatusCode(); //. 200 の想定
      HttpEntity entity = response.getEntity();
      String html = EntityUtils.toString( entity, "UTF-8" );
      System.out.println( html ); //. 取得結果をコンソールへ
      client.close();
    }catch( Exception e ){
      e.printStackTrace();
    }
  }
}

指定した URL(上記の場合は https://www.ibm.com/developerworks/jp/")に HTTP でアクセスして、GET した結果をコンソールに出力する、というものです。この内容を記述したファイル(HttpClient1.java)を Eclipse から実行します:
2017031503


で、指定した URL  の HTML が取得できることを確認します。HTTP GET は呼び出すだけなのでシンプルですね:
2017031504


アクセス先として HTML のようなテキストではなく、画像のようなバイナリデータの場合は以下のように byte 配列として結果を取得します(HTTP リクエストヘッダを設定する例も加えています):
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClient2 {
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    String url = "https://dw1.s81c.com/developerworks/i/f-ii-ibmbluemix.png"; //. HTTP GET する URL

    try{
      CloseableHttpClient client = HttpClients.createDefault();
      HttpGet get = new HttpGet( url );
      get.addHeader( "User-Agent", "MyBot/1.0" );  //. HTTP リクエストヘッダの設定
      CloseableHttpResponse response = client.execute( get );
      int sc = response.getStatusLine().getStatusCode(); //. 200 の想定
      HttpEntity entity = response.getEntity();
      byte[] img = EntityUtils.toByteArray( entity );
      System.out.println( "" + img.length ); //. 取得結果をコンソールへ
      client.close();
    }catch( Exception e ){
      e.printStackTrace();
    }
  }
}

一方、HTTP POST の場合も同様ですが、GET の時との違いとしてポストデータを送信する必要もあります。以下はテキスト情報とファイルのアップロードを同時に(Multipart で)送信する場合の例です:
import java.io.File;
import java.io.FileInputStream;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClient3 {
  public static void main(String[] args) {
    // TODO Auto-generated method stub
    String url = "https://xxx.com/posturl"; //. HTTP POST する URL

    try{
      CloseableHttpClient client = HttpClients.createDefault();
      HttpPost post = new HttpPost( url );

//. 文字情報2つとファイル1つをポスト MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addTextBody( "name", "K.Kimura", ContentType.TEXT_PLAIN ); builder.addTextBody( "email", "dotnsf@jp.ibm.com", ContentType.TEXT_PLAIN ); File f = new File( "./logo.png" ); builder.addBinaryBody( "image_file", new FileInputStream( f ), ContentType.APPLICATION_OCTET_STREAM, f.getName() ); HttpEntity multipart = builder.build(); post.setEntity( multipart ); CloseableHttpResponse response = client.execute( post ); int sc = response.getStatusLine().getStatusCode(); //. 200 の想定 HttpEntity entity = response.getEntity(); String html = EntityUtils.toString( entity, "UTF-8" ); System.out.println( html ); //. 取得結果をコンソールへ client.close(); }catch( Exception e ){ e.printStackTrace(); } } }

PUT や DELETE の場合も同様に。

この記事の続きです:


IBM LinuxONE コミュニティクラウド上に作った仮想サーバーにいわゆる "LAMP"(=Linux + Apache HTTPD + MySQL + PHP) 環境を構築してみます。まずは上記記事を参考に仮想サーバーを作り、SSH でリモートログインします:
2017010403


ミドルウェアの導入作業を伴うため、ルート権限を持ったユーザーにスイッチしておきます:
$ sudo /bin/bash
#


LAMP 環境に必要なミドルウェアや言語環境をまとめて導入します(赤字はコメント):
# yum install httpd -y (Apache HTTP サーバー)
# yum install mysql-server mysql -y (MySQL)
# yum install php php-mbstring php-mysql php-gd php-pear php-xml php-devel -y (PHP)

また以下は LAMP 環境構築においては必須ではありませんが、使うことも多いというか、あると便利だと思うので必要に応じて導入しておいてください:
# yum install screen -y (screen)
# yum install git -y (git)
# yum install java-1.8.0-ibm-devel -y (JDK 1.8)

ミドルウェアを起動する前にファイアウォールの設定を行います。デフォルトの LinuxONE では iptables によるファイアウォールが有効になっており、このままでは http(s) によるアクセスができません。今回の環境では iptables を無効にしておきましょう:
# /etc/init.d/iptables stop
# chkconfig iptables off

あらためて各ミドルウェアを起動し、また自動起動設定をしておきます:
# /etc/init.d/httpd start
# /etc/init.d/httpd mysqld
# chkconfig httpd on
# chkconfig mysqld on
# exit
$

この時点で Apache HTTP サーバーが動いています。iptables の解除が成功していれば http://(IPアドレス)/ にアクセスすることができるようになっているはずです:
2017010601


さて、MySQL に関しては root のパスワードを設定しておきましょう。この例では P@ssw0rd というパスワードにしていますが、ここは必要に応じて変えてください:
$ mysql -u root

mysql> set password for root@localhost=PASSWORD('P@ssw0rd');
mysql> exit

これで LinuxONE 上でも LAMP の環境が作れました! ちなみに PHP のバージョンは 5.3.3 が導入できます:
$ php -v
PHP 5.3.3 (cli) (built: Dec 15 2015 04:50:47)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies

このページのトップヘ