RDSでMySQLを作ってみる

RDSというものを知りました。

ponsuke-tarou.hatenablog.com

パソコンから直接使えるRDSを作成します。

VPC内のEC2インスタンスではなく、VPC外部からインターネットを経由して接続できるようにします。
一番簡単そうな以下の方法にチャレンジします(今回はお勉強用なので選びましたがお仕事用にはセキュリティが緩いです)。

Publicly Accessible オプションを有効化して接続する
https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2018/08/f3b4c300a002e4a591d2e220db0e6890.png
RDSをパブリックサブネットに配置し、Publicly Accessibleを有効にする方法です。 パブリックサブネットとは、インターネットゲートウェイへのルーティングが可能なサブネットです。 Publicly Accessibleを有効にすると、RDSのエンドポイントがパブリックIPアドレスに解決されます。 セキュリティグループでクライアントの拠点のIPアドレスだけ許可すれば、拠点からのみ接続できます。

本番データを扱う場合などは、SSLを利用した暗号化を検討します。
手元の作業端末からAmazon RDSに接続する方法 | DevelopersIO

RDSでMySQLを作成してみます。

  1. AWS マネジメントコンソールで[RDS]を選択して、RDSの画面を表示します。
  2. [データベースの作成]ボタンで[データベースの作成]画面を表示します。
  3. 以下を設定して[データベースの作成]ボタン
    1. データベース作成方法を選択 : 標準作成
    2. エンジンのオプション
      1. エンジンのタイプ : MySQL
      2. エディション : MySQL Community
      3. バージョン : MySQL 8.0.28(お好みのバージョンを選択でOK)
    3. テンプレート : 無料利用枠(お勉強用なので小さいサイズにしました)
    4. 設定
      1. DB インスタンス識別子 : mysql-80(お好みの名前でOK)
      2. マスターユーザー名 : admin(お好みの名前でOK)
      3. パスワードの自動生成 : ON
    5. 接続
      1. パブリックアクセス : あり
        • この設定によりVPC外部から接続できるようになります。設定は自己責任でしてください。
    6. データベース認証 : パスワードと IAM データベース認証
    7. 追加設定
      1. 最初のデータベース名 : myDatabase(好きな名前でOK、指定なしにして作成しなくてもOK)
      2. ログのエクスポート : 全てにチェックON
      3. 削除保護の有効化 : ON
    8. 上記以外の設定値は規定値のまま
  4. 画面上部に表示される[認証情報の詳細を表示]ボタンでパスワードを確認してどっかに記録しておきます。
    • このパスワードを表示できるのはこのときだけです。

セキュリティグループに自分のパブリックIPアドレスを設定します。

  1. アクセス情報【使用中のIPアドレス確認】で自分のパブリックIPアドレスを確認します。
    • パブリックIPアドレスは固定でないことが多いので、定期的にパブリックIPアドレスを確認して設定し直します。
  2. データベースの一覧にある作成したRDSのDB識別子リンクで詳細画面を表示します。
  3. [接続とセキュリティ]にある[VPC セキュリティグループ]のリンクでセキュリティグループの画面を表示します。
  4. [インバウンド]タブの[編集]ボタンでダイアログを表示します。
  5. [ルールの追加]ボタンで行を追加して以下を設定します。
  6. [保存]ボタンで保存してダイアログを閉じます。

RDSへmysqlコマンドで接続してみます。

(停止していたら)RDSを起動します。
  1. データベースの一覧で作成したRDSを選択します。
  2. [アクション] > [開始]で起動します(少々時間がかかります)。
  3. 一覧の[ステータス]が「利用可能」になったら起動しています。
エンドポイントを確認します。
  1. データベース一覧から作成したDB 識別子のリンクから詳細画面を開きます。
  2. [接続とセキュリティ]タブ > [エンドポイント]に表示されている「{DB識別子}.xxxxx.{リージョン}.rds.amazonaws.com」がエンドポイントで接続情報となります。
MySQLにログインします。
mysqlのオプション 意味
-h MySQL サーバーに接続するホスト
-P 接続に使用する TCP/IP ポート番号
-p サーバーに接続する際に使用するパスワードを聞く
-u サーバーへの接続時に使用する MySQL ユーザー名
$ mysql -h mysql-80.xxx.{リージョン}.rds.amazonaws.com -P 3306 -u admin -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 21
Server version: 8.0.28 Source distribution

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| myDatabase         |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.20 sec)

mysql> 

うまく接続できない場合は、RDSの[パブリックアクセシビリティ]またはセキュリティグループの設定が誤っています。
参考 : ERROR 2003 (HY000): Can't connect to MySQL server on - Qiita

自動停止するようにLambdaを設定します。

RDSは、停止していても7日で自動起動します。
そのまま使わずに放置しているとお金がかかります。
なので自動停止するようにします。

自動停止時間をAutoStopタグに設定します。

  1. データベース一覧から作成したDB 識別子のリンクから詳細画面を開きます。
  2. [タグ]タブ > [追加]ボタンで[タグの追加] ウィンドウを表示します。
  3. [タグキー]に「AutoStop」と[値]に「自動停止したい時間」を入力して[追加]ボタンでタグを追加します。

自動停止するLambdaを設定します。

以下のページに従ってLambdaを設定します。
ponsuke-tarou.hatenablog.com


秋葉原にある柳森神社内の猫神

AWSのEC2インスタンスを祝日を除いた平日に自動起動するLambdaを作る記録

EC2インスタンスを決まった時間に自動起動したいです。

起動しっぱなしでいいインスタンスですが、たまにうっかり誰かが停止しちゃったりします。
なので、始業時間になって停止していたら自動で起動してほしいです。

AutoStartタグに設定した時間になったら起動したいです。

起動したい時間は、時と共に変わるかもしれませんし、インスタンスによっても変わります。
なので、インスタンスに「AutoStart」というタグとその値に起動したい時間を設定します。

祝日は自動起動しないでほしいです。

平日(月~金曜日)にLambdaの実行を設定しても、祝日は意識してもらえません。
祝日は、使わないので節約のためにも起動しなくていいんです。

先人の知恵をパクッて使います。

xp-cloud.jp

S3にバケットを作ります。

祝日判定をするために使用するGoogleカレンダーの祝日リストを入れるためのバケットです。

祝日判定を行うにあたって、祝日のリストが必要になります。
今回はGoogleカレンダーで取得できる祝日リストを利用したいと思います。
下記URLから自由にダウンロードでき、現在から前後1年間を含む3年間分の祝日データが取得可能となっております。
https://www.google.com/calendar/ical/ja.japanese%23holiday%40group.v.calendar.google.com/public/basic.ics
Lambdaで祝日判定 | AWSやシステム・アプリ開発の最新情報|クロスパワーブログ

S3にバケットを作ります。

  1. AWSのコンソールにある[S3] > [バケットを作成する]ボタンで画面を開きます。
    • f:id:ponsuke_tarou:20200214101427p:plain
  2. 入力欄を入力して[作成]ボタンで作成する。
    • バケット名 : google-holiday-list(任意の文字列)
    • リージョン : Lambdaを作る予定のリージョンと同じリージョン
    • f:id:ponsuke_tarou:20200214102846p:plain

取得実リストを取得するLambda関数を作成します。

  1. AWSのコンソールにある[Lambda] > [関数の作成]ボタンで画面を開きます。
  2. 必要な項目を入力後に[関数の作成]ボタンで関数を作成します。
    • オプション : [一から作成]
    • 関数名 : get_google_holiday_list(任意の関数名でOK)
    • ランタイム : Python3.8
    • 実行ロール : 既存のロールを使用する
      • 既存のロール : [lambda_basic_execution]を選択します。
  3. [関数の作成]ボタンで関数を作成します。

Lambda関数を実行するトリガーを作成します。

  1. [Designer]にある[トリガーを追加]ボタンでトリガーの設定画面を開きます。
  2. プルダウンから[CloudWatch Events]を選択します。
  3. 各入力欄を記載します。
    1. ルール : [新規ルールの作成]
    2. ルール名(必須) : get_google_holiday_list(任意の関数名でOK)
    3. ルールタイプ : [スケジュール式]
    4. スケジュール式 : cron(0 8 1 * ? *)(毎月1日の午前 8:00)
  4. [追加]ボタンでトリガーを追加する

関数を実装します。

import boto3
import urllib.request
import re
import os

s3 = boto3.resource('s3')
# Googleカレンダーの祝日リストを入れるためのバケット
bucket = s3.Bucket('google-holiday-list')

def write_holiday_list(list, file):
    for num in range(len(list)):
        pattern = r"DTSTART;VALUE=DATE:"
        # DTSTART;VALUE=DATE:yyyyMMddの行の正規表現
        repattern = re.compile(pattern)
        target_line = list[num-1].decode('utf-8')
        match = repattern.search(target_line)
        if match is None:
            pass
        else:
            print('出力対象行:' + target_line)
            # 出力対象行(\r\n含む)の後ろから10文字目から8文字を出力する
            file.write(target_line[-10:-2] + '\n')
    return 0

def lambda_handler(event, context):
    # 祝日リストの取得元URL
    url = 'https://www.google.com/calendar/ical/ja.japanese%23holiday%40group.v.calendar.google.com/public/basic.ics'
    try:
        response = urllib.request.urlopen(url)
        list = response.readlines()
    except Exception as e:
        print('祝日リストの取得に失敗しました。')
        return 1

    # 書き込みでファイルを開く
    f = open('/tmp/holiday.txt','w')
    write_holiday_list(list, f)
    f.close()

    data = open('/tmp/holiday.txt', 'rb')
    result = bucket.put_object(Key='holiday.txt', Body=data)
    data.close()
    os.remove('/tmp/holiday.txt')

EC2インスタンス自動起動するLambda関数を作成します。

  1. AWSのコンソールにある[Lambda] > [関数の作成]ボタンで画面を開きます。
  2. 必要な項目を入力後に[関数の作成]ボタンで関数を作成します。
    • オプション : [一から作成]
    • 関数名 : start_instances_by_tag_value(任意の関数名でOK)
    • ランタイム : Python3.8
    • 実行ロール : 既存のロールを使用する
      • 既存のロール : [lambda_basic_execution]を選択します。
  3. [関数の作成]ボタンで関数を作成します。

f:id:ponsuke_tarou:20200206094552p:plain

Lambda関数を実行するトリガーを作成します。

  1. [Designer]にある[トリガーを追加]ボタンでトリガーの設定画面を開きます。
    • f:id:ponsuke_tarou:20200206094718p:plain
  2. プルダウンから[CloudWatch Events]を選択します。
  3. 各入力欄を記載します。
  4. ルール : [新規ルールの作成]
  5. ルール名(必須) : start_instances_by_tag_value(任意の関数名でOK)
  6. ルールタイプ : [スケジュール式]
  7. スケジュール式 : cron(0/10 8-11 ? * MON-FRI *) (平日AM8:00-11:00で10分毎)
  8. [追加]ボタンでトリガーを追加する

f:id:ponsuke_tarou:20200206101020p:plain

関数を実装します。

# -*- coding: utf-8 -*-

from __future__ import print_function

import sys
import json
from datetime import datetime, timedelta, timezone, date
import boto3
import os

DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
JST = timezone(timedelta(hours=+9))
REGION_NAME = 'ap-northeast-1'
TAG_NAME = 'AutoStart'
TMP_HOLIDAY_FILE = '/tmp/holiday.txt'
# CALENDAR_URL = "https://calendar.google.com/calendar/ical/ja.japanese%23holiday%40group.v.calendar.google.com/public/basic.ics"
# NTP_URL = "http://ntp-b1.nict.go.jp/cgi-bin/json"

print("Loading function")
s3 = boto3.resource('s3')
ec2 = boto3.client('ec2', REGION_NAME)

def get_holiday_list():
    """
    S3バケットから祝日リストを取得する
    """
    # Googleカレンダーの祝日リストを入れてあるバケット
    bucket = s3.Bucket('google-holiday-list')
    holiday_obj = bucket.Object('holiday.txt')

    with open(TMP_HOLIDAY_FILE, 'wb') as data:
        holiday_obj.download_fileobj(data)

    f = open(TMP_HOLIDAY_FILE)
    holiday_list = f.readlines()
    f.close()
    os.remove(TMP_HOLIDAY_FILE)

    return holiday_list

def is_holiday():
    """
    土日祝日判定処理
    """
    is_holiday = False
    day = date.today()
    today = day.strftime('%Y%m%d')
    week = day.weekday()

    holiday_list = get_holiday_list()

    check = today + '\n' in holiday_list

    if check == True:
        print('今日は祝日です。')
        is_holiday = True
    elif week == 5 or week == 6:
        print('今日は週末です。')
        is_holiday = True
    elif check == False:
        print('今日は平日です。')
    else:
        print('土日祝日判定に失敗しました')

    return is_holiday

def get_tag(instance):
    """
    TAG_NAMEのタグを配列で取得する.
    """
    tag_list = instance['Tags']
    tag = next(iter(filter(lambda tag: tag['Key'] == TAG_NAME and (tag['Value'] is not None and tag['Value'] != ''), tag_list)), None)
    return tag

def get_tag_value_time(tag):
    """
    タグに設定された時間を取得する.
    """
    val = tag["Value"]
    if val == '':
        print('タグに時間が指定されていません。' + tag)
        return ''

    time = val.split(':')
    now = datetime.now(JST)
    tag_time = datetime(now.year, now.month, now.day, int(time[0]), int(time[1]), 0, 0, JST)
    return tag_time

def is_start_instance(event, tag_time):
    utc_event_time = datetime.strptime(event["time"], DATETIME_FORMAT)
    print('実行時間(UTC)は、' + utc_event_time.strftime(DATETIME_FORMAT))
    jst_event_time = utc_event_time.astimezone(JST)
    print('実行時間(JST)は、' + jst_event_time.strftime(DATETIME_FORMAT))

    # AutoStopタグに指定された時刻の前後5分以内であればインスタンス起動する
    start_time_from = tag_time + timedelta(minutes=-5)
    start_time_to = tag_time + timedelta(minutes=5)
    print('実行時間(' + jst_event_time.strftime(DATETIME_FORMAT) + ')が、' + \
          start_time_from.strftime(DATETIME_FORMAT) + 'から' + start_time_to.strftime(DATETIME_FORMAT) + 'だったら起動します。')

    return (start_time_from <= jst_event_time) and (jst_event_time <= start_time_to)

def start_instance(event, instance):
    """
    インスタンス起動処理
    """
    auto_start_tag = get_tag(instance)
    tag_time = get_tag_value_time(auto_start_tag)
    if tag_time == '':
        return

    print(instance['InstanceId'] + 'のタグに指定された時間(JST)は、' + tag_time.strftime(DATETIME_FORMAT))

    if is_start_instance(event, tag_time):
        ec2.start_instances(InstanceIds=[instance['InstanceId']])
        print(instance['InstanceId'] + 'を起動しました。')

def lambda_handler(event, context):
    print("Received event: " + json.dumps(event, indent=2))
    if is_holiday():
        # 土日祝日なら処理終了
        print('土日祝日は起動しません。')
        return

    # (停止している)かつ([AutoStart]タグのついている)インスタンス情報を取得する
    instances = ec2.describe_instances(
        Filters=[
            {'Name': 'instance-state-name', 'Values': ['stopped']},
            {'Name': 'tag-key', 'Values': [TAG_NAME]}
        ]
    )['Reservations'][0]['Instances']

    if len(instances) > 0:
        # インスタンスを順番に処理していく
        for instance in instances:
            start_instance(event, instance)
使った関数の情報

失敗したこと

'ec2.ServiceResource' object has no attribute 'describe_instances'

  • 原因 : 使うオブジェクトを間違ってしまった。
    • describe_instances()は、boto3.client('ec2')で取得したオブジェクトに含まれる関数なのに誤ってboto3.resource('ec2')で取得したオブジェクトで実行してしまった。
    • 人のコードをコピペして使っているとこういうことが起こります。
    ec2 = boto3.resource('ec2') #<<<< boto3.client('ec2')が正解

    instances = ec2.describe_instances()
  • 対応 : boto3.client('ec2')で取得したオブジェクトを使う

can't compare offset-naive and offset-aware datetimes

def get_tag_value_time(tag):
    ...省略...
    # 1. タイムゾーンを指定していなかった
    tag_time = datetime(now.year, now.month, now.day, int(time[0]), int(time[1]))
    print('タグに指定された時間(JST)は、' + tag_time.strftime(DATETIME_FORMAT))
    return tag_time

def is_start_instance(event, tag_time):
    ...省略...
    # 2. 実行時間はタイムゾーンをJSTにした
    jst_event_time = utc_event_time.astimezone(timezone(timedelta(hours=+9)))
    print('実行時間(JST)は、' + jst_event_time.strftime(DATETIME_FORMAT))

    # AutoStopタグに指定された時刻の前後5分以内であればインスタンス起動する
    # 3. タイムゾーンはNoneになっていた
    start_time_from = tag_time + timedelta(minutes=-5)
    start_time_to = tag_time + timedelta(minutes=5)
    print('処理時間(' + jst_event_time.strftime(DATETIME_FORMAT) + ')が、' + \
          start_time_from.strftime(DATETIME_FORMAT) + 'から' + start_time_to.strftime(DATETIME_FORMAT) + 'だったら起動します。')

    # 4. タイムゾーンがJSTとNoneのdatetimeオブジェクトを比較してエラーになった
    return (start_time_from <= jst_event_time) and (jst_event_time <= start_time_to)

def start_instance(event, instance):
    ...省略...
    tag_time = get_tag_value_time(auto_start_tag)

    if is_start_instance(event, tag_time):
    ...省略...
  • 調べた方法 : 各datetimeオブジェクトについてutcoffset()でタイムゾーンを確認した
    print(tag_time.utcoffset())         # None
    print(jst_event_time.utcoffset())   # 9:00:00
    print(start_time_from.utcoffset())  # None
    print(start_time_to.utcoffset())    # None
  • 対応 : タグから取得した時間でdatetimeオブジェクトを作るときにタイムゾーンを指定する
JST = timezone(timedelta(hours=+9))
...省略...
def get_tag_value_time(tag):
    ...省略...
    tag_time = datetime(now.year, now.month, now.day, int(time[0]), int(time[1]), 0, 0, JST)
    print('タグに指定された時間(JST)は、' + tag_time.strftime(DATETIME_FORMAT))
    return tag_time

Unable to import module 'lambda_function': No module named 'urllib2'

  • 原因 : Python 3から urllib2 モジュールがなくなったから
import urllib2
...省略...
        response = urllib2.urlopen(url)

注釈 urllib2 モジュールは、Python 3 で urllib.request, urllib.error に分割されました。 2to3 ツールが自動的にソースコードのimportを修正します。
20.6. urllib2 --- URL を開くための拡張可能なライブラリ — Python 2.7.17 ドキュメント

import urllib.request
...省略...
        response = urllib.request.urlopen(url)

cannot use a string pattern on a bytes-like object

  • 原因 : byte型とstr型を比較するから
    • type関数で型を調べるとbyte型とstr型でした。
        pattern = r"DTSTART;VALUE=DATE:"
        # DTSTART;VALUE=DATE:yyyyMMddの行の正規表現
        repattern = re.compile(pattern)
        print(type(pattern)) # >>>>>>>>>>>>>>>>>>> <class 'str'>
        print(type(list[num-1])) #>>>>>>>>>>>>>>>>> <class 'bytes'>
        print(list[num-1]) #>>>>>>>>>>>>>>>>>>>>> b'END:VCALENDAR\r\n'
        match = repattern.search(list[num-1])
        pattern = r"DTSTART;VALUE=DATE:"
        # DTSTART;VALUE=DATE:yyyyMMddの行の正規表現
        repattern = re.compile(pattern)
        match = repattern.search(list[num-1].decode('utf-8')) #<<< 変換してから比較する

GMTとUTCとJSTとUNIX時間とPythonの狭間を泳ぐ

  • 環境

時間の種類が覚えられません。

GMTUTCJST・・・どれがなにか覚えられないので記録しておきます。

時間の種類 概要 現在時間 UTCへ変換 JSTへ変換 UNIX時間
へ変換
GMT グリニッジ天文台
での時間
- - - -
UTC GMTをちょっぴり
調整した時間
datetime.
datetime.now
(timezone.utc)
- utc_time.
astimezone
(timezone(timedelta
(hours=+9)))
utc_time.
timestamp()
JST 本の時間 datetime.
datetime.now()
jst_time.astimezone
(timezone.utc)
- jst_time.
timestamp()
UNIX
時間
コンピュータ用の時間 int(time.time()) datetime.fromtimestamp
(unix_time, timezone.utc)
datetime.
fromtimestamp
(unix_time)
-

GMTは、ロンドン郊外にあるグリニッジ天文台での時間です。

https://upload.wikimedia.org/wikipedia/commons/thumb/8/8a/Royal_observatory_greenwich.jpg/240px-Royal_observatory_greenwich.jpg
グリニッジ天文台 - Wikipedia

UTCは、GMTをちょっぴり調整した世界の標準時間です。

  • 日本語 : 協定世界時
  • 英語 : Universal Time, Coordinated(調整された)

地球の自転速度の変動でちょっとずつ時間がずれるのでうるう秒で調整しています。

citizen.jp

Pythonで現在時間のUTCを取得してみました。

>>> from datetime import datetime, timezone, timedelta
# datetime.utcnow()よりdatetime.now(timezone.utc)を使うようにしたほうが良いそうです。
>>> utc_time = datetime.utcnow()
>>> print(utc_time)
2020-02-13 13:01:52.177821
>>> utc_time = datetime.now(timezone.utc)
>>> print(utc_time)
2020-02-13 13:02:08.306656+00:00

classmethod datetime.utcnow()
tzinfo が None である現在の UTC の日付および時刻を返します。

このメソッドは now() と似ていますが、 naive な datetime オブジェクトとして現在の UTC 日付および時刻を返します。 aware な現在の UTC datetime は datetime.now(timezone.utc) を呼び出すことで取得できます。
警告 naive な datetime オブジェクトは多くの datetime メソッドでローカルな時間として扱われるため、 aware な datetime を使って UTC の時刻を表すのが好ましいです。 そのため、 UTC での現在の時刻を表すオブジェクトの作成では datetime.now(timezone.utc) を呼び出す方法が推奨されます。
datetime --- 基本的な日付型および時間型 — Python 3.8.2rc1 ドキュメント

JSTは、UTCより9時間進んでる日本での時間です。

Pythonで現在時間のJSTを取得してみました。

>>> from datetime import datetime, timezone, timedelta
>>> jst_time = datetime.now()
>>> print(jst_time)
2020-02-13 22:01:39.673451

classmethod datetime.now(tz=None)
現在のローカルな日時を返します。

オプションの引数 tz が None であるか指定されていない場合、このメソッドは today() と同様ですが、可能ならば time.time() タイムスタンプを通じて得ることができる、より高い精度で時刻を提供します (例えば、プラットフォームが C 関数 gettimeofday() をサポートする場合には可能なことがあります)。
tz が None でない場合、 tz は tzinfo のサブクラスのインスタンスでなければならず、現在の日付および時刻は tz のタイムゾーンに変換されます。
datetime --- 基本的な日付型および時間型 — Python 3.8.2rc1 ドキュメント

UNIX時間は、OSなどで使われるUTCでの1970-01-01 00:00:00からの経過秒数です。

  • 別名 : POSIX time(ポジックスタイム)
  • 日本語 : UNIX時間

Pythonで現在時間のUTCを取得してみました。

>>> import time from time
>>> unix_time = int(time())
>>> print(unix_time)
1581601932

time.time() → float
エポック からの秒数を浮動小数点数で返します。 エポックの具体的な日付とうるう秒 (leap seconds) の扱いはプラットフォーム依存です。 Windows とほとんどの Unix システムでは、エポックは (UTC で) 1970 年 1 月 1 日 0 時 0 分 0 秒で、うるう秒エポック秒の時間の勘定には入りません。 これは一般に Unix 時間 と呼ばれています。 与えられたプラットフォームでエポックが何なのかを知るには、 time.gmtime(0) の値を見てください。
time --- 時刻データへのアクセスと変換 — Python 3.8.2rc1 ドキュメント

いろんな時間があるからPythonで変換もしてみました。

UTCからJSTへ変換してみました。

>>> from datetime import datetime, timezone, timedelta
>>> utc_time = datetime.now(timezone.utc)
# UTCからJSTへ変換してみました。
>>> jst_time = utc_time.astimezone(timezone(timedelta(hours=+9)))
>>> print(utc_time)
2020-02-13 14:05:37.511844+00:00
>>> print(jst_time)
2020-02-13 23:05:37.511844+09:00

timedelta オブジェクト
timedelta オブジェクトは経過時間、すなわち二つの日付や時刻間の差を表します。

class datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
全ての引数がオプションで、デフォルト値は 0 です。 引数は整数、浮動小数点数でもよく、正でも負でもかまいません。
datetime --- 基本的な日付型および時間型 — Python 3.8.2rc1 ドキュメント

UTCからUNIX時間へ変換してみました。

>>> from datetime import datetime, timezone
>>> utc_time = datetime.now(timezone.utc)
# UTCからUNIX時間へ変換してみました。
>>> unix_time = utc_time.timestamp()
>>> print(utc_time)
2020-02-13 22:10:53.173734+00:00
>>> print(unix_time)
1581631853.173734

datetime.timestamp()
datetime インスタンスに対応する POSIX タイムスタンプを返します。 返り値は time.time() で返される値に近い float です。

このメソッドでは naive な datetime インスタンスはローカル時刻とし、プラットフォームの C 関数 mktime() に頼って変換を行います。 datetime は多くのプラットフォームの mktime() より広い範囲の値をサポートしているので、遥か過去の時刻や遥か未来の時刻に対し、このメソッドは OverflowError を送出するかもしれません。
datetime --- 基本的な日付型および時間型 — Python 3.8.2rc1 ドキュメント

JSTからUTCへ変換してみました。

>>> from datetime import datetime, timezone, timedelta
>>> jst_time = datetime.now()
# JSTからUTCへ変換してみました。
>>> utc_time = jst_time.astimezone(timezone.utc)
>>> print(jst_time)
2020-02-13 22:59:58.017838
>>> print(utc_time)
2020-02-13 13:59:58.017838+00:00

datetime.astimezone(tz=None)
tz を新たに tzinfo 属性 として持つ datetime オブジェクトを返します。 日付および時刻データを調整して、返り値が self と同じ UTC 時刻を持ち、 tz におけるローカルな時刻を表すようにします。

もし与えられた場合、 tz は tzinfo のサブクラスのインスタンスでなければならず、 インスタンスの utcoffset() および dst() メソッドは None を返してはなりません。もし self が naive ならば、おそらくシステムのタイムゾーンで時間を表現します。
datetime --- 基本的な日付型および時間型 — Python 3.8.2rc1 ドキュメント

JSTからUNIX時間へ変換してみました。

>>> from datetime import datetime, timezone
>>> jst_time = datetime.now()
# JSTからUNIX時間へ変換してみました。
>>> unix_time = jst_time.timestamp()
>>> print(jst_time)
2020-02-14 07:15:27.515381
>>> print(unix_time)
1581632127.515381

UNIX時間からUTCへ変換してみました。

>>> from datetime import datetime, timezone
>>> from time import time
>>> unix_time = int(time())
# UNIX時間からUTCへ変換してみました。
>>> utc_time = datetime.fromtimestamp(unix_time, timezone.utc)
>>> print(unix_time)
1581603619
>>> print(utc_time)
2020-02-13 14:20:19+00:00

class datetime.timezone(offset, name=None)
ローカル時刻と UTC の差分を表す timedelta オブジェクトを offset 引数に指定しなくてはいけません。これは -timedelta(hours=24) から timedelta(hours=24) までの両端を含まない範囲に収まっていなくてはなりません。そうでない場合 ValueError が送出されます。

timezone.utc
UTC タイムゾーン timezone(timedelta(0)) です。
datetime --- 基本的な日付型および時間型 — Python 3.8.2rc1 ドキュメント

UNIX時間からJSTへ変換してみました。

>>> from datetime import datetime
>>> from time import time
>>> unix_time = int(time())
# UNIX時間からJSTへ変換してみました。
>>> jst_time = datetime.fromtimestamp(unix_time)
>>> print(unix_time)
1581603238
>>> print(jst_time)
2020-02-13 23:13:58

classmethod datetime.fromtimestamp(timestamp, tz=None)
time.time() が返すような、 POSIX タイムスタンプに対応するローカルな日付と時刻を返します。オプションの引数 tz が None であるか、指定されていない場合、タイムスタンプはプラットフォームのローカルな日付および時刻に変換され、返される datetime オブジェクトは naive なものになります。
datetime --- 基本的な日付型および時間型 — Python 3.8.2rc1 ドキュメント

f:id:ponsuke_tarou:20200214072422j:plain
神奈川県二宮の吾妻公園

基本に立ち戻ってUMLのクラス図を学ぶ

前回の勉強内容

ponsuke-tarou.hatenablog.com

勉強のきっかけになった状況

同僚と実装方針を話していてクラスの関係図をホワイトボードに書いたら、わかりにくくて正されました。
試験問題ができれば、理解できることになるわけではないですが基本を勉強します。

次のクラス図におけるクラス間の関係の説明のうち,適切なものはどれか。
https://www.sc-siken.com/kakomon/22_haru/img/22.gif

  1. "バス","トラック"などのクラスが"自動車"クラスの定義を引き継ぐことを,インスタンスという。
  2. "バス","トラック"などのクラスの共通部分を抽出し"自動車"クラスとして定義することを,汎化という。
  3. "バス","トラック"などのクラスは,"自動車"クラスに対するオブジェクトという。
  4. "バス","トラック"などのそれぞれのクラスの違いを"自動車"クラスとして定義することを,特化という。

平成22年春期問22 クラス間の関係|情報処理安全確保支援士.com

クラス図は、クラス同士の関係性を中心とした静的な構図を表します。

クラスとオブジェクトは、「わく組み」と 「その実体」の関係にあります。
全てのオブジェクトは、必ず何かのクラス定義のもとに生成され、クラスなしではオブジェクトは生成できません。UMLにおいて一般的に、オブジェクトはクラスのインスタンスであると言われるのは、クラスという一定の型に基づいて、実際のオブジェクトが生じるためです。
改訂新版 基礎UML UML 2対応 - インプレスブックス

問題領域やシステムの構造を見るために使われます。

https://image.itmedia.co.jp/im/articles/0407/16/uml_b_l02_03.gif
クラス図の落とし穴 (1/3) - ITmedia エンタープライズ

クラスは、「操作」「属性」「ロール名」で構成されます。

https://www.fe-siken.com/kakomon/19_aki/img/45.gif
平成19年秋期問45 UMLのクラス図に記述するもの|基本情報技術者試験.com

ロール名は、関連におけるそれぞれのオブジェクトの役割(ロール)を示すものです。例えば、社員から見て、会社は雇用者という役割を持っています。一方、会社から見て、社員には従業員という役割があります。
https://image.itmedia.co.jp/ait/articles/0105/24/r20_fig1.gif
クラス図の詳細化とその目的:初歩のUML(2) - @IT

クラス図は、クラスの仕様を決定する重要なモデルです。

https://www.ogis-ri.co.jp/otc/hiroba/technical/JavaWorld_UMLIntroduction/img/02.png
Javaプログラマーに贈るUML入門 | オブジェクトの広場

クラス同士の関係は、「汎化」「集約」「関連」などのクラスとクラスを結ぶ矢印的な線で表します。

UMLのクラス図が表す内容はどれか。

  1. クラス間の動的な関係
  2. クラス同士が,必ず1対1に対応するような相互関係
  3. クラスを構成するクラス名,インスタンス,メッセージの3要素
  4. 汎化,集約,関連などのクラス間の関係

平成21年秋期問43 UMLのクラス図|応用情報技術者試験.com

関連を持つクラス間のオブジェクトの数の対応関係は、多重度で表します。

一方のクラスのオブジェクトが、もう一方のクラスのいくつのオブジェクトと関係するかなど関連するクラス間の数量的な関係を示します。

https://image.itmedia.co.jp/im/articles/0301/08/fig4_06.gif
オブジェクトをUMLで表現する - ITmedia エンタープライズ

関係性を表す図法があります。

https://www.fe-siken.com/kakomon/18_haru/img/39a.gif
平成18年春期問39 クラス図|基本情報技術者試験.com

図は"顧客が商品を注文する"を表現したUMLのクラス図である。"顧客が複数の商品をまとめて注文する"を表現したクラス図はどれか。ここで,"注文明細"は一つの注文に含まれる1種類の商品に対応し,"注文ヘッダ"は複数の"注文明細"を束ねた一つの注文に対応する。
https://www.ap-siken.com/kakomon/23_toku/img/45.gif
答. https://www.ap-siken.com/kakomon/23_toku/img/45a.gif
平成23年特別問45 UML クラス図|応用情報技術者試験.com

f:id:ponsuke_tarou:20200212224008j:plain
栃木県氏家雛めぐり

次回の勉強内容

勉強中・・・

AWSのEBSボリュームにタグをつけるLambdaを作った記録

EBSボリュームは管理しないと無駄にお金がかかります。

何気なくEC2を作るとEBSボリュームが作られます(既存のボリュームを使った場合を除く)。
EC2を削除(終了)してもEBSボリュームはデフォルトで削除されません。
結果、気が付いたら使ってないEBSボリュームが残っていることがあります。
aws.amazon.com

EC2インスタンスを削除するときはEBSボリュームも削除します。

f:id:ponsuke_tarou:20200106105351p:plain
EC2を削除するときのダイアログ

EC2インスタンスを作成するときは「合わせて削除」オプションを設定することもできます。

後で設定するのは大変なようです。
qiita.com

それでもアタッチされていないEBSボリュームが残ることはあります。

複数人で長期間使っていればうっかりアタッチされていないEBSボリュームが残ることはあります。
とはいえ、本当にアタッチされていなければ削除していいのか?誰かが何かの目的で残しているのかも?
となった時に何に使われていたがわかる情報があると助かります。

やりたいこと

前提 : EC2にはNameタグをつけておきます。

EC2インスタンスには、Nameというタグをつけてインスタンスを使っているプロジェクトやサーバの情報をValueに入れておきます。
例えば、ponsukeプロジェクトのMySQLデータベースサーバにしているインスタンスなら「ponsuke-MySQL」をNameタグのValueに入れておく、みたいな。

アタッチされているEC2のNameタグと同じValueをEBSボリュームのNameタグに設定します。

例えば、「ponsuke-MySQL」というNameタグのついているEC2インスタンスにアタッチされているEBSボリュームに「ponsuke-MySQL」というNameタグをつける、みたいな。
そうすれば、EC2インスタンスを削除したあとでも「ponsuke-MySQL」で使っていたんだな、じゃ削除していいね、ってなります。

同じようなことをやっている先人の知恵を流用します。

www.simpline.co.jp
core.cohalz.co

Lambdaを作る記録

Lambdaの実行権限を作成します。

IAMのポリシーを作成します。
  1. AWSマネジメントコンソールにある[IAM] > サイドメニューの[ポリシー] > [ポリシーの作成]ボタンで作成画面を開きます。
    • f:id:ponsuke_tarou:20200109100533p:plain
      [ポリシーの作成]ボタンで作成画面を開きます。
  2. [JSON]タブを開いて下にある内容を入力します。
  3. [ポリシーの確認]ボタンで確認画面へ遷移して[名前(必須)]と[説明(任意)]を入力します。
  4. [ポリシーの作成]ボタンでポリシーを作成して一覧画面に戻ります。
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:CreateTags",
        "logs:CreateLogStream",
        "logs:CreateLogGroup",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}
IAMのロールを作成します。
  1. AWSマネジメントコンソールにある[IAM] > サイドメニューの[ロール] > [ロールの作成]ボタンで作成画面を開きます。
    • f:id:ponsuke_tarou:20200109101230p:plain
      [ロールの作成]ボタンで作成画面を開きます。
  2. [AWSサービス] > [Lambda]を選択後に[次のステップ: アクセス権限]ボタンで次の画面を開きます。
  3. [Attach アクセス権限ポリシー]の[ポリシーのフィルタ]で作成したポリシーを検索して選択後に[次のステップ: タグ]ボタンで次の画面を開きます。
  4. [タグの追加 (オプション)]は任意なので設定せずに[次のステップ: 確認]ボタンで確認画面を開きます。
  5. [ロール名]を入力して[ロールの作成]ボタンでロールを作成して一覧画面に戻ります。

Lambda関数を作成します。

  1. AWSのコンソールにある[Lambda] > [関数の作成]ボタンで画面を開きます。
    • f:id:ponsuke_tarou:20200109094021p:plain
      [関数の作成]ボタンで画面を開きます。
  2. 必要な項目を入力後に[関数の作成]ボタンで関数を作成します。
    • オプション : [一から作成]
    • 関数名 : attatch_name_tag_for_ebs_volume(任意の関数名でOK)
    • ランタイム : Python3.8
    • 実行ロール : 既存のロールを使用する
    • 既存のロール : 作成したロールを選択します。
Lambda関数を実行するトリガーを作成します。
  1. [Designer]にある[トリガーを追加]ボタンでトリガーの設定画面を開きます。
    • f:id:ponsuke_tarou:20200115095939p:plain
      [Designer]にある[トリガーを追加]ボタン
  2. プルダウンから[CloudWatch Events]を選択します。
  3. 各入力欄を記載します
    • ルール : [新規ルールの作成]
    • ルール名(必須) : attatch_name_tag_for_ebs_volume(任意の関数名でOK)
    • ルールタイプ : [スケジュール式]
    • f:id:ponsuke_tarou:20200115101713p:plain
  4. [追加]ボタンでトリガーを作成します。
    • f:id:ponsuke_tarou:20200115101819p:plain
関数の内容を実装します。
import boto3

ec2 = boto3.client('ec2')

print('Loading function')

def get_name_tag_value(tags):
    """
    タグリストからNameタグの値を取得する.
    Parameters
    ----------
    tags
        辞書形式のタグリスト
    """
    name_tag_value = ''
    for tag in tags:
        if tag['Key'] == 'Name':
            name_tag_value = tag['Value']
            break
    return name_tag_value

def get_volumes_no_name_tag_and_attached():
    """
    [アタッチされていて][Nameタグが設定されていない]EBSボリュームを取得する.
    """
    volumes = ec2.describe_volumes(
        Filters=[
            {
                'Name': 'attachment.status',
                'Values': ['attached']
            }
        ]
    )['Volumes']

    volumes_no_name_tag = []
    for volume in volumes:
        if 'Tags' not in volume:
            # タグが設定されていない場合:Nameタグのないボリュームとする
            volumes_no_name_tag.append(volume)
        else:
            # タグが設定されている場合
            name_tag_value = get_name_tag_value(volume['Tags'])
            if name_tag_value == '':
                # Nameタグの値を取得できない場合:Nameタグのないボリュームとする
                volumes_no_name_tag.append(volume)
    return volumes_no_name_tag

def get_ec2_instance_name_tag_value(volume):
    """
    EBSボリュームにアタッチされているEC2インスタンスに設定されているNameタグの値を取得する.
    Parameters
    ----------
    volume
        対象となるEBSボリューム
    """
    # アタッチされているEC2インスタンスのIDを取得する
    instance_id = volume['Attachments'][0]['InstanceId']
    # EC2インスタンスのIDからインスタンスに設定されているタグ群を取得する
    tags = ec2.describe_instances(
        Filters=[
            {
                'Name': 'instance-id',
                'Values': [instance_id]
            }
        ]
    )['Reservations'][0]['Instances'][0]['Tags']
    # EC2インスタンスについているNameタグの値を取得する
    name_tag_value = get_name_tag_value(tags)
    return name_tag_value

def set_name_tag_for_volume(volume_id, name_tag_value):
    """
    EBSボリュームにNameタグを設定する.
    Parameters
    ----------
    volume_id
        NameタグをつけるEBSボリュームID
    name_tag_value
        EC2インスタンスについているNameタグの値
    """
    response = ec2.create_tags(
        Resources=[volume_id],
        Tags=[{'Key': 'Name', 'Value': name_tag_value}]
    )
    return 0

def lambda_handler(event, context):
    volumes = get_volumes_no_name_tag_and_attached()
    for volume in volumes:
        # EBSボリュームのIDを取得する.
        volume_id = volume['VolumeId']
        print(str(volume_id) + 'にはNameタグが付いていません')
        name_tag_value = get_ec2_instance_name_tag_value(volume)
        # EC2インスタンスにNameタグが設定されている場合に処理を実行する.
        if name_tag_value != '':
            set_name_tag_for_volume(volume_id, name_tag_value)
            print(str(volume_id) + 'のNameタグに「' + name_tag_value + '」とつけました')
    return 0

失敗したこと

describe_volumes()の実行権限がIAMロールになかった

[ERROR] ClientError: An error occurred (UnauthorizedOperation) when calling the DescribeVolumes operation: You are not authorized to perform this operation.
  • 事象 : describe_volumes()を実行したらエラーになった
  • 原因 : describe_volumes()を実行する権限がないから
    • エラーの時のIAMロールの権限設定 > 「ec2:DescribeInstances」しかない
...省略...
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:CreateTags",
        "logs:CreateLogStream",
        "logs:CreateLogGroup",
        "logs:PutLogEvents"
      ],
...省略...
...省略...
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeVolumes", <<< 追加
        "ec2:DescribeInstances",
        "ec2:CreateTags",
        "logs:CreateLogStream",
        "logs:CreateLogGroup",
        "logs:PutLogEvents"
      ],
...省略...

describe_volumesの引数の型が不正だった

[ERROR] ParamValidationError: Parameter validation failed:
Invalid type for parameter Filters[0].Values, value: Name, type: <class 'str'>, valid types: <class 'list'>, <class 'tuple'>
  • 事象 : Filtersに指定したValuesに文字(str)を指定して実行したらエラーになった
    # Nameタグが設定されていないEBSボリュームを取得する
    response = ec2.describe_volumes(
        Filters=[
            {
                'Name': 'tag:Name',
                'Values': ''
            }
        ]
    )['Volumes']
  • 原因 : Valuesにlistかtupleのシーケンス型を使っていないから
    • [ドキュメントにもValues (list) と書いてあります。]
  • 対応 : Valuesをlistで指定する
            {
                'Name': 'tag:Name',
                'Values': [''] <<<<<<<<<< 修正
            }

Oracle Databaseにユーザー(スキーマ)を作った記録

  • 環境
    • 接続先 : (RDS)Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production
    • 接続元 : (EC2)Ubuntu 18.04 LTS(64ビット x86)
      • SQL*Plus: Release 12.2.0.1.0 Production

RDSでOracleを作成したのでユーザーを作成します。

ponsuke-tarou.hatenablog.com

SQL*PlusでDBにログインします。

管理者ユーザーでログインします。

$ sqlplus admin/hoge@oracle-12.fuga.us-east-2.rds.amazonaws.com:1521/PONDB

SQL*Plus: Release 12.2.0.1.0 Production on 火 310 13:57:46 2020

Copyright (c) 1982, 2016, Oracle.  All rights reserved.

最終正常ログイン時間: 火 310 2020 13:45:58 +00:00


Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production
に接続されました。
SQL>

表示幅が狭いと見ずらいので広げます。

SQL> show linesize
linesize 80
SQL> set linesize 120

ユーザーに設定する情報を確認します。

ユーザーを作成する構文

CREATE USER my_name
IDENTIFIED BY "my_password"
[DEFAULT TABLESPACE my_tablespace]
[TEMPORARY TABLESPACE my_temp_tablespace]
[PROFILE my_profile]
CREATE USER、ユーザーの作成 - オラクル・Oracleをマスターするための基本と仕組み

作りたいユーザー名が既にないことを確認します。

SQL> select distinct username from all_users where username like '%ponsuke%';

レコードが選択されませんでした。
注意 : キャメルケースでユーザー名を指定すると大文字小文字が区別されないのでダサいことになります。

キャメルケースで書くと大文字小文字が区別されず、全部大文字か全部小文字(表示するツールによるらしい)になります。
「_」などで区切るスネークケースがよいです。
ダサいのを誤って作ってしまったので削除しました。

/* 所有していたオブジェクトもろともユーザーを削除する */
SQL> drop user PONSUKETAROU cascade;

使用する表領域を選びます。

「DEFAULT TABLESPACE(ユーザー・オブジェクト を作成するときの使用するデフォルト表領域)」「TEMPORARY TABLESPACE(Oracle が使用する作業用の表領域)」で指定する値を選びます。
現在存在する表領域を検索して選びます。

SQL> select tablespace_name,block_size,initial_extent from dba_tablespaces;

TABLESPACE_NAME BLOCK_SIZE INITIAL_EXTENT
--------------- ---------- --------------
SYSTEM                8192          65536
SYSAUX                8192          65536
UNDO_T1               8192          65536
TEMP                  8192        1048576
USERS                 8192          65536
RDSADMIN              8192          65536

6行が選択されました。

ユーザーを作成します。

SQL> create user ponsuke identified by ponsuke default tablespace USERS temporary tablespace TEMP profile DEFAULT;

ユーザーが作成されました。

-- 確認します。
SQL> select username from all_users where regexp_like(username, 'ponsuke', 'i');

USERNAME
------------------------------------------------------------------------------------------------------------------------
PONSUKE

SQL>

権限を付与します。

権限を付与しないとデータベースに接続すらできません。

SQL> grant create session,create table,create view,create sequence,create trigger,create synonym,unlimited tablespace to ponsuke;

権限付与が成功しました。

作成したユーザーでログインしてみます。

$ sqlplus ponsuke/ponsuke@oracle-12.fuga.us-east-2.rds.amazonaws.com:1521/PONDB

SQL*Plus: Release 12.2.0.1.0 Production on310 14:32:13 2020

Copyright (c) 1982, 2016, Oracle.  All rights reserved.

Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production
に接続されました。
SQL>

失敗したこと

ORA-01045: user username lacks CREATE SESSION privilege

qiita.com

ORA-00988: パスワードが指定されていないか、または無効です。

qiita.com

f:id:ponsuke_tarou:20161113100841j:plain

AWSのRDSの証明書を更新した記録

5年ごとに証明書を更新する必要があります。

、RDS DB インスタンスと Aurora DB クラスターの SSL/TLS 証明書は有効期限が切れ、5 年ごとに置き換えられます。現在の証明書は、2020 年 3 月 5 日に期限切れになります。 クライアントとデータベースサーバーの両方で SSL/TLS 証明書を更新しない場合、証明書を検証して SSL/TLS により RDS DB インスタンスまたは Aurora クラスターに接続するデータベースクライアントとアプリケーションは、2020 年 3 月 5 日から接続できなくなります。
Amazon RDS のお客様: 2020 年 2 月 5 日までに SSL/TLS 証明書を更新してください | Amazon Web Services ブログ

実際にやった人の手順を参考に実施します。

ajike.github.io

今の証明書を確認します。

  1. コンソール > [RDS] > [データベース] > 対象のRDSのリンクから詳細画面を表示 > [接続とセキュリティ] > [認証機関]
    • 認証機関 : rds-ca-2015
    • 証明機関の日付 : Mar 6th, 2020

認証機関をrds-ca-2019へ更新します。

  1. 対象のRDSが停止している場合は起動します。
    • 停止していると証明書の更新ができません。
  2. 対象のRDSを選択 > [変更]ボタンで[DB インスタンスの変更]画面を表示します。
    • f:id:ponsuke_tarou:20200130095519p:plain
  3. [ネットワーク & セキュリティ]の[認証機関]を「rds-ca-2015」から「rds-ca-2019」へ変更 > [次へ]ボタンで次の画面を表示します。
  4. [変更のスケジュール]で「すぐに適用」を選択 > [DB インスタンスの変更]で変更します。
    • f:id:ponsuke_tarou:20200130100144p:plain

確認します。

  1. コンソール > [RDS] > [データベース] > 対象のRDSのリンクから詳細画面を表示 > [接続とセキュリティ] > [認証機関]
    • 認証機関 : rds-ca-2019
    • 証明機関の日付 : Aug 23rd, 2024
  2. DBクライアントツールから接続できることを確認します。
  3. 開発アプリケーションから接続できることを確認します。
  4. SQL*PLUSでも接続を確認します。
$ sqlplus {ユーザ名}/{パスワード}@hoge.fuge.ap-northeast-1.rds.amazonaws.com:1521/{サービス名}

SQL*Plus: Release 12.2.0.1.0 Production on 木 130 10:25:04 2020

Copyright (c) 1982, 2017, Oracle.  All rights reserved.

最終正常ログイン時間: 火 128 2020 10:19:05 +09:00


Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production
に接続されました。
SQL>

医療情報倫理

医療業界に関わるエンジニアへの一歩目

医療業界に関わるお仕事をするエンジニアとして「医療情報技師」という資格の「医療情報システム」という分野を勉強してみます。
まずは、情報を取り扱うものとしての基本となる医療情報倫理です。

個人情報とプライバシー

医療の世界では、氏名や住所だけではなく生体情報という他人が容易には知り得ないような情報を取り扱います。
そんな医療の世界で働く人に向けて、厚生労働省からは医療・介護関係事業者における個人情報の適切な取扱いのためのガイダンスが出ています。
医療の世界に関わる人と言ってもお医者さんから介護士・研究者といろんな立場があるので、ガイダンスには以下のようなことが記載されています。

  • 医療・介護関係事業者が行う措置の透明性の確保と対外的明確化
  • 責任体制の明確化と患者・利用者窓口の設置等
  • 遺族への診療情報の提供の取扱い
  • 個人情報が研究に活用される場合の取扱い
  • 遺伝情報を診療に活用する場合の取扱い

https://d2908q01vomqb2.cloudfront.net/b3f0c7f6bb763af1be91d9e74eabfeb199dc1f1f/2018/08/22/aws-medical-info-apn-parnter-picture-1.jpg
“医療情報システム向け「Amazon Web Services」利用リファレンス”の公開:APN パートナー各社 | Amazon Web Services ブログ

「個人情報」と「プライバシー」のびみょーーーな違い

「個人情報保護」と「プライバシー保護」は同じ意味に思えます。
しかし、医療情報技師の教科書では「個人情報」と「プライバシー」の違いを説明しています。

「プライバシーに関わる情報」とは、
個人の私生活上の事実に関する情報であり、「本人がその情報を開示しないで欲しいであろうと考えられる情報」である。
これに対し
「個人情報」は、
「特定の個人を識別できる情報」であり、私生活に関する情報か否か、開示しないで欲しいと考えられる情報か否かは問わない。
医療情報第5版医療情報システム編

医療情報システムの利用者の責任

医療に関わるシステムは、限られた人がエルタンできるようなものが多いように思います。
電子カルテや薬剤情報・検査機器を扱うシステムから研究や治験の情報を蓄積するシステムまで、誰でも彼でも閲覧できるものはではありません。
それは、先に紹介した「個人情報」「プライバシー」を取り扱うからです。

不適切な閲覧によるプライバシーの侵害をしないようにします。

システムを閲覧できる権限があるひとには「個人情報」「プライバシー」を守る義務が生じます。
そのために、利用者一人ひとりの意識が重要です。

故意及び重過失をもって、これらの要件に反する行為を行えば刑法上の秘密漏示罪で犯罪として処罰される場合があるが、診療情報等については過失による漏えいや目的外利用
も同様に大きな問題となり得る。
医療情報システムの安全管理に関するガイドライン

プライバシー権利を保証するための情報セキュリティ上の義務があります。

  • 自身の認証番号やパスワードを管理し、これを他人に利用させない。
  • 電子保存システムの情報の参照や入力に際して、認証番号やパスワードなどによってシステムに自身を認識させる。
  • 電子保存システムへの入力に際して、確定操作(入力情報が正しいことを確認する操作)を行って、入力情報に対する責任を明示する。
  • 代行入力の場合は、入力権限を持つ者が最終的に確定操作を行い、入力情報に対する責任を明示する。
  • 作業終了あるいは離席する際は、必ずログアウト操作を行う。

医療情報第5版医療情報システム編

ちょっとしたセキュリティ意識の弱さから漏洩事件が発生しています。

www.johosecuritynavi.com

医療情報システムの担当者の責務

医療システムの責任者・担当者に対する指針として厚生労働省から医療情報システムの安全管理に関するガイドラインが出ています。
医療・介護関係事業者における個人情報の適切な取扱いのためのガイダンスでは、「医療情報システムの導入及びそれに伴う情報の外部保存を行う場合の取扱い」についてはこのガイダンスに従うことが記載されています。

電子的な医療情報を扱う際の責任のあり方

医療情報システムの安全管理に関するガイドラインでは、医療情報を取り扱うには管理者が善管注意義務を果たすことが求められています。

善良な管理者の注意義務を善管注意義務といいます。

善管注意義務は、不動産などで使用される用語で民法第400条に由来するそうです。

smtrc.jp

善管注意義務を果たすためには、2つの責任を負います。

  1. 通常運用における責任 : 通常の運用時において医療情報保護の体制を構築し管理する責任
  2. 事後責任 : 医療情報について何らかの問題が生じた場合に対処をすべき責任

医療の情報化と患者の医療参画

ここ数十年で医療へのコンピュータ導入の目的は、「業務の合理化」>「診療支援」>「患者中心の視点」へと視点の主役が変わってきているそうです。
電子カルテでは、

  • 医療者 : 患者へ説明しやすく
  • 患者 : カルテへの敷居が低くなる

と、「患者中心の医療」への取り組みにコンピュータ導入が寄与することとなる。

https://medical.secom.co.jp/it/karte/column/57ac80d2175251d88b3a7908741b08a9089db126.png
電子カルテを導入するメリット・デメリット | 導入するメリット・デメリット | 電子カルテサービス | SECOM セコム医療システム株式会社

https://bijicom.co.jp/wp-content/uploads/969c125748a08215305adcaa3d5a4030.gif
電子カルテ導入のメリット・デメリット | 中央ビジコム(電子カルテ・レセコン・電子薬歴)

システム化は患者の医療参画を促していきます。

医療システムが導入されることで

  • EBM(根拠に基づく医療)*1
  • 医療者の意思決定
  • 継続的ケア
  • EBH(根拠に基づく健康)*2

を支援して、患者の医療参画を促して患者が理解・判断できようにしていきます。

医学・保健医療の研究倫理とポピュレーション・ヘルス

医療システムが導入されていくことで治療へつながる研究や症例の蓄積などへ医療情報の活用が活発になっていきます。


医療情報倫理

医療情報担当職の倫理網領

医療情報化の担い手として

AWSのRDSのバックアップをインスタンスに復元する記録

Amazon RDSはインスタンスの自動バックアップをしてくれます。

Amazon RDS は、DB インスタンスの自動バックアップを作成および保存します。Amazon RDS は、DB インスタンスのストレージボリュームのスナップショットを作成し、個々のデータベースだけではなく、その DB インスタンス全体をバックアップします。
バックアップの使用

自動化バックアップによってDB スナップショットができます。

サイドメニューに[スナップショット]と[Automated backups]の2つがあるので何が違うんだろう?と思いました。
f:id:ponsuke_tarou:20191226170146p:plain
[よくある質問]を読むと「自動化バックアップ機能によってスナップショットができる。」「スナップショットは自動化バックアップ機能を使わなくても作れる。」そうです。
バックアップを復元したければスナップショットを復元すればいいんですね。

Q: 自動化バックアップと DB スナップショットの違いは何ですか?

Amazon RDS は、DB インスタンスのバックアップと復元を行うための 2 つの方法を提供しています。自動バックアップとデータベーススナップショット (DB スナップショット) です。
よくある質問 - Amazon RDS | AWS

スナップショットを復元します。

  1. RDS コンソールを開きます。
  2. サイドメニューの[スナップショット]を選択して一覧を表示します。
  3. 復元するスナップショットを選択 > [スナップショットのアクション] > [スナップショットの復元]
    • 1つずつ選択します。複数選択すると[スナップショットの復元]が選択できません。
    • f:id:ponsuke_tarou:20191226171111p:plain
  4. [DB インスタンスの復元] ページで、[DB インスタンス識別子]に、復元後のインスタンスの名前を入力します。
    • 半角英数字、ハイフンが使えます。
  5. 各設定値を選択します。
  6. [DBインスタンスの復元]ボタンで復元します。

失敗したこと

The option group hoge is associated with a different VPC than the request.

  • 事象 : [DBインスタンスの復元]ボタンを押下したところ以下のメッセージが表示されて復元できない。
The option group hoge is associated with a different VPC than the request. (Service: AmazonRDS; Status Code: 400; Error Code: InvalidParameterCombination; Request ID: 74xxxxx)

AWSのRDSを自動停止するLambdaを作る記録

RDSは停止しても7日経つと自動で起動してしまいます。

DB インスタンスは最大 7 日間停止できます。DB インスタンスを手動で起動しないで 7 日間が経過すると、DB インスタンスは自動的に起動します。
一時的に Amazon RDS DB インスタンスを停止する - Amazon Relational Database Service

なので、RDSを監視して自動で停止してほしいです。

案件が動いていない時は停止していてほしいです。ちょいちょい確認して停止するのは面倒くさいです。
そこで、世の中の知識を利用して自動停止できるようにします。

このサイトのやり方でRDSを自動停止するLambdaを作る

qiita.com

実行権限を作成する

Lambdaの実行権限を作成する - ponsuke_tarou’s blog
上記を参考にIAMのポリシーを作成し、ロールを作成します。
ポリシーに設定する内容は以下になります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "rds:StopDBInstance",
                "rds:ListTagsForResource"
            ],
            "Resource": "arn:aws:rds:*:*:db:*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "rds:DescribeDBInstances",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}

自動停止の対象と停止時間を設定できるようにするためにRDSにAutoStopタグを追加します。

Amazon RDS リソースのタグ付け

  1. AWSマネジメントコンソールにある[RDS] > サイドメニューの[データベース] > 対象となるRDSを選択して詳細画面を開きます。
  2. [タグ]タブ > [追加]ボタンで[タグの追加] ウィンドウを表示します。
  3. [タグキー]に「AutoStop」と[値]に「自動停止したい時間」を入力します。
    • f:id:ponsuke_tarou:20191126100110p:plain
      タグの追加ウィンドウ
  4. [追加]を選択します。
    • f:id:ponsuke_tarou:20191126100356p:plain
      追加後

Lambda関数を作成します。

  1. AWSのコンソールにある[Lambda] > [関数の作成]ボタンで画面を開きます。
  2. 必要な項目を入力後に[関数の作成]ボタンで関数を作成します。
    • オプション : [一から作成]
    • 関数名 : 任意の関数名
    • ランタイム : Python3.8
    • 実行ロール : 既存のロールを使用する
      • 作成したロールを選択します。

関数を実装します。

[関数コード] > [lambda_function.py]に以下のコードを張り付けて[保存]ボタンで保存します。

# -*- coding: utf-8 -*-

from __future__ import print_function

import sys
import json

import boto3
import datetime

REGION_NAME = "ap-northeast-1"

print("Loading function")
rds = boto3.client('rds')

def get_auto_stop_tag(instance_arn):
    instance_tags = rds.list_tags_for_resource(ResourceName=instance_arn)
    tag_list = instance_tags['TagList']
    tag = next(iter(filter(lambda tag: tag['Key'] == 'AutoStop' and (tag['Value'] is not None and tag['Value'] != ''), tag_list)), None)
    return tag

def get_auto_stop_time(auto_stop_tag):
    today = datetime.datetime.now()
    param_day = today.day
    auto_stop_val = auto_stop_tag["Value"].split(":")
    # 設定時刻が8:59以前である場合、GMT変換時に前日にならないよう日付を1日進めておく
    if int(auto_stop_val[0]) < 9:
        param_day = param_day + 1
    # タグに指定されたGMTでの時間
    tag_time = datetime.datetime(today.year, today.month, param_day, int(auto_stop_val[0]), int(auto_stop_val[1]))
    gmt_time = tag_time + datetime.timedelta(hours=-9)
    return gmt_time

def lambda_handler(event, context):
    print("Received event: " + json.dumps(event, indent=2))
    # インスタンスを取得
    instances = rds.describe_db_instances()
    if len(instances['DBInstances']) > 0:
        # インスタンスを順番に処理していく
        for instance in instances['DBInstances']:
            if instance['DBInstanceStatus'] == 'available':
                instance_arn = instance['DBInstanceArn']
                print(instance_arn + 'は、稼働中です。')

                tag = get_auto_stop_tag(instance_arn)
                print('取得したAutoStopタグ:' + str(tag))

                if tag:
                    # AutoStopタグが指定されているインスタンスを処理する
                    reference_time = get_auto_stop_time(tag)
                    print('AutoStopタグに指定された時間(GMT)は、' + reference_time.strftime('%Y-%m-%dT%H:%M:%SZ') + 'です。')

                    event_time = datetime.datetime.strptime(event["time"], '%Y-%m-%dT%H:%M:%SZ')
                    reference_time_from = reference_time + datetime.timedelta(minutes=-5)
                    reference_time_to = reference_time + datetime.timedelta(minutes=5)
                    print('処理時間(' + event_time.strftime('%Y-%m-%dT%H:%M:%SZ') + ')が、' + \
                          reference_time_from.strftime('%Y-%m-%dT%H:%M:%SZ') + 'から' + reference_time_to.strftime('%Y-%m-%dT%H:%M:%SZ') + 'だったら停止します。')

                    # AutoStopタグに指定された時刻の前後5分以内であればインスタンス停止する
                    if reference_time_from <= event_time and event_time <= reference_time_to:
                        rds.stop_db_instance(DBInstanceIdentifier=instance['DBInstanceIdentifier'])
                        print(instance_arn + 'を停止しました。')
関数を動かしてみます。
  1. [テスト]ボタンで[テストイベントの設定]ダイアログを表示します。
  2. [新しいテストイベントの作成]を選択します。
  3. [イベントテンプレート]で「Hello World」を選択します。
    • f:id:ponsuke_tarou:20191129101513p:plain
      テストイベントの設定
  4. 引数の欄に「{"time": "2019-12-04T10:02:00Z"}」を入力します(時間はAutoStopタグに指定した時間の近い時間)。
  5. [イベント名]に任意の名前を設定して[作成]ボタンで保存します。
    • f:id:ponsuke_tarou:20191129101840p:plain
      保存後の状態
  6. [テスト]ボタンで関数を実行します。
  7. 実行結果とログを確認して、「成功」になるまでソースや設定の見直しをします。
    • f:id:ponsuke_tarou:20191129102116p:plain
      実行結果

Lambda関数を実行するトリガーを作成します。

CloudWatch EventsをトリガーとしてLambda関数を実行できるようにするためにイベントを登録します。

CloudWatch Eventsにイベントを登録します。

  1. AWSのコンソールにある[Lambda] > 作成したLambda関数を選択して詳細画面を開きます。
  2. [Designer]にある[トリガーを追加]ボタンでトリガーの設定画面を開きます。
    • f:id:ponsuke_tarou:20191127101110p:plain
      トリガーを追加ボタン
  3. プルダウンからCloudWatch Eventsを選択します。
  4. [ルール]で「新規ルールの作成」を選択し、[ルール名(必須)][ルールの説明(任意)]を入力します。
    • f:id:ponsuke_tarou:20191127101515p:plain
      トリガーの設定画面
  5. [ルールタイプ]で「スケジュール式」を選択し、[スケジュール式]に以下のサイトを参考にスケジュールを設定します。

失敗したこと

CloudWatch Eventsの[スケジュール式]の書き方を間違えた

Cron式で時間に「10-24」と指定したところエラーになりました。24時はだめなんですね。

Parameter ScheduleExpression is not valid. (Service: AmazonCloudWatchEvents; Status Code: 400; Error Code: ValidationException; Request ID: c.....)

Lambda関数でCloudWatch Logsに書き込むための権限がなかった。

Lambda関数を実行するCloudWatch Eventsを登録してからLambda関数の[モニタリング]タブを見てみるとメッセージが表示されていました。
メッセージにある[AWSLambdaBasicExecutionRole]の権限を追加します。

アクセス権限が見つかりません
お使いの関数には、Amazon CloudWatch Logs に書き込むためのアクセス許可がありません。ログを閲覧するには、その実行ロールに AWSLambdaBasicExecutionRole の管理ポリシーを追加します。IAM コンソールを開きます。
  1. AWSマネジメントコンソールにある[IAM] > サイドメニューの[ポリシー] > [AWSLambdaBasicExecutionRole]を検索して詳細画面を表示します。
  2. [アクセス権] > [JSON]で権限の詳細を表示します。
  3. [Statement]に記載されている内容以下をコピーします。
    • f:id:ponsuke_tarou:20191128100745p:plain
  4. サイドメニューの[ポリシー] > Lambda関数に設定したポリシーを検索して詳細画面を表示します。
  5. [アクセス権] > [JSON] > [ポリシーの編集]ボタンで編集画面を表示します。
  6. コピーした内容を[Action]に追記します。
  7. [ポリシーの確認]ボタン > [変更の保存]ボタンで保存します。

describe_db_instancesを実行する権限がなかった。

Lambda関数を実行したらエラーになりました。ポリシーの[Action]に「rds:DescribeDBInstances」を追加しました。

{
  "errorMessage": "An error occurred (AccessDenied) when calling the DescribeDBInstances operation: User: arn:aws:sts::8xxxxxxxxxxx:assumed-role/{ロール名}/{Lambda関数名} is not authorized to perform: rds:DescribeDBInstances",
  "errorType": "ClientError",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 27, in lambda_handler\n    instances = rds.describe_db_instances(Filters=[{\"Name\": \"DBInstanceStatus\", \"Values\": [\"available\"]}])\n",
    "  File \"/var/runtime/botocore/client.py\", line 357, in _api_call\n    return self._make_api_call(operation_name, kwargs)\n",
    "  File \"/var/runtime/botocore/client.py\", line 661, in _make_api_call\n    raise error_class(parsed_response, operation_name)\n"
  ]
}
「rds:DescribeDBInstances」はリソースを限定できなかった。

以下のように単純に「rds:DescribeDBInstances」を追加したところ同じエラーになりました。
一部のRDSの情報の閲覧だけできるポリシーは作れなかった話 - Qiitaを読んでリソースを限定できないことを知りました。

...省略...
            "Action": [
                "rds:StopDBInstance",
                "rds:DescribeDBInstances"
            ],
            "Resource": "arn:aws:rds:*:*:db:*"
...省略...

f:id:ponsuke_tarou:20191129104647p:plain
ポリシーの[ビジュアルエディタ]タブ

DMZは内部ネットワークを守るための領域です。

前回の勉強内容

ponsuke-tarou.hatenablog.com

勉強のきっかけになった問題

DMZ上に公開しているWebサーバで入力データを受け付け,内部ネットワークのDBサーバにそのデータを蓄積するシステムがある。インターネットからDMZを経由してなされるDBサーバへの不正侵入対策の一つとして,DMZと内部ネットワークとの間にファイアウォールを設置するとき,最も有効な設定はどれか。
https://www.sc-siken.com/kakomon/22_aki/img/06.gif

  1. DBサーバの受信ポート番号を固定し,WebサーバからDBサーバの受信ポート番号への通信だけをファイアウォールで通す。
  2. DMZからDBサーバへの通信だけをファイアウォールで通す。
  3. Webサーバの発信ポート番号は任意のポート番号を使用し,ファイアウォールでは,いったん終了した通信と同じ発信ポート番号を使った通信を拒否する。
  4. Webサーバの発信ポート番号を固定し,その発信ポート番号からの通信だけをファイアウォールで通す。

情報セキュリティスペシャリスト平成22年秋期 午前Ⅱ 問6

f:id:ponsuke_tarou:20191019121844p:plain
昔見た光景

DMZは、外部ネットワークと内部ネットワークの間にあるネットワーク上の領域です。

  • 英語: DeMilitarized(離れる + 軍用化する) Zone
  • 日本語: 非武装地帯

https://software.fujitsu.com/jp/manual/manualfiles/M100008/J2X16780/02Z200/conn0101img/e-conn223.gif
2.2 DMZ(Demilitarized Zone)

Webサーバなど外部に公開するサーバなどが設置されます。

https://milestone-of-se.nesuke.com/wp-content/uploads/2018/03/dmz-2.png
図解】初心者にも分かるDMZの構成・設計 〜DMZとは?ファイアウォールを使ったNWセキュリティの基礎〜

ステートフル・インスペクションは、出入りするパケットの通信状態を把握して外部から送信されたパケットのアクセスを動的に制御するファイアウォールです。

ponsuke-tarou.hatenablog.com

DMZは、ファイアウォールやルータに隔離された領域です。

https://www.fe-siken.com/kakomon/29_haru/img/43.gif
平成29年春期問43 DMZを使用したサーバの設置方法|基本情報技術者試験.com

パケットフィルタリング型ファイアウォールを設置すると、パケットのヘッダ情報でアクセス制御を行うことができます。

ponsuke-tarou.hatenablog.com

内部ネットワークのDBサーバの受信ポート番号を固定し、WebサーバからDBサーバの受信ポート番号への通信だけをファイアウォールで通します。

「勉強のきっかけになった問題」にある設問の構成では、Webサーバから内部ネットワークへの通信はDBサーバに限れば良いので、DBサーバのポートを固定してファイアウォールへ設定します。これにより、不要な通信を通さずに済みます。
(参考)情報セキュリティスペシャリスト平成22年秋期 午前Ⅱ 問6

アプリケーションゲートウェイファイアウォールを設置すると、パケットのデータまでをチェックしてアクセス制御を行うことができます。

ponsuke-tarou.hatenablog.com

DMZを設置することで、内部ネットワークへの攻撃をファイアウォールで守ることができます。

https://tech.nikkeibp.co.jp/it/article/COLUMN/20080507/300882/zu04.jpg
https://tech.nikkeibp.co.jp/it/article/COLUMN/20080507/300882/zu05.jpg
DMZを設置しないといけない | 日経クロステック(xTECH)

DMZを設置していてもファイアウォールDMZに設置した機器の正しい対策が行われていなければ、内部ネットワークは危険にさらされます。

内部ネットワークのPCからインターネット上のWebサイトを参照するときにDMZ上に用意したVDI(Virtual Desktop Infrastructure)サーバ上のWebブラウザを利用すると,未知のマルウェアがPCにダウンロードされて,PCが感染することを防ぐというセキュリティ上の効果が期待できる。この効果を生み出すVDIサーバの動作の特徴はどれか。

答. Webサイトからの受信データを処理してVDIサーバで生成したデスクトップ画面の画像データだけをPCに送信する。
https://www.ap-siken.com/kakomon/30_haru/img/41.gif
平成30年春期問41 VDIシステムの導入|応用情報技術者試験.com

公開サーバと同じマシンに公開する必要のないサーバを一緒に入れると公開する必要のないサーバは危険にさらされます。

企業のDMZ上で1台のDNSサーバを,インターネット公開用と,社内のPC及びサーバからの名前解決の問合せに対応する社内用とで共用している。このDNSサーバが,DNSキャッシュポイズニングの被害を受けた結果,直接引き起こされ得る現象はどれか。
答. 社内の利用者が,インターネット上の特定のWebサーバにアクセスしようとすると,本来とは異なるWebサーバに誘導される。
平成28年春期問36 DNSキャッシュポイズニング|応用情報技術者試験.com

ファイアウォールでは、「不正なアクセスによる攻撃」「Webアプリケーションの脆弱性を利用した攻撃」は防げません。

https://www.intec.co.jp/service/detail/uploadfile_bs/images/WAF.png
WAF構築サービス|商品・サービス|インテック

IDSとIPSを利用することで、ネットワークを監視して危険の連絡・遮断ができます。

ponsuke-tarou.hatenablog.com

WAFは、Webアプリケーションへの攻撃を監視し阻止します。

ponsuke-tarou.hatenablog.com

個人情報など重要な情報を取り扱うサイトでは、Webサーバまでの通信をSSL/TLSで暗号化します。

ponsuke-tarou.hatenablog.com

利用者個人のディジタル証明書を用いたTLS通信を行うと、PCとWebサーバ間の通信データを暗号化するとともに,利用者を認証することができるようになります。

https://www.ap-siken.com/kakomon/30_aki/img/40.gif
平成30年秋期問40 TLS通信で期待できるセキュリティ効果|応用情報技術者試験.com

f:id:ponsuke_tarou:20191019121914p:plain
昔の記憶

次回の勉強内容

ponsuke-tarou.hatenablog.com

WAFは、Webアプリケーションへの攻撃を監視し阻止します。

前回の勉強内容

ponsuke-tarou.hatenablog.com

勉強のきっかけになった問題

図のような構成と通信サービスのシステムにおいて,Webアプリケーションの脆弱性対策としてネットワークのパケットをキャプチャしてWAFによる検査を行うとき,WAFの設置場所として最も適切な箇所はどこか。ここで,WAFには通信を暗号化したり復号したりする機能はないものとする。
https://www.sc-siken.com/kakomon/25_haru/img/04.gif
情報セキュリティスペシャリスト平成25年春期 午前Ⅱ 問4

パケットフィルタリング型ファイアウォールは、ネットワーク層の情報でアクセス制御を行うファイアウォールです。

ponsuke-tarou.hatenablog.com

アプリケーションゲートウェイファイアウォールは、アプリケーション層のチェックしてアクセス制御を行うファイアウォールです。

ponsuke-tarou.hatenablog.com

IDSとIPSは、ネットワークを監視して危険をお知らせしてくれます。

ponsuke-tarou.hatenablog.com

WAFは、Webアプリケーションへの攻撃を監視し阻止します。

  • 正式名称 : Web Application Firewall
  • 読み方 : わふ

通過するパケットのIPアドレスやポート番号だけでなくペイロード部(データ部分)をチェックすることで、Webアプリケーションに対する攻撃を検知し、遮断することが可能なファイアウォールです。
平成29年秋期問45 WAFの説明はどれか|応用情報技術者試験.com

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2016/04/waf1.png
https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2016/04/waf2.png
新入社員のためのWAF(Web Application Firewall)入門 | DevelopersIO

Webサーバ及びアプリケーションに起因する脆(ぜい)弱性への攻撃を遮断します。

Webアプリケーションの防御に特化したファイアウォールで、パケットのヘッダ部に含まれるIPアドレスやポート番号だけでなくペイロード部(データ部分)をチェックし、攻撃の兆候の有無を検証します。
予想問題vol.5問29 WAFを利用する目的はどれか|情報セキュリティマネジメント試験.com

https://jpn.nec.com/infocage/siteshell/images/waf_fig_5.jpg
WAFとは: Webアプリケーションファイアウォール(WAF) InfoCage SiteShell | NEC

特徴的なパターンが含まれるかなどWebアプリケーションへの通信内容を検査して、不正な操作を遮断します。

検出パターンには、「ウェブアプリケーション脆弱性を悪用する攻撃に含まれる可能性の高い文字列」や「ウェブアプリケーション仕様で定義されているパラメータの型、値」といったものを定義します。
f:id:ponsuke_tarou:20191017224606p:plain
安全なウェブサイトの作り方:IPA 独立行政法人 情報処理推進機構

Webサイトに対するアクセス内容を監視し、攻撃とみなされるパターンを検知したときに当該アクセスを遮断します。

チェックされる内容には「URLパラメタ」や「クッキーの内容」などのHTTPヘッダ情報や、「POSTデータの内容」などのメッセージボディ部などがあります。
基本情報技術者平成28年秋期 午前問42

https://www.intec.co.jp/service/detail/uploadfile_bs/images/WAF.png
WAF(Webアプリケーションファイアウォール)|商品・サービス|インテック

クライアントとWebサーバの間においてクライアントからWebサーバに送信されたデータを検査して、SQLインジェクションなどの攻撃を遮断するためのものです。

パケットのヘッダ部に含まれるIPアドレスやポート番号だけでなくペイロード部(データ部分)をチェックし、攻撃の兆候の有無を検証します。これによりWebアプリケーションに対する攻撃を検知し、遮断することが可能です。
平成28年春期問43 Web Application Firewall|基本情報技術者試験.com

https://www.infraexpert.com/studygif/security32.gif
WAF( Web Application Firewall )とは

WAFを設置する場合、設置場所には「ネットワーク」と「ウェブサーバ」の 2 つがあります。

ネットワークに設置するWAFは、利用者とウェブサイト間の HTTP・HTTPS通信を通信経路上に介在することで検査します。

主な特徴は以下の通りです。

  • ウェブサーバの動作環境に依存しない
  • ウェブサイトを構成するウェブサーバの台数に依存しない
  • 既存のウェブサイトに導入する場合、ネットワーク構成を見直す必要がある
  • WAFHTTPS 通信に対応していれば、HTTPS 通信も検査できる
  • WAFを導入することで、ウェブサイトの可用性が低下する可能性がある

f:id:ponsuke_tarou:20191017225947p:plain
Web Application Firewall 読本:IPA 独立行政法人 情報処理推進機構

SSLアクセラレータは、Webサーバなどの負荷を軽減するために暗号化や復号の処理を高速に行う専用ハードウェアです。

https://t-tel.net/wp-content/uploads/sslacsela.png
SSLアクセラレーターとは?特徴やサーバ証明書との関連性まとめ

https://www.hitachi-solutions.co.jp/array/sp/apv/img/funcrion2_img01.gif
SSL性能・機能について|SSLアクセラレータ|日立ソリューションズのArrayAPVシリーズ

WAFHTTPS 通信に対応していなくてもSSLアクセラレータとWebサーバの間にWAFを設置すればパケットのデータを検査することができます。

https://nekotosec.com/wp-content/uploads/2018/07/24f67a41a10c2afa8e49ad2363919804.png
https://nekotosec.com/wp-content/uploads/2018/07/4e8b51d8b5ed5bb4a8072c8814e7522e-1024x305.png
SQLインジェクションをWAFで対策する際の失敗例② | 猫とセキュリティ

ウェブサーバに設置するWAFは、利用者とウェブサイト間の HTTP 通信をウェブサーバが送受信する際に検査します。

主な特徴は以下の通りです。

  • ウェブサーバの動作環境に依存する
  • ウェブサイトを構成するウェブサーバすべてに導入する必要がある
  • 既存のウェブサイトに導入する場合でも、ネットワーク構成を見直す必要はない
  • ウェブサーバで HTTPS 通信が処理される(復号と暗号化が行われる)ため、WAFHTTPS 通信に対応していなくとも、HTTPS 通信を検査できる
  • WAFを導入することで、ウェブサーバの性能が低下する可能性がある

f:id:ponsuke_tarou:20191017230426p:plain
Web Application Firewall 読本:IPA 独立行政法人 情報処理推進機構

f:id:ponsuke_tarou:20191015230600j:plain
秋萌え

次回の勉強内容

勉強中・・・

ネットワークを監視して危険をお知らせしてくれるIDSとIPS

前回の勉強内容

ponsuke-tarou.hatenablog.com

勉強のきっかけになった問題

IDS(Intrusion Detection System)の特徴のうち,適切なものはどれか。

  1. ネットワーク型IDSでは,SSLを利用したアプリケーションを介して行われる攻撃を検知できる。
  2. ネットワーク型IDSでは,通信内容の解析によって,ファイルの改ざんを検知できる。
  3. ホスト型IDSでは,シグネチャとのパターンマッチングを失敗させるためのパケットが挿入された攻撃でも検知できる。
  4. ホスト型IDSでは,到着する不正パケットの解析によって,ネットワークセグメント上の不正パケットを検知できる。

平成18年春期問73 IDSの特徴のうち適切なもの|応用情報技術者試験.com

IDSは、サーバやネットワークを監視しセキュリティポリシを侵害するような挙動を検知した場合に管理者へ通知します。

  • 英語: Intrusion(侵入) Detection(検出) System
  • 日本語: 侵入検知システム

https://www.kagoya.jp/howto/wp-content/uploads/kagoya201903-4.jpg
IDS・IPSとは?不正侵入検知・防御サービス解説 | カゴヤのサーバー研究室

設置場所は「ネットワーク型」「ホスト型」の2種類あります。

ネットワーク型は、監視対象のネットワーク上に設置してネットワークの通信を監視します。
  • 略称: NIDS
  • 英語: Network-Based IDS

NIDSに通信内容を復号する機能がなければ、SSLを利用したアプリケーションを介して行われる攻撃を検知できません。

http://gc.sfc.keio.ac.jp/class/2009_25437/slides/11/img/29.png
ネットワーク型 IDS

ホスト型は、監視対象サーバーに設置して通信の結果生成された受信データやログを監視します。
  • 略称: HIDS
  • 英語: Host-Based IDS

ホスト型IDSでは,シグネチャとのパターンマッチングを失敗させるためのパケットが挿入された攻撃でも検知できる。
平成18年春期問73 IDSの特徴のうち適切なもの|応用情報技術者試験.com

https://tech.nikkeibp.co.jp/it/article/COLUMN/20060830/246798/zu2.gif
IDSとIPS | 日経 xTECH(クロステック)

検知方法は「シグネチャ型」「アノマリ型」の2種類があります。

https://tech.nikkeibp.co.jp/it/article/lecture/20070903/280955/zu1s.jpg
Part2 IDSのしくみを知る | 日経 xTECH(クロステック)

シグネチャ型は、異常なアクセスパターンを登録して、一致すれば不正と判断します。

https://ascii.jp/elem/000/000/449/449322/zu_08_588x.jpg
ASCII.jp:巧妙化する不正プログラムを防ぐには? (5/6)|初心者歓迎!ネットワークセキュリティ入門

アノマリ型は、正常なアクセスパターンを登録して、登録パターンと大きく異なるれば不正と判断します。
  • 英語: Anomaly(変則) Detection(検出)
  • 日本語: 異常検出

https://static.it-trend.jp/img/ids-ips/article/explain/ids-ips_explain_img02.jpg
IDS・IPS(不正侵入検知・防御)とは?違いや仕組みを図解!|ITトレンド

IPSは、サーバやネットワークへの侵入を防ぐために不正な通信を検知して遮断する装置です。

  • 英語: Intrusion Prevention System
  • 日本語: 侵入防御システム

http://www.secuavail.com/SANEWS/vol05/img/img_san02.png
http://www.secuavail.com/SANEWS/vol05/img/img_san03.jpg
不正侵入検知防御システム(IPS)|SecuAvail NEWS Vol.5|セキュリティ対策のセキュアヴェイル

https://www.canon-its.co.jp/files/user/img/news/detail/20181017_idsips_01.png
不正侵入を検知・防御する「IDS/IPSサービス」を提供開始|キヤノンITソリューションズ

次回の勉強内容

ponsuke-tarou.hatenablog.com

アプリケーションゲートウェイ型ファイアウォールは、アプリケーション層でパケットのデータまでをチェックしてアクセス制御を行うファイアウォールです。

前回の勉強内容

ponsuke-tarou.hatenablog.com

勉強のきっかけになった問題

ファイアウォールの方式に関する記述のうち,適切なものはどれか。

  1. アプリケーションゲートウェイ方式では,アプリケーションのプロトコルごとにゲートウェイ機能の設定が必要である。
  2. サーキットゲートウェイ方式では,コマンドの通過可否を制御する。
  3. トランスポートゲートウェイ方式では,アプリケーションのプロトコルに依存するゲートウェイ機能を提供する。
  4. パケットフィルタリング方式では,電子メールの中に含まれている単語によるフィルタリングが可能である。

平成17年春期問74 ファイアウォールの方式|応用情報技術者試験.com

アプリケーションゲートウェイファイアウォールは、アプリケーション層でパケットのデータまでをチェックしてアクセス制御を行うファイアウォールです。

https://ascii.jp/img/2009/08/12/853066/l/d42294373e084d19.jpg
ASCII.jp:不正アクセスを防ぐファイアウォールの仕組み (5/6)

http://ash.jp/linux/unyo/a-gate.gif
Linuxサーバ運用マニュアル

ファイアウォール セキュリティ 通信
パケットフィルタリング型 バッファオーバーフロー攻撃*1コンピュータウィルスが含まれる電子メールなどは防げない 早い
アプリケーションゲートウェイ バッファオーバーフロー攻撃やコンピュータウィルスが含まれる電子メールなどは防げるが、なりすましは防げない データまでチェックするので遅い

アプリケーションゲートウェイファイアウォールは、プロキシでもあります。

  • 英語: proxy(代理、代理権、代理人)

https://www.infraexpert.com/studygif/security36.gif
プロキシサーバとは

プロキシは、社内ネットワークからインターネット接続を行うときに、インターネットへのアクセスを中継し、Webコンテンツをキャッシュすることによってアクセスを高速にする仕組みで、セキュリティ確保にも利用されます。

https://www.ap-siken.com/kakomon/31_haru/img/35.gif
平成31年春期問35 Webサーバに中継する仕組みはどれか|応用情報技術者試験.com

アプリケーションゲートウェイファイアウォールは、インターネットと内部のネットワークを切り離します。

https://tech.nikkeibp.co.jp/it/members/NOS/ITBASIC/20020405/1/zu5s.gif
SEなら押さえておきたいIPネットワークの基礎---最終回 ファイアウオール(4) | 日経クロステック(xTECH)

インターネットからのパケットをアプリケーションゲートウェイが代理で受け取ります。

f:id:ponsuke_tarou:20191015220302p:plain

アプリケーションに対応する中継プログラムにパケットを渡します。

中継プログラムは、代理応答プログラム・プロキシとも言います。

https://image.itmedia.co.jp/enterprise/articles/0411/19/zu02.jpg
実はこんなに奥の深い「ファイアウォール」 (2/4) - ITmedia エンタープライズ

そのため、アプリケーションのプロトコルごとにゲートウェイ機能の設定が必要です。

アプリケーションゲートウェイ方式では、アプリケーション層レベルでコネクションを中継するため、HTTP,FTPSMTPなどアプリケーションプログラムごとに別々の中継プログラムを用意する必要があります。
平成17年春期問74 ファイアウォールの方式|応用情報技術者試験.com

https://kmownet.com/security/400-defense/421-lan-security/421-lan-security-fig10.jpg
LANのセキュリティ|ファイアウォール|IDS|WAF|検疫ネットワーク

f:id:ponsuke_tarou:20191015230416j:plain
秋空

次回の勉強内容

ponsuke-tarou.hatenablog.com

パケットフィルタリング型ファイアウォールは、ネットワーク層でパケットのヘッダ情報でアクセス制御を行うファイアウォールです。

前回の勉強内容

ponsuke-tarou.hatenablog.com

勉強のきっかけになった問題

ファイアウォールにおけるダイナミックパケットフィルタリングの特徴はどれか。

  1. IPアドレスの変換が行われるので,ファイアウォール内部のネットワーク構成を外部から隠蔽できる。
  2. 暗号化されたパケットのデータ部を復号して,許可された通信かどうかを判断できる。
  3. パケットのデータ部をチェックして,アプリケーション層での不正なアクセスを防止できる。
  4. 戻りのパケットに関しては,過去に通過したリクエストパケットに対応したものだけを通過させることができる。

情報セキュリティスペシャリスト平成26年春期 午前Ⅱ 問6

パケットフィルタリング型ファイアウォールは、ネットワーク層でパケットのヘッダ情報でアクセス制御を行うファイアウォールです。

f:id:ponsuke_tarou:20210329181435p:plain

パケットのヘッダ情報内のIPアドレス及びポート番号を基準にパケット通過の可否を決定します。

フィルタリングルールを用いて、本来必要なサービスに影響を及ぼすことなく外部に公開していないサービスへのアクセスを防げます。

社内ネットワークとインターネットの接続点にパケットフィルタリング型ファイアウォールを設置して,社内ネットワーク上のPCからインターネット上のWebサーバ(ポート番号80) にアクセスできるようにするとき,フィルタリングで許可するルールの適切な組合せはどれか。
https://www.fe-siken.com/kakomon/23_toku/img/44u.gif
平成23年特別問44 パケットフィルタリング|基本情報技術者試験.com

パケットフィルタリング型ファイアウォールには、種類があります。

https://tech.nikkeibp.co.jp/it/article/lecture/20070508/270250/zu1s.jpg
Part2 フィルタリング---ファイアウォールの基本機能,フィルタリングの違いを理解する | 日経クロステック(xTECH)

スタティックパケットフィルタリングは、行きと戻りのヘッダ情報をフィルタリングテーブルに登録してアクセスを制御するファイアウォールです。

http://c-router.com/chap06/network-basic13901.jpg
静的フィルタリングと動的フィルタリングの違い Ciscoルータで学ぶネットワーク

社内ネットワークとインターネットの接続点に,ステートフルインスペクション機能をもたない,静的なパケットフィルタリング型ファイアウォールを設置している。このネットワーク構成において,社内のPCからインターネット上のSMTPサーバに電子メールを送信できるようにするとき,ファイアウォールで通過を許可するTCPパケットのポート番号の組合せはどれか。ここで,SMTP通信には,デフォルトのポート番号を使うものとする。
f:id:ponsuke_tarou:20191014081135p:plain
(引用元)平成30年春期問44 パケットフィルタリングルール|基本情報技術者試験.com

ダイナミックパケットフィルタリングは、通信のやり取りを判断して動的にフィルタリングテーブルが更新するファイアウォールです。

  • 英語: dynamic(動的な) packet filtering

戻りのパケットに関しては,過去に通過したリクエストパケットに対応したものだけを通過させることができます。

https://ascii.jp/img/2009/08/12/853064/l/4ce07a5468d0a4e9.jpg
ASCII.jp:不正アクセスを防ぐファイアウォールの仕組み (4/6)

ステートフルパケットインスペクションは、出入りするパケットの通信状態を把握して外部から送信されたパケットのアクセスを動的に制御するファイアウォールです。

  • 英語: Stateful(処理状態を把握す) Packet Inspection(検査)
  • 略称: SPI
  • 別名: ステートフルインスペクション、ステートファイアウォール

ダイナミックパケットフィルタリングの1つです。

パケットフィルタリングを拡張した方式であり,過去に通過したパケットから通信セッションを認識し,受け付けたパケットを通信セッションの状態に照らし合わせて通過させるか遮断させるかを判断します。

このファイアウォールはコンテキストの中でパケットを判断します。 コンテキストとは、文脈という意味で、この場合はプログラムが処理内容を置かれた状態や状況、 与えられた条件から判断するという意味です。
覚えておくべき4種のファイアウォール

個々のセッションごとに過去の通信を保持しているため「順序の矛盾した攻撃パケットを遮断することができる」ことや、常に最小限の許可ルールが追加されるため「アクセス制御リストの設定不備を突く攻撃を受けにくい」という優れた点があり、多くのファイアウォール製品に採用されています。
情報セキュリティスペシャリスト平成27年秋期 午前Ⅱ 問3

f:id:ponsuke_tarou:20191014085606j:plain
なにかの芽

次回の勉強内容

ponsuke-tarou.hatenablog.com