Capture The Frog

かえるぴょこぴょこw

MENU

GCPにT-Pot建てる(自分の環境で出たエラーと解決)


T-Potについて

GCPの設定

ファイアーウォールの設定

  • 全てのポートを許可する設定をする
    • GCPは明示的にルールを書かないと解放されない
  • VPCネットワーク→Firewall
    • ファイアーウォールルールを設定ボタン
      • 以下の画像の通りに設定
    • 管理者用画面、sshはログインかかる
      • 必要に応じて、ポート番号(例:64294, 64295, 64297)をファイアーウォールで制限しても良い。
    • このルール以外は削除する

Compute Engineの設定

T-Pot Type RAM Storage Description
Hive 16GB 256GB SSD As a rule of thumb, the more honeypots, sensors & data, the more RAM and storage is needed.
  • e2-standard-4(4 vCPU、2 コア、16 GB メモリ)を使用する
  • Debian GNU/Linux 12 (bookworm)
  • ストレージ:256GB
  • ネットワーキング→ネットワークタグ
    • default

作ったVMインスタンスSSHで接続する

VMに接続して、T-Potをインストール

sudo apt update
env bash -c "$(curl -sL https://github.com/telekom-security/tpotce/raw/master/install.sh)"

install Typeは、一番多くのハニーポットが建てられるので、hを選択

### Choose your T-Pot type:
### (H)ive   - T-Pot Standard / HIVE installation.
###            Includes also everything you need for a distributed setup with sensors.
### (S)ensor - T-Pot Sensor installation.
###            Optimized for a distributed installation, without WebUI, Elasticsearch and Kibana.
### (L)LM    - T-Pot LLM installation.
###            Uses LLM based honeypots Beelzebub & Galah.
###            Requires Ollama (recommended) or ChatGPT subscription.
### M(i)ni   - T-Pot Mini installation.
###            Run 30+ honeypots with just a couple of honeypot daemons.
### (M)obile - T-Pot Mobile installation.
###            Includes everything to run T-Pot Mobile (available separately).
### (T)arpit - T-Pot Tarpit installation.
###            Feed data endlessly to attackers, bots and scanners.
###            Also runs a Denial of Service Honeypot (ddospot).

### Install Type? (h/s/l/i/m/t) h

### Installing T-Pot Standard / HIVE.

### T-Pot User Configuration ...

インストール完了したら、再起動する

sudo reboot

管理用のsshのポートが22番から、T-Potによって64295番に変更されてるので、「ブラウザウィンドウでカスタムポートを開く」で64295番を指定する。

T-Potは再起動後に自動的に起動するので、上手くいっているかを確認する

sudo systemctl status tpot

T-Podを自分の環境で試したときは、activeになっているので上手くいっているかのように思われた。
しかし、http://VMの外部IP:64297/にアクセスできたり出来なかったりする事象が確認された。
これは、後述するが、GCEのDebianのデフォルトのメール転送エージェント (MTA) とT-Podの仕様が噛み合ってしまって、実際には正常に動いていないのに、systemctl status tpotではactiveになり、さも動いているかのように見えてしまうという事象である。

honeypot_trap_3@t-pot-2:~$ sudo systemctl status tpot
● tpot.service - T-Pot
     Loaded: loaded (/etc/systemd/system/tpot.service; enabled; preset: enabled)
     Active: active (running) since Tue 2024-12-31 13:07:43 UTC; 19s ago
    Process: 35168 ExecStartPre=/usr/bin/docker compose -f /home/honeypot_trap_3/tpotce/docker-compose.yml down -v (code=exited, status=0/SUCCESS)
   Main PID: 37368 (docker)
      Tasks: 20 (limit: 19175)
     Memory: 43.5M
        CPU: 1.459s
     CGroup: /system.slice/tpot.service
             ├─37368 /usr/bin/docker compose -f /home/honeypot_trap_3/tpotce/docker-compose.yml up
             └─37385 /usr/libexec/docker/cli-plugins/docker-compose compose -f /home/honeypot_trap_3/tpotce/docker-compose.yml up

Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis         |   `-._    `-._`-.__.-'_.-'    _.-'
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis         |       `-._    `-.__.-'    _.-'
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis         |           `-._        _.-'
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis         |               `-.__.-'
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis         |
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis         | 1:M 31 Dec 2024 13:08:00.690 * Server initialized
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis         | 1:M 31 Dec 2024 13:08:00.690 * Ready to accept connections tcp
Dec 31 13:08:01 t-pot-2 docker[37385]: adbhoney             | INFO:config:Loading config from ./_internal/adbhoney.cfg
Dec 31 13:08:01 t-pot-2 docker[37385]: adbhoney             | INFO:ADBHoneypot:Configuration loaded with ['output_log', 'output_json'] as output plugins
Dec 31 13:08:01 t-pot-2 docker[37385]: adbhoney             | INFO:ADBHoneypot:Listening on 0.0.0.0:5555.

この事象を回避するための処理を行う。
問題なのは、T-Podは、上手くいかないとバックグラウンドでT-Podは再起動を繰り返すことである。
そのため、どこでエラーが起きているのかが、systemctlコマンドでは見つけにくいのであると思う。
まず、以下のコマンドで、T-Podのログを見る。

sudo journalctl -u tpot.service -f

しばらく見ていると、以下のようにT-podのメインプロセスが失敗し、コンテナを停止するログが見つかる。
このエラーが起こる理由は、Debianのデフォルトのメール転送エージェント (MTA) の仕様で25番ポートを使用しており、T-Podも使用しようとしているためである。

Dec 31 13:10:03 t-pot-2 docker[44591]: Error response from daemon: driver failed programming external connectivity on endpoint mailoney (7303cd1b57e19c217baa8f83fdfe1879f2a71c3953cbaae9a411203df4980333): failed to bind port 0.0.0.0:25/tcp: Error starting userland proxy: listen tcp4 0.0.0.0:25: bind: address already in use
Dec 31 13:10:03 t-pot-2 systemd[1]: tpot.service: Main process exited, code=exited, status=1/FAILURE
Dec 31 13:10:03 t-pot-2 systemd[1]: tpot.service: Failed with result 'exit-code'.

今回は、T-Podを優先したいので、Debian側の25番ポートを使用しているプロセスを停止wさせる。
そのために、まずGCE側の25番ポートを使用しているプロセスを特定する。
lsofコマンドを使用する。

sudo apt install lsof
sudo lsof -i :25

自分の環境ではこのようになっていた。

honeypot_trap_3@t-pot-2:~$ sudo lsof -i :25
COMMAND PID        USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
exim4   819 Debian-exim    4u  IPv4   2714      0t0  TCP localhost:smtp (LISTEN)
exim4   819 Debian-exim    5u  IPv6   2715      0t0  TCP localhost:smtp (LISTEN)

自分の環境では、exim4が25番ポートを使用していたため、exim4プロセスをストップさせる。

sudo systemctl stop exim4

これで自分の環境は問題が解決した。

sudo systemctl restart tpot

http://VMの外部IP:64297/にアクセスすると、T-Podの管理画面にアクセスできる。
結構見ていると、T-Podにアクセス飛んできているの見れて楽しい。

HackTheBox "Beep" 反省&writeup

概要

  • Machine Name: Beep
  • Machine Status: Retired
  • Difficulty: Easy
  • OS: Linux
  • URL :

https://app.hackthebox.com/machines/Beep


Nmap スキャン結果

スキャンコマンド

sudo nmap -vvv -sCV -Pn -p0-65535 --reason <IP>

結果の概要

Nmapの結果から、ウェブサービスが稼働していることを確認した。


初期調査

ウェブサイトの探索

https://<IP> にアクセスすると、Elastixというサービスのログインページが表示された。

使用したツール

  • Nikto: サイトの脆弱性スキャンを試みた
    bash nikto -h https://<IP>
  • Gobuster: ディレクトリ探索を実施した
    bash gobuster dir --url https://<IP> --wordlist /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -x php

  • Elastixについて、HackTheBoxのGuideを確認して初めて調査を開始した。(反省点)

  • SQLインジェクション' OR 1=1;--)を試みたが、効かなかった。

Nmapの詳細な結果

NmapのHTTPヘッダーから、ターゲットが Apache/2.2.3 (CentOS) を使用していることがわかった。

  • CentOSが古いバージョンであると推測したが、詳細な脆弱性調査は未実施だった(反省点)。

脆弱性調査とエクスプロイト

Elastixの脆弱性調査

searchsploit を使用してElastixに関連する脆弱性を検索した。

searchsploit elastix

脆弱性

Elastixには以下のような脆弱性が存在していた: - Local File Inclusion(LFI): php/webapps/37637.txt

www.exploit-db.com

エクスプロイトの確認

エクスプロイトコードを確認し、内容を理解した。

searchsploit -x php/webapps/37637.txt

LFI攻撃の実行

LFI脆弱性を利用して、amportal.conf をブラウザから直接読み出した。


amportal.confの解析

amportal.conf は FreePBX の設定ファイル - FreePBX初期設定のユーザー名とパスワード - データベース情報 - Asterisk Manager Interface(AMI)の認証情報

解析結果

ユーザー名とパスワードはデフォルト設定のままだった。 - ユーザー名: root - パスワード: jEhdIekWmdjE


SSHアクセス

問題点

SSH接続時に以下のエラーが発生した:

Unable to negotiate with <IP> port 22: no matching key exchange method found. Their offer: ssh-rsa, ssh-dss

解決方法

鍵交換アルゴリズムとホスト鍵アルゴリズムを指定して接続した。

ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa,ssh-dss [email protected]

成果

amportal.confで得た認証情報を使用し、SSH接続に成功した。root権限を取得し、userrootの両フラグを入手した。


反省点と学び

  1. ウェブサイト調査の精度向上

    • Webサービスを確認した際、適切にElastixの特定と脆弱性調査を行うべきだった。
    • 証明書エラーへの対応方法(--insecure オプションなど)を調査しておく必要があると感じた。
  2. Nmap結果の整理と優先順位付け

    • スキャン後に得られる大量の情報に対して、次のアクションを明確にする必要があった。
      • OSやサービスの特定
      • サービスごとの脆弱性調査
  3. エクスプロイトコードの読み取り

    • Metasploitに頼らず、searchsploitなどで得たエクスプロイトコードを読み解くことができた点は良かった。
  4. 焦らないこと

    • 新しい情報を得た際、焦らず一つ一つ丁寧に調査を進めることを意識した。

改善ポイント

  • Webサービスの特定: サービス名やバージョンを迅速に把握し、脆弱性を特定する。
  • ツールの扱い方: niktogobusterが失敗する原因を理解し、適切な回避策を調べる。
  • 手順の整理: 情報収集から攻撃までの流れを明確にし、次に何をすべきか見失わない。
  • エクスプロイトコードの理解: 今後もコードを読み、実行内容を正確に把握する。

コマンドリスト

本マシン攻略で使用した主要なコマンド:

  1. Nmapスキャン bash sudo nmap -vvv -sCV -Pn -p0-65535 --reason <IP>

  2. Elastixの脆弱性調査 bash searchsploit elastix searchsploit -x php/webapps/37637.txt https://www.exploit-db.com/exploits/37637

  3. LFI攻撃 https://<IP>//vtigercrm/graph.php?current_language=../../../../../../../..//etc/amportal.conf%00&module=Accounts&action:title

  4. SSHアクセス bash ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa,ssh-dss [email protected]

edfファイルからbpm取得

polar H10で、心拍数(bpm)を取ろうかなと思ったら、.edfファイルだった

ミッション : .edfからbpmを取得せよ

pythonでやりたい。

edfファイルとは、心拍データなど生体信号に使用されるデータで、拡張子が.edfとなっている。

このデータをcsvファイルのように直接見ることは出来ないが、Pythonではいくつかのパッケージがある。

インポート

下のコードで使うもの

import pyedflib
import biosppy
import matplotlib.pyplot as plt
from statistics import mean
import pandas as pd
import japanize_matplotlib
from datetime import datetime,timedelta
from scipy.signal import resample
import matplotlib.dates as mdates

edfファイルを読み込む

使用するパッケージ : pyedflib
PyEDFlib -EDF/BDF Toolbox in Python — PyEDFlib Documentation

filename = 'edfファイルパス'
file = pyedflib.EdfReader(filename)

心拍のチャンネルを取得

edfファイルには、チャンネルと呼ばれるものがあり、異なるセンサーや電極から記録される個々の信号は各チャンネルごとに各センサーのデータがまとまっています。 edfファイルから特定の信号(今回はECG)を取得するためには、チャンネルを指定する必要がある。 ここでは、自作関数を使ってECG チャンネルを特定・抽出しています。

# ECGデータが含まれるチャンネルを特定
def find_ecg_channel(file):
    # EDFファイル内のすべてのチャンネルラベルを取得する
    channel_labels = file.getSignalLabels()
    # ECGデータが含まれるチャンネルを特定する
    for i, label in enumerate(channel_labels):
        if 'ECG' in label:
            return i
    # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す
    return None

ecg_channel = find_ecg_channel(file)

心拍と心拍数の経過時間を取得

edfのチャンネルを取得したので、チャンネルを指定してデータを読み込む。 そして、biosppyのbiosppy.signals.ecg.ecgを使って、心拍と心拍数の経過時間(秒)を取得する。

def detect_heart_rate(signal, sampling_rate):
    out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False)
    # HeartRateの基準時間とHeartRateのデータのインデックスを返す
    return out['heart_rate_ts'],out['heart_rate']



if ecg_channel is not None:
    # チャンネルから信号とサンプリングレートを取得
    signal = file.readSignal(ecg_channel)
    sampling_rate = file.getSampleFrequency(ecg_channel)
    print('sampling rate',sampling_rate)
    
    #信号の軸とHeartRateの取得
    ts,heart_rate = detect_heart_rate(signal, sampling_rate)
    print('ts',ts)
    print('heart rate',heart_rate)
    print('bpm',len(heart_rate))

リサンプリング

心拍と心拍数の経過時間を調べることはできたが、このままでは測定機器のサンプリングレートに従っているので、まだBPM(1分あたりの心拍数)とは言えない。 なので、1分間あたりにリサンプリングする。

    # オリジナルのデータの長さとサンプリングレートを取得
    original_length = len(ts)
    original_sampling_rate = original_length / ts[-1]
    #1分あたりでサンプリングレート・1分あたりでサンプリングしたかったら、1
    sampling_rate =1/60
    # 新しいデータの長さを計算
    new_length = int(original_length * (sampling_rate / original_sampling_rate))

    # ダウンサンプリング
    downsampled_ts = resample(ts, new_length)
    downsampled_heart_rate= resample(heart_rate, new_length)

秒から日付時間へ

*心拍データが長い人用
以上でedfファイルからbpmを取り出すことができたので、matplotlibでプロットしていく。 その前に、長い時間心拍のデータをとっていたら、x軸をわかりづらい'秒'ではなく、日付時間で表したい。 そのため、x軸を秒から日付時間に変換する。そして、dataframeにすることでプロットが楽になる

   #ダウンサンプリングされた値をdatetimeの形に直す
    #測定開始の日付時間(例:2023年6月5日22時7分40秒)
    base_datetime = datetime(2023, 6, 5, 22, 7, 40)
    datetimes = []
    for i,value in enumerate(downsampled_ts):
        datetimes.append(base_datetime + timedelta(seconds=i))
    datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes]

    #pandasの形に直す
    heart_rate_df = pd.DataFrame(downsampled_heart_rate_h10)
    heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes))

matplotlibでプロット

#グラフを表示する領域を,figオブジェクトとして作成。
fig = plt.figure(figsize = (10,6), facecolor='lightblue')
ax = fig.add_subplot()
ax.plot(heart_rate_df)

#axのx軸ラベルを指定
# 開始日時と終了日時をdatetimeオブジェクトとして表現
start_datetime = datetime(2023, 6, 5, 22, 7, 40)
end_datetime = datetime(2023, 6, 5, 22, 48, 1)
# 5分ごとの日時をリストに格納
time_range_5min = []
current_time = start_datetime
while current_time <= end_datetime:
    time_range_5min.append(current_time)
#minutesを変えることで、何分ごとにx軸を表示するかを決める
    current_time += timedelta(minutes=5)
# datetimeオブジェクトを数値に変換
time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min]
# x軸の目盛りを設定
ax.set_xticks(time_range_5min_num)
# x軸のフォーマッタを設定
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))

#各subplotにxラベルを追加
ax.set_xlabel('time')
#各subplotにyラベルを追加
ax.set_ylabel('bpm(1m)')
ax.legend(loc = 'upper right') 
plt.show()

終結

うまくいくとこんな感じ

全体コード

import pyedflib
import biosppy
import matplotlib.pyplot as plt
from statistics import mean
import pandas as pd
import japanize_matplotlib
from datetime import datetime,timedelta
from scipy.signal import resample
import matplotlib.dates as mdates


filename = 'edfファイルパス'
file = pyedflib.EdfReader(filename)

# ECGデータが含まれるチャンネルを特定
def find_ecg_channel(file):
    # EDFファイル内のすべてのチャンネルラベルを取得する
    channel_labels = file.getSignalLabels()
    # ECGデータが含まれるチャンネルを特定する
    for i, label in enumerate(channel_labels):
        if 'ECG' in label:
            return i
    # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す
    return None

def detect_heart_rate(signal, sampling_rate):
    out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False)
    # HeartRateの基準時間とHeartRateのデータのインデックスを返す
    return out['heart_rate_ts'],out['heart_rate']



ecg_channel = find_ecg_channel(file)

if ecg_channel is not None:
    # チャンネルから信号とサンプリングレートを取得
    signal = file.readSignal(ecg_channel)
    sampling_rate = file.getSampleFrequency(ecg_channel)
    print('sampling rate',sampling_rate)
    
    #信号の軸とHeartRateの取得
    ts,heart_rate = detect_heart_rate(signal, sampling_rate)
    print('ts',ts)
    print('heart rate',heart_rate)
    print('bpm',len(heart_rate))

    # オリジナルのデータの長さとサンプリングレートを取得
    original_length = len(ts)
    original_sampling_rate = original_length / ts[-1]
    #1分あたりでサンプリングレート
    sampling_rate =1/60
    # 新しいデータの長さを計算
    new_length = int(original_length * (sampling_rate / original_sampling_rate))

    # ダウンサンプリング
    downsampled_ts = resample(ts, new_length)
    downsampled_heart_rate= resample(heart_rate, new_length)

  #ダウンサンプリングされた値をdatetimeの形に直す
    #測定開始の日付時間(例:2023年6月5日22時7分40秒)
    base_datetime = datetime(2023, 6, 5, 22, 7, 40)
    datetimes = []
    for i,value in enumerate(downsampled_ts):
        datetimes.append(base_datetime + timedelta(minutes=i))
    datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes]

    #pandasの形に直す
    heart_rate_df = pd.DataFrame(downsampled_heart_rate)
    heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes))

else:
    print('心拍データが含まれていません')
#グラフを表示する領域を,figオブジェクトとして作成。
fig = plt.figure(figsize = (10,6), facecolor='lightblue')
ax = fig.add_subplot()
ax.plot(heart_rate_df)

#axのx軸ラベルを指定
# 開始日時と終了日時をdatetimeオブジェクトとして表現
start_datetime = datetime(2023, 6, 5, 22, 7, 40)
end_datetime = datetime(2023, 6, 5, 22, 48, 1)
# 5分ごとの日時をリストに格納
time_range_5min = []
current_time = start_datetime
while current_time <= end_datetime:
    time_range_5min.append(current_time)
#minutesを変えることで、何分ごとにx軸を表示するかを決める
    current_time += timedelta(minutes=5)
# datetimeオブジェクトを数値に変換
time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min]
# x軸の目盛りを設定
ax.set_xticks(time_range_5min_num)
# x軸のフォーマッタを設定
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))

#各subplotにxラベルを追加
ax.set_xlabel('time')
#各subplotにyラベルを追加
ax.set_ylabel('bpm(1m)')
plt.show()

edfファイルからbpm取得

polar H10で、心拍数(bpm)を取ろうかなと思ったら、.edfファイルだった

ミッション : .edfからbpmを取得せよ

pythonでやりたい。

edfファイルとは、心拍データなど生体信号に使用されるデータで、拡張子が.edfとなっている。

このデータをcsvファイルのように直接見ることは出来ないが、Pythonではいくつかのパッケージがある。

インポート

下のコードで使うもの

import pyedflib
import biosppy
import matplotlib.pyplot as plt
from statistics import mean
import pandas as pd
import japanize_matplotlib
from datetime import datetime,timedelta
from scipy.signal import resample
import matplotlib.dates as mdates

edfファイルを読み込む

使用するパッケージ : pyedflib
PyEDFlib -EDF/BDF Toolbox in Python — PyEDFlib Documentation

filename = 'ファイルパス'
file = pyedflib.EdfReader(filename)

心拍のチャンネルを取得

edfファイルには、チャンネルと呼ばれるものがあり、異なるセンサーや電極から記録される個々の信号は各チャンネルごとに各センサーのデータがまとまっています。 edfファイルから特定の信号(今回はECG)を取得するためには、チャンネルを指定する必要がある。 ここでは、自作関数を使ってECG チャンネルを特定・抽出しています。

# ECGデータが含まれるチャンネルを特定
def find_ecg_channel(file):
    # EDFファイル内のすべてのチャンネルラベルを取得する
    channel_labels = file.getSignalLabels()
    # ECGデータが含まれるチャンネルを特定する
    for i, label in enumerate(channel_labels):
        if 'ECG' in label:
            return i
    # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す
    return None

ecg_channel = find_ecg_channel(file)

心拍と心拍数の経過時間を取得

edfのチャンネルを取得したので、チャンネルを指定してデータを読み込む。 そして、biosppyのbiosppy.signals.ecg.ecgを使って、心拍と心拍数の経過時間(秒)を取得する。

def detect_heart_rate(signal, sampling_rate):
    out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False)
    # HeartRateの基準時間とHeartRateのデータのインデックスを返す
    return out['heart_rate_ts'],out['heart_rate']



if ecg_channel is not None:
    # チャンネルから信号とサンプリングレートを取得
    signal = file.readSignal(ecg_channel)
    sampling_rate = file.getSampleFrequency(ecg_channel)
    print('sampling rate',sampling_rate)
    
    #信号の軸とHeartRateの取得
    ts,heart_rate = detect_heart_rate(signal, sampling_rate)
    print('ts',ts)
    print('heart rate',heart_rate)
    print('bpm',len(heart_rate))

リサンプリング

心拍と心拍数の経過時間を調べることはできたが、このままでは測定機器のサンプリングレートに従っているので、まだBPM(1分あたりの心拍数)とは言えない。 なので、1分間あたりにリサンプリングする。

    # オリジナルのデータの長さとサンプリングレートを取得
    original_length = len(ts)
    original_sampling_rate = original_length / ts[-1]
    #1分あたりでサンプリングレート・1分あたりでサンプリングしたかったら、1
    sampling_rate =1/60
    # 新しいデータの長さを計算
    new_length = int(original_length * (sampling_rate / original_sampling_rate))

    # ダウンサンプリング
    downsampled_ts = resample(ts, new_length)
    downsampled_heart_rate= resample(heart_rate, new_length)

秒から日付時間へ

*心拍データが長い人用
以上でedfファイルからbpmを取り出すことができたので、matplotlibでプロットしていく。 その前に、長い時間心拍のデータをとっていたら、x軸をわかりづらい'秒'ではなく、日付時間で表したい。 そのため、x軸を秒から日付時間に変換する。そして、dataframeにすることでプロットが楽になる

   #ダウンサンプリングされた値をdatetimeの形に直す
    #測定開始の日付時間(例:2023年6月5日22時7分40秒)
    base_datetime = datetime(2023, 6, 5, 22, 7, 40)
    datetimes = []
    for i,value in enumerate(downsampled_ts):
        datetimes.append(base_datetime + timedelta(seconds=i))
    datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes]

    #pandasの形に直す
    heart_rate_df = pd.DataFrame(downsampled_heart_rate_h10)
    heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes))

matplotlibでプロット

#グラフを表示する領域を,figオブジェクトとして作成。
fig = plt.figure(figsize = (10,6), facecolor='lightblue')
ax = fig.add_subplot()
ax.plot(heart_rate_df)

#axのx軸ラベルを指定
# 開始日時と終了日時をdatetimeオブジェクトとして表現
start_datetime = datetime(2023, 6, 5, 22, 7, 40)
end_datetime = datetime(2023, 6, 5, 22, 48, 1)
# 5分ごとの日時をリストに格納
time_range_5min = []
current_time = start_datetime
while current_time <= end_datetime:
    time_range_5min.append(current_time)
#minutesを変えることで、何分ごとにx軸を表示するかを決める
    current_time += timedelta(minutes=5)
# datetimeオブジェクトを数値に変換
time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min]
# x軸の目盛りを設定
ax.set_xticks(time_range_5min_num)
# x軸のフォーマッタを設定
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))

#各subplotにxラベルを追加
ax.set_xlabel('time')
#各subplotにyラベルを追加
ax.set_ylabel('bpm(1m)')
ax.legend(loc = 'upper right') 
plt.show()

全体コード

import pyedflib
import biosppy
import matplotlib.pyplot as plt
from statistics import mean
import pandas as pd
import japanize_matplotlib
from datetime import datetime,timedelta
from scipy.signal import resample
import matplotlib.dates as mdates


filename = ''
file = pyedflib.EdfReader(filename)

# ECGデータが含まれるチャンネルを特定
def find_ecg_channel(file):
    # EDFファイル内のすべてのチャンネルラベルを取得する
    channel_labels = file.getSignalLabels()
    # ECGデータが含まれるチャンネルを特定する
    for i, label in enumerate(channel_labels):
        if 'ECG' in label:
            return i
    # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す
    return None

def detect_heart_rate(signal, sampling_rate):
    out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False)
    # HeartRateの基準時間とHeartRateのデータのインデックスを返す
    return out['heart_rate_ts'],out['heart_rate']



ecg_channel = find_ecg_channel(file)

if ecg_channel is not None:
    # チャンネルから信号とサンプリングレートを取得
    signal = file.readSignal(ecg_channel)
    sampling_rate = file.getSampleFrequency(ecg_channel)
    print('sampling rate',sampling_rate)
    
    #信号の軸とHeartRateの取得
    ts,heart_rate = detect_heart_rate(signal, sampling_rate)
    print('ts',ts)
    print('heart rate',heart_rate)
    print('bpm',len(heart_rate))

    # オリジナルのデータの長さとサンプリングレートを取得
    original_length = len(ts)
    original_sampling_rate = original_length / ts[-1]
    #1分あたりでサンプリングレート
    sampling_rate =1/60
    # 新しいデータの長さを計算
    new_length = int(original_length * (sampling_rate / original_sampling_rate))

    # ダウンサンプリング
    downsampled_ts = resample(ts, new_length)
    downsampled_heart_rate= resample(heart_rate, new_length)

  #ダウンサンプリングされた値をdatetimeの形に直す
    #測定開始の日付時間(例:2023年6月5日22時7分40秒)
    base_datetime = datetime(2023, 6, 5, 22, 7, 40)
    datetimes = []
    for i,value in enumerate(downsampled_ts):
        datetimes.append(base_datetime + timedelta(minutes=i))
    datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes]

    #pandasの形に直す
    heart_rate_df = pd.DataFrame(downsampled_heart_rate)
    heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes))

else:
    print('心拍データが含まれていません')
#グラフを表示する領域を,figオブジェクトとして作成。
fig = plt.figure(figsize = (10,6), facecolor='lightblue')
ax = fig.add_subplot()
ax.plot(heart_rate_df)

#axのx軸ラベルを指定
# 開始日時と終了日時をdatetimeオブジェクトとして表現
start_datetime = datetime(2023, 6, 5, 22, 7, 40)
end_datetime = datetime(2023, 6, 5, 22, 48, 1)
# 5分ごとの日時をリストに格納
time_range_5min = []
current_time = start_datetime
while current_time <= end_datetime:
    time_range_5min.append(current_time)
#minutesを変えることで、何分ごとにx軸を表示するかを決める
    current_time += timedelta(minutes=5)
# datetimeオブジェクトを数値に変換
time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min]
# x軸の目盛りを設定
ax.set_xticks(time_range_5min_num)
# x軸のフォーマッタを設定
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))

#各subplotにxラベルを追加
ax.set_xlabel('time')
#各subplotにyラベルを追加
ax.set_ylabel('bpm(1m)')
plt.show()

edfファイルからbpm取得

polar H10で、心拍数(bpm)を取ろうかなと思ったら、.edfファイルだった

ミッション : .edfからbpmを取得せよ

pythonでやりたい。

edfファイルとは、心拍データなど生体信号に使用されるデータで、拡張子が.edfとなっている。

このデータをcsvファイルのように直接見ることは出来ないが、Pythonではいくつかのパッケージがある。

インポート

下のコードで使うもの

import pyedflib
import biosppy
import matplotlib.pyplot as plt
from statistics import mean
import pandas as pd
import japanize_matplotlib
from datetime import datetime,timedelta
from scipy.signal import resample
import matplotlib.dates as mdates

edfファイルを読み込む

使用するパッケージ : pyedflib PyEDFlib -EDF/BDF Toolbox in Python — PyEDFlib Documentation

filename = 'ファイルパス'
file = pyedflib.EdfReader(filename)

心拍のチャンネルを取得

edfファイルには、チャンネルと呼ばれるものがあり、異なるセンサーや電極から記録される個々の信号は各チャンネルごとに各センサーのデータがまとまっています。 edfファイルから特定の信号(今回はECG)を取得するためには、チャンネルを指定する必要がある。 ここでは、自作関数を使ってECG チャンネルを特定・抽出しています。

# ECGデータが含まれるチャンネルを特定
def find_ecg_channel(file):
    # EDFファイル内のすべてのチャンネルラベルを取得する
    channel_labels = file.getSignalLabels()
    # ECGデータが含まれるチャンネルを特定する
    for i, label in enumerate(channel_labels):
        if 'ECG' in label:
            return i
    # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す
    return None

ecg_channel = find_ecg_channel(file)

心拍と心拍数の経過時間を取得

edfのチャンネルを取得したので、チャンネルを指定してデータを読み込む。 そして、biosppyのbiosppy.signals.ecg.ecgを使って、心拍と心拍数の経過時間(秒)を取得する。

def detect_heart_rate(signal, sampling_rate):
    out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False)
    # HeartRateの基準時間とHeartRateのデータのインデックスを返す
    return out['heart_rate_ts'],out['heart_rate']



if ecg_channel is not None:
    # チャンネルから信号とサンプリングレートを取得
    signal = file.readSignal(ecg_channel)
    sampling_rate = file.getSampleFrequency(ecg_channel)
    print('sampling rate',sampling_rate)
    
    #信号の軸とHeartRateの取得
    ts,heart_rate = detect_heart_rate(signal, sampling_rate)
    print('ts',ts)
    print('heart rate',heart_rate)
    print('bpm',len(heart_rate))

リサンプリング

心拍と心拍数の経過時間を調べることはできたが、このままでは測定機器のサンプリングレートに従っているので、まだBPM(1分あたりの心拍数)とは言えない。 なので、1分間あたりにリサンプリングする。

    # オリジナルのデータの長さとサンプリングレートを取得
    original_length = len(ts)
    original_sampling_rate = original_length / ts[-1]
    #1分あたりでサンプリングレート・1分あたりでサンプリングしたかったら、1
    sampling_rate =1/60
    # 新しいデータの長さを計算
    new_length = int(original_length * (sampling_rate / original_sampling_rate))

    # ダウンサンプリング
    downsampled_ts = resample(ts, new_length)
    downsampled_heart_rate= resample(heart_rate, new_length)

秒から日付時間へ

*心拍データが長い人用 以上でedfファイルからbpmを取り出すことができたので、matplotlibでプロットしていく。 その前に、長い時間心拍のデータをとっていたら、x軸をわかりづらい'秒'ではなく、日付時間で表したい。 そのため、x軸を秒から日付時間に変換する。そして、dataframeにすることでプロットが楽になる

   #ダウンサンプリングされた値をdatetimeの形に直す
    #測定開始の日付時間(例:2023年6月5日22時7分40秒)
    base_datetime = datetime(2023, 6, 5, 22, 7, 40)
    datetimes = []
    for i,value in enumerate(downsampled_ts):
        datetimes.append(base_datetime + timedelta(seconds=i))
    datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes]

    #pandasの形に直す
    heart_rate_df = pd.DataFrame(downsampled_heart_rate_h10)
    heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes))

matplotlibでプロット

#グラフを表示する領域を,figオブジェクトとして作成。
fig = plt.figure(figsize = (10,6), facecolor='lightblue')
ax = fig.add_subplot()
ax.plot(heart_rate_df)

#axのx軸ラベルを指定
# 開始日時と終了日時をdatetimeオブジェクトとして表現
start_datetime = datetime(2023, 6, 5, 22, 7, 40)
end_datetime = datetime(2023, 6, 5, 22, 48, 1)
# 5分ごとの日時をリストに格納
time_range_5min = []
current_time = start_datetime
while current_time <= end_datetime:
    time_range_5min.append(current_time)
#minutesを変えることで、何分ごとにx軸を表示するかを決める
    current_time += timedelta(minutes=5)
# datetimeオブジェクトを数値に変換
time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min]
# x軸の目盛りを設定
ax.set_xticks(time_range_5min_num)
# x軸のフォーマッタを設定
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))

#各subplotにxラベルを追加
ax.set_xlabel('time')
#各subplotにyラベルを追加
ax.set_ylabel('bpm(1m)')
ax.legend(loc = 'upper right') 
plt.show()

全体コード

import pyedflib
import biosppy
import matplotlib.pyplot as plt
from statistics import mean
import pandas as pd
import japanize_matplotlib
from datetime import datetime,timedelta
from scipy.signal import resample
import matplotlib.dates as mdates


filename = ''
file = pyedflib.EdfReader(filename)

# ECGデータが含まれるチャンネルを特定
def find_ecg_channel(file):
    # EDFファイル内のすべてのチャンネルラベルを取得する
    channel_labels = file.getSignalLabels()
    # ECGデータが含まれるチャンネルを特定する
    for i, label in enumerate(channel_labels):
        if 'ECG' in label:
            return i
    # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す
    return None

def detect_heart_rate(signal, sampling_rate):
    out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False)
    # HeartRateの基準時間とHeartRateのデータのインデックスを返す
    return out['heart_rate_ts'],out['heart_rate']



ecg_channel = find_ecg_channel(file)

if ecg_channel is not None:
    # チャンネルから信号とサンプリングレートを取得
    signal = file.readSignal(ecg_channel)
    sampling_rate = file.getSampleFrequency(ecg_channel)
    print('sampling rate',sampling_rate)
    
    #信号の軸とHeartRateの取得
    ts,heart_rate = detect_heart_rate(signal, sampling_rate)
    print('ts',ts)
    print('heart rate',heart_rate)
    print('bpm',len(heart_rate))

    # オリジナルのデータの長さとサンプリングレートを取得
    original_length = len(ts)
    original_sampling_rate = original_length / ts[-1]
    #1分あたりでサンプリングレート
    sampling_rate =1/60
    # 新しいデータの長さを計算
    new_length = int(original_length * (sampling_rate / original_sampling_rate))

    # ダウンサンプリング
    downsampled_ts = resample(ts, new_length)
    downsampled_heart_rate= resample(heart_rate, new_length)

  #ダウンサンプリングされた値をdatetimeの形に直す
    #測定開始の日付時間(例:2023年6月5日22時7分40秒)
    base_datetime = datetime(2023, 6, 5, 22, 7, 40)
    datetimes = []
    for i,value in enumerate(downsampled_ts):
        datetimes.append(base_datetime + timedelta(minutes=i))
    datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes]

    #pandasの形に直す
    heart_rate_df = pd.DataFrame(downsampled_heart_rate)
    heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes))

else:
    print('心拍データが含まれていません')
#グラフを表示する領域を,figオブジェクトとして作成。
fig = plt.figure(figsize = (10,6), facecolor='lightblue')
ax = fig.add_subplot()
ax.plot(heart_rate_df)

#axのx軸ラベルを指定
# 開始日時と終了日時をdatetimeオブジェクトとして表現
start_datetime = datetime(2023, 6, 5, 22, 7, 40)
end_datetime = datetime(2023, 6, 5, 22, 48, 1)
# 5分ごとの日時をリストに格納
time_range_5min = []
current_time = start_datetime
while current_time <= end_datetime:
    time_range_5min.append(current_time)
#minutesを変えることで、何分ごとにx軸を表示するかを決める
    current_time += timedelta(minutes=5)
# datetimeオブジェクトを数値に変換
time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min]
# x軸の目盛りを設定
ax.set_xticks(time_range_5min_num)
# x軸のフォーマッタを設定
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))

#各subplotにxラベルを追加
ax.set_xlabel('time')
#各subplotにyラベルを追加
ax.set_ylabel('bpm(1m)')
plt.show()

streamlit cloud上でAuth0を使用したログインを行う際のAuth0の設定

streamlit上でAuth0を使用したログインを行ったときに、ローカルでのログインは出来るが、streamlit cloudでデプロイしたときには動かないことがあります。
ローカル時と同じ設定方法ではAuth0は動きません。

しかし、多くの場合、解説記事ではローカルでの設定しか書かれておらず、デプロイする際の設定は書かれていません。
多くの人がここで戸惑っています。

https://discuss.streamlit.io/t/new-component-auth0-component-a-simple-way-to-authenticate-a-user/18260/11


そこで、ここではstreamlitを使用して開発した自分のアプリをstreamlit cloudでデプロイしたときのAuth0の設定について書いていきます。


streamlit上でAuth0でログインする基本のコンポーネントとして、streamlit-auth0を使用させて頂きます。
Auth0以外の基本的な設定は、レポジトリ内のReadme.mdに沿って行ってください。
github.com

Thanks, conradbez!!!

紹介記事
discuss.streamlit.io






方法

1,callback URL

https://YOUR_APP_ON_STREAMLIT_CLOUD_DOMAIN/~/+/component/auth0_component.login_button/index.html, http://YOUR_AUTH0_DOMAIN/component/auth0_component.login_button/index.html

2,Allowed Web origin

https://YOUR_APP_ON_STREAMLIT_CLOUD_DOMAIN/~/+/component/auth0_component.login_button/index.html




↓存在しないアカウントとstreamlit cloud上のアプリ
Auth0 settings page
My App url on streamlit cloud

https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/


Auth0の設定
1,call back URL

https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/~/+/component/auth0_component.login_button/index.html, http://dev-o3y3v7wthueiyyyt.us.auth0.com/component/auth0_component.login_button/index.html


2,Allowed Web origin

https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/~/+/component/auth0_component.login_button/index.html

streamlit cloud上でAuth0を使用したログインを行う際のAuth0の設定

streamlit上でAuth0を使用したログインを行ったときに、ローカルでのログインは出来るが、streamlit cloudでデプロイしたときには動かないことがあります。
ローカル時と同じ設定方法ではAuth0は動きません。

しかし、多くの場合、解説記事ではローカルでの設定しか書かれておらず、デプロイする際の設定は書かれていません。
多くの人がここで戸惑っています。

https://discuss.streamlit.io/t/new-component-auth0-component-a-simple-way-to-authenticate-a-user/18260/11


そこで、ここではstreamlitを使用して開発した自分のアプリをstreamlit cloudでデプロイしたときのAuth0の設定について書いていきます。


streamlit上でAuth0でログインする基本のコンポーネントとして、streamlit-auth0を使用させて頂きます。
Auth0以外の基本的な設定は、レポジトリ内のReadme.mdに沿って行ってください。
github.com

Thanks, conradbez!!!

紹介記事
discuss.streamlit.io






方法

1,callback URL

https://YOUR_APP_ON_STREAMLIT_CLOUD_DOMAIN/~/+/component/auth0_component.login_button/index.html, http://YOUR_AUTH0_DOMAIN/component/auth0_component.login_button/index.html

2,Allowed Web origin

https://YOUR_APP_ON_STREAMLIT_CLOUD_DOMAIN/~/+/component/auth0_component.login_button/index.html




↓存在しないアカウントとstreamlit cloud上のアプリ
Auth0 settings page
My App url on streamlit cloud

https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/


Auth0の設定
1,call back URL

https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/~/+/component/auth0_component.login_button/index.html, http://dev-o3y3v7wthueiyyyt.us.auth0.com/component/auth0_component.login_button/index.html


2,Allowed Web origin

https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/~/+/component/auth0_component.login_button/index.html